/*******************************************************************************
* Copyright (c) 2012 - 2013 Pivotal Software, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal Software, Inc. - initial API and implementation
*******************************************************************************/
package org.springsource.ide.eclipse.dashboard.internal.ui.editors;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.eclipse.core.commands.Command;
import org.eclipse.core.expressions.EvaluationContext;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtension;
import org.eclipse.core.runtime.IExtensionPoint;
import org.eclipse.core.runtime.IExtensionRegistry;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.ISafeRunnable;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.SafeRunner;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.resource.CompositeImageDescriptor;
import org.eclipse.mylyn.commons.core.StatusHandler;
import org.eclipse.mylyn.internal.tasks.ui.TasksUiPlugin;
import org.eclipse.mylyn.internal.tasks.ui.editors.EditorUtil;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.IPartListener;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.commands.ICommandService;
import org.eclipse.ui.forms.editor.SharedHeaderFormEditor;
import org.eclipse.ui.forms.widgets.FormToolkit;
import org.eclipse.ui.handlers.IHandlerService;
import org.eclipse.ui.internal.WorkbenchPlugin;
import org.eclipse.ui.internal.forms.widgets.BusyIndicator;
import org.eclipse.ui.internal.forms.widgets.FormHeading;
import org.eclipse.ui.internal.forms.widgets.TitleRegion;
import org.springsource.ide.eclipse.commons.ui.UiUtil;
import org.springsource.ide.eclipse.dashboard.internal.ui.IIdeUiConstants;
import org.springsource.ide.eclipse.dashboard.internal.ui.IdeUiPlugin;
import org.springsource.ide.eclipse.dashboard.internal.ui.discovery.DashboardExtensionsPage;
import org.springsource.ide.eclipse.dashboard.ui.AbstractDashboardPage;
import org.springsource.ide.eclipse.dashboard.ui.IEnabledDashboardPart;
/**
* The editor for the dashboard.
* <p>
* Note: Must not have any dependencies on org.eclipse.mylyn.tasks.ui to avoid
* class loader warnings.
* @author Wesley Coelho
* @author Steffen Pingel
* @author Leo Dos Santos
* @author Terry Denney
* @author Christian Dupuis
*/
public class MultiPageDashboardEditor extends SharedHeaderFormEditor {
private static class ExtensionPageDescriptor extends PageDescriptor {
private final IConfigurationElement element;
public ExtensionPageDescriptor(IConfigurationElement element) {
super(element.getAttribute(ATTRIBUTE_ID));
this.element = element;
setLabel(element.getAttribute(ATTRIBUTE_LABEL));
setPath(element.getAttribute(ATTRIBUTE_PATH));
}
@Override
public AbstractDashboardPage createPage() {
try {
Object object = WorkbenchPlugin.createExtension(element, ATTRIBUTE_CLASS);
if (!(object instanceof AbstractDashboardPage)) {
StatusHandler.log(new Status(IStatus.ERROR, IdeUiPlugin.PLUGIN_ID, "Could not load "
+ object.getClass().getCanonicalName() + " must implement "
+ AbstractDashboardPage.class.getCanonicalName()));
return null;
}
return (AbstractDashboardPage) object;
}
catch (CoreException e) {
StatusHandler.log(new Status(IStatus.ERROR, IdeUiPlugin.PLUGIN_ID,
"Could not read dashboard extension", e));
}
return null;
}
}
private static abstract class PageDescriptor {
private final String id;
private String path;
private String label;
public PageDescriptor(String id) {
Assert.isNotNull(id);
this.id = id;
}
public abstract AbstractDashboardPage createPage();
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
PageDescriptor other = (PageDescriptor) obj;
return id.equals(other.id);
}
public final String getId() {
return id;
}
public String getLabel() {
return label;
}
public final String getPath() {
return path;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + id.hashCode();
return result;
}
public final PageDescriptor setLabel(String label) {
this.label = label;
return this;
}
public final PageDescriptor setPath(String path) {
this.path = path;
return this;
}
}
private static final String EXTENSION_ID_DASHBOARD = "com.springsource.sts.ide.ui.dashboard";
private static final String ELEMENT_PAGE = "page";
private static final String ATTRIBUTE_ID = "id";
private static final String ATTRIBUTE_LABEL = "label";
private static final String ATTRIBUTE_PATH = "path";
private static final String ATTRIBUTE_CLASS = "class";
private static final int LEFT_TOOLBAR_HEADER_TOOLBAR_PADDING = 3;
private static final int VERTICAL_TOOLBAR_PADDING = 11;
private static final String TITLE = "Spring Dashboard";
public static String EDITOR_ID = "com.springsource.sts.internal.ide.ui.editors.MultiPageDashboardEditor";
public static final String NEW_EDITOR_ID = "org.springsource.ide.eclipse.commons.gettingstarted.dashboard.WelcomeDashboard";
/**
* Copied from TasksUiInternal to avoid initialization of
* org.eclipse.mylyn.tasks.ui.
*/
private static EvaluationContext createDiscoveryWizardEvaluationContext(IHandlerService handlerService) {
EvaluationContext evaluationContext = new EvaluationContext(handlerService.getCurrentState(), Platform.class);
// must specify this variable otherwise the PlatformPropertyTester won't
// work
evaluationContext.addVariable("platform", Platform.class); //$NON-NLS-1$
return evaluationContext;
}
/**
* Copied from TasksUiInternal to avoid initialization of
* org.eclipse.mylyn.tasks.ui.
*/
private static Command getConfiguredDiscoveryWizardCommand() {
ICommandService service = (ICommandService) PlatformUI.getWorkbench().getService(ICommandService.class);
final Command discoveryWizardCommand = service
.getCommand("org.eclipse.mylyn.discovery.ui.discoveryWizardCommand"); //$NON-NLS-1$
if (discoveryWizardCommand != null) {
IHandlerService handlerService = (IHandlerService) PlatformUI.getWorkbench().getService(
IHandlerService.class);
EvaluationContext evaluationContext = createDiscoveryWizardEvaluationContext(handlerService);
// update enabled state in case something has changed (ProxyHandler
// caches state)
discoveryWizardCommand.setEnabled(evaluationContext);
}
return discoveryWizardCommand;
}
private BusyIndicator busyLabel;
private Label titleLabel;
private Image headerImage;
private DashboardMainPage mainPage;
private IPartListener partListener;
public MultiPageDashboardEditor() {
DashboardReopener.ensure();
}
@Override
protected void addPages() {
try {
List<PageDescriptor> pageDescriptors = readExtensions();
mainPage = new DashboardMainPage(this);
int index = addPage(mainPage);
setPageText(index, "Dashboard");
addPages(pageDescriptors, "tasks");
addPages(pageDescriptors, "knowledgeBase");
DashboardExtensionsPage exPage = new DashboardExtensionsPage(this);
if (exPage.shouldAdd()) {
Command discoveryWizardCommand = getConfiguredDiscoveryWizardCommand();
if (discoveryWizardCommand != null && discoveryWizardCommand.isEnabled()) {
index = addPage(exPage);
setPageText(index, "Extensions");
}
}
addPages(pageDescriptors, "additions");
getToolkit().decorateFormHeading(getHeaderForm().getForm().getForm());
}
catch (PartInitException e) {
IdeUiPlugin.log(e);
}
}
private List<AbstractDashboardPage> addPages(List<PageDescriptor> descriptors, String path) {
final List<AbstractDashboardPage> pages = new ArrayList<AbstractDashboardPage>();
final Iterator<PageDescriptor> it = descriptors.iterator();
while (it.hasNext()) {
final PageDescriptor descriptor = it.next();
// only add selected pages
if (path != null && !path.equals(descriptor.getPath())) {
continue;
}
SafeRunner.run(new ISafeRunnable() {
public void handleException(Throwable e) {
StatusHandler.log(new Status(IStatus.ERROR, IdeUiPlugin.PLUGIN_ID,
"Error creating dashboard page: \"" + descriptor.getId() + "\"", e)); //$NON-NLS-1$ //$NON-NLS-2$
}
public void run() throws Exception {
AbstractDashboardPage page = descriptor.createPage();
if (page != null) {
page.initialize(MultiPageDashboardEditor.this);
if (page instanceof IEnabledDashboardPart) {
if (!((IEnabledDashboardPart) page).shouldAdd()) {
return;
}
}
int index = addPage(page);
setPageText(index, descriptor.getLabel());
pages.add(page);
}
it.remove();
}
});
}
return pages;
}
@Override
public void close(boolean save) {
mainPage.cancelUnfinishedJobs();
super.close(save);
}
@Override
protected Composite createPageContainer(Composite parent) {
Composite composite = super.createPageContainer(parent);
EditorUtil.initializeScrollbars(getHeaderForm().getForm());
// create title label that replaces form heading label
try {
FormHeading heading = (FormHeading) getHeaderForm().getForm().getForm().getHead();
Field field = FormHeading.class.getDeclaredField("titleRegion"); //$NON-NLS-1$
field.setAccessible(true);
TitleRegion titleRegion = (TitleRegion) field.get(heading);
titleLabel = new Label(titleRegion, SWT.NONE);
titleLabel.setForeground(heading.getForeground());
titleLabel.setFont(heading.getFont());
titleLabel.setText("Spring Tool Suite");
titleLabel.setVisible(true);
getBusyLabel();
titleRegion.addControlListener(new ControlAdapter() {
@Override
public void controlResized(ControlEvent e) {
// do not create busyLabel to avoid recursion
updateSizeAndLocations();
}
});
}
catch (Exception e) {
// if (!toolBarFailureLogged) {
StatusHandler.log(new Status(IStatus.ERROR, TasksUiPlugin.ID_PLUGIN,
"Failed to obtain busy label toolbar", e)); //$NON-NLS-1$
// }
if (titleLabel != null) {
titleLabel.dispose();
titleLabel = null;
}
}
updateHeaderLabel();
return composite;
}
@Override
protected FormToolkit createToolkit(Display display) {
return new FormToolkit(UiUtil.getFormColors(display));
}
@Override
public void doSave(IProgressMonitor monitor) {
// Nothing to do
}
@Override
public void doSaveAs() {
// Nothing to do
}
private BusyIndicator getBusyLabel() {
if (busyLabel != null) {
return busyLabel;
}
try {
FormHeading heading = (FormHeading) getHeaderForm().getForm().getForm().getHead();
// ensure that busy label exists
heading.setBusy(true);
heading.setBusy(false);
Field field = FormHeading.class.getDeclaredField("titleRegion"); //$NON-NLS-1$
field.setAccessible(true);
TitleRegion titleRegion = (TitleRegion) field.get(heading);
for (Control child : titleRegion.getChildren()) {
if (child instanceof BusyIndicator) {
busyLabel = (BusyIndicator) child;
}
}
if (busyLabel == null) {
return null;
}
busyLabel.addControlListener(new ControlAdapter() {
@Override
public void controlMoved(ControlEvent e) {
updateSizeAndLocations();
}
});
// the busy label may get disposed if it has no image
busyLabel.addDisposeListener(new DisposeListener() {
public void widgetDisposed(DisposeEvent e) {
busyLabel.setMenu(null);
busyLabel = null;
}
});
if (titleLabel != null) {
titleLabel.moveAbove(busyLabel);
}
updateSizeAndLocations();
return busyLabel;
}
catch (Exception e) {
StatusHandler
.log(new Status(IStatus.ERROR, IdeUiPlugin.PLUGIN_ID, "Failed to obtain busy label toolbar", e)); //$NON-NLS-1$
busyLabel = null;
}
return busyLabel;
}
@Override
public void init(IEditorSite site, IEditorInput editorInput) throws PartInitException {
setSite(site);
setInput(editorInput);
site.getPage().addPartListener(this.partListener = new IPartListener() {
@Override
public void partOpened(IWorkbenchPart part) {
}
@Override
public void partDeactivated(IWorkbenchPart part) {
}
@Override
public void partClosed(IWorkbenchPart part) {
if (MultiPageDashboardEditor.this==part) {
IPreferenceStore prefs = IdeUiPlugin.getDefault().getPreferenceStore();
prefs.setValue(IIdeUiConstants.PREF_OPEN_DASHBOARD_STARTUP, false);
disposeListeners();
}
}
@Override
public void partBroughtToTop(IWorkbenchPart part) {
}
@Override
public void partActivated(IWorkbenchPart part) {
}
});
}
private void disposeListeners() {
IWorkbenchPage page = getSite().getPage();
if (page!=null && partListener!=null) {
page.removePartListener(partListener);
}
partListener = null;
}
@Override
public boolean isDirty() {
return false;
}
@Override
public boolean isSaveAsAllowed() {
return false;
}
private List<PageDescriptor> readExtensions() {
List<PageDescriptor> pageDescriptors = new ArrayList<PageDescriptor>();
IExtensionRegistry registry = Platform.getExtensionRegistry();
IExtensionPoint extensionPoint = registry.getExtensionPoint(EXTENSION_ID_DASHBOARD);
IExtension[] extensions = extensionPoint.getExtensions();
for (IExtension extension : extensions) {
IConfigurationElement[] elements = extension.getConfigurationElements();
for (IConfigurationElement element : elements) {
if (element.getName().equals(ELEMENT_PAGE)) {
pageDescriptors.add(new ExtensionPageDescriptor(element));
}
}
}
return pageDescriptors;
}
private void setHeaderImage(final Image image) {
BusyIndicator busyLabel = getBusyLabel();
if (busyLabel == null) {
return;
}
final Point size = busyLabel.getSize();
Point titleSize = titleLabel.computeSize(SWT.DEFAULT, SWT.DEFAULT, true);
size.x += titleSize.x + LEFT_TOOLBAR_HEADER_TOOLBAR_PADDING;
size.y = Math.max(titleSize.y, size.y) + VERTICAL_TOOLBAR_PADDING;
// ensure image is at least one pixel wide to avoid SWT error
final int padding = (size.x > 0) ? 10 : 1;
final Rectangle imageBounds = (image != null) ? image.getBounds() : new Rectangle(0, 0, 0, 0);
int tempHeight = (image != null) ? Math.max(size.y + 1, imageBounds.height) : size.y + 1;
// avoid extra padding due to margin added by TitleRegion.VMARGIN
final int height = (tempHeight > imageBounds.height + 5) ? tempHeight - 5 : tempHeight;
CompositeImageDescriptor descriptor = new CompositeImageDescriptor() {
@Override
protected void drawCompositeImage(int width, int height) {
if (image != null) {
drawImage(image.getImageData(), size.x + padding, (height - image.getBounds().height) / 2);
}
}
@Override
protected Point getSize() {
return new Point(size.x + padding + imageBounds.width, height);
}
};
Image newHeaderImage = descriptor.createImage();
// directly set on busyLabel since getHeaderForm().getForm().setImage()
// does not update
// the image if a message is currently displayed
busyLabel.setImage(newHeaderImage);
if (headerImage != null) {
headerImage.dispose();
}
headerImage = newHeaderImage;
// avoid extra padding due to large title font
getHeaderForm().getForm().reflow(true);
}
private void updateHeaderLabel() {
if (titleLabel != null) {
titleLabel.setText(TITLE);
getHeaderForm().getForm().setText(null);
setHeaderImage(null);
}
else {
getHeaderForm().getForm().setText(TITLE);
}
}
private void updateSizeAndLocations() {
// Point leftToolBarSize = new Point(0, 0);
if (titleLabel != null && !titleLabel.isDisposed()) {
// center align title text in title region
Point size = titleLabel.computeSize(SWT.DEFAULT, SWT.DEFAULT, true);
// int y = (titleLabel.getParent().computeSize(SWT.DEFAULT,
// SWT.DEFAULT, true).y - size.y) / 2;
titleLabel.setBounds(busyLabel.getLocation().x + LEFT_TOOLBAR_HEADER_TOOLBAR_PADDING,
busyLabel.getLocation().y + VERTICAL_TOOLBAR_PADDING, size.x + 200, size.y + 200);
}
}
}