/****************************************************************************** * Copyright (c) 2016 Tasktop Technologies, Oracle and Other Contributors * 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: * Benjamin Pasero - initial implementation in Mylyn as AbstractNotificationPopup * Tasktop Technologies - initial implementation in Mylyn as AbstractNotificationPopup * Ling Hao - adaptation to Sapphire requirements * Konstantin Komissarchik - [357714] Display validation messages for content outline nodes ******************************************************************************/ package org.eclipse.sapphire.ui.forms.swt; import org.eclipse.jface.dialogs.PopupDialog; import org.eclipse.jface.layout.GridDataFactory; import org.eclipse.jface.layout.GridLayoutFactory; import org.eclipse.jface.util.Util; import org.eclipse.jface.window.Window; import org.eclipse.swt.SWT; import org.eclipse.swt.events.ControlAdapter; import org.eclipse.swt.events.ControlEvent; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.graphics.Region; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Shell; import org.eclipse.ui.forms.FormColors; import org.eclipse.ui.forms.IFormColors; /** * @author <a href="mailto:ling.hao@oracle.com">Ling Hao</a> */ public abstract class Popup extends Window { private FormColors colors; private Shell shell; private Region lastUsedRegion; private Image lastUsedBgImage; /** * Flags indicating whether we are listening for shell deactivate events, * either those or our parent's. Used to prevent closure when a menu command * is chosen or a secondary popup is launched. */ private boolean listenToDeactivate; private boolean listenToParentDeactivate; private Listener parentDeactivateListener; /** * The control representing the main dialog area. */ private Control contentArea; /** * The initial position */ private final Point position; public Popup(Shell shell, Point position) { this(shell, position, SWT.NO_TRIM | SWT.ON_TOP | SWT.NO_FOCUS | SWT.TOOL); } public Popup(Shell shell, Point position, int style) { super(shell); setShellStyle(style); this.position = position; this.colors = new FormColors(shell.getDisplay()); } /** * Override to populate with notifications. * * @param parent */ protected Control createContentArea(Composite parent) { Composite composite = new Composite(parent, SWT.NONE); GridLayoutFactory.fillDefaults() .margins(PopupDialog.POPUP_MARGINWIDTH, PopupDialog.POPUP_MARGINHEIGHT) .spacing(PopupDialog.POPUP_HORIZONTALSPACING, PopupDialog.POPUP_VERTICALSPACING) .applyTo(composite); GridDataFactory.fillDefaults() .grab(true, true) .applyTo(composite); return composite; } @Override protected void configureShell(Shell newShell) { super.configureShell(newShell); this.shell = newShell; newShell.setBackground(this.colors.getBorderColor()); this.shell.addListener(SWT.Deactivate, new Listener() { public void handleEvent(Event event) { /* * Close if we are deactivating and have no child shells. If we * have child shells, we are deactivating due to their opening. * On X, we receive this when a menu child (such as the system * menu) of the shell opens, but I have not found a way to * distinguish that case here. Hence bug #113577 still exists. */ if (Popup.this.listenToDeactivate && event.widget == getShell() && getShell().getShells().length == 0) { asyncClose(); } else { /* * We typically ignore deactivates to work around * platform-specific event ordering. Now that we've ignored * whatever we were supposed to, start listening to * deactivates. Example issues can be found in * https://bugs.eclipse.org/bugs/show_bug.cgi?id=123392 */ Popup.this.listenToDeactivate = true; } } }); // Set this true whenever we activate. It may have been turned // off by a menu or secondary popup showing. this.shell.addListener(SWT.Activate, new Listener() { public void handleEvent(Event event) { // ignore this event if we have launched a child if (event.widget == getShell() && getShell().getShells().length == 0) { Popup.this.listenToDeactivate = true; // Typically we start listening for parent deactivate after // we are activated, except on the Mac, where the deactivate // is received after activate. // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=100668 Popup.this.listenToParentDeactivate = !Util.isMac(); } } }); if ((getShellStyle() & SWT.ON_TOP) != 0 && this.shell.getParent() != null) { this.parentDeactivateListener = new Listener() { public void handleEvent(Event event) { if (Popup.this.listenToParentDeactivate) { asyncClose(); } else { // Our first deactivate, now start listening on the Mac. Popup.this.listenToParentDeactivate = Popup.this.listenToDeactivate; } } }; this.shell.getParent().addListener(SWT.Deactivate, this.parentDeactivateListener); } } private void asyncClose() { // workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=152010 getShell().getDisplay().asyncExec(new Runnable() { public void run() { close(); } }); } @Override public void create() { super.create(); addRegion(this.shell); } private void addRegion(Shell shell) { Region region = new Region(); Point s = shell.getSize(); /* Add entire Shell */ region.add(0, 0, s.x, s.y); /* Subtract Top-Left Corner */ region.subtract(0, 0, 1, 1); /* Subtract Top-Right Corner */ region.subtract(s.x - 1, 0, 1, 1); /* Subtract Bottom-Left Corner */ region.subtract(0, s.y - 1, 1, 1); /* Subtract Bottom-Right Corner */ region.subtract(s.x - 1, s.y - 1, 1, 1); /* Dispose old first */ if (shell.getRegion() != null) { shell.getRegion().dispose(); } /* Apply Region */ shell.setRegion(region); /* Remember to dispose later */ this.lastUsedRegion = region; } @Override public int open() { if (this.shell == null || this.shell.isDisposed()) { this.shell = null; create(); this.shell = getShell(); } // limit the shell size to the display size constrainShellSize(); // initialize flags for listening to deactivate this.listenToDeactivate = false; this.listenToParentDeactivate = false; // open the window final Control initialFocusControl = getFocusControl(); if( initialFocusControl == null ) { // Opening the shell in this way ensures that it does not take focus from the active shell. this.shell.setVisible( true ); } else { this.shell.open(); initialFocusControl.setFocus(); } Display display = this.shell.getDisplay(); while (!this.shell.isDisposed()) { try { if (!display.readAndDispatch()) { display.sleep(); } } catch (Throwable e) { e.printStackTrace(); } } if (!display.isDisposed()) display.update(); return Window.OK; } @Override protected Point getInitialLocation( Point size ) { if( this.position == null ) { return this.shell.getLocation(); } return this.position; } /** * Returns the control that should get initial focus. Subclasses may * override this method. * * @return the Control that should receive focus when the popup opens. */ protected Control getFocusControl() { return this.contentArea; } @Override protected Control createContents(Composite parent) { ((GridLayout) parent.getLayout()).marginWidth = 1; ((GridLayout) parent.getLayout()).marginHeight = 1; /* Outer Composite holding the controls */ final Composite outerCircle = new Composite(parent, SWT.NO_FOCUS); outerCircle.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); outerCircle.setBackgroundMode(SWT.INHERIT_FORCE); outerCircle.addControlListener(new ControlAdapter() { @Override public void controlResized(ControlEvent e) { Rectangle clArea = outerCircle.getClientArea(); Popup.this.lastUsedBgImage = new Image(outerCircle.getDisplay(), clArea.width, clArea.height); GC gc = new GC(Popup.this.lastUsedBgImage); /* Gradient */ drawGradient(gc, clArea); gc.dispose(); Image oldBGImage = outerCircle.getBackgroundImage(); outerCircle.setBackgroundImage(Popup.this.lastUsedBgImage); if (oldBGImage != null) { oldBGImage.dispose(); } } private void drawGradient(GC gc, Rectangle clArea) { gc.setForeground(Popup.this.colors.getBackground()); gc.setBackground(Popup.this.colors.getColor(IFormColors.TB_BG)); gc.fillGradientRectangle(clArea.x, clArea.y, clArea.width, clArea.height, true); } }); GridLayout layout = new GridLayout(1, false); layout.marginWidth = 0; layout.marginHeight = 0; layout.verticalSpacing = 0; outerCircle.setLayout(layout); /* Outer composite to hold content controls */ Composite outerContentCircle = new Composite(outerCircle, SWT.NONE); outerContentCircle.setBackgroundMode(SWT.INHERIT_FORCE); layout = new GridLayout(1, false); layout.marginWidth = 0; layout.marginHeight = 0; outerContentCircle.setLayout(layout); outerContentCircle.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); outerContentCircle.setBackground(outerCircle.getBackground()); /* Middle composite to show a 1px black line around the content controls */ Composite middleContentCircle = new Composite(outerContentCircle, SWT.NO_FOCUS); middleContentCircle.setBackgroundMode(SWT.INHERIT_FORCE); layout = new GridLayout(1, false); layout.marginWidth = 0; layout.marginHeight = 0; layout.marginTop = 1; middleContentCircle.setLayout(layout); middleContentCircle.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); middleContentCircle.setBackground(this.colors.getBorderColor()); /* Inner composite containing the content controls */ Composite innerContent = new Composite(middleContentCircle, SWT.NO_FOCUS); innerContent.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); innerContent.setBackgroundMode(SWT.INHERIT_FORCE); layout = new GridLayout(1, false); layout.marginWidth = 0; layout.marginHeight = 5; layout.marginLeft = 5; layout.marginRight = 5; innerContent.setLayout(layout); innerContent.setBackground(this.shell.getDisplay().getSystemColor(SWT.COLOR_WHITE)); /* Content Area */ this.contentArea = createContentArea(innerContent); setNullBackground(outerCircle); return outerCircle; } private void setNullBackground(final Composite outerCircle) { for (Control c : outerCircle.getChildren()) { c.setBackground(null); if (c instanceof Composite) { setNullBackground((Composite) c); } } } @Override public boolean close() { // If already closed, there is nothing to do. // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=127505 if (getShell() == null || getShell().isDisposed()) { return true; } if (this.lastUsedRegion != null) { this.lastUsedRegion.dispose(); } if (this.lastUsedBgImage != null && !this.lastUsedBgImage.isDisposed()) { this.lastUsedBgImage.dispose(); } if (this.parentDeactivateListener != null) { getShell().getParent().removeListener(SWT.Deactivate, this.parentDeactivateListener); this.parentDeactivateListener = null; } return super.close(); } }