Mercurial > projects > dwt-addons
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/jface/dialogs/PopupDialog.d Tue Apr 01 08:00:31 2008 +0200 @@ -0,0 +1,1244 @@ +/******************************************************************************* + * Copyright (c) 2005, 2007 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + * Stefan Xenos, IBM - bug 156790: Adopt GridLayoutFactory within JFace + * Port to the D programming language: + * Frank Benoit <benoit@tionex.de> + *******************************************************************************/ +module dwtx.jface.dialogs.PopupDialog; + +import dwtx.jface.dialogs.IDialogConstants; +import dwtx.jface.dialogs.IDialogSettings; +import dwtx.jface.dialogs.Dialog; + +import tango.util.collection.ArraySeq; +import tango.util.collection.model.Seq; +import tango.util.collection.model.SeqView; + +import dwt.DWT; +import dwt.events.DisposeEvent; +import dwt.events.DisposeListener; +import dwt.events.MouseAdapter; +import dwt.events.MouseEvent; +import dwt.events.SelectionAdapter; +import dwt.events.SelectionEvent; +import dwt.graphics.Color; +import dwt.graphics.Font; +import dwt.graphics.FontData; +import dwt.graphics.Image; +import dwt.graphics.Point; +import dwt.graphics.Rectangle; +import dwt.widgets.Composite; +import dwt.widgets.Control; +import dwt.widgets.Display; +import dwt.widgets.Event; +import dwt.widgets.Label; +import dwt.widgets.Listener; +import dwt.widgets.Menu; +import dwt.widgets.Shell; +import dwt.widgets.ToolBar; +import dwt.widgets.ToolItem; +import dwt.widgets.Tracker; +import dwtx.jface.action.Action; +import dwtx.jface.action.GroupMarker; +import dwtx.jface.action.IAction; +import dwtx.jface.action.IMenuManager; +import dwtx.jface.action.MenuManager; +import dwtx.jface.action.Separator; +import dwtx.jface.layout.GridDataFactory; +import dwtx.jface.layout.GridLayoutFactory; +import dwtx.jface.resource.ImageDescriptor; +import dwtx.jface.resource.JFaceResources; +import dwtx.jface.window.Window; + +import dwt.dwthelper.utils; + +/** + * A lightweight, transient dialog that is popped up to show contextual or + * temporal information and is easily dismissed. Clients control whether the + * dialog should be able to receive input focus. An optional title area at the + * top and an optional info area at the bottom can be used to provide additional + * information. + * <p> + * Because the dialog is short-lived, most of the configuration of the dialog is + * done in the constructor. Set methods are only provided for those values that + * are expected to be dynamically computed based on a particular instance's + * internal state. + * <p> + * Clients are expected to override the creation of the main dialog area, and + * may optionally override the creation of the title area and info area in order + * to add content. In general, however, the creation of stylistic features, such + * as the dialog menu, separator styles, and fonts, is kept private so that all + * popup dialogs will have a similar appearance. + * + * @since 3.2 + */ +public class PopupDialog : Window { + + /** + * + */ + private static const GridDataFactory LAYOUTDATA_GRAB_BOTH; + + /** + * The dialog settings key name for stored dialog x location. + */ + private static const String DIALOG_ORIGIN_X = "DIALOG_X_ORIGIN"; //$NON-NLS-1$ + + /** + * The dialog settings key name for stored dialog y location. + */ + private static const String DIALOG_ORIGIN_Y = "DIALOG_Y_ORIGIN"; //$NON-NLS-1$ + + /** + * The dialog settings key name for stored dialog width. + */ + private static const String DIALOG_WIDTH = "DIALOG_WIDTH"; //$NON-NLS-1$ + + /** + * The dialog settings key name for stored dialog height. + */ + private static const String DIALOG_HEIGHT = "DIALOG_HEIGHT"; //$NON-NLS-1$ + + /** + * The dialog settings key name for remembering if the persisted bounds + * should be accessed. + */ + private static const String DIALOG_USE_PERSISTED_BOUNDS = "DIALOG_USE_PERSISTED_BOUNDS"; //$NON-NLS-1$ + + /** + * Move action for the dialog. + */ + private class MoveAction : Action { + + this() { + super(JFaceResources.getString("PopupDialog.move"), //$NON-NLS-1$ + IAction.AS_PUSH_BUTTON); + } + + /* + * (non-Javadoc) + * + * @see dwtx.jface.action.IAction#run() + */ + public void run() { + performTrackerAction(DWT.NONE); + } + + } + + /** + * Resize action for the dialog. + */ + private class ResizeAction : Action { + + this() { + super(JFaceResources.getString("PopupDialog.resize"), //$NON-NLS-1$ + IAction.AS_PUSH_BUTTON); + } + + /* + * @see dwtx.jface.action.Action#run() + */ + public void run() { + performTrackerAction(DWT.RESIZE); + } + } + + /** + * + * Remember bounds action for the dialog. + */ + private class PersistBoundsAction : Action { + + this() { + super(JFaceResources.getString("PopupDialog.persistBounds"), //$NON-NLS-1$ + IAction.AS_CHECK_BOX); + setChecked(persistBounds); + } + + /* + * (non-Javadoc) + * + * @see dwtx.jface.action.IAction#run() + */ + public void run() { + persistBounds = isChecked(); + } + } + + /** + * Shell style appropriate for a simple hover popup that cannot get focus. + */ + public const static int HOVER_SHELLSTYLE = DWT.NO_FOCUS | DWT.ON_TOP + | DWT.NO_TRIM; + + /** + * Shell style appropriate for an info popup that can get focus. + */ + public const static int INFOPOPUP_SHELLSTYLE = DWT.NO_TRIM; + + /** + * Shell style appropriate for a resizable info popup that can get focus. + */ + public const static int INFOPOPUPRESIZE_SHELLSTYLE = DWT.RESIZE; + + /** + * Margin width (in pixels) to be used in layouts inside popup dialogs + * (value is 0). + */ + public const static int POPUP_MARGINWIDTH = 0; + + /** + * Margin height (in pixels) to be used in layouts inside popup dialogs + * (value is 0). + */ + public const static int POPUP_MARGINHEIGHT = 0; + + /** + * Vertical spacing (in pixels) between cells in the layouts inside popup + * dialogs (value is 1). + */ + public const static int POPUP_VERTICALSPACING = 1; + + /** + * Vertical spacing (in pixels) between cells in the layouts inside popup + * dialogs (value is 1). + */ + public const static int POPUP_HORIZONTALSPACING = 1; + + /** + * + */ + private static const GridLayoutFactory POPUP_LAYOUT_FACTORY; + + static this(){ + LAYOUTDATA_GRAB_BOTH = GridDataFactory.fillDefaults().grab(true,true); + POPUP_LAYOUT_FACTORY = GridLayoutFactory + .fillDefaults().margins(POPUP_MARGINWIDTH, POPUP_MARGINHEIGHT) + .spacing(POPUP_HORIZONTALSPACING, POPUP_VERTICALSPACING); + } + /** + * Border thickness in pixels. + */ + private static const int BORDER_THICKNESS = 1; + + /** + * The dialog's toolbar for the move and resize capabilities. + */ + private ToolBar toolBar = null; + + /** + * The dialog's menu manager. + */ + private MenuManager menuManager = null; + + /** + * The control representing the main dialog area. + */ + private Control dialogArea; + + /** + * Labels that contain title and info text. Cached so they can be updated + * dynamically if possible. + */ + private Label titleLabel, infoLabel; + + /** + * Separator controls. Cached so they can be excluded from color changes. + */ + private Control titleSeparator, infoSeparator; + + /** + * The images for the dialog menu. + */ + private Image menuImage, disabledMenuImage = null; + + /** + * Font to be used for the info area text. Computed based on the dialog's + * font. + */ + private Font infoFont; + + /** + * Font to be used for the title area text. Computed based on the dialog's + * font. + */ + private Font titleFont; + + /** + * Flags indicating whether we are listening for shell deactivate events, + * either those or our parent's. Used to prevent closure when a menu command + * is chosen or a secondary popup is launched. + */ + private bool listenToDeactivate; + + private bool listenToParentDeactivate; + + private Listener parentDeactivateListener; + + /** + * Flag indicating whether focus should be taken when the dialog is opened. + */ + private bool takeFocusOnOpen = false; + + /** + * Flag specifying whether a menu should be shown that allows the user to + * move and resize. + */ + private bool showDialogMenu_ = false; + + /** + * Flag specifying whether a menu action allowing the user to choose whether + * the dialog bounds should be persisted is to be shown. + */ + private bool showPersistAction = false; + + /** + * Flag specifying whether the bounds of the popup should be persisted. This + * flag is updated by a menu if the menu is shown. + */ + private bool persistBounds = false; + + /** + * Text to be shown in an optional title area (on top). + */ + private String titleText; + + /** + * Text to be shown in an optional info area (at the bottom). + */ + private String infoText; + + /** + * Constructs a new instance of <code>PopupDialog</code>. + * + * @param parent + * The parent shell. + * @param shellStyle + * The shell style. + * @param takeFocusOnOpen + * A bool indicating whether focus should be taken by this + * popup when it opens. + * @param persistBounds + * A bool indicating whether the bounds should be persisted + * upon close of the dialog. The bounds can only be persisted if + * the dialog settings for persisting the bounds are also + * specified. If a menu action will be provided that allows the + * user to control this feature, then the last known value of the + * user's setting will be used instead of this flag. + * @param showDialogMenu + * A bool indicating whether a menu for moving and resizing + * the popup should be provided. + * @param showPersistAction + * A bool indicating whether an action allowing the user to + * control the persisting of the dialog bounds should be shown in + * the dialog menu. This parameter has no effect if + * <code>showDialogMenu</code> is <code>false</code>. + * @param titleText + * Text to be shown in an upper title area, or <code>null</code> + * if there is no title. + * @param infoText + * Text to be shown in a lower info area, or <code>null</code> + * if there is no info area. + * + * @see PopupDialog#getDialogSettings() + */ + public this(Shell parent, int shellStyle, bool takeFocusOnOpen, + bool persistBounds, bool showDialogMenu_, + bool showPersistAction, String titleText, String infoText) { + super(parent); + setShellStyle(shellStyle); + this.takeFocusOnOpen = takeFocusOnOpen; + this.showDialogMenu_ = showDialogMenu_; + this.showPersistAction = showPersistAction; + this.titleText = titleText; + this.infoText = infoText; + + setBlockOnOpen(false); + + this.persistBounds = persistBounds; + initializeWidgetState(); + } + + /* + * (non-Javadoc) + * + * @see dwtx.jface.window.Window#configureShell(Shell) + */ + protected void configureShell(Shell shell) { + Display display = shell.getDisplay(); + shell.setBackground(display.getSystemColor(DWT.COLOR_BLACK)); + + int border = ((getShellStyle() & DWT.NO_TRIM) is 0) ? 0 + : BORDER_THICKNESS; + GridLayoutFactory.fillDefaults().margins(border, border).spacing(5,5).applyTo(shell); + + shell.addListener(DWT.Deactivate, new class Listener { + public void handleEvent(Event event) { + /* + * Close if we are deactivating and have no child shells. If we + * have child shells, we are deactivating due to their opening. + * On X, we receive this when a menu child (such as the system + * menu) of the shell opens, but I have not found a way to + * distinguish that case here. Hence bug #113577 still exists. + */ + if (listenToDeactivate && event.widget is getShell() + && getShell().getShells().length is 0) { + close(); + } else { + /* We typically ignore deactivates to work around platform-specific + * event ordering. Now that we've ignored whatever we were supposed to, + * start listening to deactivates. Example issues can be found in + * https://bugs.eclipse.org/bugs/show_bug.cgi?id=123392 + */ + listenToDeactivate = true; + } + } + }); + // Set this true whenever we activate. It may have been turned + // off by a menu or secondary popup showing. + shell.addListener(DWT.Activate, new class Listener { + public void handleEvent(Event event) { + // ignore this event if we have launched a child + if (event.widget is getShell() + && getShell().getShells().length is 0) { + listenToDeactivate = true; + // Typically we start listening for parent deactivate after + // we are activated, except on the Mac, where the deactivate + // is received after activate. + // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=100668 + listenToParentDeactivate = !"carbon".equals(DWT.getPlatform()); //$NON-NLS-1$ + } + } + }); + + if ((getShellStyle() & DWT.ON_TOP) !is 0 && shell.getParent() !is null) { + parentDeactivateListener= new class Listener { + public void handleEvent(Event event) { + if (listenToParentDeactivate) { + close(); + } else { + // Our first deactivate, now start listening on the Mac. + listenToParentDeactivate = listenToDeactivate; + } + } + }; + shell.getParent().addListener(DWT.Deactivate, parentDeactivateListener); + } + + shell.addDisposeListener(new class DisposeListener { + public void widgetDisposed(DisposeEvent event) { + handleDispose(); + } + }); + } + + /** + * The <code>PopupDialog</code> implementation of this <code>Window</code> + * method creates and lays out the top level composite for the dialog. It + * then calls the <code>createTitleMenuArea</code>, + * <code>createDialogArea</code>, and <code>createInfoTextArea</code> + * methods to create an optional title and menu area on the top, a dialog + * area in the center, and an optional info text area at the bottom. + * Overriding <code>createDialogArea</code> and (optionally) + * <code>createTitleMenuArea</code> and <code>createTitleMenuArea</code> + * are recommended rather than overriding this method. + * + * @param parent + * the composite used to parent the contents. + * + * @return the control representing the contents. + */ + protected Control createContents(Composite parent) { + Composite composite = new Composite(parent, DWT.NONE); + POPUP_LAYOUT_FACTORY.applyTo(composite); + LAYOUTDATA_GRAB_BOTH.applyTo(composite); + + // Title area + if (hasTitleArea()) { + createTitleMenuArea(composite); + titleSeparator = createHorizontalSeparator(composite); + } + // Content + dialogArea = createDialogArea(composite); + // Create a grid data layout data if one was not provided. + // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=118025 + if (dialogArea.getLayoutData() is null) { + LAYOUTDATA_GRAB_BOTH.applyTo(dialogArea); + } + + // Info field + if (hasInfoArea()) { + infoSeparator = createHorizontalSeparator(composite); + createInfoTextArea(composite); + } + + applyColors(composite); + applyFonts(composite); + return composite; + } + + /** + * Creates and returns the contents of the dialog (the area below the title + * area and above the info text area. + * <p> + * The <code>PopupDialog</code> implementation of this framework method + * creates and returns a new <code>Composite</code> with standard margins + * and spacing. + * <p> + * The returned control's layout data must be an instance of + * <code>GridData</code>. This method must not modify the parent's + * layout. + * <p> + * Subclasses must override this method but may call <code>super</code> as + * in the following example: + * + * <pre> + * Composite composite = (Composite) super.createDialogArea(parent); + * //add controls to composite as necessary + * return composite; + * </pre> + * + * @param parent + * the parent composite to contain the dialog area + * @return the dialog area control + */ + protected Control createDialogArea(Composite parent) { + Composite composite = new Composite(parent, DWT.NONE); + POPUP_LAYOUT_FACTORY.applyTo(composite); + LAYOUTDATA_GRAB_BOTH.applyTo(composite); + return composite; + } + + /** + * Returns the control that should get initial focus. Subclasses may + * override this method. + * + * @return the Control that should receive focus when the popup opens. + */ + protected Control getFocusControl() { + return dialogArea; + } + + /** + * Sets the tab order for the popup. Clients should override to introduce + * specific tab ordering. + * + * @param composite + * the composite in which all content, including the title area + * and info area, was created. This composite's parent is the + * shell. + */ + protected void setTabOrder(Composite composite) { + // default is to do nothing + } + + /** + * Returns a bool indicating whether the popup should have a title area + * at the top of the dialog. Subclasses may override. Default behavior is to + * have a title area if there is to be a menu or title text. + * + * @return <code>true</code> if a title area should be created, + * <code>false</code> if it should not. + */ + protected bool hasTitleArea() { + return titleText !is null || showDialogMenu_; + } + + /** + * Returns a bool indicating whether the popup should have an info area + * at the bottom of the dialog. Subclasses may override. Default behavior is + * to have an info area if info text was provided at the time of creation. + * + * @return <code>true</code> if a title area should be created, + * <code>false</code> if it should not. + */ + protected bool hasInfoArea() { + return infoText !is null; + } + + /** + * Creates the title and menu area. Subclasses typically need not override + * this method, but instead should use the constructor parameters + * <code>showDialogMenu</code> and <code>showPersistAction</code> to + * indicate whether a menu should be shown, and + * <code>createTitleControl</code> to to customize the presentation of the + * title. + * + * <p> + * If this method is overridden, the returned control's layout data must be + * an instance of <code>GridData</code>. This method must not modify the + * parent's layout. + * + * @param parent + * The parent composite. + * @return The Control representing the title and menu area. + */ + protected Control createTitleMenuArea(Composite parent) { + + Composite titleAreaComposite = new Composite(parent, DWT.NONE); + POPUP_LAYOUT_FACTORY.copy().numColumns(2).applyTo(titleAreaComposite); + GridDataFactory.fillDefaults() + .align_(DWT.FILL, DWT.CENTER).grab(true, false) + .applyTo(titleAreaComposite); + + createTitleControl(titleAreaComposite); + + if (showDialogMenu_) { + createDialogMenu(titleAreaComposite); + } + return titleAreaComposite; + } + + /** + * Creates the control to be used to represent the dialog's title text. + * Subclasses may override if a different control is desired for + * representing the title text, or if something different than the title + * should be displayed in location where the title text typically is shown. + * + * <p> + * If this method is overridden, the returned control's layout data must be + * an instance of <code>GridData</code>. This method must not modify the + * parent's layout. + * + * @param parent + * The parent composite. + * @return The Control representing the title area. + */ + protected Control createTitleControl(Composite parent) { + titleLabel = new Label(parent, DWT.NONE); + + GridDataFactory.fillDefaults() + .align_(DWT.FILL, DWT.CENTER) + .grab(true, false) + .span(showDialogMenu_ ? 1 : 2, 1) + .applyTo(titleLabel); + + Font font = titleLabel.getFont(); + FontData[] fontDatas = font.getFontData(); + for (int i = 0; i < fontDatas.length; i++) { + fontDatas[i].setStyle(DWT.BOLD); + } + titleFont = new Font(titleLabel.getDisplay(), fontDatas); + titleLabel.setFont(titleFont); + + if (titleText !is null) { + titleLabel.setText(titleText); + } + return titleLabel; + } + + /** + * Creates the optional info text area. This method is only called if the + * <code>hasInfoArea()</code> method returns true. Subclasses typically + * need not override this method, but may do so. + * + * <p> + * If this method is overridden, the returned control's layout data must be + * an instance of <code>GridData</code>. This method must not modify the + * parent's layout. + * + * + * @param parent + * The parent composite. + * @return The control representing the info text area. + * + * @see PopupDialog#hasInfoArea() + * @see PopupDialog#createTitleControl(Composite) + */ + protected Control createInfoTextArea(Composite parent) { + // Status label + infoLabel = new Label(parent, DWT.RIGHT); + infoLabel.setText(infoText); + Font font = infoLabel.getFont(); + FontData[] fontDatas = font.getFontData(); + for (int i = 0; i < fontDatas.length; i++) { + fontDatas[i].setHeight(fontDatas[i].getHeight() * 9 / 10); + } + infoFont = new Font(infoLabel.getDisplay(), fontDatas); + infoLabel.setFont(infoFont); + GridDataFactory.fillDefaults().grab(true, false).align_(DWT.FILL, DWT.BEGINNING) + .applyTo(infoLabel); + infoLabel.setForeground(parent.getDisplay().getSystemColor( + DWT.COLOR_WIDGET_DARK_SHADOW)); + return infoLabel; + } + + /** + * Create a horizontal separator for the given parent. + * + * @param parent + * The parent composite. + * @return The Control representing the horizontal separator. + */ + private Control createHorizontalSeparator(Composite parent) { + Label separator = new Label(parent, DWT.SEPARATOR | DWT.HORIZONTAL + | DWT.LINE_DOT); + GridDataFactory.fillDefaults().align_(DWT.FILL, DWT.CENTER).grab(true, false).applyTo(separator); + return separator; + } + + /** + * Create the dialog's menu for the move and resize actions. + * + * @param parent + * The parent composite. + */ + private void createDialogMenu(Composite parent) { + + toolBar = new ToolBar(parent, DWT.FLAT); + ToolItem viewMenuButton = new ToolItem(toolBar, DWT.PUSH, 0); + + GridDataFactory.fillDefaults().align_(DWT.END, DWT.CENTER).applyTo(toolBar); + + menuImage = ImageDescriptor.createFromFile(PopupDialog.classinfo, + "images/popup_menu.gif").createImage();//$NON-NLS-1$ + disabledMenuImage = ImageDescriptor.createFromFile(PopupDialog.classinfo, + "images/popup_menu_disabled.gif").createImage();//$NON-NLS-1$ + viewMenuButton.setImage(menuImage); + viewMenuButton.setDisabledImage(disabledMenuImage); + viewMenuButton.setToolTipText(JFaceResources + .getString("PopupDialog.menuTooltip")); //$NON-NLS-1$ + viewMenuButton.addSelectionListener(new class SelectionAdapter { + public void widgetSelected(SelectionEvent e) { + showDialogMenu(); + } + }); + viewMenuButton.addDisposeListener(new class DisposeListener { + public void widgetDisposed(DisposeEvent e) { + menuImage.dispose(); + menuImage = null; + disabledMenuImage.dispose(); + disabledMenuImage = null; + } + }); + // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=177183 + toolBar.addMouseListener(new class MouseAdapter { + public void mouseDown(MouseEvent e) { + showDialogMenu(); + } + }); + } + + /** + * Fill the dialog's menu. Subclasses may extend or override. + * + * @param dialogMenu + * The dialog's menu. + */ + protected void fillDialogMenu(IMenuManager dialogMenu) { + dialogMenu.add(new GroupMarker("SystemMenuStart")); //$NON-NLS-1$ + dialogMenu.add(new MoveAction()); + dialogMenu.add(new ResizeAction()); + if (showPersistAction) { + dialogMenu.add(new PersistBoundsAction()); + } + dialogMenu.add(new Separator("SystemMenuEnd")); //$NON-NLS-1$ + } + + /** + * Perform the requested tracker action (resize or move). + * + * @param style + * The track style (resize or move). + */ + private void performTrackerAction(int style) { + Shell shell = getShell(); + if (shell is null || shell.isDisposed()) { + return; + } + + Tracker tracker = new Tracker(shell.getDisplay(), style); + tracker.setStippled(true); + Rectangle[] r = [ shell.getBounds() ]; + tracker.setRectangles(r); + + // Ignore any deactivate events caused by opening the tracker. + // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=120656 + bool oldListenToDeactivate = listenToDeactivate; + listenToDeactivate = false; + if (tracker.open()) { + if (shell !is null && !shell.isDisposed()) { + shell.setBounds(tracker.getRectangles()[0]); + } + } + listenToDeactivate = oldListenToDeactivate; + } + + /** + * Show the dialog's menu. This message has no effect if the receiver was + * not configured to show a menu. Clients may call this method in order to + * trigger the menu via keystrokes or other gestures. Subclasses typically + * do not override method. + */ + protected void showDialogMenu() { + if (!showDialogMenu_) { + return; + } + + if (menuManager is null) { + menuManager = new MenuManager(); + fillDialogMenu(menuManager); + } + // Setting this flag works around a problem that remains on X only, + // whereby activating the menu deactivates our shell. + listenToDeactivate = !"gtk".equals(DWT.getPlatform()); //$NON-NLS-1$ + + Menu menu = menuManager.createContextMenu(getShell()); + Rectangle bounds = toolBar.getBounds(); + Point topLeft = new Point(bounds.x, bounds.y + bounds.height); + topLeft = getShell().toDisplay(topLeft); + menu.setLocation(topLeft.x, topLeft.y); + menu.setVisible(true); + } + + /** + * Set the text to be shown in the popup's info area. This message has no + * effect if there was no info text supplied when the dialog first opened. + * Subclasses may override this method. + * + * @param text + * the text to be shown when the info area is displayed. + * + */ + protected void setInfoText(String text) { + infoText = text; + if (infoLabel !is null) { + infoLabel.setText(text); + } + } + + /** + * Set the text to be shown in the popup's title area. This message has no + * effect if there was no title label specified when the dialog was + * originally opened. Subclasses may override this method. + * + * @param text + * the text to be shown when the title area is displayed. + * + */ + protected void setTitleText(String text) { + titleText = text; + if (titleLabel !is null) { + titleLabel.setText(text); + } + } + + /** + * Return a bool indicating whether this dialog will persist its bounds. + * This value is initially set in the dialog's constructor, but can be + * modified if the persist bounds action is shown on the menu and the user + * has changed its value. Subclasses may override this method. + * + * @return <true> if the dialogs bounds will be persisted, false if it will + * not. + */ + protected bool getPersistBounds() { + return persistBounds; + } + + /** + * Opens this window, creating it first if it has not yet been created. + * <p> + * This method is reimplemented for special configuration of PopupDialogs. + * It never blocks on open, immediately returning <code>OK</code> if the + * open is successful, or <code>CANCEL</code> if it is not. It provides + * framework hooks that allow subclasses to set the focus and tab order, and + * avoids the use of <code>shell.open()</code> in cases where the focus + * should not be given to the shell initially. + * + * @return the return code + * + * @see dwtx.jface.window.Window#open() + */ + public int open() { + + Shell shell = getShell(); + if (shell is null || shell.isDisposed()) { + shell = null; + // create the window + create(); + shell = getShell(); + } + + // provide a hook for adjusting the bounds. This is only + // necessary when there is content driven sizing that must be + // adjusted each time the dialog is opened. + adjustBounds(); + + // limit the shell size to the display size + constrainShellSize(); + + // set up the tab order for the dialog + setTabOrder(cast(Composite) getContents()); + + // initialize flags for listening to deactivate + listenToDeactivate = false; + listenToParentDeactivate = false; + + // open the window + if (takeFocusOnOpen) { + shell.open(); + getFocusControl().setFocus(); + } else { + shell.setVisible(true); + } + + return OK; + + } + + /** + * Closes this window, disposes its shell, and removes this window from its + * window manager (if it has one). + * <p> + * This method is extended to save the dialog bounds and initialize widget + * state so that the widgets can be recreated if the dialog is reopened. + * This method may be extended (<code>super.close</code> must be called). + * </p> + * + * @return <code>true</code> if the window is (or was already) closed, and + * <code>false</code> if it is still open + */ + public bool close() { + // If already closed, there is nothing to do. + // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=127505 + if (getShell() is null || getShell().isDisposed()) { + return true; + } + + saveDialogBounds(getShell()); + // Widgets are about to be disposed, so null out any state + // related to them that was not handled in dispose listeners. + // We do this before disposal so that any received activate or + // deactivate events are duly ignored. + initializeWidgetState(); + + if (parentDeactivateListener !is null) { + getShell().getParent().removeListener(DWT.Deactivate, parentDeactivateListener); + parentDeactivateListener = null; + } + + return super.close(); + } + + /** + * Gets the dialog settings that should be used for remembering the bounds + * of the dialog. Subclasses should override this method when they wish to + * persist the bounds of the dialog. + * + * @return settings the dialog settings used to store the dialog's location + * and/or size, or <code>null</code> if the dialog's bounds should + * never be stored. + */ + protected IDialogSettings getDialogSettings() { + return null; + } + + /** + * Saves the bounds of the shell in the appropriate dialog settings. The + * bounds are recorded relative to the parent shell, if there is one, or + * display coordinates if there is no parent shell. Subclasses typically + * need not override this method, but may extend it (calling + * <code>super.saveDialogBounds</code> if additional bounds information + * should be stored. Clients may also call this method to persist the bounds + * at times other than closing the dialog. + * + * @param shell + * The shell whose bounds are to be stored + */ + protected void saveDialogBounds(Shell shell) { + IDialogSettings settings = getDialogSettings(); + if (settings !is null) { + Point shellLocation = shell.getLocation(); + Point shellSize = shell.getSize(); + Shell parent = getParentShell(); + if (parent !is null) { + Point parentLocation = parent.getLocation(); + shellLocation.x -= parentLocation.x; + shellLocation.y -= parentLocation.y; + } + if (persistBounds) { + String prefix = this.classinfo.name; + settings.put(prefix ~ DIALOG_ORIGIN_X, shellLocation.x); + settings.put(prefix ~ DIALOG_ORIGIN_Y, shellLocation.y); + settings.put(prefix ~ DIALOG_WIDTH, shellSize.x); + settings.put(prefix ~ DIALOG_HEIGHT, shellSize.y); + } + if (showPersistAction && showDialogMenu_) { + settings.put( + this.classinfo.name ~ DIALOG_USE_PERSISTED_BOUNDS, + persistBounds); + } + } + } + + /* + * (non-Javadoc) + * + * @see dwtx.jface.window.Window#getInitialSize() + */ + protected Point getInitialSize() { + Point result = super.getInitialSize(); + if (persistBounds) { + IDialogSettings settings = getDialogSettings(); + if (settings !is null) { + try { + int width = settings.getInt(this.classinfo.name + ~ DIALOG_WIDTH); + int height = settings.getInt(this.classinfo.name + ~ DIALOG_HEIGHT); + result = new Point(width, height); + + } catch (NumberFormatException e) { + } + } + } + // No attempt is made to constrain the bounds. The default + // constraining behavior in Window will be used. + return result; + } + + /** + * Adjust the bounds of the popup as necessary prior to opening the dialog. + * Default is to do nothing, which honors any bounds set directly by clients + * or those that have been saved in the dialog settings. Subclasses should + * override this method when there are bounds computations that must be + * checked each time the dialog is opened. + */ + protected void adjustBounds() { + } + + /** + * (non-Javadoc) + * + * @see dwtx.jface.window.Window#getInitialLocation(dwt.graphics.Point) + */ + protected Point getInitialLocation(Point initialSize) { + Point result = super.getInitialLocation(initialSize); + if (persistBounds) { + IDialogSettings settings = getDialogSettings(); + if (settings !is null) { + try { + int x = settings.getInt(this.classinfo.name + ~ DIALOG_ORIGIN_X); + int y = settings.getInt(this.classinfo.name + ~ DIALOG_ORIGIN_Y); + result = new Point(x, y); + // The coordinates were stored relative to the parent shell. + // Convert to display coordinates. + Shell parent = getParentShell(); + if (parent !is null) { + Point parentLocation = parent.getLocation(); + result.x += parentLocation.x; + result.y += parentLocation.y; + } + } catch (NumberFormatException e) { + } + } + } + // No attempt is made to constrain the bounds. The default + // constraining behavior in Window will be used. + return result; + } + + /** + * Apply any desired color to the specified composite and its children. + * + * @param composite + * the contents composite + */ + private void applyColors(Composite composite) { + applyForegroundColor(getShell().getDisplay().getSystemColor( + DWT.COLOR_INFO_FOREGROUND), composite, + getForegroundColorExclusions()); + applyBackgroundColor(getShell().getDisplay().getSystemColor( + DWT.COLOR_INFO_BACKGROUND), composite, + getBackgroundColorExclusions()); + } + + /** + * Apply any desired fonts to the specified composite and its children. + * + * @param composite + * the contents composite + */ + private void applyFonts(Composite composite) { + Dialog.applyDialogFont(composite); + + } + + /** + * Set the specified foreground color for the specified control and all of + * its children, except for those specified in the list of exclusions. + * + * @param color + * the color to use as the foreground color + * @param control + * the control whose color is to be changed + * @param exclusions + * a list of controls who are to be excluded from getting their + * color assigned + */ + private void applyForegroundColor(Color color, Control control, + SeqView!(Control) exclusions) { + if (!exclusions.contains(control)) { + control.setForeground(color); + } + if ( auto comp = cast(Composite)control ) { + Control[] children = comp.getChildren(); + for (int i = 0; i < children.length; i++) { + applyForegroundColor(color, children[i], exclusions); + } + } + } + + /** + * Set the specified background color for the specified control and all of + * its children. + * + * @param color + * the color to use as the background color + * @param control + * the control whose color is to be changed + * @param exclusions + * a list of controls who are to be excluded from getting their + * color assigned + */ + private void applyBackgroundColor(Color color, Control control, + SeqView!(Control) exclusions) { + if (!exclusions.contains(control)) { + control.setBackground(color); + } + if (auto comp = cast(Composite)control ) { + Control[] children = comp.getChildren(); + for (int i = 0; i < children.length; i++) { + applyBackgroundColor(color, children[i], exclusions); + } + } + } + + /** + * Set the specified foreground color for the specified control and all of + * its children. Subclasses may override this method, but typically do not. + * If a subclass wishes to exclude a particular control in its contents from + * getting the specified foreground color, it may instead override + * <code>PopupDialog.getForegroundColorExclusions</code>. + * + * @param color + * the color to use as the background color + * @param control + * the control whose color is to be changed + * @see PopupDialog#getBackgroundColorExclusions() + */ + protected void applyForegroundColor(Color color, Control control) { + applyForegroundColor(color, control, getForegroundColorExclusions()); + } + + /** + * Set the specified background color for the specified control and all of + * its children. Subclasses may override this method, but typically do not. + * If a subclass wishes to exclude a particular control in its contents from + * getting the specified background color, it may instead override + * <code>PopupDialog.getBackgroundColorExclusions</code>. + * + * @param color + * the color to use as the background color + * @param control + * the control whose color is to be changed + * @see PopupDialog#getBackgroundColorExclusions() + */ + protected void applyBackgroundColor(Color color, Control control) { + applyBackgroundColor(color, control, getBackgroundColorExclusions()); + } + + /** + * Return a list of controls which should never have their foreground color + * reset. Subclasses may extend this method (should always call + * <code>super.getForegroundColorExclusions</code> to aggregate the list. + * + * + * @return the List of controls + */ + protected SeqView!(Control) getForegroundColorExclusions() { + auto list = new ArraySeq!(Control); + list.capacity(3); + if (infoLabel !is null) { + list.append(infoLabel); + } + if (titleSeparator !is null) { + list.append(titleSeparator); + } + if (infoSeparator !is null) { + list.append(infoSeparator); + } + return list; + } + + /** + * Return a list of controls which should never have their background color + * reset. Subclasses may extend this method (should always call + * <code>super.getBackgroundColorExclusions</code> to aggregate the list. + * + * @return the List of controls + */ + protected SeqView!(Control) getBackgroundColorExclusions() { + auto list = new ArraySeq!(Control); + list.capacity(2); + if (titleSeparator !is null) { + list.append(titleSeparator); + } + if (infoSeparator !is null) { + list.append(infoSeparator); + } + return list; + } + + /** + * Initialize any state related to the widgetry that should be set up each + * time widgets are created. + */ + private void initializeWidgetState() { + menuManager = null; + dialogArea = null; + titleLabel = null; + titleSeparator = null; + infoSeparator = null; + infoLabel = null; + toolBar = null; + + // If the menu item for persisting bounds is displayed, use the stored + // value to determine whether any persisted bounds should be honored at + // all. + if (showDialogMenu_ && showPersistAction) { + IDialogSettings settings = getDialogSettings(); + if (settings !is null) { + persistBounds = settings.getBoolean(this.classinfo.name + ~ DIALOG_USE_PERSISTED_BOUNDS); + } + } + + } + + /** + * The dialog is being disposed. Dispose of any resources allocated. + * + */ + private void handleDispose() { + if (infoFont !is null && !infoFont.isDisposed()) { + infoFont.dispose(); + } + infoFont = null; + if (titleFont !is null && !titleFont.isDisposed()) { + titleFont.dispose(); + } + titleFont = null; + } +}