blob: cbec77d4ec4303a250f32eb60ccb27353c64507c [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2019 SAP SE and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Matthias Becker <ma.becker@sap.com> - Bug 543746
******************************************************************************/
package org.eclipse.ui.internal.views.helpers;
import java.util.ArrayList;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.jface.preference.JFacePreferences;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.resource.JFaceColors;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.resource.LocalResourceManager;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StackLayout;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Link;
import org.eclipse.ui.IPerspectiveDescriptor;
import org.eclipse.ui.IPerspectiveListener;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.actions.NewProjectAction;
import org.eclipse.ui.forms.events.HyperlinkAdapter;
import org.eclipse.ui.forms.events.HyperlinkEvent;
import org.eclipse.ui.forms.widgets.FormToolkit;
import org.eclipse.ui.forms.widgets.Hyperlink;
import org.eclipse.ui.handlers.IHandlerService;
import org.eclipse.ui.internal.IWorkbenchGraphicConstants;
import org.eclipse.ui.internal.WorkbenchImages;
import org.eclipse.ui.internal.WorkbenchPlugin;
import org.eclipse.ui.internal.dialogs.WorkbenchWizardElement;
import org.eclipse.ui.internal.ide.IDEWorkbenchPlugin;
import org.eclipse.ui.internal.navigator.wizards.WizardShortcutAction;
import org.eclipse.ui.internal.views.navigator.ResourceNavigatorMessages;
import org.eclipse.ui.wizards.IWizardDescriptor;
import org.eclipse.ui.wizards.IWizardRegistry;
/**
* This class can be re-used by views that show the workspace's projects (like
* e.g. the "Project Explorer" does). With the help of this class these views
* can show explanatory text in cases where no project is in the workspace. The
* focus here is on "on-boarding" occasional or new users. They might be lost
* when you first open their IDE and see a window with an empty editor area and
* some (mainly) empty views. Their questions are: "How do I start? How do I get
* content into here?"
*
* This class uses a stack layout to switch between the "original" composite of
* the view and an additional composite given the user the explanatory text.
* This text is displayed when no projects are in the workspace. Once projects
* are created this class switches back to the "original" composite of the view.
*
* The explanatory text explains the current situation that no projects are
* available and provides a list of options to create projects. This list
* contains links to:
* <ol>
* <li>Project creation wizards specific to the current perspective</li>
* <li>The "New Project Wizard" to allow creation of project of any type</li>
* </ol>
* If no perspective specific project creation wizards are found then a simple
* text with a link to the "New Project Wizard" is shown.
*
* This class also takes care of refreshing these links when the user switches
* the perspective.
*
*/
public final class EmptyWorkspaceHelper {
private Composite parent;
private Composite emptyArea;
private StackLayout layout;
private Control control;
private Composite displayArea;
private ArrayList<IAction> projectWizardActions;
private IAction newProjectAction;
private IAction importAction;
private LocalResourceManager resourceManager;
/**
* This method should be called at the point in time when the view's controls
* are created.
*
* @param parent The composite where the explanatory text should be put into.
* @return A new composite (a child of "parent") that has to be used by
* consumers as parent for their UI elements
*/
public Composite getComposite(Composite aParent) {
this.parent = aParent;
displayArea = new Composite(parent, SWT.NONE);
layout = new StackLayout();
displayArea.setLayout(layout);
createEmptyArea(displayArea);
registerListeners();
return displayArea;
}
/**
* This method should be used to hand over the "original" control that is
* "normally" visible in the view.
*
* @param control The "original" control of the view.
*/
public void setNonEmptyControl(Control control) {
this.control = control;
emptyArea.setBackground(control.getBackground());
switchTopControl();
}
private void dispose(Listener listener) {
PlatformUI.getWorkbench().getActiveWorkbenchWindow().removePerspectiveListener(listener);
ResourcesPlugin.getWorkspace().removeResourceChangeListener(listener);
JFaceResources.getColorRegistry().removeListener(listener);
parent.removeDisposeListener(listener);
// paranoia
parent = null;
emptyArea = null;
layout = null;
control = null;
displayArea = null;
projectWizardActions = null;
newProjectAction = null;
importAction = null;
}
private void registerListeners() {
Listener listener = new Listener();
PlatformUI.getWorkbench().getActiveWorkbenchWindow().addPerspectiveListener(listener);
ResourcesPlugin.getWorkspace().addResourceChangeListener(listener, IResourceChangeEvent.POST_CHANGE);
JFaceResources.getColorRegistry().addListener(listener);
parent.addDisposeListener(listener);
}
private void createEmptyArea(Composite displayAreas) {
if (newProjectAction == null) {
newProjectAction = new NewProjectAction();
}
if (importAction == null) {
importAction = new ImportAction();
}
if (projectWizardActions == null) {
projectWizardActions = new ArrayList<>();
readProjectWizardActions();
}
emptyArea = new Composite(displayAreas, SWT.NONE);
resourceManager = new LocalResourceManager(JFaceResources.getResources(), emptyArea);
emptyArea.setBackgroundMode(SWT.INHERIT_FORCE);
GridLayoutFactory.fillDefaults().applyTo(emptyArea);
Composite infoArea = new Composite(emptyArea, SWT.NONE);
GridDataFactory.swtDefaults().align(SWT.LEFT, SWT.TOP).grab(true, true).indent(5, 5).applyTo(infoArea);
GridLayoutFactory.swtDefaults().applyTo(infoArea);
Link messageLabel = new Link(infoArea, SWT.WRAP);
Composite optionsArea = null;
messageLabel.setText(ResourceNavigatorMessages.EmptyWorkspaceHelper_noProjectsAvailable);
GridDataFactory.swtDefaults().align(SWT.FILL, SWT.FILL).grab(true, false).applyTo(messageLabel);
optionsArea = new Composite(infoArea, SWT.NONE);
GridLayoutFactory.swtDefaults().numColumns(2).applyTo(optionsArea);
GridDataFactory.swtDefaults().indent(5, 0).grab(true, true).applyTo(optionsArea);
final FormToolkit toolkit = new FormToolkit(emptyArea.getDisplay());
emptyArea.addDisposeListener(e -> toolkit.dispose());
final Color linkColor = JFaceColors.getHyperlinkText(emptyArea.getDisplay());
for (IAction action : projectWizardActions) {
String description = action.getDescription();
if (description == null || description.isEmpty()) {
description = action.getText();
}
createOption(optionsArea, toolkit, linkColor, action, action.getImageDescriptor(), description);
}
createOption(optionsArea, toolkit, linkColor, newProjectAction, newProjectAction.getImageDescriptor(),
ResourceNavigatorMessages.EmptyWorkspaceHelper_createProject);
createOption(optionsArea, toolkit, linkColor, importAction,
WorkbenchImages.getImageDescriptor(IWorkbenchGraphicConstants.IMG_ETOOL_IMPORT_WIZ),
ResourceNavigatorMessages.EmptyWorkspaceHelper_importProjects);
}
private void recreateEmptyArea() {
disposeEmptyArea();
// re-read the project wizards and re-create the empty area
createEmptyArea(displayArea);
}
private void disposeEmptyArea() {
if (emptyArea != null) {
// throw away already existing empty area
emptyArea.dispose();
emptyArea = null;
}
}
private void readProjectWizardActions() {
IWorkbench wb = PlatformUI.getWorkbench();
IWorkbenchWindow win = wb.getActiveWorkbenchWindow();
IWorkbenchPage page = win.getActivePage();
String[] wizardIds = page.getNewWizardShortcuts();
projectWizardActions.clear();
for (String wizardId : wizardIds) {
IWizardRegistry newWizardRegistry = WorkbenchPlugin.getDefault().getNewWizardRegistry();
IWizardDescriptor wizardDesc = newWizardRegistry.findWizard(wizardId);
if (wizardDesc == null) {
continue;
}
String[] tags = wizardDesc.getTags();
for (String tag : tags) {
if (WorkbenchWizardElement.TAG_PROJECT.equals(tag)) {
IAction action = getAction(newWizardRegistry, wizardId);
if (action != null) {
projectWizardActions.add(action);
}
}
}
}
}
private void createOption(Composite optionsArea, final FormToolkit toolkit, final Color linkColor, IAction action,
ImageDescriptor imageDesc, String text) {
Label addLabel = new Label(optionsArea, SWT.NONE);
addLabel.setImage(resourceManager.createImage(imageDesc));
Hyperlink addLink = toolkit.createHyperlink(optionsArea, text, SWT.WRAP);
addLink.setForeground(linkColor);
addLink.addHyperlinkListener(new HyperlinkAdapter() {
@Override
public void linkActivated(HyperlinkEvent e) {
action.run();
}
});
GridDataFactory.swtDefaults().align(SWT.FILL, SWT.FILL).grab(true, false).applyTo(addLink);
}
private IAction getAction(IWizardRegistry registry, String id) {
IWizardDescriptor wizardDesc = registry.findWizard(id);
WizardShortcutAction action = null;
IWorkbench wb = PlatformUI.getWorkbench();
IWorkbenchWindow win = wb.getActiveWorkbenchWindow();
if (wizardDesc != null) {
action = new WizardShortcutAction(win, wizardDesc);
}
return action;
}
private Runnable switchTopControlRunnable = () -> {
if (switchTopControl()) {
displayArea.requestLayout();
}
};
private boolean switchTopControl() {
if (control == null || control.isDisposed()) {
return false;
}
Control oldTop = layout.topControl;
IProject[] projs = ResourcesPlugin.getWorkspace().getRoot().getProjects();
if (projs.length > 0) {
layout.topControl = control;
disposeEmptyArea();
} else {
if (emptyArea == null || emptyArea.isDisposed()) {
recreateEmptyArea();
}
layout.topControl = emptyArea;
}
return oldTop != layout.topControl;
}
private class Listener
implements IResourceChangeListener, IPerspectiveListener, IPropertyChangeListener, DisposeListener {
/**
* Listener to switch between the "original" control and the empty area. If no
* projects exist in the workspace the empty area is shown. If at least one
* project exists in the workspace the "original" control is shown.
*
* @noreference This method is not intended to be referenced by clients.
*/
@Override
public void resourceChanged(IResourceChangeEvent event) {
IResourceDelta resourceDelta = event.getDelta();
if (resourceDelta != null) {
IResourceDelta[] affectedChildren = resourceDelta.getAffectedChildren();
for (IResourceDelta affectedChildResourceDelta : affectedChildren) {
IResource resource = affectedChildResourceDelta.getResource();
int kind = affectedChildResourceDelta.getKind();
if (resource instanceof IProject
&& (kind == IResourceDelta.ADDED || kind == IResourceDelta.REMOVED)) {
PlatformUI.getWorkbench().getDisplay().asyncExec(
() -> PlatformUI.getWorkbench().getDisplay().timerExec(200, switchTopControlRunnable));
return;
}
}
}
}
/**
* update the list of available project wizards and refresh empty area
*
* @noreference This method is not intended to be referenced by clients.
*/
@Override
public void perspectiveActivated(IWorkbenchPage page, IPerspectiveDescriptor perspective) {
readProjectWizardActions();
if (emptyArea != null) {
recreateEmptyArea();
switchTopControlRunnable.run();
}
}
/**
* @noreference This method is not intended to be referenced by clients.
*/
@Override
public void perspectiveChanged(IWorkbenchPage page, IPerspectiveDescriptor perspective, String changeId) {
// do nothing
}
/**
* React on changes to the hyperlink color
*
* @noreference This method is not intended to be referenced by clients.
*/
@Override
public void propertyChange(PropertyChangeEvent event) {
if (emptyArea != null && JFacePreferences.HYPERLINK_COLOR.equals(event.getProperty())) {
recreateEmptyArea();
switchTopControlRunnable.run();
}
}
@Override
public void widgetDisposed(DisposeEvent e) {
dispose(this);
}
}
private static class ImportAction extends Action {
@Override
public void run() {
IHandlerService handlerService = PlatformUI.getWorkbench().getActiveWorkbenchWindow()
.getService(IHandlerService.class);
try {
handlerService.executeCommand("org.eclipse.ui.file.import", null); //$NON-NLS-1$
} catch (Exception ex) {
IDEWorkbenchPlugin.log(this.getClass(), "run", ex); //$NON-NLS-1$
}
}
}
}