package org.xmind.cathy.internal.renderer; import java.util.ArrayList; import java.util.List; import javax.inject.Inject; import javax.inject.Named; import org.eclipse.e4.core.contexts.IEclipseContext; import org.eclipse.e4.core.di.annotations.Optional; import org.eclipse.e4.ui.di.UIEventTopic; import org.eclipse.e4.ui.internal.workbench.E4Workbench; import org.eclipse.e4.ui.model.application.MApplication; 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.basic.MDialog; import org.eclipse.e4.ui.model.application.ui.basic.MPart; import org.eclipse.e4.ui.model.application.ui.basic.MWindow; import org.eclipse.e4.ui.model.application.ui.basic.MWindowElement; import org.eclipse.e4.ui.workbench.IPresentationEngine; import org.eclipse.e4.ui.workbench.UIEvents; import org.eclipse.e4.ui.workbench.UIEvents.ElementContainer; import org.eclipse.e4.ui.workbench.modeling.EPartService; import org.eclipse.e4.ui.workbench.modeling.IWindowCloseHandler; import org.eclipse.e4.ui.workbench.renderers.swt.SWTPartRenderer; import org.eclipse.jface.util.Geometry; import org.eclipse.jface.window.IShellProvider; import org.eclipse.jface.window.Window; import org.eclipse.swt.SWT; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.layout.FillLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; 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; import org.xmind.ui.internal.e4models.IModelConstants; public class XDialogRenderer extends SWTPartRenderer { private class WindowSizeUpdateJob implements Runnable { public List<MWindow> windowsToUpdate = new ArrayList<MWindow>(); 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; @Inject private IEclipseContext context; @Inject private Display display; @Inject private MApplication application; @Inject @Optional @Named("localActiveShell") private Shell parentShell; @SuppressWarnings("unchecked") @Inject @Optional private void subscribeTopicChildAdded( @UIEventTopic(ElementContainer.TOPIC_CHILDREN) Event event) { Object changedObject = event.getProperty(UIEvents.EventTags.ELEMENT); if (!(changedObject instanceof MDialog)) { return; } if (UIEvents.isADD(event)) { processContents((MElementContainer<MUIElement>) changedObject); postProcess((MDialog) changedObject); } } @Inject @Optional private void subscribeTopicWindowChanged( @UIEventTopic(UIEvents.Window.TOPIC_ALL) Event event) { Object objElement = event.getProperty(UIEvents.EventTags.ELEMENT); if (!(objElement instanceof MDialog)) { return; } // Is this listener interested ? MDialog dialogModel = (MDialog) objElement; if (dialogModel.getRenderer() != XDialogRenderer.this) { return; } // No widget == nothing to update Shell theShell = (Shell) dialogModel.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(dialogModel); theShell.getDisplay().asyncExec(boundsJob); } else { if (!boundsJob.windowsToUpdate.contains(dialogModel)) boundsJob.windowsToUpdate.add(dialogModel); } } } @Override public Object createWidget(MUIElement element, Object parent) { final Widget newWidget; if (!(element instanceof MDialog) || (parent != null && !(parent instanceof Control))) return null; MDialog dialogModel = (MDialog) element; MApplication appModel = dialogModel.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(dialogModel) | rtlStyle; if (parentShell == null) { int style = styleOverride == -1 ? SWT.SHELL_TRIM | rtlStyle : styleOverride; wbwShell = new Shell(display, style); dialogModel.getTags().add("topLevel"); //$NON-NLS-1$ } else { int style = SWT.TITLE | SWT.RESIZE | SWT.MAX | SWT.CLOSE | rtlStyle; style = styleOverride == -1 ? style : styleOverride; if (dialogModel.getTags() .contains(IPresentationEngine.WINDOW_TOP_LEVEL)) wbwShell = new Shell(display, style); else wbwShell = new Shell(parentShell, style); } wbwShell.setBackgroundMode(SWT.INHERIT_DEFAULT); Rectangle modelBounds = wbwShell.getBounds(); modelBounds.x = dialogModel.getX(); modelBounds.y = dialogModel.getY(); modelBounds.height = dialogModel.getHeight(); modelBounds.width = dialogModel.getWidth(); // Force the shell onto the display if it would be invisible otherwise Rectangle displayBounds = Display.getCurrent().getPrimaryMonitor() .getBounds(); if (!modelBounds.intersects(displayBounds)) { Rectangle clientArea = Display.getCurrent().getPrimaryMonitor() .getClientArea(); modelBounds.x = clientArea.x; modelBounds.y = clientArea.y; } wbwShell.setBounds(modelBounds); setCSSInfo(dialogModel, wbwShell); wbwShell.setLayout(new FillLayout(SWT.VERTICAL)); newWidget = wbwShell; bindWidget(element, newWidget); // set up context IEclipseContext localContext = getContext(dialogModel); localContext.set(Shell.class, wbwShell); localContext.set(E4Workbench.LOCAL_ACTIVE_SHELL, wbwShell); localContext.set(IShellProvider.class, new IShellProvider() { public Shell getShell() { return wbwShell; } }); localContext.set(IWindowCloseHandler.class, new IWindowCloseHandler() { public boolean close(MWindow window) { return closeDetachedWindow(window); } }); if (dialogModel.getLabel() != null) wbwShell.setText(dialogModel.getLocalizedLabel()); if (dialogModel.getIconURI() != null && dialogModel.getIconURI().length() > 0) { wbwShell.setImage(getImage(dialogModel)); } else { wbwShell.setImages(Window.getDefaultImages()); } return newWidget; } 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; } @Override public void hookControllerLogic(final MUIElement me) { super.hookControllerLogic(me); Widget widget = (Widget) me.getWidget(); if (widget instanceof Shell && me instanceof MWindow) { final Shell shell = (Shell) widget; shell.addDisposeListener(new DisposeListener() { public void widgetDisposed(DisposeEvent e) { //Save user dialog bounds String splitSymbol = ","; //$NON-NLS-1$ Rectangle bounds = shell.getBounds(); String location = bounds.x + splitSymbol + bounds.y + splitSymbol + bounds.width + splitSymbol + bounds.height; me.getPersistedState().put( IModelConstants.KEY_DIALOG_PART_CUSTOM_LOCATION, location); MWindow window = (MWindow) 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)) { Object parentModel = shell.getParent() .getData(OWNING_ME); if (parentModel instanceof MWindow) { List<MWindowElement> children = ((MWindow) parentModel) .getChildren(); if (children.contains(window)) { children.remove(window); } } else { MWindow trimmedWindow = application.getChildren() .get(0); List<MWindow> windows = trimmedWindow.getWindows(); if (windows.contains(window)) { windows.remove(window); } } } } }); } } @Override public void processContents(MElementContainer<MUIElement> me) { if (!(((MUIElement) me) instanceof MDialog)) return; MDialog wbwModel = (MDialog) ((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()); } } @Override public void postProcess(MUIElement shellME) { if (!(shellME instanceof MDialog)) return; MDialog dialogModel = (MDialog) shellME; super.postProcess(shellME); Shell shell = (Shell) shellME.getWidget(); String location = shellME.getPersistedState() .get(IModelConstants.KEY_DIALOG_PART_CUSTOM_LOCATION); location = location == null ? "" : location; //$NON-NLS-1$ String[] locations = location.split(","); //$NON-NLS-1$ if (locations.length < 4) { String[] tempLocations = new String[4]; for (int i = 0; i < locations.length; i++) tempLocations[i] = locations[i]; locations = tempLocations; } Point size = shell.computeSize(SWT.DEFAULT, SWT.DEFAULT, true); if (isNone(locations[2])) { locations[2] = String.valueOf(size.x); } if (isNone(locations[3])) { locations[3] = String.valueOf(size.y); } size = new Point(Integer.valueOf(locations[2]), Integer.valueOf(locations[3])); Point initLocation = getInitialLocation(shell, size); Rectangle bounds = getConstrainedShellBounds(shell, new Rectangle(initLocation.x, initLocation.y, size.x, size.y)); if (isNone(locations[0]) && isNone(locations[1])) { locations[0] = String.valueOf(bounds.x); locations[1] = String.valueOf(bounds.y); } dialogModel.setX(Integer.valueOf(locations[0])); dialogModel.setY(Integer.valueOf(locations[1])); dialogModel.setWidth(Integer.valueOf(locations[2])); dialogModel.setHeight(Integer.valueOf(locations[3])); StringBuffer sb = new StringBuffer(); sb.append(locations[0]); sb.append(","); //$NON-NLS-1$ sb.append(locations[1]); sb.append(","); //$NON-NLS-1$ sb.append(locations[2]); sb.append(","); //$NON-NLS-1$ sb.append(locations[3]); dialogModel.getPersistedState().put( IModelConstants.KEY_DIALOG_PART_CUSTOM_LOCATION, sb.toString()); shell.layout(true); forceLayout(shell); if (shellME.isVisible()) { shell.open(); } else { shell.setVisible(false); } } private boolean isNone(String value) { return value == null || "".equals(value); //$NON-NLS-1$ } private Point getInitialLocation(Shell shell, Point initialSize) { Composite parent = shell.getParent(); Monitor monitor = shell.getDisplay().getPrimaryMonitor(); if (parent != null) { monitor = parent.getMonitor(); } Rectangle monitorBounds = monitor.getClientArea(); Point centerPoint; if (parent != null) { centerPoint = Geometry.centerPoint(parent.getBounds()); } else { centerPoint = Geometry.centerPoint(monitorBounds); } return new Point(centerPoint.x - (initialSize.x / 2), centerPoint.y - (initialSize.y / 2)); } protected Rectangle getConstrainedShellBounds(Shell shell, Rectangle preferredSize) { Rectangle result = new Rectangle(preferredSize.x, preferredSize.y, preferredSize.width, preferredSize.height); Monitor mon = getClosestMonitor(shell.getDisplay(), Geometry.centerPoint(result)); Rectangle bounds = mon.getClientArea(); if (result.height > bounds.height) { result.height = bounds.height; } if (result.width > bounds.width) { result.width = bounds.width; } result.x = Math.max(bounds.x, Math.min(result.x, bounds.x + bounds.width - result.width)); result.y = Math.max(bounds.y, Math.min(result.y, bounds.y + bounds.height - result.height)); return result; } private static Monitor getClosestMonitor(Display toSearch, Point toFind) { int closest = Integer.MAX_VALUE; Monitor[] monitors = toSearch.getMonitors(); Monitor result = monitors[0]; for (int idx = 0; idx < monitors.length; idx++) { Monitor current = monitors[idx]; 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 forceLayout(Shell shell) { int i = 0; while (shell.isLayoutDeferred()) { shell.setLayoutDeferred(false); i++; } while (i > 0) { shell.setLayoutDeferred(true); i--; } } }