Package org.eclipse.jface.text

Source Code of org.eclipse.jface.text.AbstractInformationControlManager

/*******************************************************************************
* Copyright (c) 2000, 2008 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
*     IBM Corporation - initial API and implementation
*     Sean Montgomery, sean_montgomery@comcast.net - https://bugs.eclipse.org/bugs/show_bug.cgi?id=45095
*******************************************************************************/

package org.eclipse.jface.text;


import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.Platform;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
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.jface.dialogs.IDialogSettings;
import org.eclipse.jface.internal.text.InformationControlReplacer;
import org.eclipse.jface.internal.text.InternalAccessor;
import org.eclipse.jface.text.ITextViewerExtension8.EnrichMode;
import org.eclipse.jface.util.Geometry;


/**
* 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 {
    public IInformationControl getCurrentInformationControl() {
      return AbstractInformationControlManager.this.getCurrentInformationControl();
    }
   
    public void setInformationControlReplacer(InformationControlReplacer replacer) {
      AbstractInformationControlManager.this.setInformationControlReplacer(replacer);
    }
   
    public InformationControlReplacer getInformationControlReplacer() {
      return AbstractInformationControlManager.this.getInformationControlReplacer();
    }
   
    public boolean canReplace(IInformationControl control) {
      return AbstractInformationControlManager.this.canReplace(control);
    }
   
    public boolean isReplaceInProgress() {
      return AbstractInformationControlManager.this.isReplaceInProgress();
    }
   
    public void replaceInformationControl(boolean takeFocus) {
      AbstractInformationControlManager.this.replaceInformationControl(takeFocus);
    }

    public void cropToClosestMonitor(Rectangle bounds) {
      AbstractInformationControlManager.this.cropToClosestMonitor(bounds);
    }
   
    public void setHoverEnrichMode(EnrichMode mode) {
      throw new UnsupportedOperationException("only implemented in AbstractHoverInformationControlManager"); //$NON-NLS-1$
    }
   
    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;
    }
   
    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
   * @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;
  }

  /**
   * 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= new DisposeListener() {
        public void widgetDisposed(DisposeEvent 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
   */
  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());
      int width= gc.getFontMetrics().getAverageCharWidth();
      int height = gc.getFontMetrics().getHeight();
      gc.dispose();

      fSizeConstraints= new Point (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(new DisposeListener() {
        public void widgetDisposed(DisposeEvent 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 getClosestMonitor(fSubjectControl.getDisplay(), Geometry.createRectangle(center, new Point(0, 0)));
  }

  /**
   * Copied from org.eclipse.jface.window.Window. Returns the monitor whose client area contains
   * the given point. If no monitor contains the point, returns the monitor that is closest to the
   * point. If this is ever made public, it should be moved into a separate utility class.
   *
   * @param display the display to search for monitors
   * @param rectangle the rectangle to find the closest monitor for (display coordinates)
   * @return the monitor closest to the given point
   * @since 3.3
   */
  private Monitor getClosestMonitor(Display display, Rectangle rectangle) {
    int closest = Integer.MAX_VALUE;

    Point toFind= Geometry.centerPoint(rectangle);
    Monitor[] monitors = display.getMonitors();
    Monitor result = monitors[0];

    for (int idx = 0; idx < monitors.length; idx++) {
      Monitor current = monitors[idx];

      Rectangle clientArea = current.getClientArea();

      if (clientArea.contains(toFind)) {
        return current;
      }

      int distance = Geometry.distanceSquared(Geometry.centerPoint(clientArea), toFind);
      if (distance < closest) {
        closest = distance;
        result = current;
      }
    }

    return result;
  }

  /**
   * 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>.
   */
  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= getClosestMonitor(fSubjectControl.getDisplay(), 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();
    }
  }

  /**
   * 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 (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();
  }
}
TOP

Related Classes of org.eclipse.jface.text.AbstractInformationControlManager

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.