0001: /*******************************************************************************
0002: * Copyright (c) 2005, 2007 IBM Corporation and others.
0003: * All rights reserved. This program and the accompanying materials
0004: * are made available under the terms of the Eclipse Public License v1.0
0005: * which accompanies this distribution, and is available at
0006: * http://www.eclipse.org/legal/epl-v10.html
0007: *
0008: * Contributors:
0009: * IBM Corporation - initial API and implementation
0010: * Stefan Xenos, IBM - bug 156790: Adopt GridLayoutFactory within JFace
0011: *******************************************************************************/package org.eclipse.jface.dialogs;
0012:
0013: import java.util.ArrayList;
0014: import java.util.List;
0015:
0016: import org.eclipse.jface.action.Action;
0017: import org.eclipse.jface.action.GroupMarker;
0018: import org.eclipse.jface.action.IAction;
0019: import org.eclipse.jface.action.IMenuManager;
0020: import org.eclipse.jface.action.MenuManager;
0021: import org.eclipse.jface.action.Separator;
0022: import org.eclipse.jface.layout.GridDataFactory;
0023: import org.eclipse.jface.layout.GridLayoutFactory;
0024: import org.eclipse.jface.resource.JFaceResources;
0025: import org.eclipse.jface.window.Window;
0026: import org.eclipse.swt.SWT;
0027: import org.eclipse.swt.events.DisposeEvent;
0028: import org.eclipse.swt.events.DisposeListener;
0029: import org.eclipse.swt.events.MouseAdapter;
0030: import org.eclipse.swt.events.MouseEvent;
0031: import org.eclipse.swt.events.SelectionAdapter;
0032: import org.eclipse.swt.events.SelectionEvent;
0033: import org.eclipse.swt.graphics.Color;
0034: import org.eclipse.swt.graphics.Font;
0035: import org.eclipse.swt.graphics.FontData;
0036: import org.eclipse.swt.graphics.Point;
0037: import org.eclipse.swt.graphics.Rectangle;
0038: import org.eclipse.swt.widgets.Composite;
0039: import org.eclipse.swt.widgets.Control;
0040: import org.eclipse.swt.widgets.Display;
0041: import org.eclipse.swt.widgets.Event;
0042: import org.eclipse.swt.widgets.Label;
0043: import org.eclipse.swt.widgets.Listener;
0044: import org.eclipse.swt.widgets.Menu;
0045: import org.eclipse.swt.widgets.Shell;
0046: import org.eclipse.swt.widgets.ToolBar;
0047: import org.eclipse.swt.widgets.ToolItem;
0048: import org.eclipse.swt.widgets.Tracker;
0049:
0050: /**
0051: * A lightweight, transient dialog that is popped up to show contextual or
0052: * temporal information and is easily dismissed. Clients control whether the
0053: * dialog should be able to receive input focus. An optional title area at the
0054: * top and an optional info area at the bottom can be used to provide additional
0055: * information.
0056: * <p>
0057: * Because the dialog is short-lived, most of the configuration of the dialog is
0058: * done in the constructor. Set methods are only provided for those values that
0059: * are expected to be dynamically computed based on a particular instance's
0060: * internal state.
0061: * <p>
0062: * Clients are expected to override the creation of the main dialog area, and
0063: * may optionally override the creation of the title area and info area in order
0064: * to add content. In general, however, the creation of stylistic features, such
0065: * as the dialog menu, separator styles, and fonts, is kept private so that all
0066: * popup dialogs will have a similar appearance.
0067: *
0068: * @since 3.2
0069: */
0070: public class PopupDialog extends Window {
0071:
0072: /**
0073: *
0074: */
0075: private static final GridDataFactory LAYOUTDATA_GRAB_BOTH = GridDataFactory
0076: .fillDefaults().grab(true, true);
0077:
0078: /**
0079: * The dialog settings key name for stored dialog x location.
0080: */
0081: private static final String DIALOG_ORIGIN_X = "DIALOG_X_ORIGIN"; //$NON-NLS-1$
0082:
0083: /**
0084: * The dialog settings key name for stored dialog y location.
0085: */
0086: private static final String DIALOG_ORIGIN_Y = "DIALOG_Y_ORIGIN"; //$NON-NLS-1$
0087:
0088: /**
0089: * The dialog settings key name for stored dialog width.
0090: */
0091: private static final String DIALOG_WIDTH = "DIALOG_WIDTH"; //$NON-NLS-1$
0092:
0093: /**
0094: * The dialog settings key name for stored dialog height.
0095: */
0096: private static final String DIALOG_HEIGHT = "DIALOG_HEIGHT"; //$NON-NLS-1$
0097:
0098: /**
0099: * The dialog settings key name for remembering if the persisted bounds
0100: * should be accessed.
0101: */
0102: private static final String DIALOG_USE_PERSISTED_BOUNDS = "DIALOG_USE_PERSISTED_BOUNDS"; //$NON-NLS-1$
0103:
0104: /**
0105: * Move action for the dialog.
0106: */
0107: private class MoveAction extends Action {
0108:
0109: MoveAction() {
0110: super (JFaceResources.getString("PopupDialog.move"), //$NON-NLS-1$
0111: IAction.AS_PUSH_BUTTON);
0112: }
0113:
0114: /*
0115: * (non-Javadoc)
0116: *
0117: * @see org.eclipse.jface.action.IAction#run()
0118: */
0119: public void run() {
0120: performTrackerAction(SWT.NONE);
0121: }
0122:
0123: }
0124:
0125: /**
0126: * Resize action for the dialog.
0127: */
0128: private class ResizeAction extends Action {
0129:
0130: ResizeAction() {
0131: super (JFaceResources.getString("PopupDialog.resize"), //$NON-NLS-1$
0132: IAction.AS_PUSH_BUTTON);
0133: }
0134:
0135: /*
0136: * @see org.eclipse.jface.action.Action#run()
0137: */
0138: public void run() {
0139: performTrackerAction(SWT.RESIZE);
0140: }
0141: }
0142:
0143: /**
0144: *
0145: * Remember bounds action for the dialog.
0146: */
0147: private class PersistBoundsAction extends Action {
0148:
0149: PersistBoundsAction() {
0150: super (
0151: JFaceResources
0152: .getString("PopupDialog.persistBounds"), //$NON-NLS-1$
0153: IAction.AS_CHECK_BOX);
0154: setChecked(persistBounds);
0155: }
0156:
0157: /*
0158: * (non-Javadoc)
0159: *
0160: * @see org.eclipse.jface.action.IAction#run()
0161: */
0162: public void run() {
0163: persistBounds = isChecked();
0164: }
0165: }
0166:
0167: /**
0168: * Shell style appropriate for a simple hover popup that cannot get focus.
0169: */
0170: public final static int HOVER_SHELLSTYLE = SWT.NO_FOCUS
0171: | SWT.ON_TOP | SWT.NO_TRIM;
0172:
0173: /**
0174: * Shell style appropriate for an info popup that can get focus.
0175: */
0176: public final static int INFOPOPUP_SHELLSTYLE = SWT.NO_TRIM;
0177:
0178: /**
0179: * Shell style appropriate for a resizable info popup that can get focus.
0180: */
0181: public final static int INFOPOPUPRESIZE_SHELLSTYLE = SWT.RESIZE;
0182:
0183: /**
0184: * Margin width (in pixels) to be used in layouts inside popup dialogs
0185: * (value is 0).
0186: */
0187: public final static int POPUP_MARGINWIDTH = 0;
0188:
0189: /**
0190: * Margin height (in pixels) to be used in layouts inside popup dialogs
0191: * (value is 0).
0192: */
0193: public final static int POPUP_MARGINHEIGHT = 0;
0194:
0195: /**
0196: * Vertical spacing (in pixels) between cells in the layouts inside popup
0197: * dialogs (value is 1).
0198: */
0199: public final static int POPUP_VERTICALSPACING = 1;
0200:
0201: /**
0202: * Vertical spacing (in pixels) between cells in the layouts inside popup
0203: * dialogs (value is 1).
0204: */
0205: public final static int POPUP_HORIZONTALSPACING = 1;
0206:
0207: /**
0208: * Image registry key for menu image.
0209: *
0210: * @since 3.3
0211: */
0212: public static final String POPUP_IMG_MENU = "popup_menu_image"; //$NON-NLS-1$
0213:
0214: /**
0215: * Image registry key for disabled menu image.
0216: *
0217: * @since 3.3
0218: */
0219: public static final String POPUP_IMG_MENU_DISABLED = "popup_menu_image_diabled"; //$NON-NLS-1$
0220:
0221: /**
0222: *
0223: */
0224: private static final GridLayoutFactory POPUP_LAYOUT_FACTORY = GridLayoutFactory
0225: .fillDefaults().margins(POPUP_MARGINWIDTH,
0226: POPUP_MARGINHEIGHT).spacing(
0227: POPUP_HORIZONTALSPACING, POPUP_VERTICALSPACING);
0228:
0229: /**
0230: * Border thickness in pixels.
0231: */
0232: private static final int BORDER_THICKNESS = 1;
0233:
0234: /**
0235: * The dialog's toolbar for the move and resize capabilities.
0236: */
0237: private ToolBar toolBar = null;
0238:
0239: /**
0240: * The dialog's menu manager.
0241: */
0242: private MenuManager menuManager = null;
0243:
0244: /**
0245: * The control representing the main dialog area.
0246: */
0247: private Control dialogArea;
0248:
0249: /**
0250: * Labels that contain title and info text. Cached so they can be updated
0251: * dynamically if possible.
0252: */
0253: private Label titleLabel, infoLabel;
0254:
0255: /**
0256: * Separator controls. Cached so they can be excluded from color changes.
0257: */
0258: private Control titleSeparator, infoSeparator;
0259:
0260: /**
0261: * Font to be used for the info area text. Computed based on the dialog's
0262: * font.
0263: */
0264: private Font infoFont;
0265:
0266: /**
0267: * Font to be used for the title area text. Computed based on the dialog's
0268: * font.
0269: */
0270: private Font titleFont;
0271:
0272: /**
0273: * Flags indicating whether we are listening for shell deactivate events,
0274: * either those or our parent's. Used to prevent closure when a menu command
0275: * is chosen or a secondary popup is launched.
0276: */
0277: private boolean listenToDeactivate;
0278:
0279: private boolean listenToParentDeactivate;
0280:
0281: private Listener parentDeactivateListener;
0282:
0283: /**
0284: * Flag indicating whether focus should be taken when the dialog is opened.
0285: */
0286: private boolean takeFocusOnOpen = false;
0287:
0288: /**
0289: * Flag specifying whether a menu should be shown that allows the user to
0290: * move and resize.
0291: */
0292: private boolean showDialogMenu = false;
0293:
0294: /**
0295: * Flag specifying whether a menu action allowing the user to choose whether
0296: * the dialog bounds should be persisted is to be shown.
0297: */
0298: private boolean showPersistAction = false;
0299:
0300: /**
0301: * Flag specifying whether the bounds of the popup should be persisted. This
0302: * flag is updated by a menu if the menu is shown.
0303: */
0304: private boolean persistBounds = false;
0305:
0306: /**
0307: * Text to be shown in an optional title area (on top).
0308: */
0309: private String titleText;
0310:
0311: /**
0312: * Text to be shown in an optional info area (at the bottom).
0313: */
0314: private String infoText;
0315:
0316: /**
0317: * Constructs a new instance of <code>PopupDialog</code>.
0318: *
0319: * @param parent
0320: * The parent shell.
0321: * @param shellStyle
0322: * The shell style.
0323: * @param takeFocusOnOpen
0324: * A boolean indicating whether focus should be taken by this
0325: * popup when it opens.
0326: * @param persistBounds
0327: * A boolean indicating whether the bounds should be persisted
0328: * upon close of the dialog. The bounds can only be persisted if
0329: * the dialog settings for persisting the bounds are also
0330: * specified. If a menu action will be provided that allows the
0331: * user to control this feature, then the last known value of the
0332: * user's setting will be used instead of this flag.
0333: * @param showDialogMenu
0334: * A boolean indicating whether a menu for moving and resizing
0335: * the popup should be provided.
0336: * @param showPersistAction
0337: * A boolean indicating whether an action allowing the user to
0338: * control the persisting of the dialog bounds should be shown in
0339: * the dialog menu. This parameter has no effect if
0340: * <code>showDialogMenu</code> is <code>false</code>.
0341: * @param titleText
0342: * Text to be shown in an upper title area, or <code>null</code>
0343: * if there is no title.
0344: * @param infoText
0345: * Text to be shown in a lower info area, or <code>null</code>
0346: * if there is no info area.
0347: *
0348: * @see PopupDialog#getDialogSettings()
0349: */
0350: public PopupDialog(Shell parent, int shellStyle,
0351: boolean takeFocusOnOpen, boolean persistBounds,
0352: boolean showDialogMenu, boolean showPersistAction,
0353: String titleText, String infoText) {
0354: super (parent);
0355: setShellStyle(shellStyle);
0356: this .takeFocusOnOpen = takeFocusOnOpen;
0357: this .showDialogMenu = showDialogMenu;
0358: this .showPersistAction = showPersistAction;
0359: this .titleText = titleText;
0360: this .infoText = infoText;
0361:
0362: setBlockOnOpen(false);
0363:
0364: this .persistBounds = persistBounds;
0365: initializeWidgetState();
0366: }
0367:
0368: /*
0369: * (non-Javadoc)
0370: *
0371: * @see org.eclipse.jface.window.Window#configureShell(Shell)
0372: */
0373: protected void configureShell(Shell shell) {
0374: Display display = shell.getDisplay();
0375: shell.setBackground(display.getSystemColor(SWT.COLOR_BLACK));
0376:
0377: int border = ((getShellStyle() & SWT.NO_TRIM) == 0) ? 0
0378: : BORDER_THICKNESS;
0379: GridLayoutFactory.fillDefaults().margins(border, border)
0380: .spacing(5, 5).applyTo(shell);
0381:
0382: shell.addListener(SWT.Deactivate, new Listener() {
0383: public void handleEvent(Event event) {
0384: /*
0385: * Close if we are deactivating and have no child shells. If we
0386: * have child shells, we are deactivating due to their opening.
0387: * On X, we receive this when a menu child (such as the system
0388: * menu) of the shell opens, but I have not found a way to
0389: * distinguish that case here. Hence bug #113577 still exists.
0390: */
0391: if (listenToDeactivate && event.widget == getShell()
0392: && getShell().getShells().length == 0) {
0393: close();
0394: } else {
0395: /*
0396: * We typically ignore deactivates to work around
0397: * platform-specific event ordering. Now that we've ignored
0398: * whatever we were supposed to, start listening to
0399: * deactivates. Example issues can be found in
0400: * https://bugs.eclipse.org/bugs/show_bug.cgi?id=123392
0401: */
0402: listenToDeactivate = true;
0403: }
0404: }
0405: });
0406: // Set this true whenever we activate. It may have been turned
0407: // off by a menu or secondary popup showing.
0408: shell.addListener(SWT.Activate, new Listener() {
0409: public void handleEvent(Event event) {
0410: // ignore this event if we have launched a child
0411: if (event.widget == getShell()
0412: && getShell().getShells().length == 0) {
0413: listenToDeactivate = true;
0414: // Typically we start listening for parent deactivate after
0415: // we are activated, except on the Mac, where the deactivate
0416: // is received after activate.
0417: // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=100668
0418: listenToParentDeactivate = !"carbon".equals(SWT.getPlatform()); //$NON-NLS-1$
0419: }
0420: }
0421: });
0422:
0423: if ((getShellStyle() & SWT.ON_TOP) != 0
0424: && shell.getParent() != null) {
0425: parentDeactivateListener = new Listener() {
0426: public void handleEvent(Event event) {
0427: if (listenToParentDeactivate) {
0428: close();
0429: } else {
0430: // Our first deactivate, now start listening on the Mac.
0431: listenToParentDeactivate = listenToDeactivate;
0432: }
0433: }
0434: };
0435: shell.getParent().addListener(SWT.Deactivate,
0436: parentDeactivateListener);
0437: }
0438:
0439: shell.addDisposeListener(new DisposeListener() {
0440: public void widgetDisposed(DisposeEvent event) {
0441: handleDispose();
0442: }
0443: });
0444: }
0445:
0446: /**
0447: * The <code>PopupDialog</code> implementation of this <code>Window</code>
0448: * method creates and lays out the top level composite for the dialog. It
0449: * then calls the <code>createTitleMenuArea</code>,
0450: * <code>createDialogArea</code>, and <code>createInfoTextArea</code>
0451: * methods to create an optional title and menu area on the top, a dialog
0452: * area in the center, and an optional info text area at the bottom.
0453: * Overriding <code>createDialogArea</code> and (optionally)
0454: * <code>createTitleMenuArea</code> and <code>createTitleMenuArea</code>
0455: * are recommended rather than overriding this method.
0456: *
0457: * @param parent
0458: * the composite used to parent the contents.
0459: *
0460: * @return the control representing the contents.
0461: */
0462: protected Control createContents(Composite parent) {
0463: Composite composite = new Composite(parent, SWT.NONE);
0464: POPUP_LAYOUT_FACTORY.applyTo(composite);
0465: LAYOUTDATA_GRAB_BOTH.applyTo(composite);
0466:
0467: // Title area
0468: if (hasTitleArea()) {
0469: createTitleMenuArea(composite);
0470: titleSeparator = createHorizontalSeparator(composite);
0471: }
0472: // Content
0473: dialogArea = createDialogArea(composite);
0474: // Create a grid data layout data if one was not provided.
0475: // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=118025
0476: if (dialogArea.getLayoutData() == null) {
0477: LAYOUTDATA_GRAB_BOTH.applyTo(dialogArea);
0478: }
0479:
0480: // Info field
0481: if (hasInfoArea()) {
0482: infoSeparator = createHorizontalSeparator(composite);
0483: createInfoTextArea(composite);
0484: }
0485:
0486: applyColors(composite);
0487: applyFonts(composite);
0488: return composite;
0489: }
0490:
0491: /**
0492: * Creates and returns the contents of the dialog (the area below the title
0493: * area and above the info text area.
0494: * <p>
0495: * The <code>PopupDialog</code> implementation of this framework method
0496: * creates and returns a new <code>Composite</code> with standard margins
0497: * and spacing.
0498: * <p>
0499: * The returned control's layout data must be an instance of
0500: * <code>GridData</code>. This method must not modify the parent's
0501: * layout.
0502: * <p>
0503: * Subclasses must override this method but may call <code>super</code> as
0504: * in the following example:
0505: *
0506: * <pre>
0507: * Composite composite = (Composite) super.createDialogArea(parent);
0508: * //add controls to composite as necessary
0509: * return composite;
0510: * </pre>
0511: *
0512: * @param parent
0513: * the parent composite to contain the dialog area
0514: * @return the dialog area control
0515: */
0516: protected Control createDialogArea(Composite parent) {
0517: Composite composite = new Composite(parent, SWT.NONE);
0518: POPUP_LAYOUT_FACTORY.applyTo(composite);
0519: LAYOUTDATA_GRAB_BOTH.applyTo(composite);
0520: return composite;
0521: }
0522:
0523: /**
0524: * Returns the control that should get initial focus. Subclasses may
0525: * override this method.
0526: *
0527: * @return the Control that should receive focus when the popup opens.
0528: */
0529: protected Control getFocusControl() {
0530: return dialogArea;
0531: }
0532:
0533: /**
0534: * Sets the tab order for the popup. Clients should override to introduce
0535: * specific tab ordering.
0536: *
0537: * @param composite
0538: * the composite in which all content, including the title area
0539: * and info area, was created. This composite's parent is the
0540: * shell.
0541: */
0542: protected void setTabOrder(Composite composite) {
0543: // default is to do nothing
0544: }
0545:
0546: /**
0547: * Returns a boolean indicating whether the popup should have a title area
0548: * at the top of the dialog. Subclasses may override. Default behavior is to
0549: * have a title area if there is to be a menu or title text.
0550: *
0551: * @return <code>true</code> if a title area should be created,
0552: * <code>false</code> if it should not.
0553: */
0554: protected boolean hasTitleArea() {
0555: return titleText != null || showDialogMenu;
0556: }
0557:
0558: /**
0559: * Returns a boolean indicating whether the popup should have an info area
0560: * at the bottom of the dialog. Subclasses may override. Default behavior is
0561: * to have an info area if info text was provided at the time of creation.
0562: *
0563: * @return <code>true</code> if a title area should be created,
0564: * <code>false</code> if it should not.
0565: */
0566: protected boolean hasInfoArea() {
0567: return infoText != null;
0568: }
0569:
0570: /**
0571: * Creates the title and menu area. Subclasses typically need not override
0572: * this method, but instead should use the constructor parameters
0573: * <code>showDialogMenu</code> and <code>showPersistAction</code> to
0574: * indicate whether a menu should be shown, and
0575: * <code>createTitleControl</code> to to customize the presentation of the
0576: * title.
0577: *
0578: * <p>
0579: * If this method is overridden, the returned control's layout data must be
0580: * an instance of <code>GridData</code>. This method must not modify the
0581: * parent's layout.
0582: *
0583: * @param parent
0584: * The parent composite.
0585: * @return The Control representing the title and menu area.
0586: */
0587: protected Control createTitleMenuArea(Composite parent) {
0588:
0589: Composite titleAreaComposite = new Composite(parent, SWT.NONE);
0590: POPUP_LAYOUT_FACTORY.copy().numColumns(2).applyTo(
0591: titleAreaComposite);
0592: GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER)
0593: .grab(true, false).applyTo(titleAreaComposite);
0594:
0595: createTitleControl(titleAreaComposite);
0596:
0597: if (showDialogMenu) {
0598: createDialogMenu(titleAreaComposite);
0599: }
0600: return titleAreaComposite;
0601: }
0602:
0603: /**
0604: * Creates the control to be used to represent the dialog's title text.
0605: * Subclasses may override if a different control is desired for
0606: * representing the title text, or if something different than the title
0607: * should be displayed in location where the title text typically is shown.
0608: *
0609: * <p>
0610: * If this method is overridden, the returned control's layout data must be
0611: * an instance of <code>GridData</code>. This method must not modify the
0612: * parent's layout.
0613: *
0614: * @param parent
0615: * The parent composite.
0616: * @return The Control representing the title area.
0617: */
0618: protected Control createTitleControl(Composite parent) {
0619: titleLabel = new Label(parent, SWT.NONE);
0620:
0621: GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER)
0622: .grab(true, false).span(showDialogMenu ? 1 : 2, 1)
0623: .applyTo(titleLabel);
0624:
0625: Font font = titleLabel.getFont();
0626: FontData[] fontDatas = font.getFontData();
0627: for (int i = 0; i < fontDatas.length; i++) {
0628: fontDatas[i].setStyle(SWT.BOLD);
0629: }
0630: titleFont = new Font(titleLabel.getDisplay(), fontDatas);
0631: titleLabel.setFont(titleFont);
0632:
0633: if (titleText != null) {
0634: titleLabel.setText(titleText);
0635: }
0636: return titleLabel;
0637: }
0638:
0639: /**
0640: * Creates the optional info text area. This method is only called if the
0641: * <code>hasInfoArea()</code> method returns true. Subclasses typically
0642: * need not override this method, but may do so.
0643: *
0644: * <p>
0645: * If this method is overridden, the returned control's layout data must be
0646: * an instance of <code>GridData</code>. This method must not modify the
0647: * parent's layout.
0648: *
0649: *
0650: * @param parent
0651: * The parent composite.
0652: * @return The control representing the info text area.
0653: *
0654: * @see PopupDialog#hasInfoArea()
0655: * @see PopupDialog#createTitleControl(Composite)
0656: */
0657: protected Control createInfoTextArea(Composite parent) {
0658: // Status label
0659: infoLabel = new Label(parent, SWT.RIGHT);
0660: infoLabel.setText(infoText);
0661: Font font = infoLabel.getFont();
0662: FontData[] fontDatas = font.getFontData();
0663: for (int i = 0; i < fontDatas.length; i++) {
0664: fontDatas[i].setHeight(fontDatas[i].getHeight() * 9 / 10);
0665: }
0666: infoFont = new Font(infoLabel.getDisplay(), fontDatas);
0667: infoLabel.setFont(infoFont);
0668: GridDataFactory.fillDefaults().grab(true, false).align(
0669: SWT.FILL, SWT.BEGINNING).applyTo(infoLabel);
0670: infoLabel.setForeground(parent.getDisplay().getSystemColor(
0671: SWT.COLOR_WIDGET_DARK_SHADOW));
0672: return infoLabel;
0673: }
0674:
0675: /**
0676: * Create a horizontal separator for the given parent.
0677: *
0678: * @param parent
0679: * The parent composite.
0680: * @return The Control representing the horizontal separator.
0681: */
0682: private Control createHorizontalSeparator(Composite parent) {
0683: Label separator = new Label(parent, SWT.SEPARATOR
0684: | SWT.HORIZONTAL | SWT.LINE_DOT);
0685: GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER)
0686: .grab(true, false).applyTo(separator);
0687: return separator;
0688: }
0689:
0690: /**
0691: * Create the dialog's menu for the move and resize actions.
0692: *
0693: * @param parent
0694: * The parent composite.
0695: */
0696: private void createDialogMenu(Composite parent) {
0697:
0698: toolBar = new ToolBar(parent, SWT.FLAT);
0699: ToolItem viewMenuButton = new ToolItem(toolBar, SWT.PUSH, 0);
0700:
0701: GridDataFactory.fillDefaults().align(SWT.END, SWT.CENTER)
0702: .applyTo(toolBar);
0703: viewMenuButton
0704: .setImage(JFaceResources.getImage(POPUP_IMG_MENU));
0705: viewMenuButton.setDisabledImage(JFaceResources
0706: .getImage(POPUP_IMG_MENU_DISABLED));
0707: viewMenuButton.setToolTipText(JFaceResources
0708: .getString("PopupDialog.menuTooltip")); //$NON-NLS-1$
0709: viewMenuButton.addSelectionListener(new SelectionAdapter() {
0710: public void widgetSelected(SelectionEvent e) {
0711: showDialogMenu();
0712: }
0713: });
0714: // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=177183
0715: toolBar.addMouseListener(new MouseAdapter() {
0716: public void mouseDown(MouseEvent e) {
0717: showDialogMenu();
0718: }
0719: });
0720: }
0721:
0722: /**
0723: * Fill the dialog's menu. Subclasses may extend or override.
0724: *
0725: * @param dialogMenu
0726: * The dialog's menu.
0727: */
0728: protected void fillDialogMenu(IMenuManager dialogMenu) {
0729: dialogMenu.add(new GroupMarker("SystemMenuStart")); //$NON-NLS-1$
0730: dialogMenu.add(new MoveAction());
0731: dialogMenu.add(new ResizeAction());
0732: if (showPersistAction) {
0733: dialogMenu.add(new PersistBoundsAction());
0734: }
0735: dialogMenu.add(new Separator("SystemMenuEnd")); //$NON-NLS-1$
0736: }
0737:
0738: /**
0739: * Perform the requested tracker action (resize or move).
0740: *
0741: * @param style
0742: * The track style (resize or move).
0743: */
0744: private void performTrackerAction(int style) {
0745: Shell shell = getShell();
0746: if (shell == null || shell.isDisposed()) {
0747: return;
0748: }
0749:
0750: Tracker tracker = new Tracker(shell.getDisplay(), style);
0751: tracker.setStippled(true);
0752: Rectangle[] r = new Rectangle[] { shell.getBounds() };
0753: tracker.setRectangles(r);
0754:
0755: // Ignore any deactivate events caused by opening the tracker.
0756: // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=120656
0757: boolean oldListenToDeactivate = listenToDeactivate;
0758: listenToDeactivate = false;
0759: if (tracker.open()) {
0760: if (shell != null && !shell.isDisposed()) {
0761: shell.setBounds(tracker.getRectangles()[0]);
0762: }
0763: }
0764: listenToDeactivate = oldListenToDeactivate;
0765: }
0766:
0767: /**
0768: * Show the dialog's menu. This message has no effect if the receiver was
0769: * not configured to show a menu. Clients may call this method in order to
0770: * trigger the menu via keystrokes or other gestures. Subclasses typically
0771: * do not override method.
0772: */
0773: protected void showDialogMenu() {
0774: if (!showDialogMenu) {
0775: return;
0776: }
0777:
0778: if (menuManager == null) {
0779: menuManager = new MenuManager();
0780: fillDialogMenu(menuManager);
0781: }
0782: // Setting this flag works around a problem that remains on X only,
0783: // whereby activating the menu deactivates our shell.
0784: listenToDeactivate = !"gtk".equals(SWT.getPlatform()); //$NON-NLS-1$
0785:
0786: Menu menu = menuManager.createContextMenu(getShell());
0787: Rectangle bounds = toolBar.getBounds();
0788: Point topLeft = new Point(bounds.x, bounds.y + bounds.height);
0789: topLeft = getShell().toDisplay(topLeft);
0790: menu.setLocation(topLeft.x, topLeft.y);
0791: menu.setVisible(true);
0792: }
0793:
0794: /**
0795: * Set the text to be shown in the popup's info area. This message has no
0796: * effect if there was no info text supplied when the dialog first opened.
0797: * Subclasses may override this method.
0798: *
0799: * @param text
0800: * the text to be shown when the info area is displayed.
0801: *
0802: */
0803: protected void setInfoText(String text) {
0804: infoText = text;
0805: if (infoLabel != null) {
0806: infoLabel.setText(text);
0807: }
0808: }
0809:
0810: /**
0811: * Set the text to be shown in the popup's title area. This message has no
0812: * effect if there was no title label specified when the dialog was
0813: * originally opened. Subclasses may override this method.
0814: *
0815: * @param text
0816: * the text to be shown when the title area is displayed.
0817: *
0818: */
0819: protected void setTitleText(String text) {
0820: titleText = text;
0821: if (titleLabel != null) {
0822: titleLabel.setText(text);
0823: }
0824: }
0825:
0826: /**
0827: * Return a boolean indicating whether this dialog will persist its bounds.
0828: * This value is initially set in the dialog's constructor, but can be
0829: * modified if the persist bounds action is shown on the menu and the user
0830: * has changed its value. Subclasses may override this method.
0831: *
0832: * @return <true> if the dialogs bounds will be persisted, false if it will
0833: * not.
0834: */
0835: protected boolean getPersistBounds() {
0836: return persistBounds;
0837: }
0838:
0839: /**
0840: * Opens this window, creating it first if it has not yet been created.
0841: * <p>
0842: * This method is reimplemented for special configuration of PopupDialogs.
0843: * It never blocks on open, immediately returning <code>OK</code> if the
0844: * open is successful, or <code>CANCEL</code> if it is not. It provides
0845: * framework hooks that allow subclasses to set the focus and tab order, and
0846: * avoids the use of <code>shell.open()</code> in cases where the focus
0847: * should not be given to the shell initially.
0848: *
0849: * @return the return code
0850: *
0851: * @see org.eclipse.jface.window.Window#open()
0852: */
0853: public int open() {
0854:
0855: Shell shell = getShell();
0856: if (shell == null || shell.isDisposed()) {
0857: shell = null;
0858: // create the window
0859: create();
0860: shell = getShell();
0861: }
0862:
0863: // provide a hook for adjusting the bounds. This is only
0864: // necessary when there is content driven sizing that must be
0865: // adjusted each time the dialog is opened.
0866: adjustBounds();
0867:
0868: // limit the shell size to the display size
0869: constrainShellSize();
0870:
0871: // set up the tab order for the dialog
0872: setTabOrder((Composite) getContents());
0873:
0874: // initialize flags for listening to deactivate
0875: listenToDeactivate = false;
0876: listenToParentDeactivate = false;
0877:
0878: // open the window
0879: if (takeFocusOnOpen) {
0880: shell.open();
0881: getFocusControl().setFocus();
0882: } else {
0883: shell.setVisible(true);
0884: }
0885:
0886: return OK;
0887:
0888: }
0889:
0890: /**
0891: * Closes this window, disposes its shell, and removes this window from its
0892: * window manager (if it has one).
0893: * <p>
0894: * This method is extended to save the dialog bounds and initialize widget
0895: * state so that the widgets can be recreated if the dialog is reopened.
0896: * This method may be extended (<code>super.close</code> must be called).
0897: * </p>
0898: *
0899: * @return <code>true</code> if the window is (or was already) closed, and
0900: * <code>false</code> if it is still open
0901: */
0902: public boolean close() {
0903: // If already closed, there is nothing to do.
0904: // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=127505
0905: if (getShell() == null || getShell().isDisposed()) {
0906: return true;
0907: }
0908:
0909: saveDialogBounds(getShell());
0910: // Widgets are about to be disposed, so null out any state
0911: // related to them that was not handled in dispose listeners.
0912: // We do this before disposal so that any received activate or
0913: // deactivate events are duly ignored.
0914: initializeWidgetState();
0915:
0916: if (parentDeactivateListener != null) {
0917: getShell().getParent().removeListener(SWT.Deactivate,
0918: parentDeactivateListener);
0919: parentDeactivateListener = null;
0920: }
0921:
0922: return super .close();
0923: }
0924:
0925: /**
0926: * Gets the dialog settings that should be used for remembering the bounds
0927: * of the dialog. Subclasses should override this method when they wish to
0928: * persist the bounds of the dialog.
0929: *
0930: * @return settings the dialog settings used to store the dialog's location
0931: * and/or size, or <code>null</code> if the dialog's bounds should
0932: * never be stored.
0933: */
0934: protected IDialogSettings getDialogSettings() {
0935: return null;
0936: }
0937:
0938: /**
0939: * Saves the bounds of the shell in the appropriate dialog settings. The
0940: * bounds are recorded relative to the parent shell, if there is one, or
0941: * display coordinates if there is no parent shell. Subclasses typically
0942: * need not override this method, but may extend it (calling
0943: * <code>super.saveDialogBounds</code> if additional bounds information
0944: * should be stored. Clients may also call this method to persist the bounds
0945: * at times other than closing the dialog.
0946: *
0947: * @param shell
0948: * The shell whose bounds are to be stored
0949: */
0950: protected void saveDialogBounds(Shell shell) {
0951: IDialogSettings settings = getDialogSettings();
0952: if (settings != null) {
0953: Point shellLocation = shell.getLocation();
0954: Point shellSize = shell.getSize();
0955: Shell parent = getParentShell();
0956: if (parent != null) {
0957: Point parentLocation = parent.getLocation();
0958: shellLocation.x -= parentLocation.x;
0959: shellLocation.y -= parentLocation.y;
0960: }
0961: if (persistBounds) {
0962: String prefix = getClass().getName();
0963: settings.put(prefix + DIALOG_ORIGIN_X, shellLocation.x);
0964: settings.put(prefix + DIALOG_ORIGIN_Y, shellLocation.y);
0965: settings.put(prefix + DIALOG_WIDTH, shellSize.x);
0966: settings.put(prefix + DIALOG_HEIGHT, shellSize.y);
0967: }
0968: if (showPersistAction && showDialogMenu) {
0969: settings.put(getClass().getName()
0970: + DIALOG_USE_PERSISTED_BOUNDS, persistBounds);
0971: }
0972: }
0973: }
0974:
0975: /*
0976: * (non-Javadoc)
0977: *
0978: * @see org.eclipse.jface.window.Window#getInitialSize()
0979: */
0980: protected Point getInitialSize() {
0981: Point result = super .getInitialSize();
0982: if (persistBounds) {
0983: IDialogSettings settings = getDialogSettings();
0984: if (settings != null) {
0985: try {
0986: int width = settings.getInt(getClass().getName()
0987: + DIALOG_WIDTH);
0988: int height = settings.getInt(getClass().getName()
0989: + DIALOG_HEIGHT);
0990: result = new Point(width, height);
0991:
0992: } catch (NumberFormatException e) {
0993: }
0994: }
0995: }
0996: // No attempt is made to constrain the bounds. The default
0997: // constraining behavior in Window will be used.
0998: return result;
0999: }
1000:
1001: /**
1002: * Adjust the bounds of the popup as necessary prior to opening the dialog.
1003: * Default is to do nothing, which honors any bounds set directly by clients
1004: * or those that have been saved in the dialog settings. Subclasses should
1005: * override this method when there are bounds computations that must be
1006: * checked each time the dialog is opened.
1007: */
1008: protected void adjustBounds() {
1009: }
1010:
1011: /**
1012: * (non-Javadoc)
1013: *
1014: * @see org.eclipse.jface.window.Window#getInitialLocation(org.eclipse.swt.graphics.Point)
1015: */
1016: protected Point getInitialLocation(Point initialSize) {
1017: Point result = super .getInitialLocation(initialSize);
1018: if (persistBounds) {
1019: IDialogSettings settings = getDialogSettings();
1020: if (settings != null) {
1021: try {
1022: int x = settings.getInt(getClass().getName()
1023: + DIALOG_ORIGIN_X);
1024: int y = settings.getInt(getClass().getName()
1025: + DIALOG_ORIGIN_Y);
1026: result = new Point(x, y);
1027: // The coordinates were stored relative to the parent shell.
1028: // Convert to display coordinates.
1029: Shell parent = getParentShell();
1030: if (parent != null) {
1031: Point parentLocation = parent.getLocation();
1032: result.x += parentLocation.x;
1033: result.y += parentLocation.y;
1034: }
1035: } catch (NumberFormatException e) {
1036: }
1037: }
1038: }
1039: // No attempt is made to constrain the bounds. The default
1040: // constraining behavior in Window will be used.
1041: return result;
1042: }
1043:
1044: /**
1045: * Apply any desired color to the specified composite and its children.
1046: *
1047: * @param composite
1048: * the contents composite
1049: */
1050: private void applyColors(Composite composite) {
1051: applyForegroundColor(getShell().getDisplay().getSystemColor(
1052: SWT.COLOR_INFO_FOREGROUND), composite,
1053: getForegroundColorExclusions());
1054: applyBackgroundColor(getShell().getDisplay().getSystemColor(
1055: SWT.COLOR_INFO_BACKGROUND), composite,
1056: getBackgroundColorExclusions());
1057: }
1058:
1059: /**
1060: * Apply any desired fonts to the specified composite and its children.
1061: *
1062: * @param composite
1063: * the contents composite
1064: */
1065: private void applyFonts(Composite composite) {
1066: Dialog.applyDialogFont(composite);
1067:
1068: }
1069:
1070: /**
1071: * Set the specified foreground color for the specified control and all of
1072: * its children, except for those specified in the list of exclusions.
1073: *
1074: * @param color
1075: * the color to use as the foreground color
1076: * @param control
1077: * the control whose color is to be changed
1078: * @param exclusions
1079: * a list of controls who are to be excluded from getting their
1080: * color assigned
1081: */
1082: private void applyForegroundColor(Color color, Control control,
1083: List exclusions) {
1084: if (!exclusions.contains(control)) {
1085: control.setForeground(color);
1086: }
1087: if (control instanceof Composite) {
1088: Control[] children = ((Composite) control).getChildren();
1089: for (int i = 0; i < children.length; i++) {
1090: applyForegroundColor(color, children[i], exclusions);
1091: }
1092: }
1093: }
1094:
1095: /**
1096: * Set the specified background color for the specified control and all of
1097: * its children.
1098: *
1099: * @param color
1100: * the color to use as the background color
1101: * @param control
1102: * the control whose color is to be changed
1103: * @param exclusions
1104: * a list of controls who are to be excluded from getting their
1105: * color assigned
1106: */
1107: private void applyBackgroundColor(Color color, Control control,
1108: List exclusions) {
1109: if (!exclusions.contains(control)) {
1110: control.setBackground(color);
1111: }
1112: if (control instanceof Composite) {
1113: Control[] children = ((Composite) control).getChildren();
1114: for (int i = 0; i < children.length; i++) {
1115: applyBackgroundColor(color, children[i], exclusions);
1116: }
1117: }
1118: }
1119:
1120: /**
1121: * Set the specified foreground color for the specified control and all of
1122: * its children. Subclasses may override this method, but typically do not.
1123: * If a subclass wishes to exclude a particular control in its contents from
1124: * getting the specified foreground color, it may instead override
1125: * <code>PopupDialog.getForegroundColorExclusions</code>.
1126: *
1127: * @param color
1128: * the color to use as the background color
1129: * @param control
1130: * the control whose color is to be changed
1131: * @see PopupDialog#getBackgroundColorExclusions()
1132: */
1133: protected void applyForegroundColor(Color color, Control control) {
1134: applyForegroundColor(color, control,
1135: getForegroundColorExclusions());
1136: }
1137:
1138: /**
1139: * Set the specified background color for the specified control and all of
1140: * its children. Subclasses may override this method, but typically do not.
1141: * If a subclass wishes to exclude a particular control in its contents from
1142: * getting the specified background color, it may instead override
1143: * <code>PopupDialog.getBackgroundColorExclusions</code>.
1144: *
1145: * @param color
1146: * the color to use as the background color
1147: * @param control
1148: * the control whose color is to be changed
1149: * @see PopupDialog#getBackgroundColorExclusions()
1150: */
1151: protected void applyBackgroundColor(Color color, Control control) {
1152: applyBackgroundColor(color, control,
1153: getBackgroundColorExclusions());
1154: }
1155:
1156: /**
1157: * Return a list of controls which should never have their foreground color
1158: * reset. Subclasses may extend this method (should always call
1159: * <code>super.getForegroundColorExclusions</code> to aggregate the list.
1160: *
1161: *
1162: * @return the List of controls
1163: */
1164: protected List getForegroundColorExclusions() {
1165: List list = new ArrayList(3);
1166: if (infoLabel != null) {
1167: list.add(infoLabel);
1168: }
1169: if (titleSeparator != null) {
1170: list.add(titleSeparator);
1171: }
1172: if (infoSeparator != null) {
1173: list.add(infoSeparator);
1174: }
1175: return list;
1176: }
1177:
1178: /**
1179: * Return a list of controls which should never have their background color
1180: * reset. Subclasses may extend this method (should always call
1181: * <code>super.getBackgroundColorExclusions</code> to aggregate the list.
1182: *
1183: * @return the List of controls
1184: */
1185: protected List getBackgroundColorExclusions() {
1186: List list = new ArrayList(2);
1187: if (titleSeparator != null) {
1188: list.add(titleSeparator);
1189: }
1190: if (infoSeparator != null) {
1191: list.add(infoSeparator);
1192: }
1193: return list;
1194: }
1195:
1196: /**
1197: * Initialize any state related to the widgetry that should be set up each
1198: * time widgets are created.
1199: */
1200: private void initializeWidgetState() {
1201: menuManager = null;
1202: dialogArea = null;
1203: titleLabel = null;
1204: titleSeparator = null;
1205: infoSeparator = null;
1206: infoLabel = null;
1207: toolBar = null;
1208:
1209: // If the menu item for persisting bounds is displayed, use the stored
1210: // value to determine whether any persisted bounds should be honored at
1211: // all.
1212: if (showDialogMenu && showPersistAction) {
1213: IDialogSettings settings = getDialogSettings();
1214: if (settings != null) {
1215: persistBounds = settings.getBoolean(getClass()
1216: .getName()
1217: + DIALOG_USE_PERSISTED_BOUNDS);
1218: }
1219: }
1220:
1221: }
1222:
1223: /**
1224: * The dialog is being disposed. Dispose of any resources allocated.
1225: *
1226: */
1227: private void handleDispose() {
1228: if (infoFont != null && !infoFont.isDisposed()) {
1229: infoFont.dispose();
1230: }
1231: infoFont = null;
1232: if (titleFont != null && !titleFont.isDisposed()) {
1233: titleFont.dispose();
1234: }
1235: titleFont = null;
1236: }
1237: }
|