0001: /*******************************************************************************
0002: * Copyright (c) 2000, 2006 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: * Sean Montgomery, sean_montgomery@comcast.net - https://bugs.eclipse.org/bugs/show_bug.cgi?id=45095
0011: *******************************************************************************/package org.eclipse.jface.text;
0012:
0013: import org.eclipse.swt.SWT;
0014: import org.eclipse.swt.events.DisposeEvent;
0015: import org.eclipse.swt.events.DisposeListener;
0016: import org.eclipse.swt.graphics.GC;
0017: import org.eclipse.swt.graphics.Point;
0018: import org.eclipse.swt.graphics.Rectangle;
0019: import org.eclipse.swt.widgets.Control;
0020: import org.eclipse.swt.widgets.Display;
0021: import org.eclipse.swt.widgets.Monitor;
0022:
0023: import org.eclipse.core.runtime.Assert;
0024:
0025: import org.eclipse.jface.dialogs.IDialogSettings;
0026: import org.eclipse.jface.util.Geometry;
0027:
0028: /**
0029: * Manages the life cycle, visibility, layout, and contents of an
0030: * {@link org.eclipse.jface.text.IInformationControl}. This manager can be
0031: * installed on and removed from a control, referred to as the subject control,
0032: * i.e. the one from which the subject of the information to be shown is
0033: * retrieved. Also a manager can be enabled or disabled. An installed and
0034: * enabled manager can be forced to show information in its information control
0035: * using <code>showInformation</code>. An information control manager uses an
0036: * <code>IInformationControlCloser</code> to define the behavior when a
0037: * presented information control must be closed. The disposal of the subject and
0038: * the information control are internally handled by the information control
0039: * manager and are not the responsibility of the information control closer.
0040: *
0041: * @see org.eclipse.jface.text.IInformationControl
0042: * @since 2.0
0043: */
0044: abstract public class AbstractInformationControlManager {
0045:
0046: /**
0047: * Interface of an information control closer. An information control closer
0048: * monitors its information control and its subject control and closes the
0049: * information control if necessary.
0050: * <p>
0051: * Clients must implement this interface in order to equip an information
0052: * control manager accordingly.
0053: */
0054: public interface IInformationControlCloser {
0055:
0056: /**
0057: * Sets the closer's subject control. This is the control that parents
0058: * the information control and from which the subject of the information
0059: * to be shown is retrieved. <p>
0060: * Must be called before <code>start</code>. May again be called
0061: * between <code>start</code> and <code>stop</code>.
0062: *
0063: * @param subject the subject control
0064: */
0065: public void setSubjectControl(Control subject);
0066:
0067: /**
0068: * Sets the closer's information control, the one to close if necessary. <p>
0069: * Must be called before <code>start</code>. May again be called
0070: * between <code>start</code> and <code>stop</code>.
0071: *
0072: * @param control the information control
0073: */
0074: public void setInformationControl(IInformationControl control);
0075:
0076: /**
0077: * Tells this closer to start monitoring the subject and the information
0078: * control. The presented information is considered valid for the given
0079: * area of the subject control's display.
0080: *
0081: * @param subjectArea the area for which the presented information is valid
0082: */
0083: public void start(Rectangle subjectArea);
0084:
0085: /**
0086: * Tells this closer to stop monitoring the subject and the information control.
0087: */
0088: public void stop();
0089: }
0090:
0091: /**
0092: * Constitutes entities to enumerate anchors for the layout of the information control.
0093: */
0094: public static final class Anchor {
0095: private final int fFlag;
0096:
0097: private Anchor(int flag) {
0098: fFlag = flag;
0099: }
0100:
0101: /**
0102: * Returns the SWT direction flag. One of {@link SWT#BOTTOM}, {@link SWT#TOP},
0103: * {@link SWT#LEFT}, {@link SWT#RIGHT}, {@link SWT#CENTER},
0104: *
0105: * @return the SWT direction flag
0106: * @since 3.3
0107: */
0108: int getSWTFlag() {
0109: return fFlag;
0110: }
0111: }
0112:
0113: /** Internal anchor list. */
0114: private final static Anchor[] ANCHORS = { new Anchor(SWT.TOP),
0115: new Anchor(SWT.BOTTOM), new Anchor(SWT.LEFT),
0116: new Anchor(SWT.RIGHT) };
0117:
0118: /** Anchor representing the top of the information area */
0119: public final static Anchor ANCHOR_TOP = ANCHORS[0];
0120: /** Anchor representing the bottom of the information area */
0121: public final static Anchor ANCHOR_BOTTOM = ANCHORS[1];
0122: /** Anchor representing the left side of the information area */
0123: public final static Anchor ANCHOR_LEFT = ANCHORS[2];
0124: /** Anchor representing the right side of the information area */
0125: public final static Anchor ANCHOR_RIGHT = ANCHORS[3];
0126: /**
0127: * Anchor representing the middle of the subject control
0128: * @since 2.1
0129: */
0130: public final static Anchor ANCHOR_GLOBAL = new Anchor(SWT.CENTER);
0131:
0132: /**
0133: * Dialog store constant for the location's x-coordinate.
0134: * @since 3.0
0135: */
0136: public static final String STORE_LOCATION_X = "location.x"; //$NON-NLS-1$
0137: /**
0138: * Dialog store constant for the location's y-coordinate.
0139: * @since 3.0
0140: */
0141: public static final String STORE_LOCATION_Y = "location.y"; //$NON-NLS-1$
0142: /**
0143: * Dialog store constant for the size's width.
0144: * @since 3.0
0145: */
0146: public static final String STORE_SIZE_WIDTH = "size.width"; //$NON-NLS-1$
0147: /**
0148: * Dialog store constant for the size's height.
0149: * @since 3.0
0150: */
0151: public static final String STORE_SIZE_HEIGHT = "size.height"; //$NON-NLS-1$
0152:
0153: /** The subject control of the information control */
0154: private Control fSubjectControl;
0155:
0156: /** The display area for which the information to be presented is valid */
0157: private Rectangle fSubjectArea;
0158:
0159: /** The information to be presented */
0160: private Object fInformation;
0161:
0162: /** Indicates whether the information control takes focus when visible */
0163: private boolean fTakesFocusWhenVisible = false;
0164:
0165: /** The information control */
0166: protected IInformationControl fInformationControl;
0167:
0168: /** The information control creator */
0169: protected IInformationControlCreator fInformationControlCreator;
0170:
0171: /** The information control closer */
0172: protected IInformationControlCloser fInformationControlCloser;
0173:
0174: /** Indicates that the information control has been disposed */
0175: protected boolean fDisposed = false;
0176:
0177: /** Indicates the enable state of this manager */
0178: private boolean fEnabled = false;
0179:
0180: /** Cached, computed size constraints of the information control in points */
0181: private Point fSizeConstraints;
0182:
0183: /** The vertical margin when laying out the information control */
0184: private int fMarginY = 5;
0185:
0186: /** The horizontal margin when laying out the information control */
0187: private int fMarginX = 5;
0188:
0189: /** The width constraint of the information control in characters */
0190: private int fWidthConstraint = 60;
0191:
0192: /** The height constraint of the information control in characters */
0193: private int fHeightConstraint = 6;
0194:
0195: /** Indicates whether the size constraints should be enforced as minimal control size */
0196: private boolean fEnforceAsMinimalSize = false;
0197:
0198: /** Indicates whether the size constraints should be enforced as maximal control size */
0199: private boolean fEnforceAsMaximalSize = false;
0200:
0201: /** The anchor for laying out the information control in relation to the subject control */
0202: private Anchor fAnchor = ANCHOR_BOTTOM;
0203:
0204: /**
0205: * The anchor sequence used to layout the information control if the original anchor
0206: * can not be used because the information control would not fit in the display client area.
0207: * <p>
0208: * The fallback anchor for a given anchor is the one that comes directly after the given anchor or
0209: * is the first one in the sequence if the given anchor is the last one in the sequence.
0210: * <p>
0211: * </p>
0212: * Note: This sequence is ignored if the original anchor is not contained in this sequence.
0213: * </p>
0214: *
0215: * @see #fAnchor
0216: */
0217: private Anchor[] fFallbackAnchors = ANCHORS;
0218:
0219: /**
0220: * The custom information control creator.
0221: * @since 3.0
0222: */
0223: private volatile IInformationControlCreator fCustomInformationControlCreator;
0224:
0225: /**
0226: * Tells whether a custom information control is in use.
0227: * @since 3.0
0228: */
0229: private boolean fIsCustomInformationControl = false;
0230:
0231: /**
0232: * The dialog settings for the control's bounds.
0233: * @since 3.0
0234: */
0235: private IDialogSettings fDialogSettings;
0236:
0237: /**
0238: * Tells whether the control's location should be read
0239: * from the dialog settings and whether the last
0240: * valid control's size is stored back into the settings.
0241: *
0242: * @since 3.0
0243: */
0244: private boolean fIsRestoringLocation;
0245:
0246: /**
0247: * Tells whether the control's size should be read
0248: * from the dialog settings and whether the last
0249: * valid control's size is stored back into the settings.
0250: *
0251: * @since 3.0
0252: */
0253: private boolean fIsRestoringSize;
0254:
0255: /**
0256: * The dispose listener on the subject control.
0257: *
0258: * @since 3.1
0259: */
0260: private DisposeListener fSubjectControlDisposeListener;
0261:
0262: /**
0263: * Creates a new information control manager using the given information control creator.
0264: * By default the following configuration is given:
0265: * <ul>
0266: * <li> enabled == false
0267: * <li> horizontal margin == 5 points
0268: * <li> vertical margin == 5 points
0269: * <li> width constraint == 60 characters
0270: * <li> height constraint == 6 characters
0271: * <li> enforce constraints as minimal size == false
0272: * <li> enforce constraints as maximal size == false
0273: * <li> layout anchor == ANCHOR_BOTTOM
0274: * <li> fall back anchors == { ANCHOR_TOP, ANCHOR_BOTTOM, ANCHOR_LEFT, ANCHOR_RIGHT, ANCHOR_GLOBAL }
0275: * <li> takes focus when visible == false
0276: * </ul>
0277: *
0278: * @param creator the information control creator
0279: */
0280: protected AbstractInformationControlManager(
0281: IInformationControlCreator creator) {
0282: Assert.isNotNull(creator);
0283: fInformationControlCreator = creator;
0284: }
0285:
0286: /**
0287: * Computes the information to be displayed and the area in which the computed
0288: * information is valid. Implementation of this method must finish their computation
0289: * by setting the computation results using <code>setInformation</code>.
0290: */
0291: abstract protected void computeInformation();
0292:
0293: /**
0294: * Sets the parameters of the information to be displayed. These are the information itself and
0295: * the area for which the given information is valid. This so called subject area is a graphical
0296: * region of the information control's subject control. This method calls <code>presentInformation()</code>
0297: * to trigger the presentation of the computed information.
0298: *
0299: * @param information the information
0300: * @param subjectArea the subject area
0301: */
0302: protected final void setInformation(String information,
0303: Rectangle subjectArea) {
0304: fInformation = information;
0305: fSubjectArea = subjectArea;
0306: presentInformation();
0307: }
0308:
0309: /**
0310: * Sets the parameters of the information to be displayed. These are the information itself and
0311: * the area for which the given information is valid. This so called subject area is a graphical
0312: * region of the information control's subject control. This method calls <code>presentInformation()</code>
0313: * to trigger the presentation of the computed information.
0314: *
0315: * @param information the information
0316: * @param subjectArea the subject area
0317: * @since 2.1
0318: */
0319: protected final void setInformation(Object information,
0320: Rectangle subjectArea) {
0321: fInformation = information;
0322: fSubjectArea = subjectArea;
0323: presentInformation();
0324: }
0325:
0326: /**
0327: * Sets the information control closer for this manager.
0328: *
0329: * @param closer the information control closer for this manager
0330: */
0331: protected void setCloser(IInformationControlCloser closer) {
0332: fInformationControlCloser = closer;
0333: }
0334:
0335: /**
0336: * Sets the horizontal and vertical margin to be used when laying out the information control
0337: * relative to the subject control.
0338: *
0339: * @param xMargin the x-margin
0340: * @param yMargin the y-Margin
0341: */
0342: public void setMargins(int xMargin, int yMargin) {
0343: fMarginX = xMargin;
0344: fMarginY = yMargin;
0345: }
0346:
0347: /**
0348: * Sets the width- and height constraints of the information control.
0349: *
0350: * @param widthInChar the width constraint in number of characters
0351: * @param heightInChar the height constrain in number of characters
0352: * @param enforceAsMinimalSize indicates whether the constraints describe the minimal allowed size of the control
0353: * @param enforceAsMaximalSize indicates whether the constraints describe the maximal allowed size of the control
0354: */
0355: public void setSizeConstraints(int widthInChar, int heightInChar,
0356: boolean enforceAsMinimalSize, boolean enforceAsMaximalSize) {
0357: fSizeConstraints = null;
0358: fWidthConstraint = widthInChar;
0359: fHeightConstraint = heightInChar;
0360: fEnforceAsMinimalSize = enforceAsMinimalSize;
0361: fEnforceAsMaximalSize = enforceAsMaximalSize;
0362:
0363: }
0364:
0365: /**
0366: * Tells this information control manager to open the information
0367: * control with the values contained in the given dialog settings
0368: * and to store the control's last valid size in the given dialog
0369: * settings.
0370: * <p>
0371: * Note: This API is only valid if the information control implements
0372: * {@link IInformationControlExtension3}. Not following this restriction
0373: * will later result in an {@link UnsupportedOperationException}.
0374: * </p>
0375: * <p>
0376: * The constants used to store the values are:
0377: * <ul>
0378: * <li>{@link AbstractInformationControlManager#STORE_LOCATION_X}</li>
0379: * <li>{@link AbstractInformationControlManager#STORE_LOCATION_Y}</li>
0380: * <li>{@link AbstractInformationControlManager#STORE_SIZE_WIDTH}</li>
0381: * <li>{@link AbstractInformationControlManager#STORE_SIZE_HEIGHT}</li>
0382: * </ul>
0383: * </p>
0384: *
0385: * @param dialogSettings
0386: * @param restoreLocation <code>true</code> iff the location is must be (re-)stored
0387: * @param restoreSize <code>true</code>iff the size is (re-)stored
0388: * @since 3.0
0389: */
0390: public void setRestoreInformationControlBounds(
0391: IDialogSettings dialogSettings, boolean restoreLocation,
0392: boolean restoreSize) {
0393: Assert.isTrue(dialogSettings != null
0394: && (restoreLocation || restoreSize));
0395: fDialogSettings = dialogSettings;
0396: fIsRestoringLocation = restoreLocation;
0397: fIsRestoringSize = restoreSize;
0398: }
0399:
0400: /**
0401: * Sets the anchor used for laying out the information control relative to the
0402: * subject control. E.g, using <code>ANCHOR_TOP</code> indicates that the
0403: * information control is position above the area for which the information to
0404: * be displayed is valid.
0405: *
0406: * @param anchor the layout anchor
0407: */
0408: public void setAnchor(Anchor anchor) {
0409: fAnchor = anchor;
0410: }
0411:
0412: /**
0413: * Sets the anchors fallback sequence used to layout the information control if the original
0414: * anchor can not be used because the information control would not fit in the display client
0415: * area.
0416: * <p>
0417: * The fallback anchor for a given anchor is the one that comes directly after the given anchor or
0418: * is the first one in the sequence if the given anchor is the last one in the sequence.
0419: * <p>
0420: * </p>
0421: * Note: This sequence is ignored if the original anchor is not contained in this list.
0422: * </p>
0423: *
0424: * @param fallbackAnchors the array with the anchor fallback sequence
0425: * @see #setAnchor(AbstractInformationControlManager.Anchor)
0426: */
0427: public void setFallbackAnchors(Anchor[] fallbackAnchors) {
0428: if (fallbackAnchors != null) {
0429: fFallbackAnchors = new Anchor[fallbackAnchors.length];
0430: System.arraycopy(fallbackAnchors, 0, fFallbackAnchors, 0,
0431: fallbackAnchors.length);
0432: } else
0433: fFallbackAnchors = null;
0434: }
0435:
0436: /**
0437: * Sets the temporary custom control creator, overriding this manager's default information control creator.
0438: *
0439: * @param informationControlCreator the creator, possibly <code>null</code>
0440: * @since 3.0
0441: */
0442: protected void setCustomInformationControlCreator(
0443: IInformationControlCreator informationControlCreator) {
0444: if (informationControlCreator != null
0445: && fCustomInformationControlCreator instanceof IInformationControlCreatorExtension) {
0446: IInformationControlCreatorExtension extension = (IInformationControlCreatorExtension) fCustomInformationControlCreator;
0447: if (extension.canReplace(informationControlCreator))
0448: return;
0449: }
0450: fCustomInformationControlCreator = informationControlCreator;
0451: }
0452:
0453: /**
0454: * Tells the manager whether it should set the focus to the information control when made visible.
0455: *
0456: * @param takesFocus <code>true</code> if information control should take focus when made visible
0457: */
0458: public void takesFocusWhenVisible(boolean takesFocus) {
0459: fTakesFocusWhenVisible = takesFocus;
0460: }
0461:
0462: /**
0463: * Handles the disposal of the subject control. By default, the information control
0464: * is disposed by calling <code>disposeInformationControl</code>. Subclasses may extend
0465: * this method.
0466: */
0467: protected void handleSubjectControlDisposed() {
0468: disposeInformationControl();
0469: }
0470:
0471: /**
0472: * Installs this manager on the given control. The control is now taking the role of
0473: * the subject control. This implementation sets the control also as the information
0474: * control closer's subject control and automatically enables this manager.
0475: *
0476: * @param subjectControl the subject control
0477: */
0478: public void install(Control subjectControl) {
0479: if (fSubjectControl != null && !fSubjectControl.isDisposed()
0480: && fSubjectControlDisposeListener != null)
0481: fSubjectControl
0482: .removeDisposeListener(fSubjectControlDisposeListener);
0483:
0484: fSubjectControl = subjectControl;
0485:
0486: if (fSubjectControl != null)
0487: fSubjectControl
0488: .addDisposeListener(getSubjectControlDisposeListener());
0489:
0490: if (fInformationControlCloser != null)
0491: fInformationControlCloser.setSubjectControl(subjectControl);
0492:
0493: setEnabled(true);
0494: fDisposed = false;
0495: }
0496:
0497: /**
0498: * Returns the dispose listener which gets added
0499: * to the subject control.
0500: *
0501: * @return the dispose listener
0502: * @since 3.1
0503: */
0504: private DisposeListener getSubjectControlDisposeListener() {
0505: if (fSubjectControlDisposeListener == null) {
0506: fSubjectControlDisposeListener = new DisposeListener() {
0507: public void widgetDisposed(DisposeEvent e) {
0508: handleSubjectControlDisposed();
0509: }
0510: };
0511: }
0512: return fSubjectControlDisposeListener;
0513: }
0514:
0515: /**
0516: * Returns the subject control of this manager/information control.
0517: *
0518: * @return the subject control
0519: */
0520: protected Control getSubjectControl() {
0521: return fSubjectControl;
0522: }
0523:
0524: /**
0525: * Returns the actual subject area.
0526: *
0527: * @return the actual subject area
0528: */
0529: protected Rectangle getSubjectArea() {
0530: return fSubjectArea;
0531: }
0532:
0533: /**
0534: * Sets the enable state of this manager.
0535: *
0536: * @param enabled the enable state
0537: * @deprecated visibility will be changed to protected
0538: */
0539: public void setEnabled(boolean enabled) {
0540: fEnabled = enabled;
0541: }
0542:
0543: /**
0544: * Returns whether this manager is enabled or not.
0545: *
0546: * @return <code>true</code> if this manager is enabled otherwise <code>false</code>
0547: */
0548: protected boolean isEnabled() {
0549: return fEnabled;
0550: }
0551:
0552: /**
0553: * Computes the size constraints of the information control in points based on the
0554: * default font of the given subject control as well as the size constraints in character
0555: * width.
0556: *
0557: * @param subjectControl the subject control
0558: * @param informationControl the information control whose size constraints are computed
0559: * @return the computed size constraints in points
0560: */
0561: protected Point computeSizeConstraints(Control subjectControl,
0562: IInformationControl informationControl) {
0563:
0564: if (fSizeConstraints == null) {
0565:
0566: if (subjectControl == null)
0567: return null;
0568:
0569: GC gc = new GC(subjectControl);
0570: gc.setFont(subjectControl.getFont());
0571: int width = gc.getFontMetrics().getAverageCharWidth();
0572: int height = gc.getFontMetrics().getHeight();
0573: gc.dispose();
0574:
0575: fSizeConstraints = new Point(fWidthConstraint * width,
0576: fHeightConstraint * height);
0577: }
0578:
0579: return new Point(fSizeConstraints.x, fSizeConstraints.y);
0580: }
0581:
0582: /**
0583: * Computes the size constraints of the information control in points.
0584: *
0585: * @param subjectControl the subject control
0586: * @param subjectArea the subject area
0587: * @param informationControl the information control whose size constraints are computed
0588: * @return the computed size constraints in points
0589: * @since 3.0
0590: */
0591: protected Point computeSizeConstraints(Control subjectControl,
0592: Rectangle subjectArea,
0593: IInformationControl informationControl) {
0594: return computeSizeConstraints(subjectControl,
0595: informationControl);
0596: }
0597:
0598: /**
0599: * Handles the disposal of the information control. By default, the information
0600: * control closer is stopped.
0601: */
0602: protected void handleInformationControlDisposed() {
0603:
0604: storeInformationControlBounds();
0605:
0606: fInformationControl = null;
0607: if (fInformationControlCloser != null) {
0608: fInformationControlCloser.setInformationControl(null);
0609: fInformationControlCloser.stop();
0610: }
0611: }
0612:
0613: /**
0614: * Returns the information control. If the information control has not been created yet,
0615: * it is automatically created.
0616: *
0617: * @return the information control
0618: */
0619: protected IInformationControl getInformationControl() {
0620:
0621: if (fDisposed)
0622: return fInformationControl;
0623:
0624: IInformationControlCreator creator = null;
0625:
0626: if (fCustomInformationControlCreator == null) {
0627: creator = fInformationControlCreator;
0628: if (fIsCustomInformationControl
0629: && fInformationControl != null) {
0630: fInformationControl.dispose();
0631: fInformationControl = null;
0632: }
0633: fIsCustomInformationControl = false;
0634:
0635: } else {
0636:
0637: creator = fCustomInformationControlCreator;
0638: if (creator instanceof IInformationControlCreatorExtension) {
0639: IInformationControlCreatorExtension extension = (IInformationControlCreatorExtension) creator;
0640: if (fInformationControl != null
0641: && extension.canReuse(fInformationControl))
0642: return fInformationControl;
0643: }
0644: if (fInformationControl != null) {
0645: fInformationControl.dispose();
0646: fInformationControl = null;
0647: }
0648: fIsCustomInformationControl = true;
0649: }
0650:
0651: if (fInformationControl == null) {
0652: fInformationControl = creator
0653: .createInformationControl(fSubjectControl
0654: .getShell());
0655: fInformationControl
0656: .addDisposeListener(new DisposeListener() {
0657: public void widgetDisposed(DisposeEvent e) {
0658: handleInformationControlDisposed();
0659: }
0660: });
0661:
0662: if (fInformationControlCloser != null)
0663: fInformationControlCloser
0664: .setInformationControl(fInformationControl);
0665: }
0666:
0667: return fInformationControl;
0668: }
0669:
0670: /**
0671: * Computes the display location of the information control. The location is computed
0672: * considering the given subject area, the anchor at the subject area, and the
0673: * size of the information control. This method does not care about whether the information
0674: * control would be completely visible when placed at the result location.
0675: *
0676: * @param subjectArea the subject area
0677: * @param controlSize the size of the information control
0678: * @param anchor the anchor at the subject area
0679: * @return the display location of the information control
0680: */
0681: protected Point computeLocation(Rectangle subjectArea,
0682: Point controlSize, Anchor anchor) {
0683: int xShift = 0;
0684: int yShift = 0;
0685:
0686: switch (anchor.getSWTFlag()) {
0687: case SWT.CENTER:
0688: Point subjectControlSize = fSubjectControl.getSize();
0689: Point location = new Point(subjectControlSize.x / 2,
0690: subjectControlSize.y / 2);
0691: location.x -= (controlSize.x / 2);
0692: location.y -= (controlSize.y / 2);
0693: return fSubjectControl.toDisplay(location);
0694: case SWT.BOTTOM:
0695: yShift = subjectArea.height + fMarginY;
0696: break;
0697: case SWT.RIGHT:
0698: xShift = fMarginX + subjectArea.width;
0699: break;
0700: case SWT.TOP:
0701: yShift = -controlSize.y - fMarginY;
0702: break;
0703: case SWT.LEFT:
0704: xShift = -controlSize.x - fMarginX;
0705: break;
0706: }
0707:
0708: boolean isRTL = fSubjectControl != null
0709: && (fSubjectControl.getStyle() & SWT.RIGHT_TO_LEFT) != 0;
0710: if (isRTL)
0711: xShift += controlSize.x;
0712:
0713: return fSubjectControl.toDisplay(new Point(subjectArea.x
0714: + xShift, subjectArea.y + yShift));
0715: }
0716:
0717: /**
0718: * Computes the area available for an information control given an anchor and the subject area
0719: * within <code>bounds</code>.
0720: *
0721: * @param subjectArea the subject area
0722: * @param bounds the bounds
0723: * @param anchor the anchor at the subject area
0724: * @return the area available at the given anchor relative to the subject area, confined to the
0725: * monitor's client area
0726: * @since 3.3
0727: */
0728: protected Rectangle computeAvailableArea(Rectangle subjectArea,
0729: Rectangle bounds, Anchor anchor) {
0730: Rectangle area;
0731: switch (anchor.getSWTFlag()) {
0732: case SWT.CENTER:
0733: area = bounds;
0734: break;
0735: case SWT.BOTTOM:
0736: int y = subjectArea.y + subjectArea.height + fMarginY;
0737: area = new Rectangle(bounds.x, y, bounds.width, bounds.y
0738: + bounds.height - y);
0739: break;
0740: case SWT.RIGHT:
0741: int x = subjectArea.x + subjectArea.width + fMarginX;
0742: area = new Rectangle(x, bounds.y, bounds.x + bounds.width
0743: - x, bounds.height);
0744: break;
0745: case SWT.TOP:
0746: area = new Rectangle(bounds.x, bounds.y, bounds.width,
0747: subjectArea.y - bounds.y - fMarginY);
0748: break;
0749: case SWT.LEFT:
0750: area = new Rectangle(bounds.x, bounds.y, subjectArea.x
0751: - bounds.x - fMarginX, bounds.height);
0752: break;
0753: default:
0754: Assert.isLegal(false);
0755: return null;
0756: }
0757:
0758: // Don't return negative areas if the subjectArea overlaps with the monitor bounds.
0759: area.intersect(bounds);
0760: return area;
0761: }
0762:
0763: /**
0764: * Checks whether a control of the given size at the given location would be completely visible
0765: * in the given display area when laid out by using the given anchor. If not, this method tries
0766: * to shift the control orthogonal to the direction given by the anchor to make it visible. If possible
0767: * it updates the location.<p>
0768: * This method returns <code>true</code> if the potentially updated position results in a
0769: * completely visible control, or <code>false</code> otherwise.
0770: *
0771: *
0772: * @param location the location of the control
0773: * @param size the size of the control
0774: * @param displayArea the display area in which the control should be visible
0775: * @param anchor anchor for lying out the control
0776: * @return <code>true</code>if the updated location is useful
0777: */
0778: protected boolean updateLocation(Point location, Point size,
0779: Rectangle displayArea, Anchor anchor) {
0780:
0781: int displayLowerRightX = displayArea.x + displayArea.width;
0782: int displayLowerRightY = displayArea.y + displayArea.height;
0783: int lowerRightX = location.x + size.x;
0784: int lowerRightY = location.y + size.y;
0785:
0786: if (ANCHOR_BOTTOM == anchor || ANCHOR_TOP == anchor) {
0787:
0788: if (ANCHOR_BOTTOM == anchor) {
0789: if (lowerRightY > displayLowerRightY)
0790: return false;
0791: } else {
0792: if (location.y < displayArea.y)
0793: return false;
0794: }
0795:
0796: if (lowerRightX > displayLowerRightX)
0797: location.x = location.x
0798: - (lowerRightX - displayLowerRightX);
0799:
0800: return (location.x >= displayArea.x && location.y >= displayArea.y);
0801:
0802: } else if (ANCHOR_RIGHT == anchor || ANCHOR_LEFT == anchor) {
0803:
0804: if (ANCHOR_RIGHT == anchor) {
0805: if (lowerRightX > displayLowerRightX)
0806: return false;
0807: } else {
0808: if (location.x < displayArea.x)
0809: return false;
0810: }
0811:
0812: if (lowerRightY > displayLowerRightY)
0813: location.y = location.y
0814: - (lowerRightY - displayLowerRightY);
0815:
0816: return (location.x >= displayArea.x && location.y >= displayArea.y);
0817:
0818: } else if (ANCHOR_GLOBAL == anchor) {
0819:
0820: if (lowerRightX > displayLowerRightX)
0821: location.x = location.x
0822: - (lowerRightX - displayLowerRightX);
0823:
0824: if (lowerRightY > displayLowerRightY)
0825: location.y = location.y
0826: - (lowerRightY - displayLowerRightY);
0827:
0828: return (location.x >= displayArea.x && location.y >= displayArea.y);
0829: }
0830:
0831: return false;
0832: }
0833:
0834: /**
0835: * Returns the next fallback anchor as specified by this manager's
0836: * fallback anchor sequence.
0837: * <p>
0838: * The fallback anchor for the given anchor is the one that comes directly after
0839: * the given anchor or is the first one in the sequence if the given anchor is the
0840: * last one in the sequence.
0841: * </p>
0842: * <p>
0843: * Note: It is the callers responsibility to prevent an endless loop i.e. to test
0844: * whether a given anchor has already been used once.
0845: * then
0846: * </p>
0847: *
0848: * @param anchor the current anchor
0849: * @return the next fallback anchor or <code>null</code> if no fallback anchor is available
0850: */
0851: protected Anchor getNextFallbackAnchor(Anchor anchor) {
0852:
0853: if (anchor == null || fFallbackAnchors == null)
0854: return null;
0855:
0856: for (int i = 0; i < fFallbackAnchors.length; i++) {
0857: if (fFallbackAnchors[i] == anchor)
0858: return fFallbackAnchors[i + 1 == fFallbackAnchors.length ? 0
0859: : i + 1];
0860: }
0861:
0862: return null;
0863: }
0864:
0865: /**
0866: * Computes the location of the information control depending on the
0867: * subject area and the size of the information control. This method attempts
0868: * to find a location at which the information control lies completely in the display's
0869: * client area while honoring the manager's default anchor. If this isn't possible using the
0870: * default anchor, the fallback anchors are tried out.
0871: *
0872: * @param subjectArea the information area
0873: * @param controlSize the size of the information control
0874: * @return the computed location of the information control
0875: */
0876: protected Point computeInformationControlLocation(
0877: Rectangle subjectArea, Point controlSize) {
0878: Rectangle subjectAreaDisplayRelative = Geometry.toDisplay(
0879: fSubjectControl, subjectArea);
0880:
0881: Point upperLeft;
0882: Anchor testAnchor = fAnchor;
0883: Rectangle bestBounds = null;
0884: int bestArea = Integer.MIN_VALUE;
0885: Anchor bestAnchor = null;
0886: do {
0887:
0888: upperLeft = computeLocation(subjectArea, controlSize,
0889: testAnchor);
0890: Monitor monitor = getClosestMonitor(
0891: subjectAreaDisplayRelative, testAnchor);
0892: if (updateLocation(upperLeft, controlSize, monitor
0893: .getClientArea(), testAnchor))
0894: return upperLeft;
0895:
0896: // compute available area for this anchor and update if better than best
0897: Rectangle available = computeAvailableArea(
0898: subjectAreaDisplayRelative,
0899: monitor.getClientArea(), testAnchor);
0900: Rectangle proposed = new Rectangle(upperLeft.x,
0901: upperLeft.y, controlSize.x, controlSize.y);
0902: available.intersect(proposed);
0903: int area = available.width * available.height;
0904: if (area > bestArea) {
0905: bestArea = area;
0906: bestBounds = available;
0907: bestAnchor = testAnchor;
0908: }
0909:
0910: testAnchor = getNextFallbackAnchor(testAnchor);
0911:
0912: } while (testAnchor != fAnchor && testAnchor != null);
0913:
0914: // no anchor is perfect - select the one with larges area and set the size to not overlap with the subjectArea
0915: if (bestAnchor != ANCHOR_GLOBAL)
0916: Geometry.set(controlSize, Geometry.getSize(bestBounds));
0917: return Geometry.getLocation(bestBounds);
0918: }
0919:
0920: /**
0921: * Gets the closest monitor given an anchor and the subject area.
0922: *
0923: * @param area the subject area
0924: * @param anchor the anchor
0925: * @return the monitor closest to the edge of <code>area</code> defined by
0926: * <code>anchor</code>
0927: * @since 3.3
0928: */
0929: private Monitor getClosestMonitor(Rectangle area, Anchor anchor) {
0930: Point center;
0931: if (ANCHOR_GLOBAL == anchor)
0932: center = Geometry.centerPoint(area);
0933: else
0934: center = Geometry.centerPoint(Geometry.getExtrudedEdge(
0935: area, 0, anchor.getSWTFlag()));
0936: return getClosestMonitor(fSubjectControl.getDisplay(), Geometry
0937: .createRectangle(center, new Point(0, 0)));
0938: }
0939:
0940: /**
0941: * Copied from org.eclipse.jface.window.Window. Returns the monitor whose client area contains
0942: * the given point. If no monitor contains the point, returns the monitor that is closest to the
0943: * point. If this is ever made public, it should be moved into a separate utility class.
0944: *
0945: * @param display the display to search for monitors
0946: * @param rectangle the rectangle to find the closest monitor for (display coordinates)
0947: * @return the montor closest to the given point
0948: * @since 3.3
0949: */
0950: private Monitor getClosestMonitor(Display display,
0951: Rectangle rectangle) {
0952: int closest = Integer.MAX_VALUE;
0953:
0954: Point toFind = Geometry.centerPoint(rectangle);
0955: Monitor[] monitors = display.getMonitors();
0956: Monitor result = monitors[0];
0957:
0958: for (int idx = 0; idx < monitors.length; idx++) {
0959: Monitor current = monitors[idx];
0960:
0961: Rectangle clientArea = current.getClientArea();
0962:
0963: if (clientArea.contains(toFind)) {
0964: return current;
0965: }
0966:
0967: int distance = Geometry.distanceSquared(Geometry
0968: .centerPoint(clientArea), toFind);
0969: if (distance < closest) {
0970: closest = distance;
0971: result = current;
0972: }
0973: }
0974:
0975: return result;
0976: }
0977:
0978: /**
0979: * Computes information to be displayed as well as the subject area
0980: * and initiates that this information is presented in the information control.
0981: * This happens only if this controller is enabled.
0982: */
0983: public void showInformation() {
0984: if (fEnabled)
0985: doShowInformation();
0986: }
0987:
0988: /**
0989: * Computes information to be displayed as well as the subject area
0990: * and initiates that this information is presented in the information control.
0991: */
0992: protected void doShowInformation() {
0993: fSubjectArea = null;
0994: fInformation = null;
0995: computeInformation();
0996: }
0997:
0998: /**
0999: * Presents the information in the information control or hides the information
1000: * control if no information should be presented. The information has previously
1001: * been set using <code>setInformation</code>.
1002: */
1003: protected void presentInformation() {
1004: boolean hasContents = false;
1005: if (fInformation instanceof String)
1006: hasContents = ((String) fInformation).trim().length() > 0;
1007: else
1008: hasContents = (fInformation != null);
1009:
1010: if (fSubjectArea != null && hasContents)
1011: internalShowInformationControl(fSubjectArea, fInformation);
1012: else
1013: hideInformationControl();
1014: }
1015:
1016: /**
1017: * Opens the information control with the given information and the specified
1018: * subject area. It also activates the information control closer.
1019: *
1020: * @param subjectArea the information area
1021: * @param information the information
1022: */
1023: private void internalShowInformationControl(Rectangle subjectArea,
1024: Object information) {
1025:
1026: IInformationControl informationControl = getInformationControl();
1027: if (informationControl != null) {
1028:
1029: Point sizeConstraints = computeSizeConstraints(
1030: fSubjectControl, fSubjectArea, informationControl);
1031: informationControl.setSizeConstraints(sizeConstraints.x,
1032: sizeConstraints.y);
1033:
1034: if (informationControl instanceof IInformationControlExtension2)
1035: ((IInformationControlExtension2) informationControl)
1036: .setInput(information);
1037: else
1038: informationControl.setInformation(information
1039: .toString());
1040:
1041: if (informationControl instanceof IInformationControlExtension) {
1042: IInformationControlExtension extension = (IInformationControlExtension) informationControl;
1043: if (!extension.hasContents())
1044: return;
1045: }
1046:
1047: Point size = null;
1048: Point location = null;
1049: Rectangle bounds = restoreInformationControlBounds();
1050:
1051: if (bounds != null) {
1052: if (bounds.x > -1 && bounds.y > -1)
1053: location = Geometry.getLocation(bounds);
1054:
1055: if (bounds.width > -1 && bounds.height > -1)
1056: size = Geometry.getSize(bounds);
1057: }
1058:
1059: if (size == null)
1060: size = informationControl.computeSizeHint();
1061:
1062: if (fEnforceAsMinimalSize)
1063: size = Geometry.max(size, sizeConstraints);
1064: if (fEnforceAsMaximalSize)
1065: size = Geometry.min(size, sizeConstraints);
1066:
1067: if (location == null)
1068: location = computeInformationControlLocation(
1069: subjectArea, size);
1070:
1071: // Make sure it fits on the screen
1072: Rectangle controlBounds = Geometry.createRectangle(
1073: location, size);
1074: Rectangle monitorBounds = getClosestMonitor(
1075: fSubjectControl.getDisplay(), controlBounds)
1076: .getClientArea();
1077: controlBounds.intersect(monitorBounds);
1078:
1079: informationControl.setLocation(location);
1080: informationControl.setSize(size.x, size.y);
1081:
1082: showInformationControl(subjectArea);
1083: }
1084: }
1085:
1086: /**
1087: * Hides the information control and stops the information control closer.
1088: */
1089: protected void hideInformationControl() {
1090: if (fInformationControl != null) {
1091: storeInformationControlBounds();
1092: fInformationControl.setVisible(false);
1093: if (fInformationControlCloser != null)
1094: fInformationControlCloser.stop();
1095: }
1096: }
1097:
1098: /**
1099: * Shows the information control and starts the information control closer.
1100: * This method may not be called by clients.
1101: *
1102: * @param subjectArea the information area
1103: */
1104: protected void showInformationControl(Rectangle subjectArea) {
1105: fInformationControl.setVisible(true);
1106:
1107: if (fTakesFocusWhenVisible)
1108: fInformationControl.setFocus();
1109:
1110: if (fInformationControlCloser != null)
1111: fInformationControlCloser.start(subjectArea);
1112: }
1113:
1114: /**
1115: * Disposes this manager's information control.
1116: */
1117: public void disposeInformationControl() {
1118: if (fInformationControl != null) {
1119: fInformationControl.dispose();
1120: handleInformationControlDisposed();
1121: }
1122: }
1123:
1124: /**
1125: * Disposes this manager and if necessary all dependent parts such as
1126: * the information control. For symmetry it first disables this manager.
1127: */
1128: public void dispose() {
1129: if (!fDisposed) {
1130:
1131: fDisposed = true;
1132:
1133: setEnabled(false);
1134: disposeInformationControl();
1135:
1136: if (fSubjectControl != null
1137: && !fSubjectControl.isDisposed()
1138: && fSubjectControlDisposeListener != null)
1139: fSubjectControl
1140: .removeDisposeListener(fSubjectControlDisposeListener);
1141: fSubjectControl = null;
1142: fSubjectControlDisposeListener = null;
1143:
1144: fIsCustomInformationControl = false;
1145: fCustomInformationControlCreator = null;
1146: fInformationControlCreator = null;
1147: fInformationControlCloser = null;
1148: }
1149: }
1150:
1151: // ------ control's size handling dialog settings ------
1152:
1153: /**
1154: * Stores the information control's bounds.
1155: *
1156: * @since 3.0
1157: */
1158: protected void storeInformationControlBounds() {
1159: if (fDialogSettings == null || fInformationControl == null
1160: || !(fIsRestoringLocation || fIsRestoringSize))
1161: return;
1162:
1163: if (!(fInformationControl instanceof IInformationControlExtension3))
1164: throw new UnsupportedOperationException();
1165:
1166: boolean controlRestoresSize = ((IInformationControlExtension3) fInformationControl)
1167: .restoresSize();
1168: boolean controlRestoresLocation = ((IInformationControlExtension3) fInformationControl)
1169: .restoresLocation();
1170:
1171: Rectangle bounds = ((IInformationControlExtension3) fInformationControl)
1172: .getBounds();
1173: if (bounds == null)
1174: return;
1175:
1176: if (fIsRestoringSize && controlRestoresSize) {
1177: fDialogSettings.put(STORE_SIZE_WIDTH, bounds.width);
1178: fDialogSettings.put(STORE_SIZE_HEIGHT, bounds.height);
1179: }
1180: if (fIsRestoringLocation && controlRestoresLocation) {
1181: fDialogSettings.put(STORE_LOCATION_X, bounds.x);
1182: fDialogSettings.put(STORE_LOCATION_Y, bounds.y);
1183: }
1184: }
1185:
1186: /**
1187: * Restores the information control's bounds.
1188: *
1189: * @return the stored bounds
1190: * @since 3.0
1191: */
1192: protected Rectangle restoreInformationControlBounds() {
1193: if (fDialogSettings == null
1194: || !(fIsRestoringLocation || fIsRestoringSize))
1195: return null;
1196:
1197: if (!(fInformationControl instanceof IInformationControlExtension3))
1198: throw new UnsupportedOperationException();
1199:
1200: boolean controlRestoresSize = ((IInformationControlExtension3) fInformationControl)
1201: .restoresSize();
1202: boolean controlRestoresLocation = ((IInformationControlExtension3) fInformationControl)
1203: .restoresLocation();
1204:
1205: Rectangle bounds = new Rectangle(-1, -1, -1, -1);
1206:
1207: if (fIsRestoringSize && controlRestoresSize) {
1208: try {
1209: bounds.width = fDialogSettings.getInt(STORE_SIZE_WIDTH);
1210: bounds.height = fDialogSettings
1211: .getInt(STORE_SIZE_HEIGHT);
1212: } catch (NumberFormatException ex) {
1213: bounds.width = -1;
1214: bounds.height = -1;
1215: }
1216: }
1217:
1218: if (fIsRestoringLocation && controlRestoresLocation) {
1219: try {
1220: bounds.x = fDialogSettings.getInt(STORE_LOCATION_X);
1221: bounds.y = fDialogSettings.getInt(STORE_LOCATION_Y);
1222: } catch (NumberFormatException ex) {
1223: bounds.x = -1;
1224: bounds.y = -1;
1225: }
1226: }
1227:
1228: // sanity check
1229: if (bounds.x == -1 && bounds.y == -1 && bounds.width == -1
1230: && bounds.height == -1)
1231: return null;
1232:
1233: Rectangle maxBounds = null;
1234: if (fSubjectControl != null && !fSubjectControl.isDisposed())
1235: maxBounds = fSubjectControl.getDisplay().getBounds();
1236: else {
1237: // fallback
1238: Display display = Display.getCurrent();
1239: if (display == null)
1240: display = Display.getDefault();
1241: if (display != null && !display.isDisposed())
1242: maxBounds = display.getBounds();
1243: }
1244:
1245: if (bounds.width > -1 && bounds.height > -1) {
1246: if (maxBounds != null) {
1247: bounds.width = Math.min(bounds.width, maxBounds.width);
1248: bounds.height = Math.min(bounds.height,
1249: maxBounds.height);
1250: }
1251:
1252: // Enforce an absolute minimal size
1253: bounds.width = Math.max(bounds.width, 30);
1254: bounds.height = Math.max(bounds.height, 30);
1255: }
1256:
1257: if (bounds.x > -1 && bounds.y > -1 && maxBounds != null) {
1258: bounds.x = Math.max(bounds.x, maxBounds.x);
1259: bounds.y = Math.max(bounds.y, maxBounds.y);
1260:
1261: if (bounds.width > -1 && bounds.height > -1) {
1262: bounds.x = Math.min(bounds.x, maxBounds.width
1263: - bounds.width);
1264: bounds.y = Math.min(bounds.y, maxBounds.height
1265: - bounds.height);
1266: }
1267: }
1268:
1269: return bounds;
1270: }
1271: }
|