/******************************************************************************* * Copyright (c) 2008, 2016 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 * Lars Vogel <Lars.Vogel@vogella.com> - Bug 429728, 441150, 444410, 472654 * Simon Scholz <simon.scholz@vogella.com> - Bug 429729, 506306 * Mike Leneweit <mike-le@web.de> - Bug 444410 *******************************************************************************/ package org.eclipse.e4.ui.workbench.renderers.swt; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.annotation.PostConstruct; import javax.inject.Inject; import org.eclipse.e4.core.contexts.IEclipseContext; import org.eclipse.e4.core.di.annotations.Optional; import org.eclipse.e4.core.services.events.IEventBroker; import org.eclipse.e4.core.services.log.Logger; import org.eclipse.e4.ui.css.core.engine.CSSEngine; import org.eclipse.e4.ui.css.core.resources.IResourcesRegistry; import org.eclipse.e4.ui.css.swt.dom.WidgetElement; import org.eclipse.e4.ui.css.swt.resources.ResourceByDefinitionKey; import org.eclipse.e4.ui.css.swt.resources.SWTResourcesRegistry; import org.eclipse.e4.ui.di.UIEventTopic; import org.eclipse.e4.ui.internal.workbench.E4Workbench; import org.eclipse.e4.ui.internal.workbench.PartServiceSaveHandler; import org.eclipse.e4.ui.internal.workbench.renderers.swt.SWTRenderersMessages; import org.eclipse.e4.ui.internal.workbench.swt.CSSConstants; import org.eclipse.e4.ui.model.application.MApplication; 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.MUIElement; import org.eclipse.e4.ui.model.application.ui.MUILabel; import org.eclipse.e4.ui.model.application.ui.advanced.MPerspective; import org.eclipse.e4.ui.model.application.ui.basic.MPart; import org.eclipse.e4.ui.model.application.ui.basic.MPartStack; import org.eclipse.e4.ui.model.application.ui.basic.MTrimBar; 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.services.IServiceConstants; import org.eclipse.e4.ui.services.IStylingEngine; import org.eclipse.e4.ui.workbench.IPresentationEngine; import org.eclipse.e4.ui.workbench.UIEvents; import org.eclipse.e4.ui.workbench.modeling.EModelService; import org.eclipse.e4.ui.workbench.modeling.EPartService; import org.eclipse.e4.ui.workbench.modeling.ISaveHandler; import org.eclipse.e4.ui.workbench.modeling.IWindowCloseHandler; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EObject; import org.eclipse.jface.dialogs.Dialog; import org.eclipse.jface.util.Geometry; import org.eclipse.jface.viewers.ArrayContentProvider; import org.eclipse.jface.viewers.CheckboxTableViewer; import org.eclipse.jface.viewers.LabelProvider; import org.eclipse.jface.window.IShellProvider; import org.eclipse.jface.window.Window; import org.eclipse.swt.SWT; import org.eclipse.swt.events.ControlEvent; import org.eclipse.swt.events.ControlListener; import org.eclipse.swt.events.ShellAdapter; import org.eclipse.swt.events.ShellEvent; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.graphics.Resource; import org.eclipse.swt.layout.GridData; 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.swt.widgets.Menu; import org.eclipse.swt.widgets.Monitor; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Widget; import org.osgi.service.event.Event; /** * Default SWT renderer responsible for an instance of MWindow. See * {@link WorkbenchRendererFactory} */ public class WBWRenderer extends SWTPartRenderer { private static String ShellMinimizedTag = "shellMinimized"; //$NON-NLS-1$ private static String ShellMaximizedTag = "shellMaximized"; //$NON-NLS-1$ private class WindowSizeUpdateJob implements Runnable { public List<MWindow> windowsToUpdate = new ArrayList<>(); @Override public void run() { boundsJob = null; while (!windowsToUpdate.isEmpty()) { MWindow window = windowsToUpdate.remove(0); Shell shell = (Shell) window.getWidget(); if (shell == null || shell.isDisposed()) { continue; } shell.setBounds(window.getX(), window.getY(), window.getWidth(), window.getHeight()); } } } WindowSizeUpdateJob boundsJob; boolean ignoreSizeChanges = false; @Inject Logger logger; @Inject private IEclipseContext context; @Inject private IPresentationEngine engine; private ThemeDefinitionChangedHandler themeDefinitionChanged; @Inject private EModelService modelService; @Inject private Display display; @Inject @Optional private void subscribeTopicSelectedElementChanged( @UIEventTopic(UIEvents.ElementContainer.TOPIC_SELECTEDELEMENT) Event event) { // Ensure that this event is for a MApplication if (!(event.getProperty(UIEvents.EventTags.ELEMENT) instanceof MApplication)) { return; } MWindow win = (MWindow) event.getProperty(UIEvents.EventTags.NEW_VALUE); if ((win == null) || !win.getTags().contains("topLevel")) { //$NON-NLS-1$ return; } win.setToBeRendered(true); if (!(win.getRenderer() == WBWRenderer.this)) { return; } Shell shell = (Shell) win.getWidget(); if (shell.getMinimized()) { shell.setMinimized(false); } shell.setActive(); shell.moveAbove(null); } @Inject @Optional private void subscribeTopicLabelChanged(@UIEventTopic(UIEvents.UILabel.TOPIC_ALL) Event event) { Object objElement = event.getProperty(UIEvents.EventTags.ELEMENT); if (!(event.getProperty(UIEvents.EventTags.ELEMENT) instanceof MWindow)) { return; } // Is this listener interested ? MWindow windowModel = (MWindow) objElement; if (windowModel.getRenderer() != WBWRenderer.this) { return; } // No widget == nothing to update Shell theShell = (Shell) windowModel.getWidget(); if (theShell == null) { return; } String attName = (String) event.getProperty(UIEvents.EventTags.ATTNAME); if (UIEvents.UILabel.LABEL.equals(attName) || UIEvents.UILabel.LOCALIZED_LABEL.equals(attName)) { String newTitle = (String) event.getProperty(UIEvents.EventTags.NEW_VALUE); theShell.setText(newTitle); } else if (UIEvents.UILabel.ICONURI.equals(attName)) { theShell.setImage(getImage(windowModel)); // child windows may take their shell icon from the parent for (MWindow child : windowModel.getWindows()) { if (child.getRenderer() instanceof WBWRenderer) { ((WBWRenderer) child.getRenderer()).handleParentChange(child); } } } else if (UIEvents.UILabel.TOOLTIP.equals(attName) || UIEvents.UILabel.LOCALIZED_TOOLTIP.equals(attName)) { String newTTip = (String) event.getProperty(UIEvents.EventTags.NEW_VALUE); theShell.setToolTipText(newTTip); } } @Inject @Optional private void subscribeTopicWindowChanged(@UIEventTopic(UIEvents.Window.TOPIC_ALL) Event event) { if (ignoreSizeChanges) { return; } // Ensure that this event is for a MMenuItem Object objElement = event.getProperty(UIEvents.EventTags.ELEMENT); if (!(objElement instanceof MWindow)) { return; } // Is this listener interested ? MWindow windowModel = (MWindow) objElement; if (windowModel.getRenderer() != WBWRenderer.this) { return; } // No widget == nothing to update Shell theShell = (Shell) windowModel.getWidget(); if (theShell == null) { return; } String attName = (String) event.getProperty(UIEvents.EventTags.ATTNAME); if (UIEvents.Window.X.equals(attName) || UIEvents.Window.Y.equals(attName) || UIEvents.Window.WIDTH.equals(attName) || UIEvents.Window.HEIGHT.equals(attName)) { if (boundsJob == null) { boundsJob = new WindowSizeUpdateJob(); boundsJob.windowsToUpdate.add(windowModel); theShell.getDisplay().asyncExec(boundsJob); } else { if (!boundsJob.windowsToUpdate.contains(windowModel)) { boundsJob.windowsToUpdate.add(windowModel); } } } } @Inject @Optional private void subscribeTopicVisibleChanged(@UIEventTopic(UIEvents.UIElement.TOPIC_VISIBLE) Event event) { // Ensure that this event is for a MMenuItem Object objElement = event.getProperty(UIEvents.EventTags.ELEMENT); if (!(objElement instanceof MWindow)) { return; } // Is this listener interested ? MWindow windowModel = (MWindow) objElement; if (windowModel.getRenderer() != WBWRenderer.this) { return; } // No widget == nothing to update Shell theShell = (Shell) windowModel.getWidget(); if (theShell == null) { return; } String attName = (String) event.getProperty(UIEvents.EventTags.ATTNAME); if (UIEvents.UIElement.VISIBLE.equals(attName)) { boolean isVisible = (Boolean) event.getProperty(UIEvents.EventTags.NEW_VALUE); theShell.setVisible(isVisible); } } @Inject @Optional private void subscribeThemeDefinitionChanged( @UIEventTopic(UIEvents.UILifeCycle.THEME_DEFINITION_CHANGED) Event event) { themeDefinitionChanged.handleEvent(event); } @Inject @Optional private void subscribeTopicDetachedChanged(@UIEventTopic(UIEvents.Window.TOPIC_WINDOWS) Event event) { /* * Handle any changes required for parent changes on detached windows. * This isn't quite straightforward as we don't see TOPIC_PARENT events * parent changes are only described as ADD and REMOVE on the * Window.TOPIC_WINDOWS and Application.TOPIC_CHILDREN. */ if (!(event.getProperty(UIEvents.EventTags.ELEMENT) instanceof MWindow)) { return; } if (UIEvents.isREMOVE(event)) { for (Object removed : UIEvents.asIterable(event, UIEvents.EventTags.OLD_VALUE)) { if (removed instanceof MWindow && ((MWindow) removed).getRenderer() instanceof WBWRenderer) { MWindow window = (MWindow) removed; ((WBWRenderer) window.getRenderer()).handleParentChange(window); } } } else if (UIEvents.isADD(event)) { for (Object removed : UIEvents.asIterable(event, UIEvents.EventTags.NEW_VALUE)) { if (removed instanceof MWindow && ((MWindow) removed).getRenderer() instanceof WBWRenderer) { MWindow window = (MWindow) removed; ((WBWRenderer) window.getRenderer()).handleParentChange(window); } } } } /** * Update this child window with any values that may have been obtained from * the parent. * * @param child * the child window (may now be orphaned) */ private void handleParentChange(MWindow child) { // No widget == nothing to update Shell theShell = (Shell) child.getWidget(); if (theShell == null) { return; } // Detached windows may take their shell icon from the parent window theShell.setImage(getImage(child)); } /** * Closes the provided detached window. * * @param window * the detached window to close * @return <code>true</code> if the window should be closed, * <code>false</code> otherwise */ private boolean closeDetachedWindow(MWindow window) { EPartService partService = window.getContext().get(EPartService.class); List<MPart> parts = modelService.findElements(window, null, MPart.class, null); // this saves one part at a time, not ideal but better than not saving // at all for (MPart part : parts) { if (!partService.savePart(part, true)) { // user cancelled the operation, return false return false; } } // hide every part individually, following 3.x behaviour for (MPart part : parts) { partService.hidePart(part); } return true; } @PostConstruct protected void init() { themeDefinitionChanged = new ThemeDefinitionChangedHandler(); } @Override public Object createWidget(MUIElement element, Object parent) { final Widget newWidget; if (!(element instanceof MWindow) || (parent != null && !(parent instanceof Control))) { return null; } MWindow wbwModel = (MWindow) element; MApplication appModel = wbwModel.getContext().get(MApplication.class); Boolean rtlMode = (Boolean) appModel.getTransientData().get(E4Workbench.RTL_MODE); int rtlStyle = (rtlMode != null && rtlMode.booleanValue()) ? SWT.RIGHT_TO_LEFT : 0; Shell parentShell = parent == null ? null : ((Control) parent).getShell(); final Shell wbwShell; int styleOverride = getStyleOverride(wbwModel) | rtlStyle; if (parentShell == null) { int style = styleOverride == -1 ? SWT.SHELL_TRIM | rtlStyle : styleOverride; wbwShell = new Shell(display, style); wbwModel.getTags().add("topLevel"); //$NON-NLS-1$ } else { int style = SWT.TITLE | SWT.RESIZE | SWT.MAX | SWT.CLOSE | rtlStyle; style = styleOverride == -1 ? style : styleOverride; if (wbwModel.getTags().contains(IPresentationEngine.WINDOW_TOP_LEVEL)) { wbwShell = new Shell(display, style); } else { wbwShell = new Shell(parentShell, style); } // Prevent ESC from closing the DW wbwShell.addTraverseListener(e -> { if (e.detail == SWT.TRAVERSE_ESCAPE) { e.doit = false; } }); } wbwShell.setBackgroundMode(SWT.INHERIT_DEFAULT); Rectangle modelBounds = wbwShell.getBounds(); if (wbwModel instanceof EObject) { EObject wbw = (EObject) wbwModel; EClass wbwclass = wbw.eClass(); // use eIsSet rather than embed sentinel values if (wbw.eIsSet(wbwclass.getEStructuralFeature("x"))) { //$NON-NLS-1$ modelBounds.x = wbwModel.getX(); } if (wbw.eIsSet(wbwclass.getEStructuralFeature("y"))) { //$NON-NLS-1$ modelBounds.y = wbwModel.getY(); } if (wbw.eIsSet(wbwclass.getEStructuralFeature("height"))) { //$NON-NLS-1$ modelBounds.height = wbwModel.getHeight(); } if (wbw.eIsSet(wbwclass.getEStructuralFeature("width"))) { //$NON-NLS-1$ modelBounds.width = wbwModel.getWidth(); } } // Force the shell onto the display if it would be invisible otherwise Display display = Display.getCurrent(); Monitor closestMonitor = getClosestMonitor(display, Geometry.centerPoint(modelBounds)); Rectangle displayBounds = closestMonitor.getClientArea(); if (!modelBounds.intersects(displayBounds)) { Geometry.moveInside(modelBounds, displayBounds); } wbwShell.setBounds(modelBounds); setCSSInfo(wbwModel, wbwShell); // set up context IEclipseContext localContext = getContext(wbwModel); // We need to retrieve specific CSS properties for our layout. CSSEngineHelper helper = new CSSEngineHelper(localContext, wbwShell); TrimmedPartLayout tl = new TrimmedPartLayout(wbwShell); tl.gutterTop = helper.getMarginTop(0); tl.gutterBottom = helper.getMarginBottom(0); tl.gutterLeft = helper.getMarginLeft(0); tl.gutterRight = helper.getMarginRight(0); wbwShell.setLayout(tl); newWidget = wbwShell; bindWidget(element, newWidget); // Add the shell into the WBW's context localContext.set(Shell.class, wbwShell); localContext.set(E4Workbench.LOCAL_ACTIVE_SHELL, wbwShell); setCloseHandler(wbwModel); localContext.set(IShellProvider.class, () -> wbwShell); final PartServiceSaveHandler saveHandler = new PartServiceSaveHandler() { @Override public Save promptToSave(MPart dirtyPart) { Shell shell = (Shell) context.get(IServiceConstants.ACTIVE_SHELL); Object[] elements = promptForSave(shell, Collections.singleton(dirtyPart)); if (elements == null) { return Save.CANCEL; } return elements.length == 0 ? Save.NO : Save.YES; } @Override public Save[] promptToSave(Collection<MPart> dirtyParts) { List<MPart> parts = new ArrayList<>(dirtyParts); Shell shell = (Shell) context .get(IServiceConstants.ACTIVE_SHELL); Save[] response = new Save[dirtyParts.size()]; Object[] elements = promptForSave(shell, parts); if (elements == null) { Arrays.fill(response, Save.CANCEL); } else { Arrays.fill(response, Save.NO); for (int i = 0; i < elements.length; i++) { response[parts.indexOf(elements[i])] = Save.YES; } } return response; } }; saveHandler.logger = logger; localContext.set(ISaveHandler.class, saveHandler); if (wbwModel.getLabel() != null) { wbwShell.setText(wbwModel.getLocalizedLabel()); } Image windowImage = getImage(wbwModel); if (windowImage != null) { wbwShell.setImage(windowImage); } else { // TODO: This should be added to the model, see bug 308494 // it allows for a range of icon sizes that the platform gets to // choose from wbwShell.setImages(Window.getDefaultImages()); } return newWidget; } /** * TODO: Create an API for this method and delete this version. See bug * 491273 * * Returns the monitor whose client area contains the given point. If no * monitor contains the point, returns the monitor that is closest to the * point. If this is ever made public, it should be moved into a separate * utility class. * * @param toSearch * point to find (display coordinates) * @param toFind * point to find (display coordinates) * @return the montor closest to the given point */ private static Monitor getClosestMonitor(Display toSearch, Point toFind) { int closest = Integer.MAX_VALUE; Monitor[] monitors = toSearch.getMonitors(); Monitor result = monitors[0]; for (Monitor current : monitors) { Rectangle clientArea = current.getClientArea(); if (clientArea.contains(toFind)) { return current; } int distance = Geometry.distanceSquared(Geometry.centerPoint(clientArea), toFind); if (distance < closest) { closest = distance; result = current; } } return result; } private void setCloseHandler(MWindow window) { IEclipseContext context = window.getContext(); // no direct model parent, must be a detached window if (window.getParent() == null) { context.set(IWindowCloseHandler.class, window1 -> closeDetachedWindow(window1)); } else { context.set(IWindowCloseHandler.class, window1 -> { EPartService partService = window1.getContext().get(EPartService.class); return partService.saveAll(true); }); } } @Override public Image getImage(MUILabel element) { Image image = super.getImage(element); if (image == null && element instanceof MWindow) { // Detached windows should take their image from parent window MWindow parent = modelService.getTopLevelWindowFor((MWindow) element); if (parent != null && parent != element) { image = getImage(parent); } } return image; } @Override public void hookControllerLogic(MUIElement me) { super.hookControllerLogic(me); Widget widget = (Widget) me.getWidget(); if (widget instanceof Shell && me instanceof MWindow) { final Shell shell = (Shell) widget; final MWindow w = (MWindow) me; shell.addControlListener(new ControlListener() { @Override public void controlResized(ControlEvent e) { // Don't store the maximized size in the model if (shell.getMaximized()) { return; } try { ignoreSizeChanges = true; w.setWidth(shell.getSize().x); w.setHeight(shell.getSize().y); } finally { ignoreSizeChanges = false; } } @Override public void controlMoved(ControlEvent e) { // Don't store the maximized size in the model if (shell.getMaximized()) { return; } try { ignoreSizeChanges = true; w.setX(shell.getLocation().x); w.setY(shell.getLocation().y); } finally { ignoreSizeChanges = false; } } }); shell.addShellListener(new ShellAdapter() { @Override public void shellClosed(ShellEvent e) { // override the shell close event e.doit = false; MWindow window = (MWindow) e.widget.getData(OWNING_ME); IWindowCloseHandler closeHandler = window.getContext().get(IWindowCloseHandler.class); // if there's no handler or the handler permits the close // request, clean-up as necessary if (closeHandler == null || closeHandler.close(window)) { cleanUp(window); } } }); shell.addListener(SWT.Activate, event -> { MUIElement parentME = w.getParent(); if (parentME instanceof MApplication) { MApplication app = (MApplication) parentME; app.setSelectedElement(w); w.getContext().activate(); } else if (parentME == null) { parentME = modelService.getContainer(w); if (parentME instanceof MContext) { w.getContext().activate(); } } updateNonFocusState(SWT.Activate, w); }); shell.addListener(SWT.Deactivate, event -> updateNonFocusState(SWT.Deactivate, w)); } } private void updateNonFocusState(int event, MWindow win) { MPerspective perspective = modelService.getActivePerspective(win); if (perspective == null) { return; } List<MPartStack> stacks = modelService.findElements(perspective, null, MPartStack.class, Arrays.asList(CSSConstants.CSS_ACTIVE_CLASS)); if (stacks.isEmpty()) { return; } MPartStack stack = stacks.get(0); int tagsCount = stack.getTags().size(); boolean hasNonFocusTag = stack.getTags().contains( CSSConstants.CSS_NO_FOCUS_CLASS); if (event == SWT.Activate && hasNonFocusTag) { stack.getTags().remove(CSSConstants.CSS_NO_FOCUS_CLASS); } else if (event == SWT.Deactivate && !hasNonFocusTag) { stack.getTags().add(CSSConstants.CSS_NO_FOCUS_CLASS); } if (tagsCount != stack.getTags().size()) { setCSSInfo(stack, stack.getWidget()); } } private void cleanUp(MWindow window) { MUIElement parent = modelService.getContainer(window); if (parent instanceof MApplication) { MApplication application = (MApplication) parent; List<MWindow> children = application.getChildren(); if (children.size() > 1) { // not the last window, destroy and remove window.setToBeRendered(false); children.remove(window); } else { // last window, just destroy without changing the model engine.removeGui(window); } } else if (parent != null) { window.setToBeRendered(false); // this is a detached window, check for parts if (modelService.findElements(window, null, MPart.class, null) .isEmpty()) { // if no parts, remove it if (parent instanceof MWindow) { ((MWindow) parent).getWindows().remove(window); } else if (parent instanceof MPerspective) { ((MPerspective) parent).getWindows().remove(window); } } } } /* * Processing the contents of a Workbench window has to take into account * that there may be trim elements contained in its child list. Since the */ @Override public void processContents(MElementContainer<MUIElement> me) { if (!(((MUIElement) me) instanceof MWindow)) { return; } MWindow wbwModel = (MWindow) ((MUIElement) me); super.processContents(me); // Populate the main menu IPresentationEngine renderer = context.get(IPresentationEngine.class); if (wbwModel.getMainMenu() != null) { renderer.createGui(wbwModel.getMainMenu(), me.getWidget(), null); Shell shell = (Shell) me.getWidget(); shell.setMenuBar((Menu) wbwModel.getMainMenu().getWidget()); } // create Detached Windows for (MWindow dw : wbwModel.getWindows()) { renderer.createGui(dw, me.getWidget(), wbwModel.getContext()); } // Populate the trim (if any) if (wbwModel instanceof MTrimmedWindow) { Shell shell = (Shell) wbwModel.getWidget(); MTrimmedWindow tWindow = (MTrimmedWindow) wbwModel; List<MTrimBar> trimBars = new ArrayList<>( tWindow.getTrimBars()); for (MTrimBar trimBar : trimBars) { renderer.createGui(trimBar, shell, wbwModel.getContext()); // bug 387161: hack around that createGui(e, parent, context) // does not reparent the element widget to the // limbo shell wheb visible=false if (!trimBar.isVisible()) { trimBar.setVisible(true); trimBar.setVisible(false); } } } } @Override public Object getUIContainer(MUIElement element) { MUIElement parent = element.getParent(); if (parent == null) { // might be a detached window parent = modelService.getContainer(element); return parent == null ? null : parent.getWidget(); } Composite shellComp = (Composite) element.getParent().getWidget(); TrimmedPartLayout tpl = (TrimmedPartLayout) shellComp.getLayout(); return tpl.clientArea; } @Override public void postProcess(MUIElement shellME) { super.postProcess(shellME); Shell shell = (Shell) shellME.getWidget(); // Capture the max/min state final MUIElement disposeME = shellME; shell.addDisposeListener(e -> { Shell shell1 = (Shell) e.widget; if (disposeME != null) { disposeME.getTags().remove(ShellMinimizedTag); disposeME.getTags().remove(ShellMaximizedTag); if (shell1.getMinimized()) { disposeME.getTags().add(ShellMinimizedTag); } if (shell1.getMaximized()) { disposeME.getTags().add(ShellMaximizedTag); } } }); // Apply the correct shell state if (shellME.getTags().contains(ShellMaximizedTag)) { shell.setMaximized(true); } else if (shellME.getTags().contains(ShellMinimizedTag)) { shell.setMinimized(true); } shell.layout(true); forceLayout(shell); if (shellME.isVisible()) { shell.open(); } else { shell.setVisible(false); } } private Object[] promptForSave(Shell parentShell, Collection<MPart> saveableParts) { SaveablePartPromptDialog dialog = new SaveablePartPromptDialog( parentShell, saveableParts); if (dialog.open() == Window.CANCEL) { return null; } return dialog.getCheckedElements(); } private void applyDialogStyles(Control control) { IStylingEngine engine = (IStylingEngine) context .get(IStylingEngine.SERVICE_NAME); if (engine != null) { Shell shell = control.getShell(); if (shell.getBackgroundMode() == SWT.INHERIT_NONE) { shell.setBackgroundMode(SWT.INHERIT_DEFAULT); } engine.style(shell); } } class SaveablePartPromptDialog extends Dialog { private Collection<MPart> collection; private CheckboxTableViewer tableViewer; private Object[] checkedElements = new Object[0]; SaveablePartPromptDialog(Shell shell, Collection<MPart> collection) { super(shell); this.collection = collection; } @Override protected void configureShell(Shell newShell) { super.configureShell(newShell); newShell.setText(SWTRenderersMessages.choosePartsToSaveTitle); } @Override protected Control createDialogArea(Composite parent) { parent = (Composite) super.createDialogArea(parent); Label label = new Label(parent, SWT.LEAD); label.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); label.setText(SWTRenderersMessages.choosePartsToSave); tableViewer = CheckboxTableViewer.newCheckList(parent, SWT.SINGLE | SWT.BORDER); GridData data = new GridData(SWT.FILL, SWT.FILL, true, true); data.heightHint = 250; data.widthHint = 300; tableViewer.getControl().setLayoutData(data); tableViewer.setLabelProvider(new LabelProvider() { @Override public String getText(Object element) { return ((MPart) element).getLocalizedLabel(); } }); tableViewer.setContentProvider(ArrayContentProvider.getInstance()); tableViewer.setInput(collection); tableViewer.setAllChecked(true); return parent; } @Override public void create() { super.create(); applyDialogStyles(getShell()); } @Override protected void okPressed() { checkedElements = tableViewer.getCheckedElements(); super.okPressed(); } public Object[] getCheckedElements() { return checkedElements; } @Override protected boolean isResizable() { return true; } } protected static class ThemeDefinitionChangedHandler { protected Set<Resource> unusedResources = new HashSet<>(); public void handleEvent(Event event) { Object element = event.getProperty(IEventBroker.DATA); if (!(element instanceof MApplication)) { return; } Set<CSSEngine> engines = new HashSet<>(); // In theory we can have multiple engines since API allows it. // It doesn't hurt to be prepared for such case for (MWindow window : ((MApplication) element).getChildren()) { CSSEngine engine = getEngine(window); if (engine != null) { engines.add(engine); } } for (CSSEngine engine : engines) { for (Object resource : removeResources(engine.getResourcesRegistry())) { if (resource instanceof Resource && !((Resource) resource).isDisposed()) { unusedResources.add((Resource) resource); } } engine.reapply(); } } protected CSSEngine getEngine(MWindow window) { return WidgetElement.getEngine((Widget) window.getWidget()); } protected List<Object> removeResources(IResourcesRegistry registry) { if (registry instanceof SWTResourcesRegistry) { return ((SWTResourcesRegistry) registry) .removeResourcesByKeyTypeAndType( ResourceByDefinitionKey.class, Font.class, Color.class); } return Collections.emptyList(); } public void dispose() { for (Resource resource : unusedResources) { if (!resource.isDisposed()) { resource.dispose(); } } unusedResources.clear(); } } private void forceLayout(Shell shell) { int i = 0; while(shell.isLayoutDeferred()) { shell.setLayoutDeferred(false); i++; } while(i > 0) { shell.setLayoutDeferred(true); i--; } } }