comparison dwtx/jface/dialogs/PopupDialog.d @ 16:e0f0aaf75edd

PopupDialog, bindings and actions
author Frank Benoit <benoit@tionex.de>
date Tue, 01 Apr 2008 08:00:31 +0200
parents
children c884a1ab6db3
comparison
equal deleted inserted replaced
15:db8940420ed8 16:e0f0aaf75edd
1 /*******************************************************************************
2 * Copyright (c) 2005, 2007 IBM Corporation and others.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/epl-v10.html
7 *
8 * Contributors:
9 * IBM Corporation - initial API and implementation
10 * Stefan Xenos, IBM - bug 156790: Adopt GridLayoutFactory within JFace
11 * Port to the D programming language:
12 * Frank Benoit <benoit@tionex.de>
13 *******************************************************************************/
14 module dwtx.jface.dialogs.PopupDialog;
15
16 import dwtx.jface.dialogs.IDialogConstants;
17 import dwtx.jface.dialogs.IDialogSettings;
18 import dwtx.jface.dialogs.Dialog;
19
20 import tango.util.collection.ArraySeq;
21 import tango.util.collection.model.Seq;
22 import tango.util.collection.model.SeqView;
23
24 import dwt.DWT;
25 import dwt.events.DisposeEvent;
26 import dwt.events.DisposeListener;
27 import dwt.events.MouseAdapter;
28 import dwt.events.MouseEvent;
29 import dwt.events.SelectionAdapter;
30 import dwt.events.SelectionEvent;
31 import dwt.graphics.Color;
32 import dwt.graphics.Font;
33 import dwt.graphics.FontData;
34 import dwt.graphics.Image;
35 import dwt.graphics.Point;
36 import dwt.graphics.Rectangle;
37 import dwt.widgets.Composite;
38 import dwt.widgets.Control;
39 import dwt.widgets.Display;
40 import dwt.widgets.Event;
41 import dwt.widgets.Label;
42 import dwt.widgets.Listener;
43 import dwt.widgets.Menu;
44 import dwt.widgets.Shell;
45 import dwt.widgets.ToolBar;
46 import dwt.widgets.ToolItem;
47 import dwt.widgets.Tracker;
48 import dwtx.jface.action.Action;
49 import dwtx.jface.action.GroupMarker;
50 import dwtx.jface.action.IAction;
51 import dwtx.jface.action.IMenuManager;
52 import dwtx.jface.action.MenuManager;
53 import dwtx.jface.action.Separator;
54 import dwtx.jface.layout.GridDataFactory;
55 import dwtx.jface.layout.GridLayoutFactory;
56 import dwtx.jface.resource.ImageDescriptor;
57 import dwtx.jface.resource.JFaceResources;
58 import dwtx.jface.window.Window;
59
60 import dwt.dwthelper.utils;
61
62 /**
63 * A lightweight, transient dialog that is popped up to show contextual or
64 * temporal information and is easily dismissed. Clients control whether the
65 * dialog should be able to receive input focus. An optional title area at the
66 * top and an optional info area at the bottom can be used to provide additional
67 * information.
68 * <p>
69 * Because the dialog is short-lived, most of the configuration of the dialog is
70 * done in the constructor. Set methods are only provided for those values that
71 * are expected to be dynamically computed based on a particular instance's
72 * internal state.
73 * <p>
74 * Clients are expected to override the creation of the main dialog area, and
75 * may optionally override the creation of the title area and info area in order
76 * to add content. In general, however, the creation of stylistic features, such
77 * as the dialog menu, separator styles, and fonts, is kept private so that all
78 * popup dialogs will have a similar appearance.
79 *
80 * @since 3.2
81 */
82 public class PopupDialog : Window {
83
84 /**
85 *
86 */
87 private static const GridDataFactory LAYOUTDATA_GRAB_BOTH;
88
89 /**
90 * The dialog settings key name for stored dialog x location.
91 */
92 private static const 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 const 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 const String DIALOG_WIDTH = "DIALOG_WIDTH"; //$NON-NLS-1$
103
104 /**
105 * The dialog settings key name for stored dialog height.
106 */
107 private static const String DIALOG_HEIGHT = "DIALOG_HEIGHT"; //$NON-NLS-1$
108
109 /**
110 * The dialog settings key name for remembering if the persisted bounds
111 * should be accessed.
112 */
113 private static const String DIALOG_USE_PERSISTED_BOUNDS = "DIALOG_USE_PERSISTED_BOUNDS"; //$NON-NLS-1$
114
115 /**
116 * Move action for the dialog.
117 */
118 private class MoveAction : Action {
119
120 this() {
121 super(JFaceResources.getString("PopupDialog.move"), //$NON-NLS-1$
122 IAction.AS_PUSH_BUTTON);
123 }
124
125 /*
126 * (non-Javadoc)
127 *
128 * @see dwtx.jface.action.IAction#run()
129 */
130 public void run() {
131 performTrackerAction(DWT.NONE);
132 }
133
134 }
135
136 /**
137 * Resize action for the dialog.
138 */
139 private class ResizeAction : Action {
140
141 this() {
142 super(JFaceResources.getString("PopupDialog.resize"), //$NON-NLS-1$
143 IAction.AS_PUSH_BUTTON);
144 }
145
146 /*
147 * @see dwtx.jface.action.Action#run()
148 */
149 public void run() {
150 performTrackerAction(DWT.RESIZE);
151 }
152 }
153
154 /**
155 *
156 * Remember bounds action for the dialog.
157 */
158 private class PersistBoundsAction : Action {
159
160 this() {
161 super(JFaceResources.getString("PopupDialog.persistBounds"), //$NON-NLS-1$
162 IAction.AS_CHECK_BOX);
163 setChecked(persistBounds);
164 }
165
166 /*
167 * (non-Javadoc)
168 *
169 * @see dwtx.jface.action.IAction#run()
170 */
171 public void run() {
172 persistBounds = isChecked();
173 }
174 }
175
176 /**
177 * Shell style appropriate for a simple hover popup that cannot get focus.
178 */
179 public const static int HOVER_SHELLSTYLE = DWT.NO_FOCUS | DWT.ON_TOP
180 | DWT.NO_TRIM;
181
182 /**
183 * Shell style appropriate for an info popup that can get focus.
184 */
185 public const static int INFOPOPUP_SHELLSTYLE = DWT.NO_TRIM;
186
187 /**
188 * Shell style appropriate for a resizable info popup that can get focus.
189 */
190 public const static int INFOPOPUPRESIZE_SHELLSTYLE = DWT.RESIZE;
191
192 /**
193 * Margin width (in pixels) to be used in layouts inside popup dialogs
194 * (value is 0).
195 */
196 public const static int POPUP_MARGINWIDTH = 0;
197
198 /**
199 * Margin height (in pixels) to be used in layouts inside popup dialogs
200 * (value is 0).
201 */
202 public const static int POPUP_MARGINHEIGHT = 0;
203
204 /**
205 * Vertical spacing (in pixels) between cells in the layouts inside popup
206 * dialogs (value is 1).
207 */
208 public const static int POPUP_VERTICALSPACING = 1;
209
210 /**
211 * Vertical spacing (in pixels) between cells in the layouts inside popup
212 * dialogs (value is 1).
213 */
214 public const static int POPUP_HORIZONTALSPACING = 1;
215
216 /**
217 *
218 */
219 private static const GridLayoutFactory POPUP_LAYOUT_FACTORY;
220
221 static this(){
222 LAYOUTDATA_GRAB_BOTH = GridDataFactory.fillDefaults().grab(true,true);
223 POPUP_LAYOUT_FACTORY = GridLayoutFactory
224 .fillDefaults().margins(POPUP_MARGINWIDTH, POPUP_MARGINHEIGHT)
225 .spacing(POPUP_HORIZONTALSPACING, POPUP_VERTICALSPACING);
226 }
227 /**
228 * Border thickness in pixels.
229 */
230 private static const int BORDER_THICKNESS = 1;
231
232 /**
233 * The dialog's toolbar for the move and resize capabilities.
234 */
235 private ToolBar toolBar = null;
236
237 /**
238 * The dialog's menu manager.
239 */
240 private MenuManager menuManager = null;
241
242 /**
243 * The control representing the main dialog area.
244 */
245 private Control dialogArea;
246
247 /**
248 * Labels that contain title and info text. Cached so they can be updated
249 * dynamically if possible.
250 */
251 private Label titleLabel, infoLabel;
252
253 /**
254 * Separator controls. Cached so they can be excluded from color changes.
255 */
256 private Control titleSeparator, infoSeparator;
257
258 /**
259 * The images for the dialog menu.
260 */
261 private Image menuImage, disabledMenuImage = null;
262
263 /**
264 * Font to be used for the info area text. Computed based on the dialog's
265 * font.
266 */
267 private Font infoFont;
268
269 /**
270 * Font to be used for the title area text. Computed based on the dialog's
271 * font.
272 */
273 private Font titleFont;
274
275 /**
276 * Flags indicating whether we are listening for shell deactivate events,
277 * either those or our parent's. Used to prevent closure when a menu command
278 * is chosen or a secondary popup is launched.
279 */
280 private bool listenToDeactivate;
281
282 private bool listenToParentDeactivate;
283
284 private Listener parentDeactivateListener;
285
286 /**
287 * Flag indicating whether focus should be taken when the dialog is opened.
288 */
289 private bool takeFocusOnOpen = false;
290
291 /**
292 * Flag specifying whether a menu should be shown that allows the user to
293 * move and resize.
294 */
295 private bool showDialogMenu_ = false;
296
297 /**
298 * Flag specifying whether a menu action allowing the user to choose whether
299 * the dialog bounds should be persisted is to be shown.
300 */
301 private bool showPersistAction = false;
302
303 /**
304 * Flag specifying whether the bounds of the popup should be persisted. This
305 * flag is updated by a menu if the menu is shown.
306 */
307 private bool persistBounds = false;
308
309 /**
310 * Text to be shown in an optional title area (on top).
311 */
312 private String titleText;
313
314 /**
315 * Text to be shown in an optional info area (at the bottom).
316 */
317 private String infoText;
318
319 /**
320 * Constructs a new instance of <code>PopupDialog</code>.
321 *
322 * @param parent
323 * The parent shell.
324 * @param shellStyle
325 * The shell style.
326 * @param takeFocusOnOpen
327 * A bool indicating whether focus should be taken by this
328 * popup when it opens.
329 * @param persistBounds
330 * A bool indicating whether the bounds should be persisted
331 * upon close of the dialog. The bounds can only be persisted if
332 * the dialog settings for persisting the bounds are also
333 * specified. If a menu action will be provided that allows the
334 * user to control this feature, then the last known value of the
335 * user's setting will be used instead of this flag.
336 * @param showDialogMenu
337 * A bool indicating whether a menu for moving and resizing
338 * the popup should be provided.
339 * @param showPersistAction
340 * A bool indicating whether an action allowing the user to
341 * control the persisting of the dialog bounds should be shown in
342 * the dialog menu. This parameter has no effect if
343 * <code>showDialogMenu</code> is <code>false</code>.
344 * @param titleText
345 * Text to be shown in an upper title area, or <code>null</code>
346 * if there is no title.
347 * @param infoText
348 * Text to be shown in a lower info area, or <code>null</code>
349 * if there is no info area.
350 *
351 * @see PopupDialog#getDialogSettings()
352 */
353 public this(Shell parent, int shellStyle, bool takeFocusOnOpen,
354 bool persistBounds, bool showDialogMenu_,
355 bool showPersistAction, String titleText, String infoText) {
356 super(parent);
357 setShellStyle(shellStyle);
358 this.takeFocusOnOpen = takeFocusOnOpen;
359 this.showDialogMenu_ = showDialogMenu_;
360 this.showPersistAction = showPersistAction;
361 this.titleText = titleText;
362 this.infoText = infoText;
363
364 setBlockOnOpen(false);
365
366 this.persistBounds = persistBounds;
367 initializeWidgetState();
368 }
369
370 /*
371 * (non-Javadoc)
372 *
373 * @see dwtx.jface.window.Window#configureShell(Shell)
374 */
375 protected void configureShell(Shell shell) {
376 Display display = shell.getDisplay();
377 shell.setBackground(display.getSystemColor(DWT.COLOR_BLACK));
378
379 int border = ((getShellStyle() & DWT.NO_TRIM) is 0) ? 0
380 : BORDER_THICKNESS;
381 GridLayoutFactory.fillDefaults().margins(border, border).spacing(5,5).applyTo(shell);
382
383 shell.addListener(DWT.Deactivate, new class Listener {
384 public void handleEvent(Event event) {
385 /*
386 * Close if we are deactivating and have no child shells. If we
387 * have child shells, we are deactivating due to their opening.
388 * On X, we receive this when a menu child (such as the system
389 * menu) of the shell opens, but I have not found a way to
390 * distinguish that case here. Hence bug #113577 still exists.
391 */
392 if (listenToDeactivate && event.widget is getShell()
393 && getShell().getShells().length is 0) {
394 close();
395 } else {
396 /* We typically ignore deactivates to work around platform-specific
397 * event ordering. Now that we've ignored whatever we were supposed to,
398 * start listening to deactivates. Example issues can be found in
399 * https://bugs.eclipse.org/bugs/show_bug.cgi?id=123392
400 */
401 listenToDeactivate = true;
402 }
403 }
404 });
405 // Set this true whenever we activate. It may have been turned
406 // off by a menu or secondary popup showing.
407 shell.addListener(DWT.Activate, new class Listener {
408 public void handleEvent(Event event) {
409 // ignore this event if we have launched a child
410 if (event.widget is getShell()
411 && getShell().getShells().length is 0) {
412 listenToDeactivate = true;
413 // Typically we start listening for parent deactivate after
414 // we are activated, except on the Mac, where the deactivate
415 // is received after activate.
416 // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=100668
417 listenToParentDeactivate = !"carbon".equals(DWT.getPlatform()); //$NON-NLS-1$
418 }
419 }
420 });
421
422 if ((getShellStyle() & DWT.ON_TOP) !is 0 && shell.getParent() !is null) {
423 parentDeactivateListener= new class Listener {
424 public void handleEvent(Event event) {
425 if (listenToParentDeactivate) {
426 close();
427 } else {
428 // Our first deactivate, now start listening on the Mac.
429 listenToParentDeactivate = listenToDeactivate;
430 }
431 }
432 };
433 shell.getParent().addListener(DWT.Deactivate, parentDeactivateListener);
434 }
435
436 shell.addDisposeListener(new class DisposeListener {
437 public void widgetDisposed(DisposeEvent event) {
438 handleDispose();
439 }
440 });
441 }
442
443 /**
444 * The <code>PopupDialog</code> implementation of this <code>Window</code>
445 * method creates and lays out the top level composite for the dialog. It
446 * then calls the <code>createTitleMenuArea</code>,
447 * <code>createDialogArea</code>, and <code>createInfoTextArea</code>
448 * methods to create an optional title and menu area on the top, a dialog
449 * area in the center, and an optional info text area at the bottom.
450 * Overriding <code>createDialogArea</code> and (optionally)
451 * <code>createTitleMenuArea</code> and <code>createTitleMenuArea</code>
452 * are recommended rather than overriding this method.
453 *
454 * @param parent
455 * the composite used to parent the contents.
456 *
457 * @return the control representing the contents.
458 */
459 protected Control createContents(Composite parent) {
460 Composite composite = new Composite(parent, DWT.NONE);
461 POPUP_LAYOUT_FACTORY.applyTo(composite);
462 LAYOUTDATA_GRAB_BOTH.applyTo(composite);
463
464 // Title area
465 if (hasTitleArea()) {
466 createTitleMenuArea(composite);
467 titleSeparator = createHorizontalSeparator(composite);
468 }
469 // Content
470 dialogArea = createDialogArea(composite);
471 // Create a grid data layout data if one was not provided.
472 // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=118025
473 if (dialogArea.getLayoutData() is null) {
474 LAYOUTDATA_GRAB_BOTH.applyTo(dialogArea);
475 }
476
477 // Info field
478 if (hasInfoArea()) {
479 infoSeparator = createHorizontalSeparator(composite);
480 createInfoTextArea(composite);
481 }
482
483 applyColors(composite);
484 applyFonts(composite);
485 return composite;
486 }
487
488 /**
489 * Creates and returns the contents of the dialog (the area below the title
490 * area and above the info text area.
491 * <p>
492 * The <code>PopupDialog</code> implementation of this framework method
493 * creates and returns a new <code>Composite</code> with standard margins
494 * and spacing.
495 * <p>
496 * The returned control's layout data must be an instance of
497 * <code>GridData</code>. This method must not modify the parent's
498 * layout.
499 * <p>
500 * Subclasses must override this method but may call <code>super</code> as
501 * in the following example:
502 *
503 * <pre>
504 * Composite composite = (Composite) super.createDialogArea(parent);
505 * //add controls to composite as necessary
506 * return composite;
507 * </pre>
508 *
509 * @param parent
510 * the parent composite to contain the dialog area
511 * @return the dialog area control
512 */
513 protected Control createDialogArea(Composite parent) {
514 Composite composite = new Composite(parent, DWT.NONE);
515 POPUP_LAYOUT_FACTORY.applyTo(composite);
516 LAYOUTDATA_GRAB_BOTH.applyTo(composite);
517 return composite;
518 }
519
520 /**
521 * Returns the control that should get initial focus. Subclasses may
522 * override this method.
523 *
524 * @return the Control that should receive focus when the popup opens.
525 */
526 protected Control getFocusControl() {
527 return dialogArea;
528 }
529
530 /**
531 * Sets the tab order for the popup. Clients should override to introduce
532 * specific tab ordering.
533 *
534 * @param composite
535 * the composite in which all content, including the title area
536 * and info area, was created. This composite's parent is the
537 * shell.
538 */
539 protected void setTabOrder(Composite composite) {
540 // default is to do nothing
541 }
542
543 /**
544 * Returns a bool indicating whether the popup should have a title area
545 * at the top of the dialog. Subclasses may override. Default behavior is to
546 * have a title area if there is to be a menu or title text.
547 *
548 * @return <code>true</code> if a title area should be created,
549 * <code>false</code> if it should not.
550 */
551 protected bool hasTitleArea() {
552 return titleText !is null || showDialogMenu_;
553 }
554
555 /**
556 * Returns a bool indicating whether the popup should have an info area
557 * at the bottom of the dialog. Subclasses may override. Default behavior is
558 * to have an info area if info text was provided at the time of creation.
559 *
560 * @return <code>true</code> if a title area should be created,
561 * <code>false</code> if it should not.
562 */
563 protected bool hasInfoArea() {
564 return infoText !is null;
565 }
566
567 /**
568 * Creates the title and menu area. Subclasses typically need not override
569 * this method, but instead should use the constructor parameters
570 * <code>showDialogMenu</code> and <code>showPersistAction</code> to
571 * indicate whether a menu should be shown, and
572 * <code>createTitleControl</code> to to customize the presentation of the
573 * title.
574 *
575 * <p>
576 * If this method is overridden, the returned control's layout data must be
577 * an instance of <code>GridData</code>. This method must not modify the
578 * parent's layout.
579 *
580 * @param parent
581 * The parent composite.
582 * @return The Control representing the title and menu area.
583 */
584 protected Control createTitleMenuArea(Composite parent) {
585
586 Composite titleAreaComposite = new Composite(parent, DWT.NONE);
587 POPUP_LAYOUT_FACTORY.copy().numColumns(2).applyTo(titleAreaComposite);
588 GridDataFactory.fillDefaults()
589 .align_(DWT.FILL, DWT.CENTER).grab(true, false)
590 .applyTo(titleAreaComposite);
591
592 createTitleControl(titleAreaComposite);
593
594 if (showDialogMenu_) {
595 createDialogMenu(titleAreaComposite);
596 }
597 return titleAreaComposite;
598 }
599
600 /**
601 * Creates the control to be used to represent the dialog's title text.
602 * Subclasses may override if a different control is desired for
603 * representing the title text, or if something different than the title
604 * should be displayed in location where the title text typically is shown.
605 *
606 * <p>
607 * If this method is overridden, the returned control's layout data must be
608 * an instance of <code>GridData</code>. This method must not modify the
609 * parent's layout.
610 *
611 * @param parent
612 * The parent composite.
613 * @return The Control representing the title area.
614 */
615 protected Control createTitleControl(Composite parent) {
616 titleLabel = new Label(parent, DWT.NONE);
617
618 GridDataFactory.fillDefaults()
619 .align_(DWT.FILL, DWT.CENTER)
620 .grab(true, false)
621 .span(showDialogMenu_ ? 1 : 2, 1)
622 .applyTo(titleLabel);
623
624 Font font = titleLabel.getFont();
625 FontData[] fontDatas = font.getFontData();
626 for (int i = 0; i < fontDatas.length; i++) {
627 fontDatas[i].setStyle(DWT.BOLD);
628 }
629 titleFont = new Font(titleLabel.getDisplay(), fontDatas);
630 titleLabel.setFont(titleFont);
631
632 if (titleText !is null) {
633 titleLabel.setText(titleText);
634 }
635 return titleLabel;
636 }
637
638 /**
639 * Creates the optional info text area. This method is only called if the
640 * <code>hasInfoArea()</code> method returns true. Subclasses typically
641 * need not override this method, but may do so.
642 *
643 * <p>
644 * If this method is overridden, the returned control's layout data must be
645 * an instance of <code>GridData</code>. This method must not modify the
646 * parent's layout.
647 *
648 *
649 * @param parent
650 * The parent composite.
651 * @return The control representing the info text area.
652 *
653 * @see PopupDialog#hasInfoArea()
654 * @see PopupDialog#createTitleControl(Composite)
655 */
656 protected Control createInfoTextArea(Composite parent) {
657 // Status label
658 infoLabel = new Label(parent, DWT.RIGHT);
659 infoLabel.setText(infoText);
660 Font font = infoLabel.getFont();
661 FontData[] fontDatas = font.getFontData();
662 for (int i = 0; i < fontDatas.length; i++) {
663 fontDatas[i].setHeight(fontDatas[i].getHeight() * 9 / 10);
664 }
665 infoFont = new Font(infoLabel.getDisplay(), fontDatas);
666 infoLabel.setFont(infoFont);
667 GridDataFactory.fillDefaults().grab(true, false).align_(DWT.FILL, DWT.BEGINNING)
668 .applyTo(infoLabel);
669 infoLabel.setForeground(parent.getDisplay().getSystemColor(
670 DWT.COLOR_WIDGET_DARK_SHADOW));
671 return infoLabel;
672 }
673
674 /**
675 * Create a horizontal separator for the given parent.
676 *
677 * @param parent
678 * The parent composite.
679 * @return The Control representing the horizontal separator.
680 */
681 private Control createHorizontalSeparator(Composite parent) {
682 Label separator = new Label(parent, DWT.SEPARATOR | DWT.HORIZONTAL
683 | DWT.LINE_DOT);
684 GridDataFactory.fillDefaults().align_(DWT.FILL, DWT.CENTER).grab(true, false).applyTo(separator);
685 return separator;
686 }
687
688 /**
689 * Create the dialog's menu for the move and resize actions.
690 *
691 * @param parent
692 * The parent composite.
693 */
694 private void createDialogMenu(Composite parent) {
695
696 toolBar = new ToolBar(parent, DWT.FLAT);
697 ToolItem viewMenuButton = new ToolItem(toolBar, DWT.PUSH, 0);
698
699 GridDataFactory.fillDefaults().align_(DWT.END, DWT.CENTER).applyTo(toolBar);
700
701 menuImage = ImageDescriptor.createFromFile(PopupDialog.classinfo,
702 "images/popup_menu.gif").createImage();//$NON-NLS-1$
703 disabledMenuImage = ImageDescriptor.createFromFile(PopupDialog.classinfo,
704 "images/popup_menu_disabled.gif").createImage();//$NON-NLS-1$
705 viewMenuButton.setImage(menuImage);
706 viewMenuButton.setDisabledImage(disabledMenuImage);
707 viewMenuButton.setToolTipText(JFaceResources
708 .getString("PopupDialog.menuTooltip")); //$NON-NLS-1$
709 viewMenuButton.addSelectionListener(new class SelectionAdapter {
710 public void widgetSelected(SelectionEvent e) {
711 showDialogMenu();
712 }
713 });
714 viewMenuButton.addDisposeListener(new class DisposeListener {
715 public void widgetDisposed(DisposeEvent e) {
716 menuImage.dispose();
717 menuImage = null;
718 disabledMenuImage.dispose();
719 disabledMenuImage = null;
720 }
721 });
722 // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=177183
723 toolBar.addMouseListener(new class MouseAdapter {
724 public void mouseDown(MouseEvent e) {
725 showDialogMenu();
726 }
727 });
728 }
729
730 /**
731 * Fill the dialog's menu. Subclasses may extend or override.
732 *
733 * @param dialogMenu
734 * The dialog's menu.
735 */
736 protected void fillDialogMenu(IMenuManager dialogMenu) {
737 dialogMenu.add(new GroupMarker("SystemMenuStart")); //$NON-NLS-1$
738 dialogMenu.add(new MoveAction());
739 dialogMenu.add(new ResizeAction());
740 if (showPersistAction) {
741 dialogMenu.add(new PersistBoundsAction());
742 }
743 dialogMenu.add(new Separator("SystemMenuEnd")); //$NON-NLS-1$
744 }
745
746 /**
747 * Perform the requested tracker action (resize or move).
748 *
749 * @param style
750 * The track style (resize or move).
751 */
752 private void performTrackerAction(int style) {
753 Shell shell = getShell();
754 if (shell is null || shell.isDisposed()) {
755 return;
756 }
757
758 Tracker tracker = new Tracker(shell.getDisplay(), style);
759 tracker.setStippled(true);
760 Rectangle[] r = [ shell.getBounds() ];
761 tracker.setRectangles(r);
762
763 // Ignore any deactivate events caused by opening the tracker.
764 // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=120656
765 bool oldListenToDeactivate = listenToDeactivate;
766 listenToDeactivate = false;
767 if (tracker.open()) {
768 if (shell !is null && !shell.isDisposed()) {
769 shell.setBounds(tracker.getRectangles()[0]);
770 }
771 }
772 listenToDeactivate = oldListenToDeactivate;
773 }
774
775 /**
776 * Show the dialog's menu. This message has no effect if the receiver was
777 * not configured to show a menu. Clients may call this method in order to
778 * trigger the menu via keystrokes or other gestures. Subclasses typically
779 * do not override method.
780 */
781 protected void showDialogMenu() {
782 if (!showDialogMenu_) {
783 return;
784 }
785
786 if (menuManager is null) {
787 menuManager = new MenuManager();
788 fillDialogMenu(menuManager);
789 }
790 // Setting this flag works around a problem that remains on X only,
791 // whereby activating the menu deactivates our shell.
792 listenToDeactivate = !"gtk".equals(DWT.getPlatform()); //$NON-NLS-1$
793
794 Menu menu = menuManager.createContextMenu(getShell());
795 Rectangle bounds = toolBar.getBounds();
796 Point topLeft = new Point(bounds.x, bounds.y + bounds.height);
797 topLeft = getShell().toDisplay(topLeft);
798 menu.setLocation(topLeft.x, topLeft.y);
799 menu.setVisible(true);
800 }
801
802 /**
803 * Set the text to be shown in the popup's info area. This message has no
804 * effect if there was no info text supplied when the dialog first opened.
805 * Subclasses may override this method.
806 *
807 * @param text
808 * the text to be shown when the info area is displayed.
809 *
810 */
811 protected void setInfoText(String text) {
812 infoText = text;
813 if (infoLabel !is null) {
814 infoLabel.setText(text);
815 }
816 }
817
818 /**
819 * Set the text to be shown in the popup's title area. This message has no
820 * effect if there was no title label specified when the dialog was
821 * originally opened. Subclasses may override this method.
822 *
823 * @param text
824 * the text to be shown when the title area is displayed.
825 *
826 */
827 protected void setTitleText(String text) {
828 titleText = text;
829 if (titleLabel !is null) {
830 titleLabel.setText(text);
831 }
832 }
833
834 /**
835 * Return a bool indicating whether this dialog will persist its bounds.
836 * This value is initially set in the dialog's constructor, but can be
837 * modified if the persist bounds action is shown on the menu and the user
838 * has changed its value. Subclasses may override this method.
839 *
840 * @return <true> if the dialogs bounds will be persisted, false if it will
841 * not.
842 */
843 protected bool getPersistBounds() {
844 return persistBounds;
845 }
846
847 /**
848 * Opens this window, creating it first if it has not yet been created.
849 * <p>
850 * This method is reimplemented for special configuration of PopupDialogs.
851 * It never blocks on open, immediately returning <code>OK</code> if the
852 * open is successful, or <code>CANCEL</code> if it is not. It provides
853 * framework hooks that allow subclasses to set the focus and tab order, and
854 * avoids the use of <code>shell.open()</code> in cases where the focus
855 * should not be given to the shell initially.
856 *
857 * @return the return code
858 *
859 * @see dwtx.jface.window.Window#open()
860 */
861 public int open() {
862
863 Shell shell = getShell();
864 if (shell is null || shell.isDisposed()) {
865 shell = null;
866 // create the window
867 create();
868 shell = getShell();
869 }
870
871 // provide a hook for adjusting the bounds. This is only
872 // necessary when there is content driven sizing that must be
873 // adjusted each time the dialog is opened.
874 adjustBounds();
875
876 // limit the shell size to the display size
877 constrainShellSize();
878
879 // set up the tab order for the dialog
880 setTabOrder(cast(Composite) getContents());
881
882 // initialize flags for listening to deactivate
883 listenToDeactivate = false;
884 listenToParentDeactivate = false;
885
886 // open the window
887 if (takeFocusOnOpen) {
888 shell.open();
889 getFocusControl().setFocus();
890 } else {
891 shell.setVisible(true);
892 }
893
894 return OK;
895
896 }
897
898 /**
899 * Closes this window, disposes its shell, and removes this window from its
900 * window manager (if it has one).
901 * <p>
902 * This method is extended to save the dialog bounds and initialize widget
903 * state so that the widgets can be recreated if the dialog is reopened.
904 * This method may be extended (<code>super.close</code> must be called).
905 * </p>
906 *
907 * @return <code>true</code> if the window is (or was already) closed, and
908 * <code>false</code> if it is still open
909 */
910 public bool close() {
911 // If already closed, there is nothing to do.
912 // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=127505
913 if (getShell() is null || getShell().isDisposed()) {
914 return true;
915 }
916
917 saveDialogBounds(getShell());
918 // Widgets are about to be disposed, so null out any state
919 // related to them that was not handled in dispose listeners.
920 // We do this before disposal so that any received activate or
921 // deactivate events are duly ignored.
922 initializeWidgetState();
923
924 if (parentDeactivateListener !is null) {
925 getShell().getParent().removeListener(DWT.Deactivate, parentDeactivateListener);
926 parentDeactivateListener = null;
927 }
928
929 return super.close();
930 }
931
932 /**
933 * Gets the dialog settings that should be used for remembering the bounds
934 * of the dialog. Subclasses should override this method when they wish to
935 * persist the bounds of the dialog.
936 *
937 * @return settings the dialog settings used to store the dialog's location
938 * and/or size, or <code>null</code> if the dialog's bounds should
939 * never be stored.
940 */
941 protected IDialogSettings getDialogSettings() {
942 return null;
943 }
944
945 /**
946 * Saves the bounds of the shell in the appropriate dialog settings. The
947 * bounds are recorded relative to the parent shell, if there is one, or
948 * display coordinates if there is no parent shell. Subclasses typically
949 * need not override this method, but may extend it (calling
950 * <code>super.saveDialogBounds</code> if additional bounds information
951 * should be stored. Clients may also call this method to persist the bounds
952 * at times other than closing the dialog.
953 *
954 * @param shell
955 * The shell whose bounds are to be stored
956 */
957 protected void saveDialogBounds(Shell shell) {
958 IDialogSettings settings = getDialogSettings();
959 if (settings !is null) {
960 Point shellLocation = shell.getLocation();
961 Point shellSize = shell.getSize();
962 Shell parent = getParentShell();
963 if (parent !is null) {
964 Point parentLocation = parent.getLocation();
965 shellLocation.x -= parentLocation.x;
966 shellLocation.y -= parentLocation.y;
967 }
968 if (persistBounds) {
969 String prefix = this.classinfo.name;
970 settings.put(prefix ~ DIALOG_ORIGIN_X, shellLocation.x);
971 settings.put(prefix ~ DIALOG_ORIGIN_Y, shellLocation.y);
972 settings.put(prefix ~ DIALOG_WIDTH, shellSize.x);
973 settings.put(prefix ~ DIALOG_HEIGHT, shellSize.y);
974 }
975 if (showPersistAction && showDialogMenu_) {
976 settings.put(
977 this.classinfo.name ~ DIALOG_USE_PERSISTED_BOUNDS,
978 persistBounds);
979 }
980 }
981 }
982
983 /*
984 * (non-Javadoc)
985 *
986 * @see dwtx.jface.window.Window#getInitialSize()
987 */
988 protected Point getInitialSize() {
989 Point result = super.getInitialSize();
990 if (persistBounds) {
991 IDialogSettings settings = getDialogSettings();
992 if (settings !is null) {
993 try {
994 int width = settings.getInt(this.classinfo.name
995 ~ DIALOG_WIDTH);
996 int height = settings.getInt(this.classinfo.name
997 ~ DIALOG_HEIGHT);
998 result = new Point(width, height);
999
1000 } catch (NumberFormatException e) {
1001 }
1002 }
1003 }
1004 // No attempt is made to constrain the bounds. The default
1005 // constraining behavior in Window will be used.
1006 return result;
1007 }
1008
1009 /**
1010 * Adjust the bounds of the popup as necessary prior to opening the dialog.
1011 * Default is to do nothing, which honors any bounds set directly by clients
1012 * or those that have been saved in the dialog settings. Subclasses should
1013 * override this method when there are bounds computations that must be
1014 * checked each time the dialog is opened.
1015 */
1016 protected void adjustBounds() {
1017 }
1018
1019 /**
1020 * (non-Javadoc)
1021 *
1022 * @see dwtx.jface.window.Window#getInitialLocation(dwt.graphics.Point)
1023 */
1024 protected Point getInitialLocation(Point initialSize) {
1025 Point result = super.getInitialLocation(initialSize);
1026 if (persistBounds) {
1027 IDialogSettings settings = getDialogSettings();
1028 if (settings !is null) {
1029 try {
1030 int x = settings.getInt(this.classinfo.name
1031 ~ DIALOG_ORIGIN_X);
1032 int y = settings.getInt(this.classinfo.name
1033 ~ DIALOG_ORIGIN_Y);
1034 result = new Point(x, y);
1035 // The coordinates were stored relative to the parent shell.
1036 // Convert to display coordinates.
1037 Shell parent = getParentShell();
1038 if (parent !is null) {
1039 Point parentLocation = parent.getLocation();
1040 result.x += parentLocation.x;
1041 result.y += parentLocation.y;
1042 }
1043 } catch (NumberFormatException e) {
1044 }
1045 }
1046 }
1047 // No attempt is made to constrain the bounds. The default
1048 // constraining behavior in Window will be used.
1049 return result;
1050 }
1051
1052 /**
1053 * Apply any desired color to the specified composite and its children.
1054 *
1055 * @param composite
1056 * the contents composite
1057 */
1058 private void applyColors(Composite composite) {
1059 applyForegroundColor(getShell().getDisplay().getSystemColor(
1060 DWT.COLOR_INFO_FOREGROUND), composite,
1061 getForegroundColorExclusions());
1062 applyBackgroundColor(getShell().getDisplay().getSystemColor(
1063 DWT.COLOR_INFO_BACKGROUND), composite,
1064 getBackgroundColorExclusions());
1065 }
1066
1067 /**
1068 * Apply any desired fonts to the specified composite and its children.
1069 *
1070 * @param composite
1071 * the contents composite
1072 */
1073 private void applyFonts(Composite composite) {
1074 Dialog.applyDialogFont(composite);
1075
1076 }
1077
1078 /**
1079 * Set the specified foreground color for the specified control and all of
1080 * its children, except for those specified in the list of exclusions.
1081 *
1082 * @param color
1083 * the color to use as the foreground color
1084 * @param control
1085 * the control whose color is to be changed
1086 * @param exclusions
1087 * a list of controls who are to be excluded from getting their
1088 * color assigned
1089 */
1090 private void applyForegroundColor(Color color, Control control,
1091 SeqView!(Control) exclusions) {
1092 if (!exclusions.contains(control)) {
1093 control.setForeground(color);
1094 }
1095 if ( auto comp = cast(Composite)control ) {
1096 Control[] children = comp.getChildren();
1097 for (int i = 0; i < children.length; i++) {
1098 applyForegroundColor(color, children[i], exclusions);
1099 }
1100 }
1101 }
1102
1103 /**
1104 * Set the specified background color for the specified control and all of
1105 * its children.
1106 *
1107 * @param color
1108 * the color to use as the background color
1109 * @param control
1110 * the control whose color is to be changed
1111 * @param exclusions
1112 * a list of controls who are to be excluded from getting their
1113 * color assigned
1114 */
1115 private void applyBackgroundColor(Color color, Control control,
1116 SeqView!(Control) exclusions) {
1117 if (!exclusions.contains(control)) {
1118 control.setBackground(color);
1119 }
1120 if (auto comp = cast(Composite)control ) {
1121 Control[] children = comp.getChildren();
1122 for (int i = 0; i < children.length; i++) {
1123 applyBackgroundColor(color, children[i], exclusions);
1124 }
1125 }
1126 }
1127
1128 /**
1129 * Set the specified foreground color for the specified control and all of
1130 * its children. Subclasses may override this method, but typically do not.
1131 * If a subclass wishes to exclude a particular control in its contents from
1132 * getting the specified foreground color, it may instead override
1133 * <code>PopupDialog.getForegroundColorExclusions</code>.
1134 *
1135 * @param color
1136 * the color to use as the background color
1137 * @param control
1138 * the control whose color is to be changed
1139 * @see PopupDialog#getBackgroundColorExclusions()
1140 */
1141 protected void applyForegroundColor(Color color, Control control) {
1142 applyForegroundColor(color, control, getForegroundColorExclusions());
1143 }
1144
1145 /**
1146 * Set the specified background color for the specified control and all of
1147 * its children. Subclasses may override this method, but typically do not.
1148 * If a subclass wishes to exclude a particular control in its contents from
1149 * getting the specified background color, it may instead override
1150 * <code>PopupDialog.getBackgroundColorExclusions</code>.
1151 *
1152 * @param color
1153 * the color to use as the background color
1154 * @param control
1155 * the control whose color is to be changed
1156 * @see PopupDialog#getBackgroundColorExclusions()
1157 */
1158 protected void applyBackgroundColor(Color color, Control control) {
1159 applyBackgroundColor(color, control, getBackgroundColorExclusions());
1160 }
1161
1162 /**
1163 * Return a list of controls which should never have their foreground color
1164 * reset. Subclasses may extend this method (should always call
1165 * <code>super.getForegroundColorExclusions</code> to aggregate the list.
1166 *
1167 *
1168 * @return the List of controls
1169 */
1170 protected SeqView!(Control) getForegroundColorExclusions() {
1171 auto list = new ArraySeq!(Control);
1172 list.capacity(3);
1173 if (infoLabel !is null) {
1174 list.append(infoLabel);
1175 }
1176 if (titleSeparator !is null) {
1177 list.append(titleSeparator);
1178 }
1179 if (infoSeparator !is null) {
1180 list.append(infoSeparator);
1181 }
1182 return list;
1183 }
1184
1185 /**
1186 * Return a list of controls which should never have their background color
1187 * reset. Subclasses may extend this method (should always call
1188 * <code>super.getBackgroundColorExclusions</code> to aggregate the list.
1189 *
1190 * @return the List of controls
1191 */
1192 protected SeqView!(Control) getBackgroundColorExclusions() {
1193 auto list = new ArraySeq!(Control);
1194 list.capacity(2);
1195 if (titleSeparator !is null) {
1196 list.append(titleSeparator);
1197 }
1198 if (infoSeparator !is null) {
1199 list.append(infoSeparator);
1200 }
1201 return list;
1202 }
1203
1204 /**
1205 * Initialize any state related to the widgetry that should be set up each
1206 * time widgets are created.
1207 */
1208 private void initializeWidgetState() {
1209 menuManager = null;
1210 dialogArea = null;
1211 titleLabel = null;
1212 titleSeparator = null;
1213 infoSeparator = null;
1214 infoLabel = null;
1215 toolBar = null;
1216
1217 // If the menu item for persisting bounds is displayed, use the stored
1218 // value to determine whether any persisted bounds should be honored at
1219 // all.
1220 if (showDialogMenu_ && showPersistAction) {
1221 IDialogSettings settings = getDialogSettings();
1222 if (settings !is null) {
1223 persistBounds = settings.getBoolean(this.classinfo.name
1224 ~ DIALOG_USE_PERSISTED_BOUNDS);
1225 }
1226 }
1227
1228 }
1229
1230 /**
1231 * The dialog is being disposed. Dispose of any resources allocated.
1232 *
1233 */
1234 private void handleDispose() {
1235 if (infoFont !is null && !infoFont.isDisposed()) {
1236 infoFont.dispose();
1237 }
1238 infoFont = null;
1239 if (titleFont !is null && !titleFont.isDisposed()) {
1240 titleFont.dispose();
1241 }
1242 titleFont = null;
1243 }
1244 }