1 /*******************************************************************************
2  * Copyright (c) 2005, 2016 IBM Corporation and others.
3  *
4  * This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License 2.0
6  * which accompanies this distribution, and is available at
7  * https://www.eclipse.org/legal/epl-2.0/
8  *
9  * SPDX-License-Identifier: EPL-2.0
10  *
11  * Contributors:
12  *     IBM Corporation - initial API and implementation
13  *     Stefan Xenos, IBM - bug 156790: Adopt GridLayoutFactory within JFace
14  *******************************************************************************/
15 package org.eclipse.jface.dialogs;
16 
17 import static org.eclipse.swt.events.SelectionListener.widgetSelectedAdapter;
18 
19 import java.util.ArrayList;
20 import java.util.List;
21 
22 import org.eclipse.swt.SWT;
23 import org.eclipse.swt.events.MouseAdapter;
24 import org.eclipse.swt.events.MouseEvent;
25 import org.eclipse.swt.graphics.Color;
26 import org.eclipse.swt.graphics.Font;
27 import org.eclipse.swt.graphics.FontData;
28 import org.eclipse.swt.graphics.Point;
29 import org.eclipse.swt.graphics.RGB;
30 import org.eclipse.swt.graphics.Rectangle;
31 import org.eclipse.swt.widgets.Composite;
32 import org.eclipse.swt.widgets.Control;
33 import org.eclipse.swt.widgets.Display;
34 import org.eclipse.swt.widgets.Event;
35 import org.eclipse.swt.widgets.Label;
36 import org.eclipse.swt.widgets.Listener;
37 import org.eclipse.swt.widgets.Menu;
38 import org.eclipse.swt.widgets.Shell;
39 import org.eclipse.swt.widgets.ToolBar;
40 import org.eclipse.swt.widgets.ToolItem;
41 import org.eclipse.swt.widgets.Tracker;
42 
43 import org.eclipse.core.runtime.Assert;
44 
45 import org.eclipse.jface.action.Action;
46 import org.eclipse.jface.action.GroupMarker;
47 import org.eclipse.jface.action.IAction;
48 import org.eclipse.jface.action.IMenuManager;
49 import org.eclipse.jface.action.MenuManager;
50 import org.eclipse.jface.action.Separator;
51 import org.eclipse.jface.layout.GridDataFactory;
52 import org.eclipse.jface.layout.GridLayoutFactory;
53 import org.eclipse.jface.preference.JFacePreferences;
54 import org.eclipse.jface.resource.JFaceResources;
55 import org.eclipse.jface.util.Util;
56 import org.eclipse.jface.window.Window;
57 
58 /**
59  * A lightweight, transient dialog that is popped up to show contextual or
60  * temporal information and is easily dismissed. Clients control whether the
61  * dialog should be able to receive input focus. An optional title area at the
62  * top and an optional info area at the bottom can be used to provide additional
63  * information.
64  * <p>
65  * Because the dialog is short-lived, most of the configuration of the dialog is
66  * done in the constructor. Set methods are only provided for those values that
67  * are expected to be dynamically computed based on a particular instance's
68  * internal state.
69  * <p>
70  * Clients are expected to override the creation of the main dialog area, and
71  * may optionally override the creation of the title area and info area in order
72  * to add content. In general, however, the creation of stylistic features, such
73  * as the dialog menu, separator styles, and fonts, is kept private so that all
74  * popup dialogs will have a similar appearance.
75  *
76  * @since 3.2
77  */
78 public class PopupDialog extends Window {
79 
80 	private static GridDataFactory grabBothGridDataFactory;
81 
getGrabBothGridData()82 	private static GridDataFactory getGrabBothGridData() {
83 		if (grabBothGridDataFactory == null) {
84 			grabBothGridDataFactory = GridDataFactory.fillDefaults().grab(true, true);
85 		}
86 		return grabBothGridDataFactory;
87 	}
88 
89 	/**
90 	 * The dialog settings key name for stored dialog x location.
91 	 */
92 	private static final String DIALOG_ORIGIN_X = "DIALOG_X_ORIGIN"; //$NON-NLS-1$
93 
94 	/**
95 	 * The dialog settings key name for stored dialog y location.
96 	 */
97 	private static final String DIALOG_ORIGIN_Y = "DIALOG_Y_ORIGIN"; //$NON-NLS-1$
98 
99 	/**
100 	 * The dialog settings key name for stored dialog width.
101 	 */
102 	private static final String DIALOG_WIDTH = "DIALOG_WIDTH"; //$NON-NLS-1$
103 
104 	/**
105 	 * The dialog settings key name for stored dialog height.
106 	 */
107 	private static final String DIALOG_HEIGHT = "DIALOG_HEIGHT"; //$NON-NLS-1$
108 
109 	/**
110 	 * The dialog settings key name for remembering if the persisted size should
111 	 * be accessed.
112 	 */
113 	private static final String DIALOG_USE_PERSISTED_SIZE = "DIALOG_USE_PERSISTED_SIZE"; //$NON-NLS-1$
114 
115 	/**
116 	 * The dialog settings key name for remembering if the persisted location
117 	 * should be accessed.
118 	 */
119 	private static final String DIALOG_USE_PERSISTED_LOCATION = "DIALOG_USE_PERSISTED_LOCATION"; //$NON-NLS-1$
120 
121 	/**
122 	 * Move action for the dialog.
123 	 */
124 	private class MoveAction extends Action {
125 
MoveAction()126 		MoveAction() {
127 			super(JFaceResources.getString("PopupDialog.move"), //$NON-NLS-1$
128 					IAction.AS_PUSH_BUTTON);
129 		}
130 
131 		@Override
run()132 		public void run() {
133 			performTrackerAction(SWT.NONE);
134 		}
135 
136 	}
137 
138 	/**
139 	 * Resize action for the dialog.
140 	 */
141 	private class ResizeAction extends Action {
142 
ResizeAction()143 		ResizeAction() {
144 			super(JFaceResources.getString("PopupDialog.resize"), IAction.AS_PUSH_BUTTON); //$NON-NLS-1$
145 		}
146 
147 		@Override
run()148 		public void run() {
149 			performTrackerAction(SWT.RESIZE);
150 		}
151 	}
152 
153 	/**
154 	 *
155 	 * Remember bounds action for the dialog.
156 	 */
157 	private class PersistBoundsAction extends Action {
158 
PersistBoundsAction()159 		PersistBoundsAction() {
160 			super(JFaceResources.getString("PopupDialog.persistBounds"), IAction.AS_CHECK_BOX); //$NON-NLS-1$
161 			setChecked(persistLocation && persistSize);
162 		}
163 
164 		@Override
run()165 		public void run() {
166 			persistSize = isChecked();
167 			persistLocation = persistSize;
168 		}
169 	}
170 
171 	/**
172 	 *
173 	 * Remember bounds action for the dialog.
174 	 */
175 	private class PersistSizeAction extends Action {
176 
PersistSizeAction()177 		PersistSizeAction() {
178 			super(JFaceResources.getString("PopupDialog.persistSize"), IAction.AS_CHECK_BOX); //$NON-NLS-1$
179 			setChecked(persistSize);
180 		}
181 
182 		@Override
run()183 		public void run() {
184 			persistSize = isChecked();
185 		}
186 	}
187 
188 	/**
189 	 *
190 	 * Remember location action for the dialog.
191 	 */
192 	private class PersistLocationAction extends Action {
193 
PersistLocationAction()194 		PersistLocationAction() {
195 			super(JFaceResources.getString("PopupDialog.persistLocation"), IAction.AS_CHECK_BOX); //$NON-NLS-1$
196 			setChecked(persistLocation);
197 		}
198 
199 		@Override
run()200 		public void run() {
201 			persistLocation = isChecked();
202 		}
203 	}
204 
205 	/**
206 	 * Shell style appropriate for a simple hover popup that cannot get focus.
207 	 *
208 	 */
209 	public static final int HOVER_SHELLSTYLE = SWT.NO_FOCUS | SWT.ON_TOP | SWT.TOOL;
210 
211 	/**
212 	 * Shell style appropriate for an info popup that can get focus.
213 	 */
214 	public static final int INFOPOPUP_SHELLSTYLE = SWT.TOOL;
215 
216 	/**
217 	 * Shell style appropriate for a resizable info popup that can get focus.
218 	 */
219 	public static final int INFOPOPUPRESIZE_SHELLSTYLE = SWT.RESIZE;
220 
221 	/**
222 	 * Margin width (in pixels) to be used in layouts inside popup dialogs
223 	 * (value is 0).
224 	 */
225 	public static final int POPUP_MARGINWIDTH = 0;
226 
227 	/**
228 	 * Margin height (in pixels) to be used in layouts inside popup dialogs
229 	 * (value is 0).
230 	 */
231 	public static final int POPUP_MARGINHEIGHT = 0;
232 
233 	/**
234 	 * Vertical spacing (in pixels) between cells in the layouts inside popup
235 	 * dialogs (value is 1).
236 	 */
237 	public static final int POPUP_VERTICALSPACING = 1;
238 
239 	/**
240 	 * Vertical spacing (in pixels) between cells in the layouts inside popup
241 	 * dialogs (value is 1).
242 	 */
243 	public static final int POPUP_HORIZONTALSPACING = 1;
244 
245 	/**
246 	 * Image registry key for menu image.
247 	 *
248 	 * @since 3.4
249 	 */
250 	public static final String POPUP_IMG_MENU = "popup_menu_image"; //$NON-NLS-1$
251 
252 	/**
253 	 * Image registry key for disabled menu image.
254 	 *
255 	 * @since 3.4
256 	 */
257 	public static final String POPUP_IMG_MENU_DISABLED = "popup_menu_image_diabled"; //$NON-NLS-1$
258 
259 	/**
260 	 *
261 	 */
262 	private static GridLayoutFactory popupLayoutFactory;
getPopupLayout()263 	private static GridLayoutFactory getPopupLayout() {
264 		if (popupLayoutFactory == null) {
265 			popupLayoutFactory = GridLayoutFactory.fillDefaults()
266 					.margins(POPUP_MARGINWIDTH, POPUP_MARGINHEIGHT)
267 					.spacing(POPUP_HORIZONTALSPACING, POPUP_VERTICALSPACING);
268 		}
269 		return popupLayoutFactory;
270 	}
271 
272 	/**
273 	 * The dialog's toolbar for the move and resize capabilities.
274 	 */
275 	private ToolBar toolBar = null;
276 
277 	/**
278 	 * The dialog's menu manager.
279 	 */
280 	private MenuManager menuManager = null;
281 
282 	/**
283 	 * The control representing the main dialog area.
284 	 */
285 	private Control dialogArea;
286 
287 	/**
288 	 * Labels that contain title and info text. Cached so they can be updated
289 	 * dynamically if possible.
290 	 */
291 	private Label titleLabel, infoLabel;
292 
293 	/**
294 	 * Separator controls. Cached so they can be excluded from color changes.
295 	 */
296 	private Control titleSeparator, infoSeparator;
297 
298 	/**
299 	 * Color to be used for the info area text.
300 	 *
301 	 * @since 3.6
302 	 */
303 	private Color infoColor;
304 
305 	/**
306 	 * Font to be used for the info area text. Computed based on the dialog's
307 	 * font.
308 	 */
309 	private Font infoFont;
310 
311 	/**
312 	 * Font to be used for the title area text. Computed based on the dialog's
313 	 * font.
314 	 */
315 	private Font titleFont;
316 
317 	/**
318 	 * Flags indicating whether we are listening for shell deactivate events,
319 	 * either those or our parent's. Used to prevent closure when a menu command
320 	 * is chosen or a secondary popup is launched.
321 	 */
322 	private boolean listenToDeactivate;
323 
324 	private boolean listenToParentDeactivate;
325 
326 	private Listener parentDeactivateListener;
327 
328 	/**
329 	 * Flag indicating whether focus should be taken when the dialog is opened.
330 	 */
331 	private boolean takeFocusOnOpen = false;
332 
333 	/**
334 	 * Flag specifying whether a menu should be shown that allows the user to
335 	 * move and resize.
336 	 */
337 	private boolean showDialogMenu = false;
338 
339 	/**
340 	 * Flag specifying whether menu actions allowing the user to choose whether
341 	 * the dialog bounds and location should be persisted are to be shown.
342 	 */
343 	private boolean showPersistActions = false;
344 
345 	/**
346 	 * Flag specifying whether the size of the popup should be persisted. This
347 	 * flag is used as initial default and updated by the menu if it is shown.
348 	 */
349 	private boolean persistSize = false;
350 
351 	/**
352 	 * Flag specifying whether the location of the popup should be persisted.
353 	 * This flag is used as initial default and updated by the menu if it is
354 	 * shown.
355 	 */
356 	private boolean persistLocation = false;
357 	/**
358 	 * Flag specifying whether to use new 3.4 API instead of the old one.
359 	 *
360 	 * @since 3.4
361 	 */
362 	private boolean isUsing34API = true;
363 
364 	/**
365 	 * Text to be shown in an optional title area (on top).
366 	 */
367 	private String titleText;
368 
369 	/**
370 	 * Text to be shown in an optional info area (at the bottom).
371 	 */
372 	private String infoText;
373 
374 	/**
375 	 * Constructs a new instance of <code>PopupDialog</code>.
376 	 *
377 	 * @param parent
378 	 *                               The parent shell.
379 	 * @param shellStyle
380 	 *                               The shell style.
381 	 * @param takeFocusOnOpen
382 	 *                               A boolean indicating whether focus should be
383 	 *                               taken by this popup when it opens.
384 	 * @param persistBounds
385 	 *                               A boolean indicating whether the bounds (size
386 	 *                               and location) of the dialog should be persisted
387 	 *                               upon close of the dialog. The bounds can only
388 	 *                               be persisted if the dialog settings for
389 	 *                               persisting the bounds are also specified. If a
390 	 *                               menu action will be provided that allows the
391 	 *                               user to control this feature, then the last
392 	 *                               known value of the user's setting will be used
393 	 *                               instead of this flag.
394 	 * @param showDialogMenu
395 	 *                               A boolean indicating whether a menu for moving
396 	 *                               and resizing the popup should be provided.
397 	 * @param showPersistActions
398 	 *                               A boolean indicating whether actions allowing
399 	 *                               the user to control the persisting of the
400 	 *                               dialog size and location should be shown in the
401 	 *                               dialog menu. This parameter has no effect if
402 	 *                               <code>showDialogMenu</code> is
403 	 *                               <code>false</code>.
404 	 * @param titleText
405 	 *                               Text to be shown in an upper title area, or
406 	 *                               <code>null</code> if there is no title.
407 	 * @param infoText
408 	 *                               Text to be shown in a lower info area, or
409 	 *                               <code>null</code> if there is no info area.
410 	 *
411 	 * @see PopupDialog#getDialogSettings()
412 	 * @deprecated As of 3.4, replaced by
413 	 *             {@link #PopupDialog(Shell, int, boolean, boolean, boolean, boolean, boolean, String, String)}
414 	 *
415 	 * @noreference Planned for deletion see
416 	 *              https://bugs.eclipse.org/bugs/show_bug.cgi?id=531913
417 	 */
418 	@Deprecated
PopupDialog(Shell parent, int shellStyle, boolean takeFocusOnOpen, boolean persistBounds, boolean showDialogMenu, boolean showPersistActions, String titleText, String infoText)419 	public PopupDialog(Shell parent, int shellStyle, boolean takeFocusOnOpen,
420 			boolean persistBounds, boolean showDialogMenu,
421 			boolean showPersistActions, String titleText, String infoText) {
422 		this(parent, shellStyle, takeFocusOnOpen, persistBounds, persistBounds,
423 				showDialogMenu, showPersistActions, titleText, infoText, false);
424 	}
425 
426 	/**
427 	 * Constructs a new instance of <code>PopupDialog</code>.
428 	 *
429 	 * @param parent
430 	 *            The parent shell.
431 	 * @param shellStyle
432 	 *            The shell style.
433 	 * @param takeFocusOnOpen
434 	 *            A boolean indicating whether focus should be taken by this
435 	 *            popup when it opens.
436 	 * @param persistSize
437 	 *            A boolean indicating whether the size should be persisted upon
438 	 *            close of the dialog. The size can only be persisted if the
439 	 *            dialog settings for persisting the bounds are also specified.
440 	 *            If a menu action will be provided that allows the user to
441 	 *            control this feature and the user hasn't changed that setting,
442 	 *            then this flag is used as initial default for the menu.
443 	 * @param persistLocation
444 	 *            A boolean indicating whether the location should be persisted
445 	 *            upon close of the dialog. The location can only be persisted
446 	 *            if the dialog settings for persisting the bounds are also
447 	 *            specified. If a menu action will be provided that allows the
448 	 *            user to control this feature and the user hasn't changed that
449 	 *            setting, then this flag is used as initial default for the
450 	 *            menu. default for the menu until the user changed it.
451 	 * @param showDialogMenu
452 	 *            A boolean indicating whether a menu for moving and resizing
453 	 *            the popup should be provided.
454 	 * @param showPersistActions
455 	 *            A boolean indicating whether actions allowing the user to
456 	 *            control the persisting of the dialog bounds and location
457 	 *            should be shown in the dialog menu. This parameter has no
458 	 *            effect if <code>showDialogMenu</code> is <code>false</code>.
459 	 * @param titleText
460 	 *            Text to be shown in an upper title area, or <code>null</code>
461 	 *            if there is no title.
462 	 * @param infoText
463 	 *            Text to be shown in a lower info area, or <code>null</code>
464 	 *            if there is no info area.
465 	 *
466 	 * @see PopupDialog#getDialogSettings()
467 	 *
468 	 * @since 3.4
469 	 */
PopupDialog(Shell parent, int shellStyle, boolean takeFocusOnOpen, boolean persistSize, boolean persistLocation, boolean showDialogMenu, boolean showPersistActions, String titleText, String infoText)470 	public PopupDialog(Shell parent, int shellStyle, boolean takeFocusOnOpen,
471 			boolean persistSize, boolean persistLocation,
472 			boolean showDialogMenu, boolean showPersistActions,
473 			String titleText, String infoText) {
474 		this(parent, shellStyle, takeFocusOnOpen, persistSize, persistLocation,
475 				showDialogMenu, showPersistActions, titleText, infoText, true);
476 
477 	}
478 
479 	/**
480 	 * Constructs a new instance of <code>PopupDialog</code>.
481 	 *
482 	 * @param parent
483 	 *            The parent shell.
484 	 * @param shellStyle
485 	 *            The shell style.
486 	 * @param takeFocusOnOpen
487 	 *            A boolean indicating whether focus should be taken by this
488 	 *            popup when it opens.
489 	 * @param persistSize
490 	 *            A boolean indicating whether the size should be persisted upon
491 	 *            close of the dialog. The size can only be persisted if the
492 	 *            dialog settings for persisting the bounds are also specified.
493 	 *            If a menu action will be provided that allows the user to
494 	 *            control this feature and the user hasn't changed that setting,
495 	 *            then this flag is used as initial default for the menu.
496 	 * @param persistLocation
497 	 *            A boolean indicating whether the location should be persisted
498 	 *            upon close of the dialog. The location can only be persisted
499 	 *            if the dialog settings for persisting the bounds are also
500 	 *            specified. If a menu action will be provided that allows the
501 	 *            user to control this feature and the user hasn't changed that
502 	 *            setting, then this flag is used as initial default for the
503 	 *            menu. default for the menu until the user changed it.
504 	 * @param showDialogMenu
505 	 *            A boolean indicating whether a menu for moving and resizing
506 	 *            the popup should be provided.
507 	 * @param showPersistActions
508 	 *            A boolean indicating whether actions allowing the user to
509 	 *            control the persisting of the dialog bounds and location
510 	 *            should be shown in the dialog menu. This parameter has no
511 	 *            effect if <code>showDialogMenu</code> is <code>false</code>.
512 	 * @param titleText
513 	 *            Text to be shown in an upper title area, or <code>null</code>
514 	 *            if there is no title.
515 	 * @param infoText
516 	 *            Text to be shown in a lower info area, or <code>null</code>
517 	 *            if there is no info area.
518 	 * @param use34API
519 	 *            <code>true</code> if 3.4 API should be used
520 	 *
521 	 * @see PopupDialog#getDialogSettings()
522 	 *
523 	 * @since 3.4
524 	 */
PopupDialog(Shell parent, int shellStyle, boolean takeFocusOnOpen, boolean persistSize, boolean persistLocation, boolean showDialogMenu, boolean showPersistActions, String titleText, String infoText, boolean use34API)525 	private PopupDialog(Shell parent, int shellStyle, boolean takeFocusOnOpen,
526 			boolean persistSize, boolean persistLocation,
527 			boolean showDialogMenu, boolean showPersistActions,
528 			String titleText, String infoText, boolean use34API) {
529 		super(parent);
530 		// Prior to 3.4, we encouraged use of SWT.NO_TRIM and provided a
531 		// border using a black composite background and margin. Now we
532 		// use SWT.TOOL to get the border for some cases and this conflicts
533 		// with SWT.NO_TRIM. Clients who previously have used SWT.NO_TRIM
534 		// and still had a border drawn for them would find their border go
535 		// away unless we do the following:
536 		// see https://bugs.eclipse.org/bugs/show_bug.cgi?id=219743
537 		if ((shellStyle & SWT.NO_TRIM) != 0) {
538 			shellStyle &= ~(SWT.NO_TRIM | SWT.SHELL_TRIM);
539 		}
540 
541 		setShellStyle(shellStyle);
542 		this.takeFocusOnOpen = takeFocusOnOpen;
543 		this.showDialogMenu = showDialogMenu;
544 		this.showPersistActions = showPersistActions;
545 		this.titleText = titleText;
546 		this.infoText = infoText;
547 
548 		setBlockOnOpen(false);
549 
550 		this.isUsing34API = use34API;
551 
552 		this.persistSize = persistSize;
553 		this.persistLocation = persistLocation;
554 
555 		initializeWidgetState();
556 	}
557 
558 	@Override
configureShell(Shell shell)559 	protected void configureShell(Shell shell) {
560 		GridLayoutFactory.fillDefaults().margins(0, 0).spacing(5, 5).applyTo(
561 				shell);
562 		shell.addListener(SWT.Deactivate, event -> {
563 			/*
564 			 * Close if we are deactivating and have no child shells. If we
565 			 * have child shells, we are deactivating due to their opening.
566 			 *
567 			 * Feature in GTK: this causes the Quick Outline/Type Hierarchy
568 			 * Shell to close on re-size/movement on Gtk3. For this reason,
569 			 * the asyncClose() call is disabled in GTK. See Eclipse Bugs
570 			 * 466500 and 113577 for more information.
571 			 */
572 			if (listenToDeactivate && event.widget == getShell()
573 					&& getShell().getShells().length == 0) {
574 				if (!Util.isGtk()) {
575 					asyncClose();
576 				}
577 			} else {
578 				/*
579 				 * We typically ignore deactivates to work around
580 				 * platform-specific event ordering. Now that we've ignored
581 				 * whatever we were supposed to, start listening to
582 				 * deactivates. Example issues can be found in
583 				 * https://bugs.eclipse.org/bugs/show_bug.cgi?id=123392
584 				 */
585 				listenToDeactivate = true;
586 			}
587 		});
588 		// Set this true whenever we activate. It may have been turned
589 		// off by a menu or secondary popup showing.
590 		shell.addListener(SWT.Activate, event -> {
591 			// ignore this event if we have launched a child
592 			if (event.widget == getShell() && getShell().getShells().length == 0) {
593 				listenToDeactivate = true;
594 				// Typically we start listening for parent deactivate after
595 				// we are activated, except on the Mac, where the deactivate
596 				// is received after activate.
597 				// See https://bugs.eclipse.org/bugs/show_bug.cgi?id=100668
598 				listenToParentDeactivate = !Util.isMac();
599 			}
600 		});
601 
602 		final Composite parent = shell.getParent();
603 		if (parent != null) {
604 			if ((getShellStyle() & SWT.ON_TOP) != 0) {
605 				parentDeactivateListener = event -> {
606 					if (listenToParentDeactivate) {
607 						asyncClose();
608 					} else {
609 						// Our first deactivate, now start listening on the Mac.
610 						listenToParentDeactivate = listenToDeactivate;
611 					}
612 				};
613 				parent.addListener(SWT.Deactivate, parentDeactivateListener);
614 			} else if (Util.isGtk()) {
615 				/*
616 				 * Fix for bug 485745 on GTK: popup does not close on parent
617 				 * shell activation.
618 				 */
619 				parent.addListener(SWT.Activate, new Listener() {
620 					@Override
621 					public void handleEvent(Event event) {
622 						/*
623 						 * NB: we must wait with closing until
624 						 * listenToDeactivate is set to true, otherwise it may
625 						 * happen that the popup closes immediately after
626 						 * showing up (seem to be timing issue with shell
627 						 * creation).
628 						 *
629 						 * E.g. "Display" popup does not need this, but
630 						 * "Show all Instances" and "Show all References" do.
631 						 * They all are InspectPopupDialog instances...
632 						 */
633 						if (event.widget != parent || !listenToDeactivate || parent.isDisposed()) {
634 							return;
635 						}
636 						parent.removeListener(SWT.Activate, this);
637 						asyncClose();
638 					}
639 				});
640 			}
641 		}
642 
643 		shell.addDisposeListener(event -> handleDispose());
644 	}
645 
asyncClose()646 	private void asyncClose() {
647 		// workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=152010
648 		Shell shell = getShell();
649 		if (shell != null && !shell.isDisposed()) {
650 			shell.getDisplay().asyncExec(() -> close());
651 		}
652 	}
653 
654 	/**
655 	 * The <code>PopupDialog</code> implementation of this <code>Window</code>
656 	 * method creates and lays out the top level composite for the dialog. It
657 	 * then calls the <code>createTitleMenuArea</code>,
658 	 * <code>createDialogArea</code>, and <code>createInfoTextArea</code>
659 	 * methods to create an optional title and menu area on the top, a dialog
660 	 * area in the center, and an optional info text area at the bottom.
661 	 * Overriding <code>createDialogArea</code> and (optionally)
662 	 * <code>createTitleMenuArea</code> and <code>createTitleMenuArea</code>
663 	 * are recommended rather than overriding this method.
664 	 *
665 	 * @param parent
666 	 *            the composite used to parent the contents.
667 	 *
668 	 * @return the control representing the contents.
669 	 */
670 	@Override
createContents(Composite parent)671 	protected Control createContents(Composite parent) {
672 		Composite composite = new Composite(parent, SWT.NONE);
673 		getPopupLayout().applyTo(composite);
674 		getGrabBothGridData().applyTo(composite);
675 
676 		// Title area
677 		if (hasTitleArea()) {
678 			createTitleMenuArea(composite);
679 			titleSeparator = createHorizontalSeparator(composite);
680 		}
681 		// Content
682 		dialogArea = createDialogArea(composite);
683 		// Create a grid data layout data if one was not provided.
684 		// See https://bugs.eclipse.org/bugs/show_bug.cgi?id=118025
685 		if (dialogArea.getLayoutData() == null) {
686 			getGrabBothGridData().applyTo(dialogArea);
687 		}
688 
689 		// Info field
690 		if (hasInfoArea()) {
691 			infoSeparator = createHorizontalSeparator(composite);
692 			createInfoTextArea(composite);
693 		}
694 
695 		applyColors(composite);
696 		applyFonts(composite);
697 		return composite;
698 	}
699 
700 	/**
701 	 * Creates and returns the contents of the dialog (the area below the title
702 	 * area and above the info text area.
703 	 * <p>
704 	 * The <code>PopupDialog</code> implementation of this framework method
705 	 * creates and returns a new <code>Composite</code> with standard margins
706 	 * and spacing.
707 	 * <p>
708 	 * The returned control's layout data must be an instance of
709 	 * <code>GridData</code>. This method must not modify the parent's
710 	 * layout.
711 	 * <p>
712 	 * Subclasses must override this method but may call <code>super</code> as
713 	 * in the following example:
714 	 *
715 	 * <pre>
716 	 * Composite composite = (Composite) super.createDialogArea(parent);
717 	 * //add controls to composite as necessary
718 	 * return composite;
719 	 * </pre>
720 	 *
721 	 * @param parent
722 	 *            the parent composite to contain the dialog area
723 	 * @return the dialog area control
724 	 */
createDialogArea(Composite parent)725 	protected Control createDialogArea(Composite parent) {
726 		Composite composite = new Composite(parent, SWT.NONE);
727 		getPopupLayout().applyTo(composite);
728 		getGrabBothGridData().applyTo(composite);
729 		return composite;
730 	}
731 
732 	/**
733 	 * Returns the control that should get initial focus. Subclasses may
734 	 * override this method.
735 	 *
736 	 * @return the Control that should receive focus when the popup opens.
737 	 */
getFocusControl()738 	protected Control getFocusControl() {
739 		return dialogArea;
740 	}
741 
742 	/**
743 	 * Sets the tab order for the popup. Clients should override to introduce
744 	 * specific tab ordering.
745 	 *
746 	 * @param composite
747 	 *            the composite in which all content, including the title area
748 	 *            and info area, was created. This composite's parent is the
749 	 *            shell.
750 	 */
setTabOrder(Composite composite)751 	protected void setTabOrder(Composite composite) {
752 		// default is to do nothing
753 	}
754 
755 	/**
756 	 * Returns a boolean indicating whether the popup should have a title area
757 	 * at the top of the dialog. Subclasses may override. Default behavior is to
758 	 * have a title area if there is to be a menu or title text.
759 	 *
760 	 * @return <code>true</code> if a title area should be created,
761 	 *         <code>false</code> if it should not.
762 	 */
hasTitleArea()763 	protected boolean hasTitleArea() {
764 		return titleText != null || showDialogMenu;
765 	}
766 
767 	/**
768 	 * Returns a boolean indicating whether the popup should have an info area
769 	 * at the bottom of the dialog. Subclasses may override. Default behavior is
770 	 * to have an info area if info text was provided at the time of creation.
771 	 *
772 	 * @return <code>true</code> if a title area should be created,
773 	 *         <code>false</code> if it should not.
774 	 */
hasInfoArea()775 	protected boolean hasInfoArea() {
776 		return infoText != null;
777 	}
778 
779 	/**
780 	 * Creates the title and menu area. Subclasses typically need not override
781 	 * this method, but instead should use the constructor parameters
782 	 * <code>showDialogMenu</code> and <code>showPersistAction</code> to
783 	 * indicate whether a menu should be shown, and
784 	 * <code>createTitleControl</code> to to customize the presentation of the
785 	 * title.
786 	 *
787 	 * <p>
788 	 * If this method is overridden, the returned control's layout data must be
789 	 * an instance of <code>GridData</code>. This method must not modify the
790 	 * parent's layout.
791 	 *
792 	 * @param parent
793 	 *            The parent composite.
794 	 * @return The Control representing the title and menu area.
795 	 */
createTitleMenuArea(Composite parent)796 	protected Control createTitleMenuArea(Composite parent) {
797 
798 		Composite titleAreaComposite = new Composite(parent, SWT.NONE);
799 		getPopupLayout().copy().numColumns(2).applyTo(titleAreaComposite);
800 		GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).grab(true, false).applyTo(titleAreaComposite);
801 
802 		createTitleControl(titleAreaComposite);
803 
804 		if (showDialogMenu) {
805 			createDialogMenu(titleAreaComposite);
806 		}
807 		return titleAreaComposite;
808 	}
809 
810 	/**
811 	 * Creates the control to be used to represent the dialog's title text.
812 	 * Subclasses may override if a different control is desired for
813 	 * representing the title text, or if something different than the title
814 	 * should be displayed in location where the title text typically is shown.
815 	 *
816 	 * <p>
817 	 * If this method is overridden, the returned control's layout data must be
818 	 * an instance of <code>GridData</code>. This method must not modify the
819 	 * parent's layout.
820 	 *
821 	 * @param parent
822 	 *            The parent composite.
823 	 * @return The Control representing the title area.
824 	 */
createTitleControl(Composite parent)825 	protected Control createTitleControl(Composite parent) {
826 		titleLabel = new Label(parent, SWT.NONE);
827 
828 		GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).grab(true,
829 				false).span(showDialogMenu ? 1 : 2, 1).applyTo(titleLabel);
830 
831 		if (titleText != null) {
832 			titleLabel.setText(titleText);
833 		}
834 		return titleLabel;
835 	}
836 
837 	/**
838 	 * Creates the optional info text area. This method is only called if the
839 	 * <code>hasInfoArea()</code> method returns true. Subclasses typically
840 	 * need not override this method, but may do so.
841 	 *
842 	 * <p>
843 	 * If this method is overridden, the returned control's layout data must be
844 	 * an instance of <code>GridData</code>. This method must not modify the
845 	 * parent's layout.
846 	 *
847 	 *
848 	 * @param parent
849 	 *            The parent composite.
850 	 * @return The control representing the info text area.
851 	 *
852 	 * @see PopupDialog#hasInfoArea()
853 	 * @see PopupDialog#createTitleControl(Composite)
854 	 */
createInfoTextArea(Composite parent)855 	protected Control createInfoTextArea(Composite parent) {
856 		// Status label
857 		infoLabel = new Label(parent, SWT.RIGHT);
858 		infoLabel.setText(infoText);
859 
860 		GridDataFactory.fillDefaults().grab(true, false).align(SWT.FILL,
861 				SWT.BEGINNING).applyTo(infoLabel);
862 		Display display = parent.getDisplay();
863 
864 		Color backgroundColor = getBackground();
865 		if (backgroundColor == null)
866 			backgroundColor = getDefaultBackground();
867 		Color foregroundColor = getForeground();
868 		if (foregroundColor == null)
869 			foregroundColor = getDefaultForeground();
870 		infoColor = new Color(display, blend(
871 				backgroundColor.getRGB(), foregroundColor.getRGB(),
872 				0.56f));
873 
874 		infoLabel.setForeground(infoColor);
875 		return infoLabel;
876 	}
877 
878 	/**
879 	 * Returns an RGB that lies between the given foreground and background
880 	 * colors using the given mixing factor. A <code>factor</code> of 1.0 will produce a
881 	 * color equal to <code>fg</code>, while a <code>factor</code> of 0.0 will produce one
882 	 * equal to <code>bg</code>.
883 	 * @param bg the background color
884 	 * @param fg the foreground color
885 	 * @param factor the mixing factor, must be in [0,&nbsp;1]
886 	 *
887 	 * @return the interpolated color
888 	 * @since 3.6
889 	 */
blend(RGB bg, RGB fg, float factor)890 	private static RGB blend(RGB bg, RGB fg, float factor) {
891 		// copy of org.eclipse.jface.internal.text.revisions.Colors#blend(..)
892 		Assert.isLegal(bg != null);
893 		Assert.isLegal(fg != null);
894 		Assert.isLegal(factor >= 0f && factor <= 1f);
895 
896 		float complement = 1f - factor;
897 		return new RGB(
898 				(int) (complement * bg.red + factor * fg.red),
899 				(int) (complement * bg.green + factor * fg.green),
900 				(int) (complement * bg.blue + factor * fg.blue)
901 		);
902 	}
903 
904 	/**
905 	 * Create a horizontal separator for the given parent.
906 	 *
907 	 * @param parent
908 	 *            The parent composite.
909 	 * @return The Control representing the horizontal separator.
910 	 */
createHorizontalSeparator(Composite parent)911 	private Control createHorizontalSeparator(Composite parent) {
912 		Label separator = new Label(parent, SWT.SEPARATOR | SWT.HORIZONTAL);
913 		GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).grab(true,
914 				false).applyTo(separator);
915 		return separator;
916 	}
917 
918 	/**
919 	 * Create the dialog's menu for the move and resize actions.
920 	 *
921 	 * @param parent
922 	 *            The parent composite.
923 	 */
createDialogMenu(Composite parent)924 	private void createDialogMenu(Composite parent) {
925 
926 		toolBar = new ToolBar(parent, SWT.FLAT);
927 		ToolItem viewMenuButton = new ToolItem(toolBar, SWT.PUSH, 0);
928 
929 		GridDataFactory.fillDefaults().align(SWT.END, SWT.CENTER).applyTo(toolBar);
930 		viewMenuButton.setImage(JFaceResources.getImage(POPUP_IMG_MENU));
931 		viewMenuButton.setDisabledImage(JFaceResources.getImage(POPUP_IMG_MENU_DISABLED));
932 		viewMenuButton.setToolTipText(JFaceResources.getString("PopupDialog.menuTooltip")); //$NON-NLS-1$
933 		viewMenuButton.addSelectionListener(widgetSelectedAdapter(e -> showDialogMenu()));
934 		// See https://bugs.eclipse.org/bugs/show_bug.cgi?id=177183
935 		toolBar.addMouseListener(new MouseAdapter() {
936 			@Override
937 			public void mouseDown(MouseEvent e) {
938 				showDialogMenu();
939 			}
940 		});
941 	}
942 
943 	/**
944 	 * Fill the dialog's menu. Subclasses may extend or override.
945 	 *
946 	 * @param dialogMenu
947 	 *            The dialog's menu.
948 	 */
fillDialogMenu(IMenuManager dialogMenu)949 	protected void fillDialogMenu(IMenuManager dialogMenu) {
950 		dialogMenu.add(new GroupMarker("SystemMenuStart")); //$NON-NLS-1$
951 		dialogMenu.add(new MoveAction());
952 		dialogMenu.add(new ResizeAction());
953 		if (showPersistActions) {
954 			if (isUsing34API) {
955 				dialogMenu.add(new PersistLocationAction());
956 				dialogMenu.add(new PersistSizeAction());
957 			} else {
958 				dialogMenu.add(new PersistBoundsAction());
959 			}
960 		}
961 		dialogMenu.add(new Separator("SystemMenuEnd")); //$NON-NLS-1$
962 	}
963 
964 	/**
965 	 * Perform the requested tracker action (resize or move).
966 	 *
967 	 * @param style
968 	 *            The track style (resize or move).
969 	 */
performTrackerAction(int style)970 	private void performTrackerAction(int style) {
971 		Shell shell = getShell();
972 		if (shell == null || shell.isDisposed()) {
973 			return;
974 		}
975 
976 		Tracker tracker = new Tracker(shell.getDisplay(), style);
977 		tracker.setStippled(true);
978 		Rectangle[] r = new Rectangle[] { shell.getBounds() };
979 		tracker.setRectangles(r);
980 
981 		// Ignore any deactivate events caused by opening the tracker.
982 		// See https://bugs.eclipse.org/bugs/show_bug.cgi?id=120656
983 		boolean oldListenToDeactivate = listenToDeactivate;
984 		listenToDeactivate = false;
985 		if (tracker.open()) {
986 			if (!shell.isDisposed()) {
987 				shell.setBounds(tracker.getRectangles()[0]);
988 			}
989 		}
990 		tracker.dispose();
991 		listenToDeactivate = oldListenToDeactivate;
992 	}
993 
994 	/**
995 	 * Show the dialog's menu. This message has no effect if the receiver was
996 	 * not configured to show a menu. Clients may call this method in order to
997 	 * trigger the menu via keystrokes or other gestures. Subclasses typically
998 	 * do not override method.
999 	 */
showDialogMenu()1000 	protected void showDialogMenu() {
1001 		if (!showDialogMenu) {
1002 			return;
1003 		}
1004 
1005 		if (menuManager == null) {
1006 			menuManager = new MenuManager();
1007 			fillDialogMenu(menuManager);
1008 		}
1009 		// Setting this flag works around a problem that remains on X only,
1010 		// whereby activating the menu deactivates our shell.
1011 		listenToDeactivate = !Util.isGtk();
1012 
1013 		Menu menu = menuManager.createContextMenu(getShell());
1014 		Rectangle bounds = toolBar.getBounds();
1015 		Point topLeft = new Point(bounds.x, bounds.y + bounds.height);
1016 		topLeft = getShell().toDisplay(topLeft);
1017 		menu.setLocation(topLeft.x, topLeft.y);
1018 		menu.setVisible(true);
1019 	}
1020 
1021 	/**
1022 	 * Set the text to be shown in the popup's info area. This message has no
1023 	 * effect if there was no info text supplied when the dialog first opened.
1024 	 * Subclasses may override this method.
1025 	 *
1026 	 * @param text
1027 	 *            the text to be shown when the info area is displayed.
1028 	 *
1029 	 */
setInfoText(String text)1030 	protected void setInfoText(String text) {
1031 		infoText = text;
1032 		if (infoLabel != null) {
1033 			infoLabel.setText(text);
1034 		}
1035 	}
1036 
1037 	/**
1038 	 * Set the text to be shown in the popup's title area. This message has no
1039 	 * effect if there was no title label specified when the dialog was
1040 	 * originally opened. Subclasses may override this method.
1041 	 *
1042 	 * @param text
1043 	 *            the text to be shown when the title area is displayed.
1044 	 *
1045 	 */
setTitleText(String text)1046 	protected void setTitleText(String text) {
1047 		titleText = text;
1048 		if (titleLabel != null) {
1049 			titleLabel.setText(text);
1050 		}
1051 	}
1052 
1053 	/**
1054 	 * Return a boolean indicating whether this dialog will persist its bounds. This
1055 	 * value is initially set in the dialog's constructor, but can be modified if
1056 	 * the persist bounds action is shown on the menu and the user has changed its
1057 	 * value. Subclasses may override this method.
1058 	 *
1059 	 * @return <code>true</code> if the dialog's bounds will be persisted,
1060 	 *         <code>false</code> if it will not.
1061 	 *
1062 	 * @deprecated As of 3.4, please use {@link #getPersistLocation()} or
1063 	 *             {@link #getPersistSize()} to determine separately whether size or
1064 	 *             location should be persisted.
1065 	 *
1066 	 * @noreference Planned for deletion see
1067 	 *              https://bugs.eclipse.org/bugs/show_bug.cgi?id=531913
1068 	 */
1069 	@Deprecated
getPersistBounds()1070 	protected boolean getPersistBounds() {
1071 		return persistLocation && persistSize;
1072 	}
1073 
1074 	/**
1075 	 * Return a boolean indicating whether this dialog will persist its
1076 	 * location. This value is initially set in the dialog's constructor, but
1077 	 * can be modified if the persist location action is shown on the menu and
1078 	 * the user has changed its value. Subclasses may override this method.
1079 	 *
1080 	 * @return <code>true</code> if the dialog's location will be persisted,
1081 	 *         <code>false</code> if it will not.
1082 	 *
1083 	 * @see #getPersistSize()
1084 	 * @since 3.4
1085 	 */
getPersistLocation()1086 	protected boolean getPersistLocation() {
1087 		return persistLocation;
1088 	}
1089 
1090 	/**
1091 	 * Return a boolean indicating whether this dialog will persist its size.
1092 	 * This value is initially set in the dialog's constructor, but can be
1093 	 * modified if the persist size action is shown on the menu and the user has
1094 	 * changed its value. Subclasses may override this method.
1095 	 *
1096 	 * @return <code>true</code> if the dialog's size will be persisted,
1097 	 *         <code>false</code> if it will not.
1098 	 *
1099 	 * @see #getPersistLocation()
1100 	 * @since 3.4
1101 	 */
getPersistSize()1102 	protected boolean getPersistSize() {
1103 		return persistSize;
1104 	}
1105 
1106 	/**
1107 	 * Opens this window, creating it first if it has not yet been created.
1108 	 * <p>
1109 	 * This method is reimplemented for special configuration of PopupDialogs.
1110 	 * It never blocks on open, immediately returning <code>OK</code> if the
1111 	 * open is successful, or <code>CANCEL</code> if it is not. It provides
1112 	 * framework hooks that allow subclasses to set the focus and tab order, and
1113 	 * avoids the use of <code>shell.open()</code> in cases where the focus
1114 	 * should not be given to the shell initially.
1115 	 *
1116 	 * @return the return code
1117 	 *
1118 	 * @see org.eclipse.jface.window.Window#open()
1119 	 */
1120 	@Override
open()1121 	public int open() {
1122 
1123 		Shell shell = getShell();
1124 		if (shell == null || shell.isDisposed()) {
1125 			shell = null;
1126 			// create the window
1127 			create();
1128 			shell = getShell();
1129 		}
1130 
1131 		// provide a hook for adjusting the bounds. This is only
1132 		// necessary when there is content driven sizing that must be
1133 		// adjusted each time the dialog is opened.
1134 		adjustBounds();
1135 
1136 		// limit the shell size to the display size
1137 		constrainShellSize();
1138 
1139 		// set up the tab order for the dialog
1140 		setTabOrder((Composite) getContents());
1141 
1142 		// initialize flags for listening to deactivate
1143 		listenToDeactivate = false;
1144 		listenToParentDeactivate = false;
1145 
1146 		// open the window
1147 		if (takeFocusOnOpen) {
1148 			shell.open();
1149 			getFocusControl().setFocus();
1150 		} else {
1151 			shell.setVisible(true);
1152 		}
1153 
1154 		return OK;
1155 
1156 	}
1157 
1158 	/**
1159 	 * Closes this window, disposes its shell, and removes this window from its
1160 	 * window manager (if it has one).
1161 	 * <p>
1162 	 * This method is extended to save the dialog bounds and initialize widget
1163 	 * state so that the widgets can be recreated if the dialog is reopened.
1164 	 * This method may be extended (<code>super.close</code> must be called).
1165 	 * </p>
1166 	 *
1167 	 * @return <code>true</code> if the window is (or was already) closed, and
1168 	 *         <code>false</code> if it is still open
1169 	 */
1170 	@Override
close()1171 	public boolean close() {
1172 		// If already closed, there is nothing to do.
1173 		// See https://bugs.eclipse.org/bugs/show_bug.cgi?id=127505
1174 		if (getShell() == null || getShell().isDisposed()) {
1175 			return true;
1176 		}
1177 
1178 		saveDialogBounds(getShell());
1179 		// Widgets are about to be disposed, so null out any state
1180 		// related to them that was not handled in dispose listeners.
1181 		// We do this before disposal so that any received activate or
1182 		// deactivate events are duly ignored.
1183 		initializeWidgetState();
1184 
1185 		if (parentDeactivateListener != null) {
1186 			getShell().getParent().removeListener(SWT.Deactivate,
1187 					parentDeactivateListener);
1188 			parentDeactivateListener = null;
1189 		}
1190 
1191 		return super.close();
1192 	}
1193 
1194 	/**
1195 	 * Gets the dialog settings that should be used for remembering the bounds
1196 	 * of the dialog. Subclasses should override this method when they wish to
1197 	 * persist the bounds of the dialog.
1198 	 *
1199 	 * @return settings the dialog settings used to store the dialog's location
1200 	 *         and/or size, or <code>null</code> if the dialog's bounds should
1201 	 *         never be stored.
1202 	 */
getDialogSettings()1203 	protected IDialogSettings getDialogSettings() {
1204 		return null;
1205 	}
1206 
1207 	/**
1208 	 * Saves the bounds of the shell in the appropriate dialog settings. The
1209 	 * bounds are recorded relative to the parent shell, if there is one, or
1210 	 * display coordinates if there is no parent shell. Subclasses typically
1211 	 * need not override this method, but may extend it (calling
1212 	 * <code>super.saveDialogBounds</code> if additional bounds information
1213 	 * should be stored. Clients may also call this method to persist the bounds
1214 	 * at times other than closing the dialog.
1215 	 *
1216 	 * @param shell
1217 	 *            The shell whose bounds are to be stored
1218 	 */
saveDialogBounds(Shell shell)1219 	protected void saveDialogBounds(Shell shell) {
1220 		IDialogSettings settings = getDialogSettings();
1221 		if (settings != null) {
1222 			Point shellLocation = shell.getLocation();
1223 			Point shellSize = shell.getSize();
1224 			Shell parent = getParentShell();
1225 			if (parent != null) {
1226 				Point parentLocation = parent.getLocation();
1227 				shellLocation.x -= parentLocation.x;
1228 				shellLocation.y -= parentLocation.y;
1229 			}
1230 			String prefix = getClass().getName();
1231 			if (persistSize) {
1232 				settings.put(prefix + DIALOG_WIDTH, shellSize.x);
1233 				settings.put(prefix + DIALOG_HEIGHT, shellSize.y);
1234 			}
1235 			if (persistLocation) {
1236 				settings.put(prefix + DIALOG_ORIGIN_X, shellLocation.x);
1237 				settings.put(prefix + DIALOG_ORIGIN_Y, shellLocation.y);
1238 			}
1239 			if (showPersistActions && showDialogMenu) {
1240 				settings.put(getClass().getName() + DIALOG_USE_PERSISTED_SIZE,
1241 						persistSize);
1242 				settings.put(getClass().getName()
1243 						+ DIALOG_USE_PERSISTED_LOCATION, persistLocation);
1244 
1245 			}
1246 		}
1247 	}
1248 
1249 	@Override
getInitialSize()1250 	protected Point getInitialSize() {
1251 		Point result = getDefaultSize();
1252 		if (persistSize) {
1253 			IDialogSettings settings = getDialogSettings();
1254 			if (settings != null) {
1255 				try {
1256 					int width = settings.getInt(getClass().getName()
1257 							+ DIALOG_WIDTH);
1258 					int height = settings.getInt(getClass().getName()
1259 							+ DIALOG_HEIGHT);
1260 					result = new Point(width, height);
1261 
1262 				} catch (NumberFormatException e) {
1263 				}
1264 			}
1265 		}
1266 		// No attempt is made to constrain the bounds. The default
1267 		// constraining behavior in Window will be used.
1268 		return result;
1269 	}
1270 
1271 	/**
1272 	 * Return the default size to use for the shell. This default size is used
1273 	 * if the dialog does not have any persisted size to restore. The default
1274 	 * implementation returns the preferred size of the shell. Subclasses should
1275 	 * override this method when an alternate default size is desired, rather
1276 	 * than overriding {@link #getInitialSize()}.
1277 	 *
1278 	 * @return the initial size of the shell
1279 	 *
1280 	 * @see #getPersistSize()
1281 	 * @since 3.4
1282 	 */
getDefaultSize()1283 	protected Point getDefaultSize() {
1284 		return super.getInitialSize();
1285 	}
1286 
1287 	/**
1288 	 * Returns the default location to use for the shell. This default location
1289 	 * is used if the dialog does not have any persisted location to restore.
1290 	 * The default implementation uses the location computed by
1291 	 * {@link org.eclipse.jface.window.Window#getInitialLocation(Point)}.
1292 	 * Subclasses should override this method when an alternate default location
1293 	 * is desired, rather than overriding {@link #getInitialLocation(Point)}.
1294 	 *
1295 	 * @param initialSize
1296 	 *            the initial size of the shell, as returned by
1297 	 *            <code>getInitialSize</code>.
1298 	 * @return the initial location of the shell
1299 	 *
1300 	 * @see #getPersistLocation()
1301 	 * @since 3.4
1302 	 */
getDefaultLocation(Point initialSize)1303 	protected Point getDefaultLocation(Point initialSize) {
1304 		return super.getInitialLocation(initialSize);
1305 	}
1306 
1307 	/**
1308 	 * Adjust the bounds of the popup as necessary prior to opening the dialog.
1309 	 * Default is to do nothing, which honors any bounds set directly by clients
1310 	 * or those that have been saved in the dialog settings. Subclasses should
1311 	 * override this method when there are bounds computations that must be
1312 	 * checked each time the dialog is opened.
1313 	 */
adjustBounds()1314 	protected void adjustBounds() {
1315 	}
1316 
1317 	@Override
getInitialLocation(Point initialSize)1318 	protected Point getInitialLocation(Point initialSize) {
1319 		Point result = getDefaultLocation(initialSize);
1320 		if (persistLocation) {
1321 			IDialogSettings settings = getDialogSettings();
1322 			if (settings != null) {
1323 				try {
1324 					int x = settings.getInt(getClass().getName()
1325 							+ DIALOG_ORIGIN_X);
1326 					int y = settings.getInt(getClass().getName()
1327 							+ DIALOG_ORIGIN_Y);
1328 					result = new Point(x, y);
1329 					// The coordinates were stored relative to the parent shell.
1330 					// Convert to display coordinates.
1331 					Shell parent = getParentShell();
1332 					if (parent != null) {
1333 						Point parentLocation = parent.getLocation();
1334 						result.x += parentLocation.x;
1335 						result.y += parentLocation.y;
1336 					}
1337 				} catch (NumberFormatException e) {
1338 				}
1339 			}
1340 		}
1341 		// No attempt is made to constrain the bounds. The default
1342 		// constraining behavior in Window will be used.
1343 		return result;
1344 	}
1345 
1346 	/**
1347 	 * Apply any desired color to the specified composite and its children.
1348 	 *
1349 	 * @param composite
1350 	 *            the contents composite
1351 	 */
applyColors(Composite composite)1352 	private void applyColors(Composite composite) {
1353 		// The getForeground() and getBackground() methods
1354 		// should not answer null, but IColorProvider clients
1355 		// are accustomed to null meaning use the default, so we guard
1356 		// against this assumption.
1357 		Color color = getForeground();
1358 		if (color == null)
1359 			color = getDefaultForeground();
1360 		applyForegroundColor(color, composite, getForegroundColorExclusions());
1361 		color = getBackground();
1362 		if (color == null)
1363 			color = getDefaultBackground();
1364 		applyBackgroundColor(color, composite, getBackgroundColorExclusions());
1365 	}
1366 
1367 	/**
1368 	 * Get the foreground color that should be used for this popup. Subclasses
1369 	 * may override.
1370 	 *
1371 	 * @return the foreground color to be used. Should not be <code>null</code>.
1372 	 *
1373 	 * @since 3.4
1374 	 *
1375 	 * @see #getForegroundColorExclusions()
1376 	 */
getForeground()1377 	protected Color getForeground() {
1378 		return getDefaultForeground();
1379 	}
1380 
1381 	/**
1382 	 * Get the background color that should be used for this popup. Subclasses
1383 	 * may override.
1384 	 *
1385 	 * @return the background color to be used. Should not be <code>null</code>.
1386 	 *
1387 	 * @since 3.4
1388 	 *
1389 	 * @see #getBackgroundColorExclusions()
1390 	 */
getBackground()1391 	protected Color getBackground() {
1392 		return getDefaultBackground();
1393 	}
1394 
1395 	/**
1396 	 * Return the default foreground color used for popup dialogs.
1397 	 *
1398 	 * @return the default foreground color.
1399 	 */
getDefaultForeground()1400 	private Color getDefaultForeground() {
1401 		if ((getShellStyle() & SWT.NO_FOCUS) != 0) {
1402 			return JFaceResources.getColorRegistry().get(JFacePreferences.INFORMATION_FOREGROUND_COLOR);
1403 		}
1404 		return JFaceResources.getColorRegistry().get(JFacePreferences.CONTENT_ASSIST_FOREGROUND_COLOR);
1405 	}
1406 
1407 	/**
1408 	 * Return the default background color used for popup dialogs.
1409 	 *
1410 	 * @return the default background color
1411 	 */
getDefaultBackground()1412 	private Color getDefaultBackground() {
1413 		if ((getShellStyle() & SWT.NO_FOCUS) != 0) {
1414 			return JFaceResources.getColorRegistry().get(JFacePreferences.INFORMATION_BACKGROUND_COLOR);
1415 		}
1416 		return JFaceResources.getColorRegistry().get(JFacePreferences.CONTENT_ASSIST_BACKGROUND_COLOR);
1417 	}
1418 
1419 	/**
1420 	 * Apply any desired fonts to the specified composite and its children.
1421 	 *
1422 	 * @param composite
1423 	 *            the contents composite
1424 	 */
applyFonts(Composite composite)1425 	private void applyFonts(Composite composite) {
1426 		Dialog.applyDialogFont(composite);
1427 
1428 		if (titleLabel != null) {
1429 			Font font = titleLabel.getFont();
1430 			FontData[] fontDatas = font.getFontData();
1431 			for (FontData fontData : fontDatas) {
1432 				fontData.setStyle(SWT.BOLD);
1433 			}
1434 			titleFont = new Font(titleLabel.getDisplay(), fontDatas);
1435 			titleLabel.setFont(titleFont);
1436 		}
1437 
1438 		if (infoLabel != null) {
1439 			Font font = infoLabel.getFont();
1440 			FontData[] fontDatas = font.getFontData();
1441 			for (FontData fontData : fontDatas) {
1442 				fontData.setHeight(fontData.getHeight() * 9 / 10);
1443 			}
1444 			infoFont = new Font(infoLabel.getDisplay(), fontDatas);
1445 			infoLabel.setFont(infoFont);
1446 		}
1447 	}
1448 
1449 	/**
1450 	 * Set the specified foreground color for the specified control and all of
1451 	 * its children, except for those specified in the list of exclusions.
1452 	 *
1453 	 * @param color
1454 	 *            the color to use as the foreground color
1455 	 * @param control
1456 	 *            the control whose color is to be changed
1457 	 * @param exclusions
1458 	 *            a list of controls who are to be excluded from getting their
1459 	 *            color assigned
1460 	 */
applyForegroundColor(Color color, Control control, List<Control> exclusions)1461 	private void applyForegroundColor(Color color, Control control,
1462 			List<Control> exclusions) {
1463 		if (!exclusions.contains(control)) {
1464 			control.setForeground(color);
1465 		}
1466 		if (control instanceof Composite) {
1467 			Control[] children = ((Composite) control).getChildren();
1468 			for (Control element : children) {
1469 				applyForegroundColor(color, element, exclusions);
1470 			}
1471 		}
1472 	}
1473 
1474 	/**
1475 	 * Set the specified background color for the specified control and all of
1476 	 * its children, except for those specified in the list of exclusions.
1477 	 *
1478 	 * @param color
1479 	 *            the color to use as the background color
1480 	 * @param control
1481 	 *            the control whose color is to be changed
1482 	 * @param exclusions
1483 	 *            a list of controls who are to be excluded from getting their
1484 	 *            color assigned
1485 	 */
applyBackgroundColor(Color color, Control control, List<Control> exclusions)1486 	private void applyBackgroundColor(Color color, Control control,
1487 			List<Control> exclusions) {
1488 		if (!exclusions.contains(control)) {
1489 			control.setBackground(color);
1490 		}
1491 		if (control instanceof Composite) {
1492 			Control[] children = ((Composite) control).getChildren();
1493 			for (Control element : children) {
1494 				applyBackgroundColor(color, element, exclusions);
1495 			}
1496 		}
1497 	}
1498 
1499 	/**
1500 	 * Set the specified foreground color for the specified control and all of
1501 	 * its children. Subclasses may override this method, but typically do not.
1502 	 * If a subclass wishes to exclude a particular control in its contents from
1503 	 * getting the specified foreground color, it may instead override
1504 	 * {@link #getForegroundColorExclusions()}.
1505 	 *
1506 	 * @param color
1507 	 *            the color to use as the foreground color
1508 	 * @param control
1509 	 *            the control whose color is to be changed
1510 	 * @see PopupDialog#getForegroundColorExclusions()
1511 	 */
applyForegroundColor(Color color, Control control)1512 	protected void applyForegroundColor(Color color, Control control) {
1513 		applyForegroundColor(color, control, getForegroundColorExclusions());
1514 	}
1515 
1516 	/**
1517 	 * Set the specified background color for the specified control and all of
1518 	 * its children. Subclasses may override this method, but typically do not.
1519 	 * If a subclass wishes to exclude a particular control in its contents from
1520 	 * getting the specified background color, it may instead override
1521 	 * {@link #getBackgroundColorExclusions()}
1522 	 *
1523 	 * @param color
1524 	 *            the color to use as the background color
1525 	 * @param control
1526 	 *            the control whose color is to be changed
1527 	 * @see PopupDialog#getBackgroundColorExclusions()
1528 	 */
applyBackgroundColor(Color color, Control control)1529 	protected void applyBackgroundColor(Color color, Control control) {
1530 		applyBackgroundColor(color, control, getBackgroundColorExclusions());
1531 	}
1532 
1533 	/**
1534 	 * Return a list of controls which should never have their foreground color
1535 	 * reset. Subclasses may extend this method, but should always call
1536 	 * <code>super.getForegroundColorExclusions</code> to aggregate the list.
1537 	 *
1538 	 *
1539 	 * @return the List of controls
1540 	 */
getForegroundColorExclusions()1541 	protected List<Control> getForegroundColorExclusions() {
1542 		List<Control> list = new ArrayList<>(3);
1543 		if (infoLabel != null) {
1544 			list.add(infoLabel);
1545 		}
1546 		if (titleSeparator != null) {
1547 			list.add(titleSeparator);
1548 		}
1549 		if (infoSeparator != null) {
1550 			list.add(infoSeparator);
1551 		}
1552 		return list;
1553 	}
1554 
1555 	/**
1556 	 * Return a list of controls which should never have their background color
1557 	 * reset. Subclasses may extend this method, but should always call
1558 	 * <code>super.getBackgroundColorExclusions</code> to aggregate the list.
1559 	 *
1560 	 * @return the List of controls
1561 	 */
getBackgroundColorExclusions()1562 	protected List<Control> getBackgroundColorExclusions() {
1563 		List<Control> list = new ArrayList<>(2);
1564 		if (titleSeparator != null) {
1565 			list.add(titleSeparator);
1566 		}
1567 		if (infoSeparator != null) {
1568 			list.add(infoSeparator);
1569 		}
1570 		return list;
1571 	}
1572 
1573 	/**
1574 	 * Initialize any state related to the widgetry that should be set up each
1575 	 * time widgets are created.
1576 	 */
initializeWidgetState()1577 	private void initializeWidgetState() {
1578 		menuManager = null;
1579 		dialogArea = null;
1580 		titleLabel = null;
1581 		titleSeparator = null;
1582 		infoSeparator = null;
1583 		infoLabel = null;
1584 		toolBar = null;
1585 
1586 		// If the menu item for persisting bounds is displayed, use the stored
1587 		// value to determine whether any persisted bounds should be honored at
1588 		// all.
1589 		if (showDialogMenu && showPersistActions) {
1590 			IDialogSettings settings = getDialogSettings();
1591 			if (settings != null) {
1592 				String key = getClass().getName() + DIALOG_USE_PERSISTED_SIZE;
1593 				if (settings.get(key) != null || !isUsing34API)
1594 					persistSize = settings.getBoolean(key);
1595 				key = getClass().getName() + DIALOG_USE_PERSISTED_LOCATION;
1596 				if (settings.get(key) != null || !isUsing34API)
1597 					persistLocation = settings.getBoolean(key);
1598 			}
1599 		}
1600 	}
1601 
1602 	/**
1603 	 * The dialog is being disposed. Dispose of any resources allocated.
1604 	 *
1605 	 */
handleDispose()1606 	private void handleDispose() {
1607 		if (infoColor != null && !infoColor.isDisposed()) {
1608 			infoColor.dispose();
1609 		}
1610 		infoColor = null;
1611 		if (infoFont != null && !infoFont.isDisposed()) {
1612 			infoFont.dispose();
1613 		}
1614 		infoFont = null;
1615 		if (titleFont != null && !titleFont.isDisposed()) {
1616 			titleFont.dispose();
1617 		}
1618 		titleFont = null;
1619 	}
1620 }
1621