Java tutorial
/******************************************************************************* * Copyright (c) 2008, 2013 IBM Corporation and others. * 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: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.e4.ui.internal.workbench.swt; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.inject.Inject; import javax.inject.Named; import org.eclipse.core.databinding.observable.Realm; import org.eclipse.core.runtime.FileLocator; import org.eclipse.core.runtime.ISafeRunnable; import org.eclipse.core.runtime.SafeRunner; import org.eclipse.e4.core.contexts.ContextInjectionFactory; import org.eclipse.e4.core.contexts.EclipseContextFactory; import org.eclipse.e4.core.contexts.IEclipseContext; import org.eclipse.e4.core.di.InjectionException; import org.eclipse.e4.core.di.annotations.Optional; import org.eclipse.e4.core.services.contributions.IContributionFactory; import org.eclipse.e4.core.services.events.IEventBroker; import org.eclipse.e4.core.services.log.Logger; import org.eclipse.e4.core.services.statusreporter.StatusReporter; import org.eclipse.e4.ui.bindings.keys.KeyBindingDispatcher; import org.eclipse.e4.ui.css.core.util.impl.resources.OSGiResourceLocator; import org.eclipse.e4.ui.css.swt.dom.WidgetElement; import org.eclipse.e4.ui.css.swt.engine.CSSSWTEngineImpl; import org.eclipse.e4.ui.css.swt.theme.IThemeEngine; import org.eclipse.e4.ui.css.swt.theme.IThemeManager; import org.eclipse.e4.ui.di.Focus; import org.eclipse.e4.ui.di.PersistState; import org.eclipse.e4.ui.internal.workbench.Activator; import org.eclipse.e4.ui.internal.workbench.E4Workbench; import org.eclipse.e4.ui.internal.workbench.Policy; import org.eclipse.e4.ui.model.application.MApplication; import org.eclipse.e4.ui.model.application.MApplicationElement; import org.eclipse.e4.ui.model.application.MContribution; import org.eclipse.e4.ui.model.application.ui.MContext; import org.eclipse.e4.ui.model.application.ui.MElementContainer; import org.eclipse.e4.ui.model.application.ui.MGenericStack; import org.eclipse.e4.ui.model.application.ui.MUIElement; import org.eclipse.e4.ui.model.application.ui.advanced.MPerspective; import org.eclipse.e4.ui.model.application.ui.advanced.MPlaceholder; import org.eclipse.e4.ui.model.application.ui.basic.MTrimmedWindow; import org.eclipse.e4.ui.model.application.ui.basic.MWindow; import org.eclipse.e4.ui.model.application.ui.menu.MMenu; import org.eclipse.e4.ui.model.application.ui.menu.MToolBar; import org.eclipse.e4.ui.services.IStylingEngine; import org.eclipse.e4.ui.workbench.IPresentationEngine; import org.eclipse.e4.ui.workbench.IResourceUtilities; import org.eclipse.e4.ui.workbench.IWorkbench; import org.eclipse.e4.ui.workbench.UIEvents; import org.eclipse.e4.ui.workbench.modeling.EModelService; import org.eclipse.e4.ui.workbench.swt.factories.IRendererFactory; import org.eclipse.emf.ecore.EObject; import org.eclipse.equinox.app.IApplication; import org.eclipse.equinox.app.IApplicationContext; import org.eclipse.jface.bindings.keys.SWTKeySupport; import org.eclipse.jface.bindings.keys.formatting.KeyFormatterFactory; import org.eclipse.jface.databinding.swt.SWTObservables; import org.eclipse.osgi.util.NLS; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.CTabFolder; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Widget; import org.eclipse.ui.testing.TestableObject; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceReference; import org.osgi.service.event.Event; import org.osgi.service.event.EventHandler; import org.w3c.dom.Element; import org.w3c.dom.css.CSSStyleDeclaration; public class PartRenderingEngine implements IPresentationEngine { public static final String EARLY_STARTUP_HOOK = "runEarlyStartup"; public static final String engineURI = "bundleclass://org.eclipse.e4.ui.workbench.swt/" + "org.eclipse.e4.ui.internal.workbench.swt.PartRenderingEngine"; private static final String defaultFactoryUrl = "bundleclass://org.eclipse.e4.ui.workbench.renderers.swt/" + "org.eclipse.e4.ui.workbench.renderers.swt.WorkbenchRendererFactory"; private String factoryUrl; IRendererFactory curFactory = null; private Map<String, AbstractPartRenderer> customRendererMap = new HashMap<String, AbstractPartRenderer>(); org.eclipse.swt.widgets.Listener keyListener; // Life Cycle handlers private EventHandler toBeRenderedHandler = new EventHandler() { public void handleEvent(Event event) { MUIElement changedElement = (MUIElement) event.getProperty(UIEvents.EventTags.ELEMENT); MElementContainer<?> parent = changedElement.getParent(); // Handle Detached Windows if (parent == null) { parent = (MElementContainer<?>) ((EObject) changedElement).eContainer(); } boolean menuChild = parent instanceof MMenu; // If the parent isn't displayed who cares? if (!(parent instanceof MApplication) && (parent == null || parent.getWidget() == null || menuChild)) return; if (changedElement.isToBeRendered()) { Activator.trace(Policy.DEBUG_RENDERER, "visible -> true", null); //$NON-NLS-1$ // Note that the 'createGui' protocol calls 'childAdded' Object w = createGui(changedElement); if (w instanceof Control && !(w instanceof Shell)) { fixZOrder(changedElement); } } else { Activator.trace(Policy.DEBUG_RENDERER, "visible -> false", null); //$NON-NLS-1$ // Ensure that the element about to be removed is not the // selected element if (parent.getSelectedElement() == changedElement) parent.setSelectedElement(null); // Un-maximize the element before tearing it down if (changedElement.getTags().contains(MAXIMIZED)) changedElement.getTags().remove(MAXIMIZED); // Note that the 'removeGui' protocol calls 'childRemoved' removeGui(changedElement); } } }; private EventHandler visibilityHandler = new EventHandler() { public void handleEvent(Event event) { MUIElement changedElement = (MUIElement) event.getProperty(UIEvents.EventTags.ELEMENT); MUIElement parent = changedElement.getParent(); if (parent == null) { parent = (MUIElement) ((EObject) changedElement).eContainer(); if (parent == null) { return; } } AbstractPartRenderer renderer = (AbstractPartRenderer) parent.getRenderer(); if (renderer == null || parent instanceof MToolBar) return; // Re-parent the control based on the visible state if (changedElement.isVisible()) { if (changedElement.isToBeRendered()) { if (changedElement.getWidget() instanceof Control) { // Ensure that the control is under its 'real' parent if // it's visible Composite realComp = (Composite) renderer.getUIContainer(changedElement); Control ctrl = (Control) changedElement.getWidget(); ctrl.setParent(realComp); fixZOrder(changedElement); } if (parent instanceof MElementContainer<?>) { renderer.childRendered((MElementContainer<MUIElement>) parent, changedElement); } } } else { // Put the control under the 'limbo' shell if (changedElement.getWidget() instanceof Control) { Control ctrl = (Control) changedElement.getWidget(); if (!(ctrl instanceof Shell)) { ctrl.getShell().layout(new Control[] { ctrl }, SWT.DEFER); } ctrl.setParent(getLimboShell()); } if (parent instanceof MElementContainer<?>) { renderer.hideChild((MElementContainer<MUIElement>) parent, changedElement); } } } }; private EventHandler trimHandler = new EventHandler() { public void handleEvent(Event event) { Object changedObj = event.getProperty(UIEvents.EventTags.ELEMENT); if (!(changedObj instanceof MTrimmedWindow)) return; MTrimmedWindow window = (MTrimmedWindow) changedObj; if (window.getWidget() == null) return; if (UIEvents.isADD(event)) { for (Object o : UIEvents.asIterable(event, UIEvents.EventTags.NEW_VALUE)) { MUIElement added = (MUIElement) o; if (added.isToBeRendered()) createGui(added, window.getWidget(), window.getContext()); } } else if (UIEvents.isREMOVE(event)) { for (Object o : UIEvents.asIterable(event, UIEvents.EventTags.NEW_VALUE)) { MUIElement removed = (MUIElement) o; if (removed.getRenderer() != null) removeGui(removed); } } } }; private EventHandler childrenHandler = new EventHandler() { public void handleEvent(Event event) { Object changedObj = event.getProperty(UIEvents.EventTags.ELEMENT); if (!(changedObj instanceof MElementContainer<?>)) return; MElementContainer<MUIElement> changedElement = (MElementContainer<MUIElement>) changedObj; boolean isApplication = changedObj instanceof MApplication; boolean menuChild = changedObj instanceof MMenu; // If the parent isn't in the UI then who cares? AbstractPartRenderer renderer = getRendererFor(changedElement); if ((!isApplication && renderer == null) || menuChild) return; if (UIEvents.isADD(event)) { Activator.trace(Policy.DEBUG_RENDERER, "Child Added", null); //$NON-NLS-1$ for (Object o : UIEvents.asIterable(event, UIEvents.EventTags.NEW_VALUE)) { MUIElement added = (MUIElement) o; // OK, we have a new -visible- part we either have to create // it or host it under the correct parent. Note that we // explicitly do *not* render non-selected elements in // stacks (to support lazy loading). boolean isStack = changedObj instanceof MGenericStack<?>; boolean hasWidget = added.getWidget() != null; boolean isSelected = added == changedElement.getSelectedElement(); boolean renderIt = !isStack || hasWidget || isSelected; if (renderIt) { // NOTE: createGui will call 'childAdded' if successful Object w = createGui(added); if (w instanceof Control && !(w instanceof Shell)) { final Control ctrl = (Control) w; fixZOrder(added); if (!ctrl.isDisposed()) { ctrl.getShell().layout(new Control[] { ctrl }, SWT.DEFER); } } } else { if (renderer != null && added.isToBeRendered()) renderer.childRendered(changedElement, added); } // If the element being added is a placeholder, check to see // if // it's 'globally visible' and, if so, remove all other // 'local' placeholders referencing the same element. int newLocation = modelService.getElementLocation(added); if (newLocation == EModelService.IN_SHARED_AREA || newLocation == EModelService.OUTSIDE_PERSPECTIVE) { MWindow topWin = modelService.getTopLevelWindowFor(added); modelService.hideLocalPlaceholders(topWin, null); } } } else if (UIEvents.isREMOVE(event)) { Activator.trace(Policy.DEBUG_RENDERER, "Child Removed", null); //$NON-NLS-1$ for (Object o : UIEvents.asIterable(event, UIEvents.EventTags.OLD_VALUE)) { MUIElement removed = (MUIElement) o; // Removing invisible elements is a NO-OP as far as the // renderer is concerned if (!removed.isToBeRendered()) continue; if (removed.getWidget() instanceof Control) { Control ctrl = (Control) removed.getWidget(); ctrl.setLayoutData(null); ctrl.getParent().layout(new Control[] { ctrl }, SWT.CHANGED | SWT.DEFER); } // Ensure that the element about to be removed is not the // selected element if (changedElement.getSelectedElement() == removed) changedElement.setSelectedElement(null); if (renderer != null) renderer.hideChild(changedElement, removed); } } } }; private EventHandler windowsHandler = new EventHandler() { public void handleEvent(Event event) { childrenHandler.handleEvent(event); } }; private IEclipseContext appContext; protected Shell testShell; protected MApplication theApp; @Inject @Optional protected IEventBroker eventBroker; @Inject EModelService modelService; @Inject protected Logger logger; private Shell limbo; private MUIElement removeRoot = null; @Inject public PartRenderingEngine(@Named(E4Workbench.RENDERER_FACTORY_URI) @Optional String factoryUrl) { if (factoryUrl == null) { factoryUrl = defaultFactoryUrl; } this.factoryUrl = factoryUrl; } protected void fixZOrder(MUIElement element) { MElementContainer<MUIElement> parent = element.getParent(); if (parent == null) { Object container = ((EObject) element).eContainer(); if (container instanceof MElementContainer<?>) { parent = (MElementContainer<MUIElement>) container; } } if (parent == null || !(element.getWidget() instanceof Control)) return; Control elementCtrl = (Control) element.getWidget(); Control prevCtrl = null; for (MUIElement kid : parent.getChildren()) { if (kid == element) { if (prevCtrl != null) elementCtrl.moveBelow(prevCtrl); else elementCtrl.moveAbove(null); break; } else if (kid.getWidget() instanceof Control) { prevCtrl = (Control) kid.getWidget(); } } Object widget = parent.getWidget(); if (widget instanceof Composite) { Composite composite = (Composite) widget; if (composite.getShell() == elementCtrl.getShell()) { Composite temp = elementCtrl.getParent(); while (temp != composite) { if (temp == null) { return; } temp = temp.getParent(); } composite.layout(true, true); } } } /** * Initialize a part renderer from the extension point. * * @param context * the context for the part factories */ @PostConstruct void initialize(IEclipseContext context) { this.appContext = context; // initialize the correct key-binding display formatter KeyFormatterFactory.setDefault(SWTKeySupport.getKeyFormatterForPlatform()); // Add the renderer to the context context.set(IPresentationEngine.class.getName(), this); IRendererFactory factory = null; IContributionFactory contribFactory = context.get(IContributionFactory.class); try { factory = (IRendererFactory) contribFactory.create(factoryUrl, context); } catch (Exception e) { logger.warn(e, "Could not create rendering factory"); } // Try to load the default one if (factory == null) { try { factory = (IRendererFactory) contribFactory.create(defaultFactoryUrl, context); } catch (Exception e) { logger.error(e, "Could not create default rendering factory"); } } if (factory == null) { throw new IllegalStateException("Could not create any rendering factory. Aborting ..."); } curFactory = factory; context.set(IRendererFactory.class, curFactory); // Hook up the widget life-cycle subscriber if (eventBroker != null) { eventBroker.subscribe(UIEvents.UIElement.TOPIC_TOBERENDERED, toBeRenderedHandler); eventBroker.subscribe(UIEvents.UIElement.TOPIC_VISIBLE, visibilityHandler); eventBroker.subscribe(UIEvents.ElementContainer.TOPIC_CHILDREN, childrenHandler); eventBroker.subscribe(UIEvents.Window.TOPIC_WINDOWS, windowsHandler); eventBroker.subscribe(UIEvents.Perspective.TOPIC_WINDOWS, windowsHandler); eventBroker.subscribe(UIEvents.TrimmedWindow.TOPIC_TRIMBARS, trimHandler); } } @PreDestroy void contextDisposed() { if (eventBroker == null) return; eventBroker.unsubscribe(toBeRenderedHandler); eventBroker.unsubscribe(visibilityHandler); eventBroker.unsubscribe(childrenHandler); eventBroker.unsubscribe(trimHandler); } private static void populateModelInterfaces(MContext contextModel, IEclipseContext context, Class<?>[] interfaces) { for (Class<?> intf : interfaces) { Activator.trace(Policy.DEBUG_CONTEXTS, "Adding " + intf.getName() + " for " //$NON-NLS-1$ //$NON-NLS-2$ + contextModel.getClass().getName(), null); context.set(intf.getName(), contextModel); populateModelInterfaces(contextModel, context, intf.getInterfaces()); } } private String getContextName(MUIElement element) { StringBuilder builder = new StringBuilder(element.getClass().getSimpleName()); String elementId = element.getElementId(); if (elementId != null && elementId.length() != 0) { builder.append(" (").append(elementId).append(") "); } builder.append("Context"); return builder.toString(); } public Object createGui(final MUIElement element, final Object parentWidget, final IEclipseContext parentContext) { final Object[] gui = { null }; // wrap the handling in a SafeRunner so that exceptions do not prevent // the renderer from processing other elements SafeRunner.run(new ISafeRunnable() { public void handleException(Throwable e) { if (e instanceof Error) { // errors are deadly, we shouldn't ignore these throw (Error) e; } else { // log exceptions otherwise if (logger != null) { String message = "Exception occurred while rendering: {0}"; //$NON-NLS-1$ logger.error(e, NLS.bind(message, element)); } } } public void run() throws Exception { gui[0] = safeCreateGui(element, parentWidget, parentContext); } }); return gui[0]; } public Object safeCreateGui(MUIElement element, Object parentWidget, IEclipseContext parentContext) { if (!element.isToBeRendered()) return null; // no creates while processing a remove if (removeRoot != null) { return null; } Object currentWidget = element.getWidget(); if (currentWidget != null) { if (currentWidget instanceof Control) { Control control = (Control) currentWidget; // make sure the control is visible if (!(element instanceof MPlaceholder)) control.setVisible(true); if (parentWidget instanceof Composite) { Composite currentParent = control.getParent(); if (currentParent != parentWidget) { // check if the original parent was a tab folder if (currentParent instanceof CTabFolder) { CTabFolder folder = (CTabFolder) currentParent; // if we used to be the tab folder's top right // control, unset it if (folder.getTopRight() == control) { folder.setTopRight(null); } } // the parents are different so we should reparent it control.setParent((Composite) parentWidget); } } } // Reparent the context (or the kid's context) if (element instanceof MContext) { IEclipseContext ctxt = ((MContext) element).getContext(); if (ctxt != null) ctxt.setParent(parentContext); } else { List<MContext> childContexts = modelService.findElements(element, null, MContext.class, null); for (MContext c : childContexts) { // Ensure that we only reset the context of our direct // children MUIElement kid = (MUIElement) c; MUIElement parent = kid.getParent(); if (parent == null && kid.getCurSharedRef() != null) parent = kid.getCurSharedRef().getParent(); if (!(element instanceof MPlaceholder) && parent != element) continue; if (c.getContext() != null && c.getContext().getParent() != parentContext) { c.getContext().setParent(parentContext); } } } // Now that we have a widget let the parent (if any) know if (element.getParent() instanceof MUIElement) { MElementContainer<MUIElement> parentElement = element.getParent(); AbstractPartRenderer parentRenderer = getRendererFor(parentElement); if (parentRenderer != null) parentRenderer.childRendered(parentElement, element); } return element.getWidget(); } if (element instanceof MContext) { MContext ctxt = (MContext) element; // Assert.isTrue(ctxt.getContext() == null, // "Before rendering Context should be null"); if (ctxt.getContext() == null) { IEclipseContext lclContext = parentContext.createChild(getContextName(element)); populateModelInterfaces(ctxt, lclContext, element.getClass().getInterfaces()); ctxt.setContext(lclContext); // System.out.println("New Context: " + lclContext.toString() // + " parent: " + parentContext.toString()); // make sure the context knows about these variables that have // been defined in the model for (String variable : ctxt.getVariables()) { lclContext.declareModifiable(variable); } Map<String, String> props = ctxt.getProperties(); for (String key : props.keySet()) { lclContext.set(key, props.get(key)); } } } // Create a control appropriate to the part Object newWidget = createWidget(element, parentWidget); // Remember that we've created the control if (newWidget != null) { AbstractPartRenderer renderer = getRendererFor(element); // Have the renderer hook up any widget specific listeners renderer.hookControllerLogic(element); // Process its internal structure through the renderer that created // it if (element instanceof MElementContainer) { renderer.processContents((MElementContainer<MUIElement>) element); } // Allow a final chance to set up renderer.postProcess(element); // Now that we have a widget let the parent (if any) know if (element.getParent() instanceof MUIElement) { MElementContainer<MUIElement> parentElement = element.getParent(); AbstractPartRenderer parentRenderer = getRendererFor(parentElement); if (parentRenderer != null) parentRenderer.childRendered(parentElement, element); } } else { // failed to create the widget, dispose its context if necessary if (element instanceof MContext) { MContext ctxt = (MContext) element; IEclipseContext lclContext = ctxt.getContext(); if (lclContext != null) { lclContext.dispose(); ctxt.setContext(null); } } } return newWidget; } private IEclipseContext getContext(MUIElement parent) { if (parent instanceof MContext) { return ((MContext) parent).getContext(); } return modelService.getContainingContext(parent); } public Object createGui(final MUIElement element) { final Object[] gui = { null }; // wrap the handling in a SafeRunner so that exceptions do not prevent // the renderer from processing other elements SafeRunner.run(new ISafeRunnable() { public void handleException(Throwable e) { if (e instanceof Error) { // errors are deadly, we shouldn't ignore these throw (Error) e; } else { // log exceptions otherwise if (logger != null) { String message = "Exception occurred while rendering: {0}"; //$NON-NLS-1$ logger.error(e, NLS.bind(message, element)); } } } public void run() throws Exception { gui[0] = safeCreateGui(element); } }); return gui[0]; } private Object safeCreateGui(MUIElement element) { // Obtain the necessary parent widget Object parent = null; MUIElement parentME = element.getParent(); if (parentME == null) parentME = (MUIElement) ((EObject) element).eContainer(); if (parentME != null) { AbstractPartRenderer renderer = getRendererFor(parentME); if (renderer != null) { if (!element.isVisible()) { parent = getLimboShell(); } else { parent = renderer.getUIContainer(element); } } } // Obtain the necessary parent context IEclipseContext parentContext = null; if (element.getCurSharedRef() != null) { MPlaceholder ph = element.getCurSharedRef(); parentContext = getContext(ph.getParent()); } else if (parentContext == null && element.getParent() != null) { parentContext = getContext(element.getParent()); } else if (parentContext == null && element.getParent() == null) { parentContext = getContext((MUIElement) ((EObject) element).eContainer()); } return safeCreateGui(element, parent, parentContext); } /* * (non-Javadoc) * * @see * org.eclipse.e4.ui.workbench.IPresentationEngine#focusGui(org.eclipse. * e4.ui.model.application.ui.MUIElement) */ public void focusGui(MUIElement element) { AbstractPartRenderer renderer = (AbstractPartRenderer) element.getRenderer(); if (renderer == null || element.getWidget() == null) return; Object implementation = element instanceof MContribution ? ((MContribution) element).getObject() : null; // If there is no class to call @Focus on then revert to the default if (!(element instanceof MContribution)) { renderer.forceFocus(element); return; } try { IEclipseContext context = getContext(element); Object defaultValue = new Object(); Object returnValue = ContextInjectionFactory.invoke(implementation, Focus.class, context, defaultValue); if (returnValue == defaultValue) { // No @Focus method, force the focus renderer.forceFocus(element); } } catch (InjectionException e) { log("Failed to grant focus to element", "Failed to grant focus to element ({0})", //$NON-NLS-1$ //$NON-NLS-2$ element.getElementId(), e); } catch (RuntimeException e) { log("Failed to grant focus to element via DI", //$NON-NLS-1$ "Failed to grant focus via DI to element ({0})", element.getElementId(), e); //$NON-NLS-1$ } } private void log(String unidentifiedMessage, String identifiedMessage, String id, Exception e) { if (id == null || id.length() == 0) { logger.error(e, unidentifiedMessage); } else { logger.error(e, NLS.bind(identifiedMessage, id)); } } private Shell getLimboShell() { if (limbo == null) { limbo = new Shell(Display.getCurrent(), SWT.NONE); limbo.setText("PartRenderingEngine's limbo"); //$NON-NLS-1$ // just for debugging, not shown anywhere // Place the limbo shell 'off screen' limbo.setLocation(0, 10000); limbo.setBackgroundMode(SWT.INHERIT_DEFAULT); limbo.setData(ShellActivationListener.DIALOG_IGNORE_KEY, Boolean.TRUE); } return limbo; } /** * @param element */ public void removeGui(final MUIElement element) { // wrap the handling in a SafeRunner so that exceptions do not prevent // the menu from being shown SafeRunner.run(new ISafeRunnable() { public void handleException(Throwable e) { if (e instanceof Error) { // errors are deadly, we shouldn't ignore these throw (Error) e; } else { // log exceptions otherwise if (logger != null) { String message = "Exception occurred while unrendering: {0}"; //$NON-NLS-1$ logger.error(e, NLS.bind(message, element)); } } } public void run() throws Exception { safeRemoveGui(element); } }); } private void safeRemoveGui(MUIElement element) { if (removeRoot == null) removeRoot = element; // We call 'hideChild' *before* checking if the actual element // has been rendered in order to pick up cases of 'lazy loading' MUIElement parent = element.getParent(); AbstractPartRenderer parentRenderer = parent != null ? getRendererFor(parent) : null; if (parentRenderer != null) { parentRenderer.hideChild(element.getParent(), element); } AbstractPartRenderer renderer = getRendererFor(element); // If the element hasn't been rendered then this is a NO-OP if (renderer != null) { if (element instanceof MElementContainer<?>) { MElementContainer<MUIElement> container = (MElementContainer<MUIElement>) element; MUIElement selectedElement = container.getSelectedElement(); List<MUIElement> children = container.getChildren(); for (MUIElement child : children) { // remove stuff in the "back" first if (child != selectedElement) { removeGui(child); } } if (selectedElement != null && children.contains(selectedElement)) { // now remove the selected element removeGui(selectedElement); } } if (element instanceof MPerspective) { MPerspective perspective = (MPerspective) element; for (MWindow subWindow : perspective.getWindows()) { removeGui(subWindow); } } else if (element instanceof MWindow) { MWindow window = (MWindow) element; for (MWindow subWindow : window.getWindows()) { removeGui(subWindow); } if (window instanceof MTrimmedWindow) { MTrimmedWindow trimmedWindow = (MTrimmedWindow) window; for (MUIElement trimBar : trimmedWindow.getTrimBars()) { removeGui(trimBar); } } } if (element instanceof MContribution) { MContribution contribution = (MContribution) element; Object client = contribution.getObject(); IEclipseContext parentContext = renderer.getContext(element); if (parentContext != null && client != null) { try { ContextInjectionFactory.invoke(client, PersistState.class, parentContext, null); } catch (Exception e) { if (logger != null) { logger.error(e); } } } } renderer.disposeWidget(element); // unset the client object if (element instanceof MContribution) { MContribution contribution = (MContribution) element; Object client = contribution.getObject(); IEclipseContext parentContext = renderer.getContext(element); if (parentContext != null && client != null) { try { ContextInjectionFactory.uninject(client, parentContext); } catch (Exception e) { if (logger != null) { logger.error(e); } } } contribution.setObject(null); } // dispose the context if (element instanceof MContext) { clearContext((MContext) element); } } if (removeRoot == element) removeRoot = null; } private void clearContext(MContext contextME) { MContext ctxt = (MContext) contextME; IEclipseContext lclContext = ctxt.getContext(); if (lclContext != null) { IEclipseContext parentContext = lclContext.getParent(); IEclipseContext child = parentContext.getActiveChild(); if (child == lclContext) { child.deactivate(); } ctxt.setContext(null); lclContext.dispose(); } } protected Object createWidget(MUIElement element, Object parent) { AbstractPartRenderer renderer = getRenderer(element, parent); if (renderer != null) { // Remember which renderer is responsible for this widget element.setRenderer(renderer); Object newWidget = renderer.createWidget(element, parent); if (newWidget != null) { renderer.bindWidget(element, newWidget); return newWidget; } } return null; } private AbstractPartRenderer getRenderer(MUIElement uiElement, Object parent) { // Is there a custom renderer defined ? String customURI = uiElement.getPersistedState().get(IPresentationEngine.CUSTOM_RENDERER_KEY); if (customURI != null) { if (customRendererMap.get(customURI) instanceof AbstractPartRenderer) return customRendererMap.get(customURI); IEclipseContext owningContext = modelService.getContainingContext(uiElement); IContributionFactory contributionFactory = (IContributionFactory) owningContext .get(IContributionFactory.class.getName()); Object customRenderer = contributionFactory.create(customURI, owningContext); if (customRenderer instanceof AbstractPartRenderer) { customRendererMap.put(customURI, (AbstractPartRenderer) customRenderer); return (AbstractPartRenderer) customRenderer; } } // If not then use the default renderer return curFactory.getRenderer(uiElement, parent); } protected AbstractPartRenderer getRendererFor(MUIElement element) { return (AbstractPartRenderer) element.getRenderer(); } public Object run(final MApplicationElement uiRoot, final IEclipseContext runContext) { final Display display; if (runContext.get(Display.class) != null) { display = runContext.get(Display.class); } else { display = Display.getDefault(); runContext.set(Display.class, display); } Realm.runWithDefault(SWTObservables.getRealm(display), new Runnable() { public void run() { initializeStyling(display, runContext); // Register an SWT resource handler runContext.set(IResourceUtilities.class.getName(), new ResourceUtility()); // set up the keybinding manager KeyBindingDispatcher dispatcher = (KeyBindingDispatcher) ContextInjectionFactory .make(KeyBindingDispatcher.class, runContext); runContext.set(KeyBindingDispatcher.class.getName(), dispatcher); keyListener = dispatcher.getKeyDownFilter(); display.addFilter(SWT.KeyDown, keyListener); display.addFilter(SWT.Traverse, keyListener); // Show the initial UI // Create a 'limbo' shell (used to host controls that shouldn't // be in the current layout) Shell limbo = getLimboShell(); runContext.set("limbo", limbo); // HACK!! we should loop until the display gets disposed... // ...then we listen for the last 'main' window to get disposed // and dispose the Display testShell = null; theApp = null; boolean spinOnce = true; if (uiRoot instanceof MApplication) { ShellActivationListener shellDialogListener = new ShellActivationListener( (MApplication) uiRoot); display.addFilter(SWT.Activate, shellDialogListener); display.addFilter(SWT.Deactivate, shellDialogListener); spinOnce = false; // loop until the app closes theApp = (MApplication) uiRoot; // long startTime = System.currentTimeMillis(); MWindow selected = theApp.getSelectedElement(); if (selected == null) { for (MWindow window : theApp.getChildren()) { createGui(window); } } else { // render the selected one first createGui(selected); for (MWindow window : theApp.getChildren()) { if (selected != window) { createGui(window); } } } // long endTime = System.currentTimeMillis(); // System.out.println("Render: " + (endTime - startTime)); // tell the app context we are starting so the splash is // torn down IApplicationContext ac = appContext.get(IApplicationContext.class); if (ac != null) { ac.applicationRunning(); if (eventBroker != null) eventBroker.post(UIEvents.UILifeCycle.APP_STARTUP_COMPLETE, theApp); } } else if (uiRoot instanceof MUIElement) { if (uiRoot instanceof MWindow) { testShell = (Shell) createGui((MUIElement) uiRoot); } else { // Special handling for partial models (for testing...) testShell = new Shell(display, SWT.SHELL_TRIM); createGui((MUIElement) uiRoot, testShell, null); } } // allow any early startup extensions to run Runnable earlyStartup = (Runnable) runContext.get(EARLY_STARTUP_HOOK); if (earlyStartup != null) { earlyStartup.run(); } TestableObject testableObject = (TestableObject) runContext.get(TestableObject.class.getName()); if (testableObject instanceof E4Testable) { ((E4Testable) testableObject).init(display, (IWorkbench) runContext.get(IWorkbench.class.getName())); } IEventLoopAdvisor advisor = runContext.getActiveLeaf().get(IEventLoopAdvisor.class); if (advisor == null) { advisor = new IEventLoopAdvisor() { public void eventLoopIdle(Display display) { display.sleep(); } public void eventLoopException(Throwable exception) { StatusReporter statusReporter = (StatusReporter) appContext .get(StatusReporter.class.getName()); if (statusReporter != null) { statusReporter.show(StatusReporter.ERROR, "Internal Error", exception); } else { if (logger != null) { logger.error(exception); } } } }; } // Spin the event loop until someone disposes the display while (((testShell != null && !testShell.isDisposed()) || (theApp != null && someAreVisible(theApp.getChildren()))) && !display.isDisposed()) { try { if (!display.readAndDispatch()) { runContext.processWaiting(); if (spinOnce) return; advisor.eventLoopIdle(display); } } catch (ThreadDeath th) { throw th; } catch (Exception ex) { handle(ex, advisor); } catch (Error err) { handle(err, advisor); } } if (!spinOnce) { cleanUp(); } } private void handle(Throwable ex, IEventLoopAdvisor advisor) { try { advisor.eventLoopException(ex); } catch (Throwable t) { if (t instanceof ThreadDeath) { throw (ThreadDeath) t; } // couldn't handle the exception, print to console t.printStackTrace(); } } }); return IApplication.EXIT_OK; } protected boolean someAreVisible(List<MWindow> windows) { // This method is called from the event dispatch loop, so the // following optimization is in order... // Ideally, we'd just do: // for (MWindow win : theApp.getChildren()) { // But this creates an iterator (which must be GC'd) // at every call. The code below creates no objects. final int limit = windows.size(); for (int i = 0; i < limit; i++) { final MWindow win = windows.get(i); // Note: Removed isVisible test, as this should have // no impact on the whether the event loop // terminates - non-visible windows still exists // and can receive events. // Note: isToBeRendered() == true => win.getWidget() != null // but I'm not sure whether there is latency between setting // toBeRendered and the creation of the widget. So, keeping // both tests seems prudent. if (win.isToBeRendered() && win.getWidget() != null) { return true; } } return false; } public void stop() { // FIXME Without this call the test-suite fails cleanUp(); if (theApp != null) { for (MWindow window : theApp.getChildren()) { if (window.getWidget() != null) { removeGui(window); } } } else if (testShell != null && !testShell.isDisposed()) { Object model = testShell.getData(AbstractPartRenderer.OWNING_ME); if (model instanceof MUIElement) { removeGui((MUIElement) model); } else { testShell.close(); } } } /* * There are situations where this is called more than once until we know * why this is needed we should make this safe for multiple calls */ private void cleanUp() { if (keyListener != null) { Display display = Display.getDefault(); if (!display.isDisposed()) { display.removeFilter(SWT.KeyDown, keyListener); display.removeFilter(SWT.Traverse, keyListener); keyListener = null; } } } public static void initializeStyling(Display display, IEclipseContext appContext) { String cssTheme = (String) appContext.get(E4Application.THEME_ID); String cssURI = (String) appContext.get(IWorkbench.CSS_URI_ARG); if (cssTheme != null) { String cssResourcesURI = (String) appContext.get(IWorkbench.CSS_RESOURCE_URI_ARG); Bundle bundle = WorkbenchSWTActivator.getDefault().getBundle(); BundleContext context = bundle.getBundleContext(); ServiceReference ref = context.getServiceReference(IThemeManager.class.getName()); IThemeManager mgr = (IThemeManager) context.getService(ref); final IThemeEngine engine = mgr.getEngineForDisplay(display); // Store the app context IContributionFactory contribution = (IContributionFactory) appContext .get(IContributionFactory.class.getName()); IEclipseContext cssContext = EclipseContextFactory.create(); cssContext.set(IContributionFactory.class.getName(), contribution); display.setData("org.eclipse.e4.ui.css.context", cssContext); //$NON-NLS-1$ // Create the OSGi resource locator if (cssResourcesURI != null) { // TODO: Should this be set through an extension as well? engine.registerResourceLocator(new OSGiResourceLocator(cssResourcesURI)); } engine.restore(cssTheme); // TODO Should we create an empty default theme? appContext.set(IThemeEngine.class.getName(), engine); appContext.set(IStylingEngine.SERVICE_NAME, new IStylingEngine() { public void setClassname(Object widget, String classname) { WidgetElement.setCSSClass((Widget) widget, classname); engine.applyStyles((Widget) widget, true); } public void setId(Object widget, String id) { WidgetElement.setID((Widget) widget, id); engine.applyStyles((Widget) widget, true); } public void style(Object widget) { engine.applyStyles((Widget) widget, true); } public CSSStyleDeclaration getStyle(Object widget) { return engine.getStyle((Widget) widget); } public void setClassnameAndId(Object widget, String classname, String id) { WidgetElement.setCSSClass((Widget) widget, classname); WidgetElement.setID((Widget) widget, id); engine.applyStyles((Widget) widget, true); } }); } else if (cssURI != null) { String cssResourcesURI = (String) appContext.get(IWorkbench.CSS_RESOURCE_URI_ARG); final CSSSWTEngineImpl engine = new CSSSWTEngineImpl(display, true); WidgetElement.setEngine(display, engine); if (cssResourcesURI != null) { engine.getResourcesLocatorManager() .registerResourceLocator(new OSGiResourceLocator(cssResourcesURI.toString())); } // FIXME: is this needed? display.setData("org.eclipse.e4.ui.css.context", appContext); //$NON-NLS-1$ appContext.set(IStylingEngine.SERVICE_NAME, new IStylingEngine() { public void setClassname(Object widget, String classname) { WidgetElement.setCSSClass((Widget) widget, classname); engine.applyStyles((Widget) widget, true); } public void setId(Object widget, String id) { WidgetElement.setID((Widget) widget, id); engine.applyStyles((Widget) widget, true); } public void style(Object widget) { engine.applyStyles((Widget) widget, true); } public CSSStyleDeclaration getStyle(Object widget) { Element e = engine.getCSSElementContext(widget).getElement(); if (e == null) { return null; } return engine.getViewCSS().getComputedStyle(e, null); } public void setClassnameAndId(Object widget, String classname, String id) { WidgetElement.setCSSClass((Widget) widget, classname); WidgetElement.setID((Widget) widget, id); engine.applyStyles((Widget) widget, true); } }); URL url; InputStream stream = null; try { url = FileLocator.resolve(new URL(cssURI)); stream = url.openStream(); engine.parseStyleSheet(stream); } catch (MalformedURLException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { if (stream != null) { try { stream.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } Shell[] shells = display.getShells(); for (Shell s : shells) { try { s.setRedraw(false); s.reskin(SWT.ALL); engine.applyStyles(s, true); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { s.setRedraw(true); } } } CSSRenderingUtils cssUtils = ContextInjectionFactory.make(CSSRenderingUtils.class, appContext); appContext.set(CSSRenderingUtils.class, cssUtils); } }