blob: 689e13852255fdd6a15c63c5dbfbd6e519c694f1 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2018 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
* Sean Montgomery, sean_montgomery@comcast.net - https://bugs.eclipse.org/bugs/show_bug.cgi?id=45095
*******************************************************************************/
package org.eclipse.jface.text;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Monitor;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.Platform;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.internal.text.InformationControlReplacer;
import org.eclipse.jface.internal.text.InternalAccessor;
import org.eclipse.jface.util.Geometry;
import org.eclipse.jface.util.Util;
import org.eclipse.jface.text.ITextViewerExtension8.EnrichMode;
/**
* Manages the life cycle, visibility, layout, and contents of an
* {@link org.eclipse.jface.text.IInformationControl}. This manager can be
* installed on and removed from a control, referred to as the subject control,
* i.e. the one from which the subject of the information to be shown is
* retrieved. Also a manager can be enabled or disabled. An installed and
* enabled manager can be forced to show information in its information control
* using <code>showInformation</code>. An information control manager uses an
* <code>IInformationControlCloser</code> to define the behavior when a
* presented information control must be closed. The disposal of the subject and
* the information control are internally handled by the information control
* manager and are not the responsibility of the information control closer.
*
* @see org.eclipse.jface.text.IInformationControl
* @since 2.0
*/
abstract public class AbstractInformationControlManager {
/**
* An internal class that gives access to internal methods.
*
* @since 3.4
*/
class MyInternalAccessor extends InternalAccessor {
@Override
public IInformationControl getCurrentInformationControl() {
return AbstractInformationControlManager.this.getCurrentInformationControl();
}
@Override
public void setInformationControlReplacer(InformationControlReplacer replacer) {
AbstractInformationControlManager.this.setInformationControlReplacer(replacer);
}
@Override
public InformationControlReplacer getInformationControlReplacer() {
return AbstractInformationControlManager.this.getInformationControlReplacer();
}
@Override
public boolean canReplace(IInformationControl control) {
return AbstractInformationControlManager.this.canReplace(control);
}
@Override
public boolean isReplaceInProgress() {
return AbstractInformationControlManager.this.isReplaceInProgress();
}
@Override
public void replaceInformationControl(boolean takeFocus) {
AbstractInformationControlManager.this.replaceInformationControl(takeFocus);
}
@Override
public void cropToClosestMonitor(Rectangle bounds) {
AbstractInformationControlManager.this.cropToClosestMonitor(bounds);
}
@Override
public void setHoverEnrichMode(EnrichMode mode) {
throw new UnsupportedOperationException("only implemented in AbstractHoverInformationControlManager"); //$NON-NLS-1$
}
@Override
public boolean getAllowMouseExit() {
throw new UnsupportedOperationException("only implemented in AnnotationBarHoverManager"); //$NON-NLS-1$
}
}
/**
* Interface of an information control closer. An information control closer
* monitors its information control and its subject control and closes the
* information control if necessary.
* <p>
* Clients must implement this interface in order to equip an information
* control manager accordingly.
*/
public interface IInformationControlCloser {
/**
* Sets the closer's subject control. This is the control that parents
* the information control and from which the subject of the information
* to be shown is retrieved. <p>
* Must be called before <code>start</code>. May again be called
* between <code>start</code> and <code>stop</code>.
*
* @param subject the subject control
*/
public void setSubjectControl(Control subject);
/**
* Sets the closer's information control, the one to close if necessary. <p>
* Must be called before <code>start</code>. May again be called
* between <code>start</code> and <code>stop</code>.
*
* @param control the information control
*/
public void setInformationControl(IInformationControl control);
/**
* Tells this closer to start monitoring the subject and the information
* control. The presented information is considered valid for the given
* area of the subject control's display.
*
* @param subjectArea the area for which the presented information is valid
*/
public void start(Rectangle subjectArea);
/**
* Tells this closer to stop monitoring the subject and the information control.
*/
public void stop();
}
/**
* Constitutes entities to enumerate anchors for the layout of the information control.
*/
public static final class Anchor {
private final int fFlag;
private Anchor(int flag) {
fFlag= flag;
}
/**
* Returns the SWT direction flag. One of {@link SWT#BOTTOM}, {@link SWT#TOP},
* {@link SWT#LEFT}, {@link SWT#RIGHT}, {@link SWT#CENTER},
*
* @return the SWT direction flag
* @since 3.3
*/
int getSWTFlag() {
return fFlag;
}
@Override
public String toString() {
switch (fFlag) {
case SWT.BOTTOM: return "BOTTOM"; //$NON-NLS-1$
case SWT.TOP: return "TOP"; //$NON-NLS-1$
case SWT.LEFT: return "LEFT"; //$NON-NLS-1$
case SWT.RIGHT: return "RIGHT"; //$NON-NLS-1$
case SWT.CENTER: return "CENTER"; //$NON-NLS-1$
default: return Integer.toHexString(fFlag);
}
}
}
/** Internal anchor list. */
private final static Anchor[] ANCHORS= { new Anchor(SWT.TOP), new Anchor(SWT.BOTTOM), new Anchor(SWT.LEFT), new Anchor(SWT.RIGHT) };
/** Anchor representing the top of the information area */
public final static Anchor ANCHOR_TOP= ANCHORS[0];
/** Anchor representing the bottom of the information area */
public final static Anchor ANCHOR_BOTTOM= ANCHORS[1];
/** Anchor representing the left side of the information area */
public final static Anchor ANCHOR_LEFT= ANCHORS[2];
/** Anchor representing the right side of the information area */
public final static Anchor ANCHOR_RIGHT= ANCHORS[3];
/**
* Anchor representing the middle of the subject control
* @since 2.1
*/
public final static Anchor ANCHOR_GLOBAL= new Anchor(SWT.CENTER);
/**
* Dialog store constant for the location's x-coordinate.
* @since 3.0
*/
public static final String STORE_LOCATION_X= "location.x"; //$NON-NLS-1$
/**
* Dialog store constant for the location's y-coordinate.
* @since 3.0
*/
public static final String STORE_LOCATION_Y= "location.y"; //$NON-NLS-1$
/**
* Dialog store constant for the size's width.
* @since 3.0
*/
public static final String STORE_SIZE_WIDTH= "size.width"; //$NON-NLS-1$
/**
* Dialog store constant for the size's height.
* @since 3.0
*/
public static final String STORE_SIZE_HEIGHT= "size.height"; //$NON-NLS-1$
/**
* Tells whether this class and its subclasses are in debug mode.
* <p>
* Subclasses may use this.
* </p>
* @since 3.4
*/
protected static final boolean DEBUG= "true".equalsIgnoreCase(Platform.getDebugOption("org.eclipse.jface.text/debug/AbstractInformationControlManager")); //$NON-NLS-1$//$NON-NLS-2$
/** The subject control of the information control */
private Control fSubjectControl;
/** The display area for which the information to be presented is valid */
private Rectangle fSubjectArea;
/** The information to be presented */
private Object fInformation;
/** Indicates whether the information control takes focus when visible */
private boolean fTakesFocusWhenVisible= false;
/**
* The information control.
*
* <p>This field should not be referenced by subclasses. It is <code>protected</code> for API
* compatibility reasons.
*/
protected IInformationControl fInformationControl;
/**
* The information control creator.
*
* <p>This field should not be referenced by subclasses. It is <code>protected</code> for API
* compatibility reasons.
*/
protected IInformationControlCreator fInformationControlCreator;
/**
* The information control closer.
*
* <p>This field should not be referenced by subclasses. It is <code>protected</code> for API
* compatibility reasons.
*/
protected IInformationControlCloser fInformationControlCloser;
/**
* Indicates that the information control has been disposed.
*
* <p>This field should not be referenced by subclasses. It is <code>protected</code> for API
* compatibility reasons.
*/
protected boolean fDisposed= false;
/**
* The information control replacer to be used when this information control
* needs to be replaced with another information control.
*
* @since 3.4
*/
private InformationControlReplacer fInformationControlReplacer;
/** Indicates the enable state of this manager */
private boolean fEnabled= false;
/** Cached, computed size constraints of the information control in points */
private Point fSizeConstraints;
/** The vertical margin when laying out the information control */
private int fMarginY= 5;
/** The horizontal margin when laying out the information control */
private int fMarginX= 5;
/** The width constraint of the information control in characters */
private int fWidthConstraint= 60;
/** The height constraint of the information control in characters */
private int fHeightConstraint= 6;
/** Indicates whether the size constraints should be enforced as minimal control size */
private boolean fEnforceAsMinimalSize= false;
/** Indicates whether the size constraints should be enforced as maximal control size */
private boolean fEnforceAsMaximalSize= false;
/** The anchor for laying out the information control in relation to the subject control */
private Anchor fAnchor= ANCHOR_BOTTOM;
/**
* The anchor sequence used to layout the information control if the original anchor
* can not be used because the information control would not fit in the display client area.
* <p>
* The fallback anchor for a given anchor is the one that comes directly after the given anchor or
* is the first one in the sequence if the given anchor is the last one in the sequence.
* <p>
* </p>
* Note: This sequence is ignored if the original anchor is not contained in this sequence.
* </p>
*
* @see #fAnchor
*/
private Anchor[] fFallbackAnchors= ANCHORS;
/**
* The custom information control creator.
* @since 3.0
*/
private volatile IInformationControlCreator fCustomInformationControlCreator;
/**
* Tells whether a custom information control is in use.
* @since 3.0
*/
private boolean fIsCustomInformationControl= false;
/**
* The dialog settings for the control's bounds.
* @since 3.0
*/
private IDialogSettings fDialogSettings;
/**
* Tells whether the control's location should be read
* from the dialog settings and whether the last
* valid control's size is stored back into the settings.
*
* @since 3.0
*/
private boolean fIsRestoringLocation;
/**
* Tells whether the control's size should be read
* from the dialog settings and whether the last
* valid control's size is stored back into the settings.
*
* @since 3.0
*/
private boolean fIsRestoringSize;
/**
* The dispose listener on the subject control.
*
* @since 3.1
*/
private DisposeListener fSubjectControlDisposeListener;
/**
* Creates a new information control manager using the given information control creator.
* By default the following configuration is given:
* <ul>
* <li> enabled == false
* <li> horizontal margin == 5 points
* <li> vertical margin == 5 points
* <li> width constraint == 60 characters
* <li> height constraint == 6 characters
* <li> enforce constraints as minimal size == false
* <li> enforce constraints as maximal size == false
* <li> layout anchor == ANCHOR_BOTTOM
* <li> fall back anchors == { ANCHOR_TOP, ANCHOR_BOTTOM, ANCHOR_LEFT, ANCHOR_RIGHT, ANCHOR_GLOBAL }
* <li> takes focus when visible == false
* </ul>
*
* @param creator the information control creator
*/
protected AbstractInformationControlManager(IInformationControlCreator creator) {
Assert.isNotNull(creator);
fInformationControlCreator= creator;
}
/**
* Computes the information to be displayed and the area in which the computed
* information is valid. Implementation of this method must finish their computation
* by setting the computation results using <code>setInformation</code>.
*/
abstract protected void computeInformation();
/**
* Sets the parameters of the information to be displayed. These are the information itself and
* the area for which the given information is valid. This so called subject area is a graphical
* region of the information control's subject control. This method calls <code>presentInformation()</code>
* to trigger the presentation of the computed information.
*
* @param information the information, or <code>null</code> if none is available
* @param subjectArea the subject area, or <code>null</code> if none is available
*/
protected final void setInformation(String information, Rectangle subjectArea) {
setInformation((Object)information, subjectArea);
}
/**
* Sets the parameters of the information to be displayed. These are the information itself and
* the area for which the given information is valid. This so called subject area is a graphical
* region of the information control's subject control. This method calls <code>presentInformation()</code>
* to trigger the presentation of the computed information.
*
* @param information the information, or <code>null</code> if none is available
* @param subjectArea the subject area, or <code>null</code> if none is available
* @since 2.1
*/
protected final void setInformation(Object information, Rectangle subjectArea) {
fInformation= information;
fSubjectArea= subjectArea;
presentInformation();
}
/**
* Sets the information control closer for this manager.
*
* @param closer the information control closer for this manager
*/
protected void setCloser(IInformationControlCloser closer) {
fInformationControlCloser= closer;
}
/**
* Sets the information control replacer for this manager and disposes the
* old one if set.
*
* @param replacer the information control replacer for this manager, or
* <code>null</code> if no information control replacing should
* take place
* @since 3.4
*/
void setInformationControlReplacer(InformationControlReplacer replacer) {
if (fInformationControlReplacer != null)
fInformationControlReplacer.dispose();
fInformationControlReplacer= replacer;
}
/**
* Returns the current information control replacer or <code>null</code> if none has been installed.
*
* @return the current information control replacer or <code>null</code> if none has been installed
* @since 3.4
*/
InformationControlReplacer getInformationControlReplacer() {
return fInformationControlReplacer;
}
/**
* Returns whether an information control replacer has been installed.
*
* @return whether an information control replacer has been installed
* @since 3.4
*/
boolean hasInformationControlReplacer() {
return fInformationControlReplacer != null;
}
/**
* Tests whether the given information control is replaceable.
*
* @param iControl information control or <code>null</code> if none
* @return <code>true</code> if information control is replaceable, <code>false</code> otherwise
* @since 3.4
*/
boolean canReplace(IInformationControl iControl) {
return iControl instanceof IInformationControlExtension3
&& iControl instanceof IInformationControlExtension5
&& ((IInformationControlExtension5) iControl).getInformationPresenterControlCreator() != null;
}
/**
* Returns the current information control, or <code>null</code> if none.
*
* @return the current information control, or <code>null</code> if none
* @since 3.4
*/
IInformationControl getCurrentInformationControl() {
return fInformationControl;
}
/**
* Tells whether this manager's information control is currently being replaced.
*
* @return <code>true</code> if a replace is in progress
* @since 3.4
*/
boolean isReplaceInProgress() {
return fInformationControlReplacer != null && fInformationControlReplacer.isReplacing();
}
/**
* Sets the horizontal and vertical margin to be used when laying out the
* information control relative to the subject control.
*
* @param xMargin the x-margin
* @param yMargin the y-Margin
*/
public void setMargins(int xMargin, int yMargin) {
fMarginX= xMargin;
fMarginY= yMargin;
}
/**
* Sets the width- and height constraints of the information control.
*
* @param widthInChar the width constraint in number of characters
* @param heightInChar the height constrain in number of characters
* @param enforceAsMinimalSize indicates whether the constraints describe the minimal allowed size of the control
* @param enforceAsMaximalSize indicates whether the constraints describe the maximal allowed size of the control
*/
public void setSizeConstraints(int widthInChar, int heightInChar, boolean enforceAsMinimalSize, boolean enforceAsMaximalSize) {
fSizeConstraints= null;
fWidthConstraint= widthInChar;
fHeightConstraint= heightInChar;
fEnforceAsMinimalSize= enforceAsMinimalSize;
fEnforceAsMaximalSize= enforceAsMaximalSize;
}
/**
* Tells this information control manager to open the information control with the values
* contained in the given dialog settings and to store the control's last valid size in the
* given dialog settings.
* <p>
* Note: This API is only valid if the information control implements
* {@link IInformationControlExtension3}. Not following this restriction will later result in an
* {@link UnsupportedOperationException}.
* </p>
* <p>
* The constants used to store the values are:
* <ul>
* <li>{@link AbstractInformationControlManager#STORE_LOCATION_X}</li>
* <li>{@link AbstractInformationControlManager#STORE_LOCATION_Y}</li>
* <li>{@link AbstractInformationControlManager#STORE_SIZE_WIDTH}</li>
* <li>{@link AbstractInformationControlManager#STORE_SIZE_HEIGHT}</li>
* </ul>
* </p>
*
* @param dialogSettings the dialog settings
* @param restoreLocation <code>true</code> iff the location is must be (re-)stored
* @param restoreSize <code>true</code>iff the size is (re-)stored
* @since 3.0
*/
public void setRestoreInformationControlBounds(IDialogSettings dialogSettings, boolean restoreLocation, boolean restoreSize) {
Assert.isTrue(dialogSettings != null && (restoreLocation || restoreSize));
fDialogSettings= dialogSettings;
fIsRestoringLocation= restoreLocation;
fIsRestoringSize= restoreSize;
}
/**
* Sets the anchor used for laying out the information control relative to the
* subject control. E.g, using <code>ANCHOR_TOP</code> indicates that the
* information control is position above the area for which the information to
* be displayed is valid.
*
* @param anchor the layout anchor
*/
public void setAnchor(Anchor anchor) {
fAnchor= anchor;
}
/**
* Sets the anchors fallback sequence used to layout the information control if the original
* anchor can not be used because the information control would not fit in the display client
* area.
* <p>
* The fallback anchor for a given anchor is the one that comes directly after the given anchor or
* is the first one in the sequence if the given anchor is the last one in the sequence.
* <p>
* </p>
* Note: This sequence is ignored if the original anchor is not contained in this list.
* </p>
*
* @param fallbackAnchors the array with the anchor fallback sequence
* @see #setAnchor(AbstractInformationControlManager.Anchor)
*/
public void setFallbackAnchors(Anchor[] fallbackAnchors) {
if (fallbackAnchors != null) {
fFallbackAnchors= new Anchor[fallbackAnchors.length];
System.arraycopy(fallbackAnchors, 0, fFallbackAnchors, 0, fallbackAnchors.length);
} else
fFallbackAnchors= null;
}
/**
* Sets the temporary custom control creator, overriding this manager's default information control creator.
*
* @param informationControlCreator the creator, possibly <code>null</code>
* @since 3.0
*/
protected void setCustomInformationControlCreator(IInformationControlCreator informationControlCreator) {
if (informationControlCreator != null && fCustomInformationControlCreator instanceof IInformationControlCreatorExtension) {
IInformationControlCreatorExtension extension= (IInformationControlCreatorExtension) fCustomInformationControlCreator;
if (extension.canReplace(informationControlCreator))
return;
}
fCustomInformationControlCreator= informationControlCreator;
}
/**
* Tells the manager whether it should set the focus to the information control when made visible.
*
* @param takesFocus <code>true</code> if information control should take focus when made visible
*/
public void takesFocusWhenVisible(boolean takesFocus) {
fTakesFocusWhenVisible= takesFocus;
}
/**
* Tells whether the control takes focus when visible.
*
* @return <code>true</code> if the control takes focus when visible, <code>false</code>
* otherwise
* @since 3.7
*/
protected boolean isTakingFocusWhenVisible() {
return fTakesFocusWhenVisible;
}
/**
* Handles the disposal of the subject control. By default, the information control
* is disposed by calling <code>disposeInformationControl</code>. Subclasses may extend
* this method.
*/
protected void handleSubjectControlDisposed() {
disposeInformationControl();
}
/**
* Installs this manager on the given control. The control is now taking the role of
* the subject control. This implementation sets the control also as the information
* control closer's subject control and automatically enables this manager.
*
* @param subjectControl the subject control
*/
public void install(Control subjectControl) {
if (fSubjectControl != null && !fSubjectControl.isDisposed() && fSubjectControlDisposeListener != null)
fSubjectControl.removeDisposeListener(fSubjectControlDisposeListener);
fSubjectControl= subjectControl;
if (fSubjectControl != null)
fSubjectControl.addDisposeListener(getSubjectControlDisposeListener());
if (fInformationControlCloser != null)
fInformationControlCloser.setSubjectControl(subjectControl);
setEnabled(true);
fDisposed= false;
}
/**
* Returns the dispose listener which gets added
* to the subject control.
*
* @return the dispose listener
* @since 3.1
*/
private DisposeListener getSubjectControlDisposeListener() {
if (fSubjectControlDisposeListener == null) {
fSubjectControlDisposeListener= e -> handleSubjectControlDisposed();
}
return fSubjectControlDisposeListener;
}
/**
* Returns the subject control of this manager/information control.
*
* @return the subject control
*/
protected Control getSubjectControl() {
return fSubjectControl;
}
/**
* Returns the actual subject area.
*
* @return the actual subject area
*/
protected Rectangle getSubjectArea() {
return fSubjectArea;
}
/**
* Sets the enable state of this manager.
*
* @param enabled the enable state
* @deprecated visibility will be changed to protected
*/
@Deprecated
public void setEnabled(boolean enabled) {
fEnabled= enabled;
}
/**
* Returns whether this manager is enabled or not.
*
* @return <code>true</code> if this manager is enabled otherwise <code>false</code>
*/
protected boolean isEnabled() {
return fEnabled;
}
/**
* Computes the size constraints of the information control in points based on the
* default font of the given subject control as well as the size constraints in character
* width.
*
* @param subjectControl the subject control
* @param informationControl the information control whose size constraints are computed
* @return the computed size constraints in points
*/
protected Point computeSizeConstraints(Control subjectControl, IInformationControl informationControl) {
if (fSizeConstraints == null) {
if (informationControl instanceof IInformationControlExtension5) {
IInformationControlExtension5 iControl5= (IInformationControlExtension5) informationControl;
fSizeConstraints= iControl5.computeSizeConstraints(fWidthConstraint, fHeightConstraint);
if (fSizeConstraints != null)
return Geometry.copy(fSizeConstraints);
}
if (subjectControl == null)
return null;
GC gc= new GC(subjectControl);
gc.setFont(subjectControl.getFont());
double width= gc.getFontMetrics().getAverageCharacterWidth();
int height = gc.getFontMetrics().getHeight();
gc.dispose();
fSizeConstraints= new Point((int) (fWidthConstraint * width), fHeightConstraint * height);
}
return new Point(fSizeConstraints.x, fSizeConstraints.y);
}
/**
* Computes the size constraints of the information control in points.
*
* @param subjectControl the subject control
* @param subjectArea the subject area
* @param informationControl the information control whose size constraints are computed
* @return the computed size constraints in points
* @since 3.0
*/
protected Point computeSizeConstraints(Control subjectControl, Rectangle subjectArea, IInformationControl informationControl) {
return computeSizeConstraints(subjectControl, informationControl);
}
/**
* Handles the disposal of the information control. By default, the information
* control closer is stopped.
*/
protected void handleInformationControlDisposed() {
storeInformationControlBounds();
if (fInformationControl instanceof IInformationControlExtension5)
fSizeConstraints= null;
fInformationControl= null;
if (fInformationControlCloser != null) {
fInformationControlCloser.setInformationControl(null); //XXX: null is against the spec
fInformationControlCloser.stop();
}
}
/**
* Returns the information control. If the information control has not been created yet,
* it is automatically created.
*
* @return the information control
*/
protected IInformationControl getInformationControl() {
if (fDisposed)
return fInformationControl;
IInformationControlCreator creator= null;
if (fCustomInformationControlCreator == null) {
creator= fInformationControlCreator;
if (fIsCustomInformationControl && fInformationControl != null) {
if (fInformationControl instanceof IInformationControlExtension5)
fSizeConstraints= null;
fInformationControl.dispose();
fInformationControl= null;
}
fIsCustomInformationControl= false;
} else {
creator= fCustomInformationControlCreator;
if (creator instanceof IInformationControlCreatorExtension) {
IInformationControlCreatorExtension extension= (IInformationControlCreatorExtension) creator;
if (fInformationControl != null && extension.canReuse(fInformationControl))
return fInformationControl;
}
if (fInformationControl != null) {
if (fInformationControl instanceof IInformationControlExtension5)
fSizeConstraints= null;
fInformationControl.dispose();
fInformationControl= null;
}
fIsCustomInformationControl= true;
}
if (fInformationControl == null) {
fInformationControl= creator.createInformationControl(fSubjectControl.getShell());
fInformationControl.addDisposeListener(e -> handleInformationControlDisposed());
if (fInformationControlCloser != null)
fInformationControlCloser.setInformationControl(fInformationControl);
}
return fInformationControl;
}
/**
* Computes the display location of the information control. The location is computed
* considering the given subject area, the anchor at the subject area, and the
* size of the information control. This method does not care about whether the information
* control would be completely visible when placed at the result location.
*
* @param subjectArea the subject area
* @param controlSize the size of the information control
* @param anchor the anchor at the subject area
* @return the display location of the information control
*/
protected Point computeLocation(Rectangle subjectArea, Point controlSize, Anchor anchor) {
int xShift= 0;
int yShift= 0;
switch (anchor.getSWTFlag()) {
case SWT.CENTER:
Point subjectControlSize= fSubjectControl.getSize();
Point location= new Point(subjectControlSize.x / 2, subjectControlSize.y / 2);
location.x -= (controlSize.x / 2);
location.y -= (controlSize.y / 2);
return fSubjectControl.toDisplay(location);
case SWT.BOTTOM:
yShift= subjectArea.height + fMarginY;
break;
case SWT.RIGHT:
xShift= fMarginX + subjectArea.width;
break;
case SWT.TOP:
yShift= -controlSize.y - fMarginY;
break;
case SWT.LEFT:
xShift= -controlSize.x - fMarginX;
break;
}
boolean isRTL= fSubjectControl != null && (fSubjectControl.getStyle() & SWT.RIGHT_TO_LEFT) != 0;
if (isRTL)
xShift += controlSize.x;
return fSubjectControl.toDisplay(new Point(subjectArea.x + xShift, subjectArea.y + yShift));
}
/**
* Computes the area available for an information control given an anchor and the subject area
* within <code>bounds</code>.
*
* @param subjectArea the subject area
* @param bounds the bounds
* @param anchor the anchor at the subject area
* @return the area available at the given anchor relative to the subject area, confined to the
* monitor's client area
* @since 3.3
*/
protected Rectangle computeAvailableArea(Rectangle subjectArea, Rectangle bounds, Anchor anchor) {
Rectangle area;
switch (anchor.getSWTFlag()) {
case SWT.CENTER:
area= bounds;
break;
case SWT.BOTTOM:
int y= subjectArea.y + subjectArea.height + fMarginY;
area= new Rectangle(bounds.x, y, bounds.width, bounds.y + bounds.height - y);
break;
case SWT.RIGHT:
int x= subjectArea.x + subjectArea.width + fMarginX;
area= new Rectangle(x, bounds.y, bounds.x + bounds.width - x, bounds.height);
break;
case SWT.TOP:
area= new Rectangle(bounds.x, bounds.y, bounds.width, subjectArea.y - bounds.y - fMarginY);
break;
case SWT.LEFT:
area= new Rectangle(bounds.x, bounds.y, subjectArea.x - bounds.x - fMarginX, bounds.height);
break;
default:
Assert.isLegal(false);
return null;
}
// Don't return negative areas if the subjectArea overlaps with the monitor bounds.
area.intersect(bounds);
return area;
}
/**
* Checks whether a control of the given size at the given location would be completely visible
* in the given display area when laid out by using the given anchor. If not, this method tries
* to shift the control orthogonal to the direction given by the anchor to make it visible. If possible
* it updates the location.<p>
* This method returns <code>true</code> if the potentially updated position results in a
* completely visible control, or <code>false</code> otherwise.
*
*
* @param location the location of the control
* @param size the size of the control
* @param displayArea the display area in which the control should be visible
* @param anchor anchor for lying out the control
* @return <code>true</code>if the updated location is useful
*/
protected boolean updateLocation(Point location, Point size, Rectangle displayArea, Anchor anchor) {
int displayLowerRightX= displayArea.x + displayArea.width;
int displayLowerRightY= displayArea.y + displayArea.height;
int lowerRightX= location.x + size.x;
int lowerRightY= location.y + size.y;
if (ANCHOR_BOTTOM == anchor || ANCHOR_TOP == anchor) {
if (ANCHOR_BOTTOM == anchor) {
if (lowerRightY > displayLowerRightY)
return false;
} else {
if (location.y < displayArea.y)
return false;
}
if (lowerRightX > displayLowerRightX)
location.x= location.x - (lowerRightX - displayLowerRightX);
return (location.x >= displayArea.x && location.y >= displayArea.y);
} else if (ANCHOR_RIGHT == anchor || ANCHOR_LEFT == anchor) {
if (ANCHOR_RIGHT == anchor) {
if (lowerRightX > displayLowerRightX)
return false;
} else {
if (location.x < displayArea.x)
return false;
}
if (lowerRightY > displayLowerRightY)
location.y= location.y - (lowerRightY - displayLowerRightY);
return (location.x >= displayArea.x && location.y >= displayArea.y);
} else if (ANCHOR_GLOBAL == anchor) {
if (lowerRightX > displayLowerRightX)
location.x= location.x - (lowerRightX - displayLowerRightX);
if (lowerRightY > displayLowerRightY)
location.y= location.y - (lowerRightY - displayLowerRightY);
return (location.x >= displayArea.x && location.y >= displayArea.y);
}
return false;
}
/**
* Returns the next fallback anchor as specified by this manager's
* fallback anchor sequence.
* <p>
* The fallback anchor for the given anchor is the one that comes directly after
* the given anchor or is the first one in the sequence if the given anchor is the
* last one in the sequence.
* </p>
* <p>
* Note: It is the callers responsibility to prevent an endless loop i.e. to test
* whether a given anchor has already been used once.
* then
* </p>
*
* @param anchor the current anchor
* @return the next fallback anchor or <code>null</code> if no fallback anchor is available
*/
protected Anchor getNextFallbackAnchor(Anchor anchor) {
if (anchor == null || fFallbackAnchors == null)
return null;
for (int i= 0; i < fFallbackAnchors.length; i++) {
if (fFallbackAnchors[i] == anchor)
return fFallbackAnchors[i + 1 == fFallbackAnchors.length ? 0 : i + 1];
}
return null;
}
/**
* Computes the location of the information control depending on the
* subject area and the size of the information control. This method attempts
* to find a location at which the information control lies completely in the display's
* client area while honoring the manager's default anchor. If this isn't possible using the
* default anchor, the fallback anchors are tried out.
*
* @param subjectArea the information area
* @param controlSize the size of the information control
* @return the computed location of the information control
*/
protected Point computeInformationControlLocation(Rectangle subjectArea, Point controlSize) {
Rectangle subjectAreaDisplayRelative= Geometry.toDisplay(fSubjectControl, subjectArea);
Point upperLeft;
Anchor testAnchor= fAnchor;
Rectangle bestBounds= null;
int bestArea= Integer.MIN_VALUE;
Anchor bestAnchor= null;
do {
upperLeft= computeLocation(subjectArea, controlSize, testAnchor);
Monitor monitor= getClosestMonitor(subjectAreaDisplayRelative, testAnchor);
if (updateLocation(upperLeft, controlSize, monitor.getClientArea(), testAnchor))
return upperLeft;
// compute available area for this anchor and update if better than best
Rectangle available= computeAvailableArea(subjectAreaDisplayRelative, monitor.getClientArea(), testAnchor);
Rectangle proposed= new Rectangle(upperLeft.x, upperLeft.y, controlSize.x, controlSize.y);
available.intersect(proposed);
int area= available.width * available.height;
if (area > bestArea) {
bestArea= area;
bestBounds= available;
bestAnchor= testAnchor;
}
testAnchor= getNextFallbackAnchor(testAnchor);
} while (testAnchor != fAnchor && testAnchor != null);
// no anchor is perfect - select the one with larges area and set the size to not overlap with the subjectArea
if (bestAnchor != ANCHOR_GLOBAL)
Geometry.set(controlSize, Geometry.getSize(bestBounds));
return Geometry.getLocation(bestBounds);
}
/**
* Gets the closest monitor given an anchor and the subject area.
*
* @param area the subject area
* @param anchor the anchor
* @return the monitor closest to the edge of <code>area</code> defined by
* <code>anchor</code>
* @since 3.3
*/
private Monitor getClosestMonitor(Rectangle area, Anchor anchor) {
Point center;
if (ANCHOR_GLOBAL == anchor)
center= Geometry.centerPoint(area);
else
center= Geometry.centerPoint(Geometry.getExtrudedEdge(area, 0, anchor.getSWTFlag()));
return Util.getClosestMonitor(fSubjectControl.getDisplay(), center);
}
/**
* Computes information to be displayed as well as the subject area
* and initiates that this information is presented in the information control.
* This happens only if this controller is enabled.
*/
public void showInformation() {
if (fEnabled)
doShowInformation();
}
/**
* Computes information to be displayed as well as the subject area
* and initiates that this information is presented in the information control.
*/
protected void doShowInformation() {
fSubjectArea= null;
fInformation= null;
computeInformation();
}
/**
* Presents the information in the information control or hides the information
* control if no information should be presented. The information has previously
* been set using <code>setInformation</code>.
* <p>
* This method should only be called from overriding methods or from <code>setInformation</code>.
* </p>
*/
protected void presentInformation() {
boolean hasContents= false;
if (fInformation instanceof String)
hasContents= ((String)fInformation).trim().length() > 0;
else
hasContents= (fInformation != null);
if (fSubjectArea != null && hasContents)
internalShowInformationControl(fSubjectArea, fInformation);
else
hideInformationControl();
}
/**
* Opens the information control with the given information and the specified
* subject area. It also activates the information control closer.
*
* @param subjectArea the information area
* @param information the information
*/
private void internalShowInformationControl(Rectangle subjectArea, Object information) {
if (this instanceof InformationControlReplacer) {
((InformationControlReplacer) this).showInformationControl(subjectArea, information);
return;
}
IInformationControl informationControl= getInformationControl();
if (informationControl != null) {
Point sizeConstraints= computeSizeConstraints(fSubjectControl, fSubjectArea, informationControl);
if (informationControl instanceof IInformationControlExtension3) {
IInformationControlExtension3 iControl3= (IInformationControlExtension3) informationControl;
Rectangle trim= iControl3.computeTrim();
sizeConstraints.x += trim.width;
sizeConstraints.y += trim.height;
}
informationControl.setSizeConstraints(sizeConstraints.x, sizeConstraints.y);
if (informationControl instanceof IInformationControlExtension2)
((IInformationControlExtension2)informationControl).setInput(information);
else
informationControl.setInformation(information.toString());
if (informationControl instanceof IInformationControlExtension) {
IInformationControlExtension extension= (IInformationControlExtension)informationControl;
if (!extension.hasContents())
return;
}
Point size= null;
Point location= null;
Rectangle bounds= restoreInformationControlBounds();
if (bounds != null) {
if (bounds.x > -1 && bounds.y > -1)
location= Geometry.getLocation(bounds);
if (bounds.width > -1 && bounds.height > -1)
size= Geometry.getSize(bounds);
}
if (size == null)
size= informationControl.computeSizeHint();
if (fEnforceAsMinimalSize)
size= Geometry.max(size, sizeConstraints);
if (fEnforceAsMaximalSize)
size= Geometry.min(size, sizeConstraints);
if (location == null)
location= computeInformationControlLocation(subjectArea, size);
Rectangle controlBounds= Geometry.createRectangle(location, size);
cropToClosestMonitor(controlBounds);
location= Geometry.getLocation(controlBounds);
size= Geometry.getSize(controlBounds);
informationControl.setLocation(location);
informationControl.setSize(size.x, size.y);
showInformationControl(subjectArea);
}
}
/**
* Crops the given bounds such that they lie completely on the closest monitor.
*
* @param bounds shell bounds to crop
* @since 3.4
*/
void cropToClosestMonitor(Rectangle bounds) {
Rectangle monitorBounds= Util.getClosestMonitor(fSubjectControl.getDisplay(), Geometry.centerPoint(bounds)).getClientArea();
bounds.intersect(monitorBounds);
}
/**
* Hides the information control and stops the information control closer.
*/
protected void hideInformationControl() {
if (fInformationControl != null) {
storeInformationControlBounds();
fInformationControl.setVisible(false);
if (fInformationControlCloser != null)
fInformationControlCloser.stop();
}
if (canClearDataOnHide()) {
fSubjectArea= null;
fInformation= null; // allow garbage collection of potentially large object
}
}
/**
* Tells whether internal data can be cleared on hide.
*
* @return <code>true</code> if data can be cleared on hide
* @see #hideInformationControl()
* @since 3.6
*/
protected boolean canClearDataOnHide() {
return true;
}
/**
* Shows the information control and starts the information control closer.
* This method may not be called by clients.
*
* @param subjectArea the information area
*/
protected void showInformationControl(Rectangle subjectArea) {
fInformationControl.setVisible(true);
if (fInformationControl == null)
return; // could already be disposed if setVisible(..) runs the display loop
if (fTakesFocusWhenVisible)
fInformationControl.setFocus();
if (fInformationControlCloser != null)
fInformationControlCloser.start(subjectArea);
}
/**
* Replaces this manager's information control as defined by
* the information control replacer.
* <strong>Must only be called when {@link #fInformationControl} instanceof {@link IInformationControlExtension3}!</strong>
*
* @param takeFocus <code>true</code> iff the replacing information control should take focus
*
* @since 3.4
*/
void replaceInformationControl(boolean takeFocus) {
if (fInformationControlReplacer != null && canReplace(fInformationControl)) {
IInformationControlExtension3 iControl3= (IInformationControlExtension3) fInformationControl;
Rectangle b= iControl3.getBounds();
Rectangle t= iControl3.computeTrim();
Rectangle contentBounds= new Rectangle(b.x - t.x, b.y - t.y, b.width - t.width, b.height - t.height);
IInformationControlCreator informationPresenterControlCreator= ((IInformationControlExtension5) fInformationControl).getInformationPresenterControlCreator();
fInformationControlReplacer.replaceInformationControl(informationPresenterControlCreator, contentBounds, fInformation, fSubjectArea, takeFocus);
}
hideInformationControl();
}
/**
* Disposes this manager's information control.
*/
public void disposeInformationControl() {
if (fInformationControl != null) {
fInformationControl.dispose();
handleInformationControlDisposed();
}
}
/**
* Disposes this manager and if necessary all dependent parts such as
* the information control. For symmetry it first disables this manager.
*/
public void dispose() {
if (!fDisposed) {
fDisposed= true;
setEnabled(false);
disposeInformationControl();
if (fInformationControlReplacer != null) {
fInformationControlReplacer.dispose();
fInformationControlReplacer= null;
}
if (fSubjectControl != null && !fSubjectControl.isDisposed() && fSubjectControlDisposeListener != null)
fSubjectControl.removeDisposeListener(fSubjectControlDisposeListener);
fSubjectControl= null;
fSubjectControlDisposeListener= null;
fIsCustomInformationControl= false;
fCustomInformationControlCreator= null;
fInformationControlCreator= null;
fInformationControlCloser= null;
}
}
// ------ control's size handling dialog settings ------
/**
* Stores the information control's bounds.
*
* @since 3.0
*/
protected void storeInformationControlBounds() {
if (fDialogSettings == null || fInformationControl == null || !(fIsRestoringLocation || fIsRestoringSize))
return;
if (!(fInformationControl instanceof IInformationControlExtension3))
throw new UnsupportedOperationException();
boolean controlRestoresSize= ((IInformationControlExtension3)fInformationControl).restoresSize();
boolean controlRestoresLocation= ((IInformationControlExtension3)fInformationControl).restoresLocation();
Rectangle bounds= ((IInformationControlExtension3)fInformationControl).getBounds();
if (bounds == null)
return;
if (fIsRestoringSize && controlRestoresSize) {
fDialogSettings.put(STORE_SIZE_WIDTH, bounds.width);
fDialogSettings.put(STORE_SIZE_HEIGHT, bounds.height);
}
if (fIsRestoringLocation && controlRestoresLocation) {
fDialogSettings.put(STORE_LOCATION_X, bounds.x);
fDialogSettings.put(STORE_LOCATION_Y, bounds.y);
}
}
/**
* Restores the information control's bounds.
*
* @return the stored bounds
* @since 3.0
*/
protected Rectangle restoreInformationControlBounds() {
if (fDialogSettings == null || !(fIsRestoringLocation || fIsRestoringSize))
return null;
if (!(fInformationControl instanceof IInformationControlExtension3))
throw new UnsupportedOperationException();
boolean controlRestoresSize= ((IInformationControlExtension3)fInformationControl).restoresSize();
boolean controlRestoresLocation= ((IInformationControlExtension3)fInformationControl).restoresLocation();
Rectangle bounds= new Rectangle(-1, -1, -1, -1);
if (fIsRestoringSize && controlRestoresSize) {
try {
bounds.width= fDialogSettings.getInt(STORE_SIZE_WIDTH);
bounds.height= fDialogSettings.getInt(STORE_SIZE_HEIGHT);
} catch (NumberFormatException ex) {
bounds.width= -1;
bounds.height= -1;
}
}
if (fIsRestoringLocation && controlRestoresLocation) {
try {
bounds.x= fDialogSettings.getInt(STORE_LOCATION_X);
bounds.y= fDialogSettings.getInt(STORE_LOCATION_Y);
} catch (NumberFormatException ex) {
bounds.x= -1;
bounds.y= -1;
}
}
// sanity check
if (bounds.x == -1 && bounds.y == -1 && bounds.width == -1 && bounds.height == -1)
return null;
Rectangle maxBounds= null;
if (fSubjectControl != null && !fSubjectControl.isDisposed())
maxBounds= fSubjectControl.getDisplay().getBounds();
else {
// fallback
Display display= Display.getCurrent();
if (display == null)
display= Display.getDefault();
if (display != null && !display.isDisposed())
maxBounds= display.getBounds();
}
if (bounds.width > -1 && bounds.height > -1) {
if (maxBounds != null) {
bounds.width= Math.min(bounds.width, maxBounds.width);
bounds.height= Math.min(bounds.height, maxBounds.height);
}
// Enforce an absolute minimal size
bounds.width= Math.max(bounds.width, 30);
bounds.height= Math.max(bounds.height, 30);
}
if (bounds.x > -1 && bounds.y > -1 && maxBounds != null) {
bounds.x= Math.max(bounds.x, maxBounds.x);
bounds.y= Math.max(bounds.y, maxBounds.y);
if (bounds .width > -1 && bounds.height > -1) {
bounds.x= Math.min(bounds.x, maxBounds.width - bounds.width);
bounds.y= Math.min(bounds.y, maxBounds.height - bounds.height);
}
}
return bounds;
}
/**
* Returns an adapter that gives access to internal methods.
* <p>
* <strong>Note:</strong> This method is not intended to be referenced or overridden by clients.</p>
*
* @return the replaceable information control accessor
* @since 3.4
* @noreference This method is not intended to be referenced by clients.
* @nooverride This method is not intended to be re-implemented or extended by clients.
*/
public InternalAccessor getInternalAccessor() {
return new MyInternalAccessor();
}
}