diff options
author | Kai Maetzel | 2002-09-24 16:39:07 +0000 |
---|---|---|
committer | Kai Maetzel | 2002-09-24 16:39:07 +0000 |
commit | db4ac57b04d3091f1e46fa274fe527444c03a1a0 (patch) | |
tree | 5818e06543de8325238c2bb0634e2f79270c9b7f | |
parent | 7a653a7e825daf117f57a8457d1d4a2c6648c992 (diff) | |
download | eclipse.platform.text-db4ac57b04d3091f1e46fa274fe527444c03a1a0.tar.gz eclipse.platform.text-db4ac57b04d3091f1e46fa274fe527444c03a1a0.tar.xz eclipse.platform.text-db4ac57b04d3091f1e46fa274fe527444c03a1a0.zip |
First cut of org.eclipse.ui split
213 files changed, 32687 insertions, 0 deletions
diff --git a/org.eclipse.jface.text/.classpath b/org.eclipse.jface.text/.classpath new file mode 100644 index 000000000..a72fd369d --- /dev/null +++ b/org.eclipse.jface.text/.classpath @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="UTF-8"?> +<classpath> + <classpathentry kind="src" path="src"/> + <classpathentry kind="src" path="/org.eclipse.text"/> + <classpathentry kind="src" path="/org.eclipse.jface"/> + <classpathentry kind="var" path="JRE_LIB" rootpath="JRE_SRCROOT" sourcepath="JRE_SRC"/> + <classpathentry kind="src" path="/org.eclipse.core.runtime"/> + <classpathentry kind="src" path="/org.eclipse.swt"/> + <classpathentry kind="output" path="bin"/> +</classpath> diff --git a/org.eclipse.jface.text/.cvsignore b/org.eclipse.jface.text/.cvsignore new file mode 100644 index 000000000..ba077a403 --- /dev/null +++ b/org.eclipse.jface.text/.cvsignore @@ -0,0 +1 @@ +bin diff --git a/org.eclipse.jface.text/.project b/org.eclipse.jface.text/.project new file mode 100644 index 000000000..85dd82450 --- /dev/null +++ b/org.eclipse.jface.text/.project @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<projectDescription> + <name>org.eclipse.jface.text</name> + <comment></comment> + <projects> + <project>org.eclipse.core.runtime</project> + <project>org.eclipse.jface</project> + <project>org.eclipse.swt</project> + <project>org.eclipse.text</project> + </projects> + <buildSpec> + <buildCommand> + <name>org.eclipse.jdt.core.javabuilder</name> + <arguments> + </arguments> + </buildCommand> + </buildSpec> + <natures> + <nature>org.eclipse.jdt.core.javanature</nature> + </natures> +</projectDescription> diff --git a/org.eclipse.jface.text/build.properties b/org.eclipse.jface.text/build.properties new file mode 100644 index 000000000..4518f3a09 --- /dev/null +++ b/org.eclipse.jface.text/build.properties @@ -0,0 +1,4 @@ +source.jfacetext.jar = src/ +bin.includes = fragment.properties,\ + fragment.xml,\ + *.jar diff --git a/org.eclipse.jface.text/fragment.properties b/org.eclipse.jface.text/fragment.properties new file mode 100644 index 000000000..044b21535 --- /dev/null +++ b/org.eclipse.jface.text/fragment.properties @@ -0,0 +1,2 @@ +fragmentName = JFace Text +providerName = Eclipse.org diff --git a/org.eclipse.jface.text/fragment.xml b/org.eclipse.jface.text/fragment.xml new file mode 100644 index 000000000..d41ba8065 --- /dev/null +++ b/org.eclipse.jface.text/fragment.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<fragment + id="org.eclipse.jface.text" + name="%fragmentName" + version="2.1.0" + provider-name="%providerName" + plugin-id="org.eclipse.jface" + plugin-version="2.1.0"> + + <runtime> + <library name="jfacetext.jar"> + <export name="*"/> + </library> + </runtime> + + <requires> + <import plugin="org.eclipse.text" export="true"/> + </requires> + + +</fragment> diff --git a/org.eclipse.jface.text/scripts/exportplugin.xml b/org.eclipse.jface.text/scripts/exportplugin.xml new file mode 100644 index 000000000..ab3d52a4f --- /dev/null +++ b/org.eclipse.jface.text/scripts/exportplugin.xml @@ -0,0 +1,28 @@ +<project name="Export JFace Text" default="export" basedir=".."> + <target name="init"> + <tstamp/> + <property name="destdir" value="../../plugin-export" /> + <property name="plugin" value="org.eclipse.jface.text" /> + <property name="version" value="_2.1.0" /> + <property name="dest" value="${destdir}/${plugin}${version}" /> + </target> + + <target name="build" depends="init"> + <eclipse.incrementalBuild project="${plugin}" kind="incr"/> + </target> + + <target name="export" depends="build"> + <mkdir dir="${destdir}" /> + <delete dir="${dest}" /> + <mkdir dir="${dest}" /> + <jar + jarfile="${dest}/jfacetext.jar" + basedir="bin" + /> + <copy file="fragment.xml" todir="${dest}"/> + <copy file="fragment.properties" todir="${dest}"/> + <zip zipfile="${dest}/jfacetextsrc.zip"> + <fileset dir="src" /> + </zip> + </target> +</project> diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/AbstractHoverInformationControlManager.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/AbstractHoverInformationControlManager.java new file mode 100644 index 000000000..48cb94064 --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/AbstractHoverInformationControlManager.java @@ -0,0 +1,464 @@ +/********************************************************************** +Copyright (c) 2000, 2002 IBM Corp. and others. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Common Public License v1.0 +which accompanies this distribution, and is available at +http://www.eclipse.org/legal/cpl-v10.html + +Contributors: + IBM Corporation - Initial implementation +**********************************************************************/ + +package org.eclipse.jface.text; + + +import org.eclipse.swt.events.ControlEvent; +import org.eclipse.swt.events.ControlListener; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.KeyListener; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.MouseListener; +import org.eclipse.swt.events.MouseMoveListener; +import org.eclipse.swt.events.MouseTrackAdapter; +import org.eclipse.swt.events.MouseTrackListener; +import org.eclipse.swt.events.ShellAdapter; +import org.eclipse.swt.events.ShellEvent; +import org.eclipse.swt.events.ShellListener; +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.jface.util.Assert; + + +/** + * An information control manager that shows information on mouse hover events. + * The mouse hover events are caught by registering a <code>MouseTrackListener</code> + * on the manager's subject control. The manager has by default an information control closer + * that closes the information control as soon as the mouse pointer leaves the subject area, the + * user presses a key, or the subject control is resized, moved, or deactivated.<p> + * When being activated by a mouse hover event, the manager disables itself, until the mouse + * leaves the subject area. Thus, the manager is usually still disabled, when the information control + * has already been closed by the closer. + * + * @see org.eclipse.swt.events.MouseTrackListener + * @since 2.0 + */ +abstract public class AbstractHoverInformationControlManager extends AbstractInformationControlManager { + + + /** + * The information control closer for the hover information. Closes the information control as + * soon as the mouse pointer leaves the subject area, the user presses a key, or the subject + * control is resized or moved. + */ + class Closer extends MouseTrackAdapter + implements IInformationControlCloser, MouseListener, MouseMoveListener, ControlListener, KeyListener { + + /** The closer's subject control */ + private Control fSubjectControl; + /** The closer's information control */ + private IInformationControl fInformationControl; + /** The subject area */ + private Rectangle fSubjectArea; + /** Indicates whether this closer is active */ + private boolean fIsActive= false; + + /** + * Creates a new information control closer. + */ + public Closer() { + } + + /* + * @see IInformationControlCloser#setSubjectControl(Control) + */ + public void setSubjectControl(Control control) { + fSubjectControl= control; + } + + /* + * @see IInformationControlCloser#setHoverControl(IHoverControl) + */ + public void setInformationControl(IInformationControl control) { + fInformationControl= control; + } + + /* + * @see IInformationControlCloser#start(Rectangle) + */ + public void start(Rectangle subjectArea) { + + if (fIsActive) + return; + fIsActive= true; + + fSubjectArea= subjectArea; + + setEnabled(false); + + if (fSubjectControl != null && !fSubjectControl.isDisposed()) { + fSubjectControl.addMouseListener(this); + fSubjectControl.addMouseMoveListener(this); + fSubjectControl.addMouseTrackListener(this); + fSubjectControl.addControlListener(this); + fSubjectControl.addKeyListener(this); + } + } + + /* + * @see IInformationControlCloser#stop() + */ + public void stop() { + stop(false); + } + + /** + * Stops the information control and if <code>delayRestart</code> is set + * allows restart only after a certain delay. + * + * @param delayRestart <code>true</code> if restart should be delayed + */ + protected void stop(boolean delayRestart) { + + if (!fIsActive) + return; + fIsActive= false; + + hideInformationControl(); + + if (fSubjectControl != null && !fSubjectControl.isDisposed()) { + fSubjectControl.removeMouseListener(this); + fSubjectControl.removeMouseMoveListener(this); + fSubjectControl.removeMouseTrackListener(this); + fSubjectControl.removeControlListener(this); + fSubjectControl.removeKeyListener(this); + } + } + + /* + * @see MouseMoveListener#mouseMove + */ + public void mouseMove(MouseEvent event) { + if (!fSubjectArea.contains(event.x, event.y)) + stop(); + } + + /* + * @see MouseListener#mouseUp(MouseEvent) + */ + public void mouseUp(MouseEvent event) { + } + + /* + * @see MouseListener#mouseDown(MouseEvent) + */ + public void mouseDown(MouseEvent event) { + stop(); + } + + /* + * @see MouseListener#mouseDoubleClick(MouseEvent) + */ + public void mouseDoubleClick(MouseEvent event) { + stop(); + } + + /* + * @see MouseTrackAdapter#mouseExit(MouseEvent) + */ + public void mouseExit(MouseEvent event) { + stop(); + } + + /* + * @see ControlListener#controlResized(ControlEvent) + */ + public void controlResized(ControlEvent event) { + stop(); + } + + /* + * @see ControlListener#controlMoved(ControlEvent) + */ + public void controlMoved(ControlEvent event) { + stop(); + } + + /* + * @see KeyListener#keyReleased(KeyEvent) + */ + public void keyReleased(KeyEvent event) { + } + + /* + * @see KeyListener#keyPressed(KeyEvent) + */ + public void keyPressed(KeyEvent event) { + stop(true); + } + }; + + + /** + * To be installed on the manager's subject control. Serves two different purposes: + * <ul> + * <li> start function: initiates the computation of the information to be presented. This happens on + * receipt of a mouse hover event and disables the information control manager, + * <li> restart function: tracks mouse move and shell activation event to determine when the information + * control manager needs to be reactivated. + * </ul> + */ + class MouseTracker extends ShellAdapter implements MouseTrackListener, MouseMoveListener { + + // http://bugs.eclipse.org/bugs/show_bug.cgi?id=18393 + // http://bugs.eclipse.org/bugs/show_bug.cgi?id=19686 + // http://bugs.eclipse.org/bugs/show_bug.cgi?id=19719 + + /** Margin around the original hover event location for coputing the hover area. */ + private final static int EPSILON= 3; + + /** The area in which the original hover event occurred. */ + private Rectangle fHoverArea; + /** The area for which is computed information is valid. */ + private Rectangle fSubjectArea; + /** The tracker's subject control. */ + private Control fSubjectControl; + + /** Indicates whether the tracker is computing the start function. */ + private boolean fIsActive= false; + /** Indicates whether the mouse has been lost. */ + private boolean fMouseLost= false; + /** Indicates whether the subject control's shelll has been deactivated. */ + private boolean fShellDeactivated= false; + + /** + * Creates a new mouse tracker. + */ + public MouseTracker() { + } + + /** + * Sets this mouse tracker's subject area, the area to be tracked in order + * to reenable the information control manager. + * + * @param subjectArea the subject area + */ + public void setSubjectArea(Rectangle subjectArea) { + Assert.isNotNull(subjectArea); + fSubjectArea= subjectArea; + } + + /** + * Starts this mouse tracker. The given control becomes this tracker's subject control. + * Installs itself as mouse track listener on the subject control. + * + * @param subjectControl the subject control + */ + public void start(Control subjectControl) { + fSubjectControl= subjectControl; + if (fSubjectControl != null && !fSubjectControl.isDisposed()) + fSubjectControl.addMouseTrackListener(this); + + fIsActive= false; + fMouseLost= false; + fShellDeactivated= false; + } + + /** + * Stops this mouse tracker. Removes itself as mouse track, mouse move, and + * shell listener from the subject control. + */ + public void stop() { + if (fSubjectControl != null && !fSubjectControl.isDisposed()) { + fSubjectControl.removeMouseTrackListener(this); + fSubjectControl.removeMouseMoveListener(this); + fSubjectControl.getShell().removeShellListener(this); + } + + fMouseLost= false; + fShellDeactivated= false; + } + + /** + * Initiates the computation of the information to be presented. Sets the initial hover area + * to a small rectangle around the hover event location. Adds mouse move and shell activation listeners + * to track whether the computed information is, after completion, useful for presentation and to + * implement the restart function. + */ + public void mouseHover(MouseEvent event) { + + if (fIsActive) + return; + + fIsActive= true; + fMouseLost= false; + fShellDeactivated= false; + + setEnabled(false); + + fHoverEventLocation= new Point(event.x, event.y); + fHoverArea= new Rectangle(event.x - EPSILON, event.y - EPSILON, 2 * EPSILON, 2 * EPSILON ); + if (fHoverArea.x < 0) fHoverArea.x= 0; + if (fHoverArea.y < 0) fHoverArea.y= 0; + setSubjectArea(fHoverArea); + + if (fSubjectControl != null && !fSubjectControl.isDisposed()) { + fSubjectControl.addMouseMoveListener(this); + fSubjectControl.getShell().addShellListener(this); + } + + doShowInformation(); + } + + /** + * Deactivates this tracker's restart function and enables the information control + * manager. Does not have any effect if the tracker is still executing the start function (i.e. + * computing the information to be presented. + */ + protected void deactivate() { + if (fIsActive) + return; + + if (fSubjectControl != null && !fSubjectControl.isDisposed()) { + fSubjectControl.removeMouseMoveListener(this); + fSubjectControl.getShell().removeShellListener(this); + } + + setEnabled(true); + } + + /* + * @see MouseTrackListener#mouseEnter(MouseEvent) + */ + public void mouseEnter(MouseEvent e) { + } + + /* + * @see MouseTrackListener#mouseExit(MouseEvent) + */ + public void mouseExit(MouseEvent e) { + fMouseLost= true; + deactivate(); + } + + /* + * @see MouseMoveListener#mouseMove(MouseEvent) + */ + public void mouseMove(MouseEvent event) { + if (!fSubjectArea.contains(event.x, event.y)) + deactivate(); + } + + /* + * @see ShellListener#shellDeactivated(ShellEvent) + */ + public void shellDeactivated(ShellEvent e) { + fShellDeactivated= true; + deactivate(); + } + + /* + * @see ShellListener#shellIconified(ShellEvent) + */ + public void shellIconified(ShellEvent e) { + fShellDeactivated= true; + deactivate(); + } + + /** + * Tells this tracker that the start function processing has been completed. + */ + public void computationCompleted() { + fIsActive= false; + fMouseLost= false; + fShellDeactivated= false; + } + + /** + * Determines whether the computed information is still useful for presentation. + * This is the case, if the shell of the subject control has been deactivated, the mouse + * left the subject control, or the mouse moved on, so that it is no longer in the subject + * area. + * + * @return <code>true</code> if information is still useful for presentation, <code>false</code> otherwise + */ + public boolean isMouseLost() { + + if (fMouseLost || fShellDeactivated) + return true; + + if (fSubjectControl != null && !fSubjectControl.isDisposed()) { + Display display= fSubjectControl.getDisplay(); + Point p= display.getCursorLocation(); + p= fSubjectControl.toControl(p); + if (!fSubjectArea.contains(p) && !fHoverArea.contains(p)) + return true; + } + + return false; + } + }; + + /** The mouse tracker on the subject control */ + private MouseTracker fMouseTracker= new MouseTracker(); + /** The remembered hover event location */ + private Point fHoverEventLocation= new Point(-1, -1); + + /** + * Creates a new hover information control manager using the given information control creator. + * By default a <code>Closer</code> instance is set as this manager's closer. + * + * @param creator the information control creator + */ + protected AbstractHoverInformationControlManager(IInformationControlCreator creator) { + super(creator); + setCloser(new Closer()); + } + + /* + * @see AbstractInformationControlManager#presentInformation() + */ + protected void presentInformation() { + + Rectangle area= getSubjectArea(); + if (area != null) + fMouseTracker.setSubjectArea(area); + + if (fMouseTracker.isMouseLost()) { + fMouseTracker.computationCompleted(); + fMouseTracker.deactivate(); + } else { + fMouseTracker.computationCompleted(); + super.presentInformation(); + } + } + + /* + * @see AbstractInformationControlManager#setEnabled(boolean) + */ + public void setEnabled(boolean enabled) { + + boolean was= isEnabled(); + super.setEnabled(enabled); + boolean is= isEnabled(); + + if (was != is) { + if (is) + fMouseTracker.start(getSubjectControl()); + else + fMouseTracker.stop(); + } + } + + /** + * Returns the location at which the most recent mouse hover event + * has been issued. + * + * @return the location of the most recent mouse hover event + */ + protected Point getHoverEventLocation() { + return fHoverEventLocation; + } +}
\ No newline at end of file diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/AbstractInformationControlManager.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/AbstractInformationControlManager.java new file mode 100644 index 000000000..d9d8c54b8 --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/AbstractInformationControlManager.java @@ -0,0 +1,680 @@ +/********************************************************************** +Copyright (c) 2000, 2002 IBM Corp. and others. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Common Public License v1.0 +which accompanies this distribution, and is available at +http://www.eclipse.org/legal/cpl-v10.html + +Contributors: + IBM Corporation - Initial implementation +**********************************************************************/ + +package org.eclipse.jface.text; + + +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.jface.util.Assert; + + +/** + * Manages the life cycle, visibility, layout, and contents of an <code>IInformationControl</code>. + * This manager can be installed on and uninstalled from a control, refered 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. + * + * @since 2.0 + */ +abstract public class AbstractInformationControlManager { + + /** + * Interface of a 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 equipe an information + * control manager accordingly. + */ + public static 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(); + }; + + + + /** + * Constitues entities to enumerate anchors for the layout of the information control. + */ + public static final class Anchor { + private Anchor() { + }; + }; + + /** Internal anchor list. */ + private final static Anchor[] ANCHORS= { new Anchor(), new Anchor(), new Anchor(), new Anchor() }; + + /** 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]; + + + + /** 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 String fInformation; + + /** Indicates whether the information control takes focus when visible */ + private boolean fTakesFocusWhenVisible= false; + + /** The information control */ + private IInformationControl fInformationControl; + + /** The information control creator */ + private IInformationControlCreator fInformationControlCreator; + + /** The information control closer */ + private IInformationControlCloser fInformationControlCloser; + + /** Indicates that the information control has been disposed */ + private boolean fDisposed= false; + + /** 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 y margin when laying out the information control */ + private int fMarginY= 5; + + /** The x margin when laying out the information control */ + private int fMarginX= 5; + + /** The width contraint of the information control in characters */ + private int fWidthConstraint= 60; + + /** The height constraint of the information control in characters */ + private int fHeightConstraint= 6; + + /** Indicates wether 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; + + /** + * A list of anchors 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. + */ + private Anchor[] fFallbackAnchors= ANCHORS; + + + /** + * Creates a new information control manager using the given information control creator. + * By default the following configuration is given: + * <ul> + * <li> enabled == false + * <li> x-margin == 5 points + * <li> y-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> fallback anchors == { ANCHOR_TOP, ANCHOR_BOTTOM, ANCHOR_LEFT, ANCHOR_RIGHT } + * <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 + * @param subjectArea the subject area + */ + protected final void setInformation(String 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 x- and y- 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; + } + + /** + * 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 sequence of anchors along which the information control is tried to + * be laid out until it is fully visible. This fallback is initiated when the information + * control does not fit into the client area of the subject control's display. + * + * @param fallbackAnchors the list of anchors to be tried + */ + public void setFallbackAnchors(Anchor[] fallbackAnchors) { + fFallbackAnchors= fallbackAnchors; + } + + /** + * 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) { + fSubjectControl= subjectControl; + + if (fSubjectControl != null) { + fSubjectControl.addDisposeListener(new DisposeListener() { + public void widgetDisposed(DisposeEvent e) { + handleSubjectControlDisposed(); + } + }); + } + + if (fInformationControlCloser != null) + fInformationControlCloser.setSubjectControl(subjectControl); + + setEnabled(true); + } + + /** + * 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 (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 fSizeConstraints; + } + + /** + * Handles the disposal of the information control. By default, the information + * control closer is stopped. + */ + protected void handleInformationControlDisposed() { + fInformationControl= null; + if (fInformationControlCloser != null) { + fInformationControlCloser.setInformationControl(null); + 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 (fInformationControl == null && !fDisposed) { + + fInformationControl= fInformationControlCreator.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 + */ + protected Point computeLocation(Rectangle subjectArea, Point controlSize, Anchor anchor) { + + int xShift= 0; + int yShift= 0; + + if (ANCHOR_BOTTOM == anchor) { + xShift= fMarginX; + yShift= subjectArea.height + fMarginY; + } else if (ANCHOR_RIGHT == anchor) { + xShift= fMarginX + subjectArea.width; + yShift= fMarginY; + } else if (ANCHOR_TOP == anchor) { + xShift= fMarginX; + yShift= -controlSize.y - fMarginY; + } else if (ANCHOR_LEFT == anchor) { + xShift= -controlSize.x - fMarginX; + yShift= fMarginY; + } + + return fSubjectControl.toDisplay(new Point(subjectArea.x + xShift, subjectArea.y + yShift)); + } + + /** + * 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 alying 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 true; + + } 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 true; + } + + return false; + } + + /** + * Returns the next fallback anchor from this manager's list of fallback anchors. + * If no more fallback anchor is available <code>null</code> is returned. + * + * @param anchor the current anchor + * @return the next fallback anchor or <code>null</code> if no more 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 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 displayBounds= fSubjectControl.getDisplay().getClientArea(); + + Point upperLeft; + Anchor testAnchor= fAnchor; + do { + + upperLeft= computeLocation(subjectArea, controlSize, testAnchor); + if (updateLocation(upperLeft, controlSize, displayBounds, testAnchor)) + break; + testAnchor= getNextFallbackAnchor(testAnchor); + + } while (testAnchor != fAnchor && testAnchor != null); + + return upperLeft; + } + + /** + * 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() { + if (fSubjectArea != null && fInformation != null && fInformation.trim().length() > 0) + 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, String information) { + + IInformationControl hoverControl= getInformationControl(); + if (hoverControl != null) { + + Point sizeConstraints= computeSizeConstraints(fSubjectControl, hoverControl); + hoverControl.setSizeConstraints(sizeConstraints.x, sizeConstraints.y); + hoverControl.setInformation(information); + + if (hoverControl instanceof IInformationControlExtension) { + IInformationControlExtension extension= (IInformationControlExtension) hoverControl; + if (!extension.hasContents()) + return; + } + + Point size= hoverControl.computeSizeHint(); + + if (fEnforceAsMinimalSize) { + if (size.x < sizeConstraints.x) + size.x= sizeConstraints.x; + if (size.y < sizeConstraints.y) + size.y= sizeConstraints.y; + } + + if (fEnforceAsMaximalSize) { + if (size.x > sizeConstraints.x) + size.x= sizeConstraints.x; + if (size.y > sizeConstraints.y) + size.y= sizeConstraints.y; + } + + hoverControl.setSize(size.x, size.y); + + Point location= computeInformationControlLocation(subjectArea, size); + hoverControl.setLocation(location); + + showInformationControl(subjectArea); + } + } + + /** + * Hides the information control and stops the information control closer. + */ + protected void hideInformationControl() { + if (fInformationControl != null) { + 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); + } + + /** + * 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(); + + fInformationControlCreator= null; + fInformationControlCloser= null; + } + } +}
\ No newline at end of file diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/DefaultAutoIndentStrategy.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/DefaultAutoIndentStrategy.java new file mode 100644 index 000000000..7b63ae3d6 --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/DefaultAutoIndentStrategy.java @@ -0,0 +1,85 @@ +package org.eclipse.jface.text; + +/* + * (c) Copyright IBM Corp. 2000, 2001. + * All Rights Reserved. + */ + + + +/** + * Default implementation of <code>IAutoIndentStrategy</code>. + * This strategy always copies the indentation of the previous line. + */ +public class DefaultAutoIndentStrategy implements IAutoIndentStrategy { + + /** + * Creates a new default auto indent strategy which can be installed on + * text viewers. + */ + public DefaultAutoIndentStrategy() { + } + + /** + * Returns the first offset greater than <code>offset</code> and smaller than + * <code>end</code> whose character is not a space or tab character. If no such + * offset is found, <code>end</code> is returned. + * + * @param document the document to search in + * @param offset the offset at which searching start + * @param end the offset at which searching stops + * @return the offset in the specifed range whose character is not a space or tab + * @exception BadLocationException if position is an invalid range in the given document + */ + protected int findEndOfWhiteSpace(IDocument document, int offset, int end) throws BadLocationException { + while (offset < end) { + char c= document.getChar(offset); + if (c != ' ' && c != '\t') { + return offset; + } + offset++; + } + return end; + } + + /** + * Copies the indentation of the previous line. + * + * @param d the document to work on + * @param c the command to deal with + */ + private void autoIndentAfterNewLine(IDocument d, DocumentCommand c) { + + if (c.offset == -1 || d.getLength() == 0) + return; + + try { + // find start of line + int p= (c.offset == d.getLength() ? c.offset - 1 : c.offset); + IRegion info= d.getLineInformationOfOffset(p); + int start= info.getOffset(); + + // find white spaces + int end= findEndOfWhiteSpace(d, start, c.offset); + + StringBuffer buf= new StringBuffer(c.text); + if (end > start) { + // append to input + buf.append(d.get(start, end - start)); + } + + c.text= buf.toString(); + + } catch (BadLocationException excp) { + // stop work + } + } + + /* + * @see IAutoIndentStrategy#customizeDocumentCommand + */ + public void customizeDocumentCommand(IDocument d, DocumentCommand c) { + if (c.length == 0 && c.text != null && TextUtilities.endsWith(d.getLegalLineDelimiters(), c.text) != -1) + autoIndentAfterNewLine(d, c); + } +}
\ No newline at end of file diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/DefaultInformationControl.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/DefaultInformationControl.java new file mode 100644 index 000000000..e1d2294fb --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/DefaultInformationControl.java @@ -0,0 +1,284 @@ +/********************************************************************** +Copyright (c) 2000, 2002 IBM Corp. and others. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Common Public License v1.0 +which accompanies this distribution, and is available at +http://www.eclipse.org/legal/cpl-v10.html + +Contributors: + IBM Corporation - Initial implementation +**********************************************************************/ + +package org.eclipse.jface.text; + + +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.StyledText; +import org.eclipse.swt.events.DisposeListener; +import org.eclipse.swt.events.FocusListener; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.KeyListener; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; + + + +/** + * Text based implementation of <code>IInformationControl</code>. + * Displays information in a styled text widget. Before displaying, the + * information set to this information control is processed by an + * <code>IInformationPresenter</code>. + * + * @since 2.0 + */ +public class DefaultInformationControl implements IInformationControl, IInformationControlExtension { + + /** + * An information presenter determines the style presentation + * of information displayed in the default information control. + * The interface can be implemented by clients. + */ + public static interface IInformationPresenter { + + /** + * Updates the given presentation of the given information and + * thereby may manipulate the information to be displayed. The manipulation + * could be the extraction of textual encoded style information etc. Returns the + * manipulated information. + * + * @param display the display of the information control + * @param hoverInfo the information to be presented + * @param presentation the presentation to be updated + * @param maxWidth the maximal width in pixels + * @param maxHeight the maximal height in pixels + * + * @return the manipulated information + */ + String updatePresentation(Display display, String hoverInfo, TextPresentation presentation, int maxWidth, int maxHeight); + }; + + + /** Border thickness in pixels. */ + private static final int BORDER= 1; + /** Right margin in pixels. */ + private static final int RIGHT_MARGIN= 3; + + /** The control's shell */ + private Shell fShell; + /** The control's text widget */ + private StyledText fText; + /** The information presenter */ + private IInformationPresenter fPresenter; + /** A cached text presentation */ + private TextPresentation fPresentation= new TextPresentation(); + /** The control width constraint */ + private int fMaxWidth= -1; + /** The control height constraint */ + private int fMaxHeight= -1; + + + + /** + * Creates a default information control with the given shell as parent. The given + * information presenter is used to process the information to be displayed. The given + * styles are applied to the created styled text widget. + * + * @param parent the parent shell + * @param presenter the presenter to be used + * @param style the additional styles for the styled text widget + */ + public DefaultInformationControl(Shell parent, int style, IInformationPresenter presenter) { + + fShell= new Shell(parent, SWT.NO_FOCUS | SWT.NO_TRIM | SWT.ON_TOP); + fText= new StyledText(fShell, SWT.MULTI | SWT.READ_ONLY | style); + + Display display= fShell.getDisplay(); + + fShell.setBackground(display.getSystemColor(SWT.COLOR_BLACK)); + + fText.setForeground(display.getSystemColor(SWT.COLOR_INFO_FOREGROUND)); + fText.setBackground(display.getSystemColor(SWT.COLOR_INFO_BACKGROUND)); + + fText.addKeyListener(new KeyListener() { + + public void keyPressed(KeyEvent e) { + if (e.character == 0x1B) // ESC + fShell.dispose(); + } + + public void keyReleased(KeyEvent e) {} + }); + + fPresenter= presenter; + } + + /** + * Creates a default information control with the given shell as parent. + * No information presenter is used to process the information + * to be displayed. No additional styles are applied to the styled text widget. + * + * @param parent the parent shell + */ + public DefaultInformationControl(Shell parent) { + this(parent, SWT.NONE, null); + } + + /** + * Creates a default information control with the given shell as parent. The given + * information presenter is used to process the information to be displayed. + * No additional styles are applied to the styled text widget. + * + * @param parent the parent shell + * @param presenter the presenter to be used + */ + public DefaultInformationControl(Shell parent, IInformationPresenter presenter) { + this(parent, SWT.NONE, presenter); + } + + /* + * @see IInformationControl#setInformation(String) + */ + public void setInformation(String content) { + if (fPresenter == null) { + fText.setText(content); + } else { + fPresentation.clear(); + content= fPresenter.updatePresentation(fShell.getDisplay(), content, fPresentation, fMaxWidth, fMaxHeight); + if (content != null) { + fText.setText(content); + TextPresentation.applyTextPresentation(fPresentation, fText); + } else { + fText.setText(""); //$NON-NLS-1$ + } + } + } + + /* + * @see IInformationControl#setVisible(boolean) + */ + public void setVisible(boolean visible) { + fShell.setVisible(visible); + } + + /* + * @see IInformationControl#dispose() + */ + public void dispose() { + if (fShell != null) { + if (!fShell.isDisposed()) + fShell.dispose(); + fShell= null; + fText= null; + } + } + + /* + * @see IInformationControl#setSize(int, int) + */ + public void setSize(int width, int height) { + fShell.setSize(width, height); + + width -= BORDER * 2; + if (width < 0) + width= 0; + + height -= BORDER * 2; + if (height < 0) + height= 0; + + fText.setSize(width, height); + } + + /* + * @see IInformationControl#setLocation(Point) + */ + public void setLocation(Point location) { + fText.setLocation(BORDER, BORDER); + fShell.setLocation(location); + } + + /* + * @see IInformationControl#setSizeConstraints(int, int) + */ + public void setSizeConstraints(int maxWidth, int maxHeight) { + fMaxWidth= maxWidth; + fMaxHeight= maxHeight; + } + + /* + * @see IInformationControl#computeSizeHint() + */ + public Point computeSizeHint() { + Point point= fText.computeSize(SWT.DEFAULT, SWT.DEFAULT, true); + point.x += RIGHT_MARGIN + BORDER * 2; + point.y += BORDER * 2; + + return point; + } + + /* + * @see IInformationControl#addDisposeListener(DisposeListener) + */ + public void addDisposeListener(DisposeListener listener) { + fShell.addDisposeListener(listener); + } + + /* + * @see IInformationControl#removeDisposeListener(DisposeListener) + */ + public void removeDisposeListener(DisposeListener listener) { + fShell.removeDisposeListener(listener); + } + + /* + * @see IInformationControl#setForegroundColor(Color) + */ + public void setForegroundColor(Color foreground) { + fText.setForeground(foreground); + } + + /* + * @see IInformationControl#setBackgroundColor(Color) + */ + public void setBackgroundColor(Color background) { + fText.setBackground(background); + } + + /* + * @see IInformationControl#isFocusControl() + */ + public boolean isFocusControl() { + return fText.isFocusControl(); + } + + /* + * @see IInformationControl#setFocus() + */ + public void setFocus() { + fText.setFocus(); + } + + /* + * @see IInformationControl#addFocusListener(FocusListener) + */ + public void addFocusListener(FocusListener listener) { + fText.addFocusListener(listener); + } + + /* + * @see IInformationControl#removeFocusListener(FocusListener) + */ + public void removeFocusListener(FocusListener listener) { + fText.removeFocusListener(listener); + } + + /* + * @see IInformationControlExtension#hasContents() + */ + public boolean hasContents() { + return fText.getCharCount() > 0; + } +} + diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/DefaultTextDoubleClickStrategy.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/DefaultTextDoubleClickStrategy.java new file mode 100644 index 000000000..a8934267a --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/DefaultTextDoubleClickStrategy.java @@ -0,0 +1,218 @@ +/********************************************************************** +Copyright (c) 2000, 2002 IBM Corp. and others. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Common Public License v1.0 +which accompanies this distribution, and is available at +http://www.eclipse.org/legal/cpl-v10.html + +Contributors: + IBM Corporation - Initial implementation +**********************************************************************/ + +package org.eclipse.jface.text; + +import java.text.BreakIterator; +import java.text.CharacterIterator; + + +/** + * Standard implementation of <code>ITextDoubleClickStrategy</code>. + * Selects words using <code>java.text.BreakIterator</code> for the + * default locale. This class is not intended to be subclassed. + * + * @see java.text.BreakIterator + */ +public class DefaultTextDoubleClickStrategy implements ITextDoubleClickStrategy { + + + /** + * Implements a character iterator that works directly on + * instances of <code>IDocument</code>. Used to collaborate with + * the break iterator. + * + * @see IDocument + * @since 2.0 + */ + static class DocumentCharacterIterator implements CharacterIterator { + + /** Document to iterate over. */ + private IDocument fDocument; + /** Start offset of iteration. */ + private int fOffset= -1; + /** Endoffset of iteration. */ + private int fEndOffset= -1; + /** Current offset of iteration. */ + private int fIndex= -1; + + /** Creates a new document iterator. */ + public DocumentCharacterIterator() { + } + + /** + * Configures this document iterator with the document section to be iteratored. + * + * @param document the document to be iterated + * @param iteratorRange the range in the document to be iterated + */ + public void setDocument(IDocument document, IRegion iteratorRange) { + fDocument= document; + fOffset= iteratorRange.getOffset(); + fEndOffset= fOffset + iteratorRange.getLength(); + } + + /* + * @see CharacterIterator#first() + */ + public char first() { + fIndex= fOffset; + return current(); + } + + /* + * @see CharacterIterator#last() + */ + public char last() { + fIndex= fOffset < fEndOffset ? fEndOffset -1 : fEndOffset; + return current(); + } + + /* + * @see CharacterIterator#current() + */ + public char current() { + if (fOffset <= fIndex && fIndex < fEndOffset) { + try { + return fDocument.getChar(fIndex); + } catch (BadLocationException x) { + } + } + return DONE; + } + + /* + * @see CharacterIterator#next() + */ + public char next() { + if (fIndex == fEndOffset -1) + return DONE; + + if (fIndex < fEndOffset) + ++ fIndex; + + return current(); + } + + /* + * @see CharacterIterator#previous() + */ + public char previous() { + if (fIndex == fOffset) + return DONE; + + if (fIndex > fOffset) + -- fIndex; + + return current(); + } + + /* + * @see CharacterIterator#setIndex(int) + */ + public char setIndex(int index) { + fIndex= index; + return current(); + } + + /* + * @see CharacterIterator#getBeginIndex() + */ + public int getBeginIndex() { + return fOffset; + } + + /* + * @see CharacterIterator#getEndIndex() + */ + public int getEndIndex() { + return fEndOffset; + } + + /* + * @see CharacterIterator#getIndex() + */ + public int getIndex() { + return fIndex; + } + + /* + * @see CharacterIterator#clone() + */ + public Object clone() { + DocumentCharacterIterator i= new DocumentCharacterIterator(); + i.fDocument= fDocument; + i.fIndex= fIndex; + i.fOffset= fOffset; + i.fEndOffset= fEndOffset; + return i; + } + }; + + + /** + * The document character iterator used by this strategy. + * @since 2.0 + */ + private DocumentCharacterIterator fDocIter= new DocumentCharacterIterator(); + + + /** + * Creates a new default text double click strategy. + */ + public DefaultTextDoubleClickStrategy() { + super(); + } + + /* + * @see ITextDoubleClickStrategy#doubleClicked + */ + public void doubleClicked(ITextViewer text) { + + int position= text.getSelectedRange().x; + + if (position < 0) + return; + + try { + + IDocument document= text.getDocument(); + IRegion line= document.getLineInformationOfOffset(position); + if (position == line.getOffset() + line.getLength()) + return; + + fDocIter.setDocument(document, line); + + BreakIterator breakIter= BreakIterator.getWordInstance(); + breakIter.setText(fDocIter); + + int start= breakIter.preceding(position); + if (start == BreakIterator.DONE) + start= line.getOffset(); + + int end= breakIter.following(position); + if (end == BreakIterator.DONE) + end= line.getOffset() + line.getLength(); + + if (breakIter.isBoundary(position)) { + if (end - position > position- start) + start= position; + else + end= position; + } + + if (start != end) + text.setSelectedRange(start, end - start); + + } catch (BadLocationException x) { + } + } +} diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/DefaultUndoManager.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/DefaultUndoManager.java new file mode 100644 index 000000000..58ca1ea89 --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/DefaultUndoManager.java @@ -0,0 +1,786 @@ +/********************************************************************** +Copyright (c) 2000, 2002 IBM Corp. and others. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Common Public License v1.0 +which accompanies this distribution, and is available at +http://www.eclipse.org/legal/cpl-v10.html + +Contributors: + IBM Corporation - Initial implementation +**********************************************************************/ + +package org.eclipse.jface.text; + + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.StyledText; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.KeyListener; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.MouseListener; + + +/** + * Standard implementation of <code>IUndoManager</code>. + * It registers with the connected text viewer as text listeners and logs all changes. + * It also monitors mouse and keyboard activities in order to partition the stream of + * text changes into undoable edit commands. <p> + * This class is not intended to be subclassed. + * + * @see ITextViewer + * @see ITextListener + * @see MouseListener + * @see KeyListener + */ +public class DefaultUndoManager implements IUndoManager { + + /** + * Represents an undoable edit command. + */ + class TextCommand { + + /** The start index of the replaced text */ + protected int fStart= -1; + /** The end index of the replaced text */ + protected int fEnd= -1; + /** The newly inserted text */ + protected String fText; + /** The replaced text */ + protected String fPreservedText; + + /** + * Reinitializes this text command. + */ + protected void reinitialize() { + fStart= fEnd= -1; + fText= fPreservedText= null; + } + + /** + * Sets the start and the end index of this command. + * + * @param start the start index + * @param end the end index + */ + protected void set(int start, int end) { + fStart= start; + fEnd= end; + fText= null; + fPreservedText= null; + } + + /** + * Undo the change described by this command. + * + * @param text the text widget to be modified + * @since 2.0 + */ + protected void undoTextChange(StyledText text) { + text.replaceTextRange(fStart, fText.length(), fPreservedText); + } + + /** + * Undo the change described by this command. Also selects and + * reveals the change. + * + * @param text the text widget to be modified + */ + protected void undo(StyledText text) { + + undoTextChange(text); + + int length= fPreservedText == null ? 0 : fPreservedText.length(); + IRegion visible= fTextViewer.getVisibleRegion(); + int offset= fStart + visible.getOffset(); + fTextViewer.setSelectedRange(offset, length); + fTextViewer.revealRange(offset, length); + } + + /** + * Redo the change described by this command. + * + * @param text the text widget to be modified + * @since 2.0 + */ + protected void redoTextChange(StyledText text) { + text.replaceTextRange(fStart, fEnd - fStart, fText); + } + + /** + * Redo the change described by this command that previously been + * rolled back. Also selects and reveals the change. + * + * @param text the text widget to be modified + */ + protected void redo(StyledText text) { + + redoTextChange(text); + + int length= fText == null ? 0 : fText.length(); + IRegion visible= fTextViewer.getVisibleRegion(); + int offset= fStart + visible.getOffset(); + fTextViewer.setSelectedRange(offset, length); + fTextViewer.revealRange(offset, length); + } + + /** + * Updates the command stack in response to committing + * the current change into this command. + */ + protected void updateCommandStack() { + + int length= fCommandStack.size(); + for (int i= fCommandCounter + 1; i < length; i++) + fCommandStack.remove(fCommandCounter + 1); + + fCommandStack.add(this); + + while (fCommandStack.size() > fUndoLevel) + fCommandStack.remove(0); + + fCommandCounter= fCommandStack.size() - 1; + } + + /** + * Creates a new uncommitted text command depending on whether + * a compound change is currently being executed. + * + * @return a new, uncommitted text command or a compound text command + */ + protected TextCommand createCurrent() { + return fFoldingIntoCompoundChange ? new CompoundTextCommand() : new TextCommand(); + } + + /** + * Commits the current change into this command. + */ + protected void commit() { + + if (fStart < 0) { + reinitialize(); + } else { + + fText= fTextBuffer.toString(); + fTextBuffer.setLength(0); + fPreservedText= fPreservedTextBuffer.toString(); + fPreservedTextBuffer.setLength(0); + + updateCommandStack(); + } + + fCurrent= createCurrent(); + } + }; + + /** + * Represents an undoable edit command consisting of several + * individual edit commands. + */ + class CompoundTextCommand extends TextCommand { + + /** The list of individual commands */ + private List fCommands= new ArrayList(); + + /** + * Adds a new individual command to this compound command. + * + * @param command the command to be added + */ + protected void add(TextCommand command) { + fCommands.add(command); + } + + /* + * @see TextCommand#undo + */ + protected void undo(StyledText text) { + ITextViewerExtension extension= null; + if (fTextViewer instanceof ITextViewerExtension) + extension= (ITextViewerExtension) fTextViewer; + + if (extension != null) + extension.setRedraw(false); + + try { + + int size= fCommands.size(); + if (size > 0) { + + TextCommand c; + + for (int i= size -1; i > 0; --i) { + c= (TextCommand) fCommands.get(i); + c.undoTextChange(text); + } + + c= (TextCommand) fCommands.get(0); + c.undo(text); + } + + } finally { + if (extension != null) + extension.setRedraw(true); + } + } + + /* + * @see TextCommand#redo + */ + protected void redo(StyledText text) { + + ITextViewerExtension extension= null; + if (fTextViewer instanceof ITextViewerExtension) + extension= (ITextViewerExtension) fTextViewer; + + if (extension != null) + extension.setRedraw(false); + + try { + + int size= fCommands.size(); + if (size > 0) { + + TextCommand c; + + for (int i= 0; i < size -1; ++i) { + c= (TextCommand) fCommands.get(i); + c.redoTextChange(text); + } + + c= (TextCommand) fCommands.get(size -1); + c.redo(text); + } + + } finally { + if (extension != null) + extension.setRedraw(true); + } + } + + /* + * @see TextCommand#updateCommandStack + */ + protected void updateCommandStack() { + TextCommand c= new TextCommand(); + c.fStart= fStart; + c.fEnd= fEnd; + c.fText= fText; + c.fPreservedText= fPreservedText; + + add(c); + + if (!fFoldingIntoCompoundChange) + super.updateCommandStack(); + } + + /* + * @see TextCommand#createCurrent + */ + protected TextCommand createCurrent() { + + if (!fFoldingIntoCompoundChange) + return new TextCommand(); + + reinitialize(); + return this; + } + }; + + /** + * Represents pretended <code>UndoManager</code> state. + */ + class PretendedUndoManagerState { + protected int cmdCounter= -1; + protected int stackSize= -1; + }; + + /** + * Internal listener to mouse and key events. + */ + class KeyAndMouseListener implements MouseListener, KeyListener { + + /* + * @see MouseListener#mouseDoubleClick + */ + public void mouseDoubleClick(MouseEvent e) { + } + + /* + * If the right mouse button is pressed, the current editing command is closed + * @see MouseListener#mouseDown + */ + public void mouseDown(MouseEvent e) { + if (e.button == 1) + commit(); + } + + /* + * @see MouseListener#mouseUp + */ + public void mouseUp(MouseEvent e) { + } + + /* + * @see KeyListener#keyPressed + */ + public void keyReleased(KeyEvent e) { + } + + /* + * On cursor keys, the current editing command is closed + * @see KeyListener#keyPressed + */ + public void keyPressed(KeyEvent e) { + switch (e.keyCode) { + case SWT.ARROW_UP: + case SWT.ARROW_DOWN: + case SWT.ARROW_LEFT: + case SWT.ARROW_RIGHT: + commit(); + break; + } + } + }; + + /** + * Internal listener to text changes. + */ + class TextListener implements ITextListener { + + /* + * @see ITextListener#textChanged + */ + public void textChanged(TextEvent e) { + if (e.getDocumentEvent() != null) + processTextEvent(e); + } + }; + + /** Text buffer to collect text which is inserted into the viewer */ + private StringBuffer fTextBuffer= new StringBuffer(); + /** Text buffer to collect viewer content which has been replaced */ + private StringBuffer fPreservedTextBuffer= new StringBuffer(); + /** Pretended undo manager state */ + private PretendedUndoManagerState fPretendedState= new PretendedUndoManagerState(); + + /** The internal text listener */ + private ITextListener fTextListener; + /** The internal key and mouse event listener */ + private KeyAndMouseListener fKeyAndMouseListener; + + + /** Indicates inserting state */ + private boolean fInserting= false; + /** Indicates deleteing state */ + private boolean fDeleting= false; + /** Indicates overwriting state */ + private boolean fOverwriting= false; + /** Indicates whether the current change belongs to a compound change */ + private boolean fFoldingIntoCompoundChange= false; + + /** The text viewer the undo manager is connected to */ + private ITextViewer fTextViewer; + + /** Supported undo level */ + private int fUndoLevel; + /** The list of undoable edit commands */ + private List fCommandStack; + /** The currently constructed edit command */ + private TextCommand fCurrent; + /** The last delete edit command */ + private TextCommand fPreviousDelete; + /** Command counter into the edit command stack */ + private int fCommandCounter= -1; + + + /** + * Creates a new undo manager who remembers the specified number of edit commands. + * + * @param undoLevel the length of this manager's history + */ + public DefaultUndoManager(int undoLevel) { + setMaximalUndoLevel(undoLevel); + } + + /* + * @see IUndoManager#beginCompoundChange + */ + public void beginCompoundChange() { + fFoldingIntoCompoundChange= true; + commit(); + } + + /* + * @see IUndoManager#endCompoundChange + */ + public void endCompoundChange() { + fFoldingIntoCompoundChange= false; + commit(); + } + + /** + * Registers all necessary listeners with the text viewer. + */ + private void addListeners() { + StyledText text= fTextViewer.getTextWidget(); + if (text != null) { + fKeyAndMouseListener= new KeyAndMouseListener(); + text.addMouseListener(fKeyAndMouseListener); + text.addKeyListener(fKeyAndMouseListener); + listenToTextChanges(true); + } + } + + /** + * Deregister all previously installed listeners from the text viewer. + */ + private void removeListeners() { + StyledText text= fTextViewer.getTextWidget(); + if (text != null && fKeyAndMouseListener != null) { + text.removeMouseListener(fKeyAndMouseListener); + text.removeKeyListener(fKeyAndMouseListener); + listenToTextChanges(false); + } + } + + /** + * Switches the state of whether there is a text listener or not. + * + * @param listen the state which should be established + */ + private void listenToTextChanges(boolean listen) { + if (listen && fTextListener == null) { + fTextListener= new TextListener(); + fTextViewer.addTextListener(fTextListener); + } else if (!listen && fTextListener != null) { + fTextViewer.removeTextListener(fTextListener); + fTextListener= null; + } + } + + /** + * Closes the current editing command and opens a new one. + */ + private void commit() { + + fInserting= false; + fDeleting= false; + fOverwriting= false; + fPreviousDelete.reinitialize(); + + fCurrent.commit(); + } + + /** + * Does redo the previously undone editing command. + */ + private void internalRedo() { + StyledText text= fTextViewer.getTextWidget(); + if (text != null) { + + ++fCommandCounter; + TextCommand cmd= (TextCommand) fCommandStack.get(fCommandCounter); + + listenToTextChanges(false); + cmd.redo(text); + listenToTextChanges(true); + + fCurrent= new TextCommand(); + } + } + + /** + * Does undo the last editing command. + */ + private void internalUndo() { + StyledText text= fTextViewer.getTextWidget(); + if (text != null) { + + TextCommand cmd= (TextCommand) fCommandStack.get(fCommandCounter); + -- fCommandCounter; + + listenToTextChanges(false); + cmd.undo(text); + listenToTextChanges(true); + + fCurrent= new TextCommand(); + } + } + + /** + * Checks whether the given text starts with a line delimiter and + * subsequently contains a white space only. + * + * @param text the text to check + */ + private boolean isWhitespaceText(String text) { + + if (text == null || text.length() == 0) + return false; + + String[] delimiters= fTextViewer.getDocument().getLegalLineDelimiters(); + int index= TextUtilities.startsWith(delimiters, text); + if (index > -1) { + char c; + int length= text.length(); + for (int i= delimiters[index].length(); i < length; i++) { + c= text.charAt(i); + if (c != ' ' && c != '\t') + return false; + } + } + + return true; + } + + /** + * Returns the state the would result if the current editing command would be closed. + * + * @return the pretended state after closing the current editing command + */ + private PretendedUndoManagerState pretendCommit() { + if (fCurrent.fStart < 0) { + fPretendedState.stackSize= fCommandStack.size(); + fPretendedState.cmdCounter= fCommandCounter; + } else { + int sz= Math.max(fCommandCounter, 0) + 1; + if (sz > fUndoLevel) + sz -= fUndoLevel; + fPretendedState.stackSize= sz; + fPretendedState.cmdCounter= sz - 1; + } + return fPretendedState; + } + + /** + * Processes the given text event in order to determine editor command. + * + * @param e the text event + */ + private void processTextEvent(TextEvent e) { + + int start= e.getOffset(); + int end= e.getOffset() + e.getLength(); + String newText= e.getText(); + String oldText= e.getReplacedText(); + + + if (newText == null) + newText= ""; //$NON-NLS-1$ + + if (oldText == null) + oldText= ""; //$NON-NLS-1$ + + int length= newText.length(); + int diff= end - start; + + // normalize verify command + if (diff < 0) { + int tmp= end; + end= start; + start= tmp; + diff= -diff; + } + + if (start == end) { + // text will be inserted + if ((length == 1) || isWhitespaceText(newText)) { + // by typing or model manipulation + if (!fInserting || (start != fCurrent.fStart + fTextBuffer.length())) { + commit(); + fInserting= true; + } + if (fCurrent.fStart < 0) + fCurrent.fStart= fCurrent.fEnd= start; + if (length > 0) + fTextBuffer.append(newText); + } else if (length > 0) { + // by pasting + commit(); + fCurrent.fStart= fCurrent.fEnd= start; + fTextBuffer.append(newText); + } + } else { + if (length == 0) { + // text will be deleted by backspace or DEL key or empty clipboard + length= oldText.length(); + String[] delimiters= fTextViewer.getDocument().getLegalLineDelimiters(); + + if ((length == 1) || TextUtilities.equals(delimiters, oldText) > -1) { + + // whereby selection is empty + + if (fPreviousDelete.fStart == start && fPreviousDelete.fEnd == end) { + // repeated DEL + + // correct wrong settings of fCurrent + if (fCurrent.fStart == end && fCurrent.fEnd == start) { + fCurrent.fStart= start; + fCurrent.fEnd= end; + } + // append to buffer && extend command range + fPreservedTextBuffer.append(oldText); + ++fCurrent.fEnd; + + } else if (fPreviousDelete.fStart == end) { + // repeated backspace + + // insert in buffer and extend command range + fPreservedTextBuffer.insert(0, oldText); + fCurrent.fStart= start; + + } else { + // either DEL or backspace for the first time + + commit(); + fDeleting= true; + + // as we can not decide whether it was DEL or backspace we initialize for backspace + fPreservedTextBuffer.append(oldText); + fCurrent.fStart= start; + fCurrent.fEnd= end; + } + + fPreviousDelete.set(start, end); + + } else if (length > 0) { + // whereby selection is not empty + commit(); + fCurrent.fStart= start; + fCurrent.fEnd= end; + fPreservedTextBuffer.append(oldText); + } + } else { + // text will be replaced + + if (length == 1) { + length= oldText.length(); + String[] delimiters= fTextViewer.getDocument().getLegalLineDelimiters(); + + if ((length == 1) || TextUtilities.equals(delimiters, oldText) > -1) { + // because of overwrite mode or model manipulation + if (!fOverwriting || (start != fCurrent.fStart + fTextBuffer.length())) { + commit(); + fOverwriting= true; + } + + if (fCurrent.fStart < 0) + fCurrent.fStart= start; + + fCurrent.fEnd= end; + fTextBuffer.append(newText); + fPreservedTextBuffer.append(oldText); + return; + } + } + // because of typing or pasting whereby selection is not empty + commit(); + fCurrent.fStart= start; + fCurrent.fEnd= end; + fTextBuffer.append(newText); + fPreservedTextBuffer.append(oldText); + } + } + } + + /* + * @see IUndoManager#setMaximalUndoLevel + */ + public void setMaximalUndoLevel(int undoLevel) { + fUndoLevel= undoLevel; + } + + /* + * @see IUndoManager#connect + */ + public void connect(ITextViewer textViewer) { + if (fTextViewer == null) { + fTextViewer= textViewer; + fCommandStack= new ArrayList(); + fCurrent= new TextCommand(); + fPreviousDelete= new TextCommand(); + addListeners(); + } + } + + /* + * @see IUndoManager#disconnect + */ + public void disconnect() { + if (fTextViewer != null) { + + removeListeners(); + + fCurrent= null; + if (fCommandStack != null) { + fCommandStack.clear(); + fCommandStack= null; + } + fTextBuffer= null; + fPreservedTextBuffer= null; + fTextViewer= null; + } + } + + /* + * @see IUndoManager#reset + */ + public void reset() { + if (fCommandStack != null) + fCommandStack.clear(); + fCommandCounter= -1; + if (fCurrent != null) + fCurrent.reinitialize(); + fFoldingIntoCompoundChange= false; + fInserting= false; + fDeleting= false; + fOverwriting= false; + fTextBuffer.setLength(0); + fPreservedTextBuffer.setLength(0); + } + + /* + * @see IUndoManager#redoable + */ + public boolean redoable() { + if (fCommandStack != null) { + PretendedUndoManagerState s= pretendCommit(); + return (0 <= s.cmdCounter + 1) && (s.cmdCounter + 1 < s.stackSize); + } + return false; + } + + /* + * @see IUndoManager#undoable + */ + public boolean undoable() { + if (fCommandStack != null) { + PretendedUndoManagerState s= pretendCommit(); + return (0 <= s.cmdCounter) && (s.cmdCounter < s.stackSize); + } + return false; + } + + /* + * @see IUndoManager#redo + */ + public void redo() { + if (redoable()) { + commit(); + internalRedo(); + } + } + + /* + * @see IUndoManager#undo + */ + public void undo() { + if (undoable()) { + commit(); + internalUndo(); + } + } +} diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/DocumentAdapter.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/DocumentAdapter.java new file mode 100644 index 000000000..8ca9b21eb --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/DocumentAdapter.java @@ -0,0 +1,338 @@ +/********************************************************************** +Copyright (c) 2000, 2002 IBM Corp. and others. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Common Public License v1.0 +which accompanies this distribution, and is available at +http://www.eclipse.org/legal/cpl-v10.html + +Contributors: + IBM Corporation - Initial implementation +**********************************************************************/ + +package org.eclipse.jface.text; + + +import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.TextChangeListener;
import org.eclipse.swt.custom.TextChangedEvent;
import org.eclipse.swt.custom.TextChangingEvent;
import org.eclipse.jface.util.Assert; + + +/** + * Adapts an <code>IDocument</code> to the <code>StyledTextContent</code> interface. + */ +class DocumentAdapter implements IDocumentAdapter, IDocumentListener, IDocumentAdapterExtension { + + /** The adapted document. */ + private IDocument fDocument; + /** The registered text change listeners */ + private List fTextChangeListeners= new ArrayList(1); + /** + * The remembered document event + * @since 2.0 + */ + private DocumentEvent fEvent; + /** The line delimiter */ + private String fLineDelimiter= null; + /** + * Indicates whether this adapter is forwarding document changes + * @since 2.0 + */ + private boolean fIsForwarding= true; + /** + * Indicates whether the current document event describes a complete replace. + * @since 2.0 + */ + private boolean fDocumentContentReplaced= false; + /** + * Indicates that the line delimiter cache is flushed + * @since 2.0 + */ + private boolean fInvalidateLineDelimiter; + + + /** + * Creates a new document adapter which is initiallly not connected to + * any document. + */ + public DocumentAdapter() { + } + + /** + * Sets the given document as the document to be adapted. + * + * @param document the document to be adapted or <code>null</code> if there is no document + */ + public void setDocument(IDocument document) { + + if (fDocument != null) + fDocument.removePrenotifiedDocumentListener(this); + + fDocument= document; + fLineDelimiter= null; + + if (fDocument != null) + fDocument.addPrenotifiedDocumentListener(this); + } + + /* + * @see StyledTextContent#addTextChangeListener + */ + public void addTextChangeListener(TextChangeListener listener) { + Assert.isNotNull(listener); + if (! fTextChangeListeners.contains(listener)) + fTextChangeListeners.add(listener); + } + + /* + * @see StyledTextContent#removeTextChangeListener + */ + public void removeTextChangeListener(TextChangeListener listener) { + Assert.isNotNull(listener); + fTextChangeListeners.remove(listener); + } + + /* + * @see StyledTextContent#getLine(int) + */ + public String getLine(int line) { + try { + IRegion r= fDocument.getLineInformation(line); + return fDocument.get(r.getOffset(), r.getLength()); + } catch (BadLocationException x) { + SWT.error(SWT.ERROR_INVALID_ARGUMENT); + return null; + } + } + + /* + * @see StyledTextContent#getLineAtOffset(int) + */ + public int getLineAtOffset(int offset) { + try { + return fDocument.getLineOfOffset(offset); + } catch (BadLocationException x) { + SWT.error(SWT.ERROR_INVALID_ARGUMENT); + return -1; + } + } + + /* + * @see StyledTextContent#getLineCount() + */ + public int getLineCount() { + return fDocument.getNumberOfLines(); + } + + /* + * @see StyledTextContent#getOffsetAtLine(int) + */ + public int getOffsetAtLine(int line) { + try { + return fDocument.getLineOffset(line); + } catch (BadLocationException x) { + SWT.error(SWT.ERROR_INVALID_ARGUMENT); + return -1; + } + } + + /* + * @see StyledTextContent#getTextRange(int, int) + */ + public String getTextRange(int offset, int length) { + try { + return fDocument.get(offset, length); + } catch (BadLocationException x) { + SWT.error(SWT.ERROR_INVALID_ARGUMENT); + return null; + } + } + + /* + * @see StyledTextContent#replaceTextRange(int, int, String) + */ + public void replaceTextRange(int pos, int length, String text) { + try { + fDocument.replace(pos, length, text); + } catch (BadLocationException x) { + SWT.error(SWT.ERROR_INVALID_ARGUMENT); + } + } + + /* + * @see StyledTextContent#setText + */ + public void setText(String text) { + fDocument.set(text); + } + + /* + * @see StyledTextContent#getCharCount() + */ + public int getCharCount() { + return fDocument.getLength(); + } + + /* + * @see StyledTextContent#getLineDelimiter + */ + public String getLineDelimiter() { + + if (fLineDelimiter == null) { + + try { + fLineDelimiter= fDocument.getLineDelimiter(0); + } catch (BadLocationException x) { + } + + if (fLineDelimiter == null) { + /* + * Follow up fix for: 1GF5UU0: ITPJUI:WIN2000 - "Organize Imports" in java editor inserts lines in wrong format + * The line delimiter must always be a legal document line delimiter. + */ + String sysLineDelimiter= System.getProperty("line.separator"); //$NON-NLS-1$ + String[] delimiters= fDocument.getLegalLineDelimiters(); + Assert.isTrue(delimiters.length > 0); + for (int i= 0; i < delimiters.length; i++) { + if (delimiters[i].equals(sysLineDelimiter)) { + fLineDelimiter= sysLineDelimiter; + break; + } + } + + if (fLineDelimiter == null) { + // system line delimiter is not a legal document line delimiter + fLineDelimiter= delimiters[0]; + } + } + } + + return fLineDelimiter; + } + + /* + * @see IDocumentListener#documentChanged(DocumentEvent) + */ + public void documentChanged(DocumentEvent event) { + // check whether the given event is the one which was remembered + if (fEvent != null && event == fEvent) { + + // http://dev.eclipse.org/bugs/show_bug.cgi?id=15159 + if (fDocumentContentReplaced) { + fDocumentContentReplaced= false; + fireTextSet(); + } else + fireTextChanged(); + + if (fInvalidateLineDelimiter) { + fLineDelimiter= null; + fInvalidateLineDelimiter= false; + } + } + } + + /* + * @see IDocumentListener#documentAboutToBeChanged(DocumentEvent) + */ + public void documentAboutToBeChanged(DocumentEvent event) { + + if (event.getOffset() == 0 && event.getLength() == fDocument.getLength()) { + // http://dev.eclipse.org/bugs/show_bug.cgi?id=15159 + fEvent= event; + fInvalidateLineDelimiter= true; + fDocumentContentReplaced= true; + + } else { + + try { + // invalidate cached line delimiter if first line of document was changed + if (event.getOffset() < fDocument.getLineLength(0)) + fInvalidateLineDelimiter= true; + } catch (BadLocationException e) { + } + + // Remember the given event + fEvent= event; + fireTextChanging(); + } + } + + /** + * Sends a text changed event to all registered listeners. + */ + private void fireTextChanged() { + + if (!fIsForwarding) + return; + + TextChangedEvent event= new TextChangedEvent(this); + + if (fTextChangeListeners != null && fTextChangeListeners.size() > 0) { + Iterator e= new ArrayList(fTextChangeListeners).iterator(); + while (e.hasNext()) + ((TextChangeListener) e.next()).textChanged(event); + } + } + + /** + * Sends a text set event to all registered listeners. + */ + private void fireTextSet() { + + if (!fIsForwarding) + return; + + TextChangedEvent event = new TextChangedEvent(this); + + if (fTextChangeListeners != null && fTextChangeListeners.size() > 0) { + Iterator e= new ArrayList(fTextChangeListeners).iterator(); + while (e.hasNext()) + ((TextChangeListener) e.next()).textSet(event); + } + } + + /** + * Sends the text changing event to all registered listeners. + */ + private void fireTextChanging() { + + if (!fIsForwarding) + return; + + try { + IDocument document= fEvent.getDocument(); + if (document == null) + return; + + TextChangingEvent event= new TextChangingEvent(this); + event.start= fEvent.fOffset; + event.replaceCharCount= fEvent.fLength; + event.replaceLineCount= document.getNumberOfLines(fEvent.fOffset, fEvent.fLength) - 1; + event.newText= fEvent.fText; + event.newCharCount= (fEvent.fText == null ? 0 : fEvent.fText.length()); + event.newLineCount= (fEvent.fText == null ? 0 : document.computeNumberOfLines(fEvent.fText)); + + if (fTextChangeListeners != null && fTextChangeListeners.size() > 0) { + Iterator e= new ArrayList(fTextChangeListeners).iterator(); + while (e.hasNext()) + ((TextChangeListener) e.next()).textChanging(event); + } + + } catch (BadLocationException e) { + } + } + + /* + * @see IDocumentAdapterExtension#resumeForwardingDocumentChanges() + * @since 2.0 + */ + public void resumeForwardingDocumentChanges() { + fIsForwarding= true; + fireTextSet(); + } + + /* + * @see IDocumentAdapterExtension#stopForwardingDocumentChanges() + * @since 2.0 + */ + public void stopForwardingDocumentChanges() { + fIsForwarding= false; + } +}
\ No newline at end of file diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/DocumentCommand.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/DocumentCommand.java new file mode 100644 index 000000000..a79245254 --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/DocumentCommand.java @@ -0,0 +1,77 @@ +package org.eclipse.jface.text; + +/* + * (c) Copyright IBM Corp. 2000, 2001. + * All Rights Reserved. + */ + + +import org.eclipse.swt.events.VerifyEvent; + + +/** + * Represents a text modification as a document replace command. The text modification is given + * as a <code>VerifyEvent</code> and translated into a document replace command relative + * to a given offset. A document command can also be used to initialize a given <code>VerifyEvent</code>. + */ +public final class DocumentCommand { + + /** Must the command be updated */ + public boolean doit= false; + /** The offset of the command */ + public int offset; + /** The length of the command */ + public int length; + /** The text to be inserted */ + public String text; + + + /** + * Creates a new document command. + */ + DocumentCommand() { + } + + /** + * Translates a verify event into a document replace command using the given offset. + * + * @param event the event to be translated + * @param offset the offset used for the translation + */ + void setEvent(VerifyEvent event, int offset) { + + doit= true; + + text= event.text; + + this.offset= event.start; + length= event.end - event.start; + + if (length < 0) { + this.offset += length; + length= -length; + } + + this.offset += offset; + } + + /** + * Fills the given verify event with the replace text and the doit + * flag of this document command. Returns whether the document command + * covers the same range as the verify event considering the given offset. + * + * @param event the event to be changed + * @param offset to be considered for range comparison + * @return <code>true</code> if this command and the event cover the same range + */ + boolean fillEvent(VerifyEvent event, int offset) { + + int start= this.offset - offset; + + event.text= text; + event.doit= (start == event.start && start + length == event.end) && doit; + return event.doit; + } +} + + diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/IAutoIndentStrategy.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/IAutoIndentStrategy.java new file mode 100644 index 000000000..c0e48790d --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/IAutoIndentStrategy.java @@ -0,0 +1,26 @@ +package org.eclipse.jface.text; + +/* + * (c) Copyright IBM Corp. 2000, 2001. + * All Rights Reserved. + */ + + +/** + * An auto indent strategy can adapt changes that will be applied to + * a text viewer's document. The strategy is informed by the text viewer + * about each upcoming change in form of a document command. By manipulating + * this document command, the strategy can influence in which way the text + * viewer's document is changed. Clients may implement this interface or + * use the standard implementation <code>DefaultAutoIndentStrategy</code>. + */ +public interface IAutoIndentStrategy { + + /** + * Allows the strategy to manipulate the document command. + * + * @param document the document that will be changed + * @param command the document command describing the indented change + */ + void customizeDocumentCommand(IDocument document, DocumentCommand command); +}
\ No newline at end of file diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/IDocumentAdapter.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/IDocumentAdapter.java new file mode 100644 index 000000000..49c6c6a6f --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/IDocumentAdapter.java @@ -0,0 +1,31 @@ +package org.eclipse.jface.text; + +/* + * (c) Copyright IBM Corp. 2000, 2001. + * All Rights Reserved. + */ + + +import org.eclipse.swt.custom.StyledTextContent; + + +/** + * Adapts an <code>IDocument</code> to the <code>StyledTextContent</code> interface. + * The document adapter is used by <code>TextViewer</code> to translate document changes + * into styled text content changes and vice versa. + * Clients may implement this interface and override <code>TextViewer.createDocumentAdapter</code> + * if they want to intercept the communication between the viewer's text widget and + * the viewer's document. + * + * @see IDocument + * @see StyledTextContent + */ +public interface IDocumentAdapter extends StyledTextContent { + + /** + * Sets the adapters document. + * + * @param document the document to be adapted + */ + void setDocument(IDocument document); +} diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/IDocumentAdapterExtension.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/IDocumentAdapterExtension.java new file mode 100644 index 000000000..d44d18e6a --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/IDocumentAdapterExtension.java @@ -0,0 +1,39 @@ +/********************************************************************** +Copyright (c) 2000, 2002 IBM Corp. and others. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Common Public License v1.0 +which accompanies this distribution, and is available at +http://www.eclipse.org/legal/cpl-v10.html + +Contributors: + IBM Corporation - Initial implementation +**********************************************************************/ + +package org.eclipse.jface.text; + +/** + * Extension interface for <code>IDocumentAdapter</code>. Introduces the concepts of + * batching a series of document changes into one styled text content change. Batching start + * when a client calls <code>stopForwardingDocumentChanges</code>. After that call this document + * adapter does not send out any styled text content change until + * <code>resumeForwardingDocumentChanges</code> is called. Then, it sends out one styled text + * content change that covers all changes that have been applied to the document since calling + * <code>stopForwardingDocumentChanges</code>. + * + * @since 2.0 + */ +public interface IDocumentAdapterExtension { + + /** + * Stops forwarding document changes to the styled text. + */ + void stopForwardingDocumentChanges(); + + /** + * Resumes forwarding document changes to the styled text. + * Also forces the styled text to catch up with all the changes + * that have been applied since <code>stopTranslatingDocumentChanges</code> + * has been called. + */ + void resumeForwardingDocumentChanges(); +} diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/IEventConsumer.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/IEventConsumer.java new file mode 100644 index 000000000..700704992 --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/IEventConsumer.java @@ -0,0 +1,31 @@ +package org.eclipse.jface.text; + +/* + * (c) Copyright IBM Corp. 2000, 2001. + * All Rights Reserved. + */ + + +import org.eclipse.swt.events.VerifyEvent; + +/** + * Implementers can register with an text viewer and + * receive <code>VerifyEvent</code>s before the text viewer + * they are registered with. If the event consumer marks events + * as processed by turning their <code>doit</code> field to + * <code>false</code> the text viewer subsequently ignores them. + * Clients may implement this interface. + * + * @see ITextViewer + * @see org.eclipse.swt.events.VerifyEvent + */ +public interface IEventConsumer { + + /** + * Processes the given event and marks it as done if it should + * be ignored by subsequent receivers. + * + * @param event the verify event which will be investigated + */ + public void processEvent(VerifyEvent event); +} diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/IFindReplaceTarget.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/IFindReplaceTarget.java new file mode 100644 index 000000000..658369367 --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/IFindReplaceTarget.java @@ -0,0 +1,65 @@ +package org.eclipse.jface.text; + +/* + * (c) Copyright IBM Corp. 2000, 2001. + * All Rights Reserved. + */ + + +import org.eclipse.swt.graphics.Point; + + +/** + * Defines the interface for finding and replacing strings. + */ +public interface IFindReplaceTarget { + + /** + * Returns whether a find operation can be performed. + * + * @return whether a find operation can be performed + */ + boolean canPerformFind(); + + /** + * Finds and selects a string starting at the given offset using the specified search + * directives. + * + * @param offset the offset at which searching starts + * @param findString the string which should be found + * @param searchForward <code>true</code> searches forward, <code>false</code> backwards + * @param caseSensitive <code>true</code> performes a case sensitve search, <code>false</code> an insensitive search + * @param wholeWord if <code>true</code> only occurences are reported in which the findString stands as a word by itself + * @return the position of the specified string, or -1 if the string has not been found + */ + int findAndSelect(int offset, String findString, boolean searchForward, boolean caseSensitive, boolean wholeWord); + + /** + * Returns the currently selected range of characters as a offset and length. + * + * @return the currently selected character range + */ + Point getSelection(); + + /** + * Returns the currently selected characters as a string. + * + * @return the currently selected characters + */ + String getSelectionText(); + + /** + * Returns whether this target can be modified. + * + * @return <code>true</code> if target can be modified + */ + boolean isEditable(); + + /** + * Replaces the currently selected range of characters with the given text. + * This target must be editable. Otherwise nothing happens. + * + * @param text the substitution text + */ + void replaceSelection(String text); +} diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/IFindReplaceTargetExtension.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/IFindReplaceTargetExtension.java new file mode 100644 index 000000000..2cc904da7 --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/IFindReplaceTargetExtension.java @@ -0,0 +1,89 @@ +/********************************************************************** +Copyright (c) 2000, 2002 IBM Corp. and others. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Common Public License v1.0 +which accompanies this distribution, and is available at +http://www.eclipse.org/legal/cpl-v10.html + +Contributors: + IBM Corporation - Initial implementation +**********************************************************************/ + +package org.eclipse.jface.text; + +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Point; + + +/** + * Extension interface for <code>IFindReplaceTarget</code>. Extends the find replace target with + * the concept of searching in a limiting scope and introduces the state of a replace-all mode. + * + * @since 2.0 + */ +public interface IFindReplaceTargetExtension { + + /** + * Indicates that a session with the target begins. + * All calls except <code>beginSession()</code> and <code>endSession()</code> to + * <code>IFindReplaceTarget</code> and + * <code>IFindReplaceTargetExtension</code> must be embedded within calls to + * <code>beginSession()</code> and <code>endSession()</code>. + * + * @see #endSession() + */ + void beginSession(); + + /** + * Indicates that a session with the target ends. + * + * @see #beginSession() + */ + void endSession(); + + /** + * Returns the find scope of the target, <code>null</code> for global scope. + * + * @return returns the find scope of the target, may be <code>null</code> + */ + IRegion getScope(); + + /** + * Sets the find scope of the target to operate on. <code>null</code> + * indicates that the global scope should be used. + * + * @param scope the find scope of the target, may be <code>null</code> + */ + void setScope(IRegion scope); + + /** + * Returns the currently selected range of lines as a offset and length. + * + * @return the currently selected line range + */ + Point getLineSelection(); + + /** + * Sets a selection. + * + * @param offset the offset of the selection + * @param length the length of the selection + */ + void setSelection(int offset, int length); + + /** + * Sets the scope highlight color + * + * @param color the color of the scope highlight + */ + void setScopeHighlightColor(Color color); + + + /** + * Sets the target's replace-all mode. + * + * @param replaceAll <code>true</code> if this target should switch into replace-all mode, + * <code>false</code> if it should leave the replace-all state + */ + void setReplaceAllMode(boolean replaceAll); +} diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/IInformationControl.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/IInformationControl.java new file mode 100644 index 000000000..5a4518517 --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/IInformationControl.java @@ -0,0 +1,142 @@ +/********************************************************************** +Copyright (c) 2000, 2002 IBM Corp. and others. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Common Public License v1.0 +which accompanies this distribution, and is available at +http://www.eclipse.org/legal/cpl-v10.html + +Contributors: + IBM Corporation - Initial implementation +**********************************************************************/ + +package org.eclipse.jface.text; + + +import org.eclipse.swt.events.DisposeListener; +import org.eclipse.swt.events.FocusListener; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Point; + + +/** + * Interface of a control presenting information. The information is given + * in textual form. It can either be the content itself or a description + * of the content. This specification is left to the implementers of this interface.<p> + * The information control may not grap focus when made visible using + * <code>setVisible(true)</code>. + * + * @since 2.0 + */ +public interface IInformationControl { + + /** + * Sets the information to be presented in this information control. + * + * @param information the information to be presented + */ + void setInformation(String information); + + /** + * Sets the information control's size constraints. A constraint value of + * <code>-1</code> indicates no constraint. This method is called before + * <code>computeSizeHint</code> is called. + * + * @param maxWidth the maximal width of the control to present the information, or <code>-1</code> for not constraint + * @param maxHeight the maximal height of the control to present the information, or <code>-1</code> for not constraint + */ + void setSizeConstraints(int maxWidth, int maxHeight); + + /** + * Computes and returns a proposal for the size of this information control depending + * on the information to present. The method tries to honor known size constraints but might + * returns a size that exceeds them. + * + * @return the computed size hint + */ + Point computeSizeHint(); + + /** + * Controls the visibility of this information control. + * + * @param visible <code>true</code> if the control should be visible + */ + void setVisible(boolean visible); + + /** + * Sets the size of this information control. + * + * @param width the width of the control + * @param height the height of the control + */ + void setSize(int width, int height); + + /** + * Sets the location of this information control. + * + * @param location the location + */ + void setLocation(Point location); + + /** + * Disposes this information control. + */ + void dispose(); + + /** + * Adds the given listener to the list of dispose listeners. + * If the listener is already registered it is not registered again. + * + * @param listener the listener to be added + */ + void addDisposeListener(DisposeListener listener); + + /** + * Removes the given listeners from the list of dispose listeners. + * If the listener is not registered this call has no affect. + * + * @param listener the listener to be removed + */ + void removeDisposeListener(DisposeListener listener); + + /** + * Sets the foreground color of this information control. + * + * @param foreground the foreground color of this information control + */ + void setForegroundColor(Color foreground); + + /** + * Sets the background color of this information control. + * + * @param background the background color of this information control + */ + void setBackgroundColor(Color background); + + /** + * Returns whether this information control has the focus. + * + * @return <code>true</code> when the information control has the focus otherwise <code>false</code> + */ + boolean isFocusControl(); + + /** + * Sets the keyboard focus to this information control. + */ + void setFocus(); + + /** + * Adds the given listener to the list of focus listeners. + * If the listener is already registered it is not registered again. + * + * @param listener the listener to be added + */ + void addFocusListener(FocusListener listener); + + /** + * Removes the given listeners from the list of focus listeners. + * If the listener is not registered this call has no affect. + * + * @param listener the listener to be removed + */ + void removeFocusListener(FocusListener listener); +}
\ No newline at end of file diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/IInformationControlCreator.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/IInformationControlCreator.java new file mode 100644 index 000000000..be08cefd1 --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/IInformationControlCreator.java @@ -0,0 +1,33 @@ +/********************************************************************** +Copyright (c) 2000, 2002 IBM Corp. and others. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Common Public License v1.0 +which accompanies this distribution, and is available at +http://www.eclipse.org/legal/cpl-v10.html + +Contributors: + IBM Corporation - Initial implementation +**********************************************************************/ + +package org.eclipse.jface.text; + + +import org.eclipse.swt.widgets.Shell; + + +/** + * Interface of a factory of information controls. + * + * @since 2.0 + */ +public interface IInformationControlCreator { + + /** + * Creates a new information control with the given shell as the control's parent. + * + * @param parent the parent shell + * @return the created information control + */ + IInformationControl createInformationControl(Shell parent); +} + diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/IInformationControlExtension.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/IInformationControlExtension.java new file mode 100644 index 000000000..077ee59d0 --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/IInformationControlExtension.java @@ -0,0 +1,29 @@ +/********************************************************************** +Copyright (c) 2000, 2002 IBM Corp. and others. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Common Public License v1.0 +which accompanies this distribution, and is available at +http://www.eclipse.org/legal/cpl-v10.html + +Contributors: + IBM Corporation - Initial implementation +**********************************************************************/ + +package org.eclipse.jface.text; + +/** + * Extension interface for <code>IInformationControl</code>. As it is the responsibility of + * the implementer of <code>IInformationControl</code> to specify whether the information + * set is the information itself or a description of the information, only the infomation control + * can decide whether there is something that must be displayed. + * + * @since 2.0 + */ +public interface IInformationControlExtension { + + /** + * Returns whether this information control has contents to be displayed. + * @return <code>true</code> if there is contents to be displayed. + */ + boolean hasContents(); +}
\ No newline at end of file diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/IMarkRegionTarget.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/IMarkRegionTarget.java new file mode 100644 index 000000000..f65f08f26 --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/IMarkRegionTarget.java @@ -0,0 +1,32 @@ +/********************************************************************** +Copyright (c) 2000, 2002 IBM Corp. and others. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Common Public License v1.0 +which accompanies this distribution, and is available at +http://www.eclipse.org/legal/cpl-v10.html + +Contributors: + IBM Corporation - Initial implementation +**********************************************************************/ + +package org.eclipse.jface.text; + +/** + * A mark region target to support marked regions as found in emacs. + * + * @since 2.0 + */ +public interface IMarkRegionTarget { + + /** + * Sets or clears a mark at the current cursor position. + * + * @param set sets the mark if <code>true</code>, clears otherwise. + */ + void setMarkAtCursor(boolean set); + + /** + * Swaps the mark and cursor position if the mark is in visible region. + */ + void swapMarkAndCursor(); +} diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/IMarkSelection.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/IMarkSelection.java new file mode 100644 index 000000000..91341a8b5 --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/IMarkSelection.java @@ -0,0 +1,48 @@ +/********************************************************************** +Copyright (c) 2000, 2002 IBM Corp. and others. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Common Public License v1.0 +which accompanies this distribution, and is available at +http://www.eclipse.org/legal/cpl-v10.html + +Contributors: + IBM Corporation - Initial implementation +**********************************************************************/ + +package org.eclipse.jface.text; + + +import org.eclipse.jface.viewers.ISelection; + + +/** + * A mark selection. Can be returned by text viewers implementing the + * <code>IMarkRegionTarget</code> interface. + * + * @since 2.0 + */ +public interface IMarkSelection extends ISelection { + + /** + * Returns the marked document. + * + * @return the marked document + */ + IDocument getDocument(); + + /** + * Returns the mark position. The offset may be <code>-1</code> if there's no marked region. + * + * @return the mark position or <code>-1</code> if there is no marked region + */ + int getOffset(); + + /** + * Returns the length of the mark selection. The length may be negative, if the caret + * is before the mark position. The length has no meaning if <code>getOffset()</code> + * returns <code>-1</code>. + * + * @return the length of the mark selection. Result is undefined for <code>getOffset == -1</code> + */ + int getLength(); +} diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/IRewriteTarget.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/IRewriteTarget.java new file mode 100644 index 000000000..559108d91 --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/IRewriteTarget.java @@ -0,0 +1,55 @@ +/********************************************************************** +Copyright (c) 2000, 2002 IBM Corp. and others. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Common Public License v1.0 +which accompanies this distribution, and is available at +http://www.eclipse.org/legal/cpl-v10.html + +Contributors: + IBM Corporation - Initial implementation +**********************************************************************/ + +package org.eclipse.jface.text; + + /** + * A target publishing the required functions to modify a document that is displayed + * in the ui, such as in a text viewer. It provides access to the document and control + * over the redraw behavior and the batching of document changes into undo commands. + * + * @see org.eclipse.jface.text.ITextViewer + * @see org.eclipse.jface.text.IDocument + * @see org.eclipse.jface.text.IUndoManager + * @since 2.0 + */ +public interface IRewriteTarget { + + /** + * Returns the document of this target. + * + * @return the document of this target + */ + IDocument getDocument(); + + /** + * Disables/enables redrawing of the ui while modifying the target's document. + * + * @param redraw <code>true</code> if the document's ui presentation should + * be updated, <code>false</code> otherwise + */ + void setRedraw(boolean redraw); + + /** + * If an undo manager is connected to the document's ui presentation, this + * method tells the undo manager to fold all subsequent changes into + * one single undo command until <code>endCompoundChange</code> is called. + */ + void beginCompoundChange(); + + /** + * If an undo manager is connected to the document's ui presentation, this method + * tells the undo manager to stop the folding of changes into a single undo command. + * After this call, all subsequent changes are considered to be individually undoable. + */ + void endCompoundChange(); +} + diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/ITextDoubleClickStrategy.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/ITextDoubleClickStrategy.java new file mode 100644 index 000000000..c77c7dc72 --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/ITextDoubleClickStrategy.java @@ -0,0 +1,27 @@ +package org.eclipse.jface.text; + +/* + * (c) Copyright IBM Corp. 2000, 2001. + * All Rights Reserved. + */ + + +/** + * A text double click strategy defines the reaction of a text viewer + * to mouse double click events. For that the strategy must be installed + * on the text viewer.<p> + * Clients may implements this interface or use the standard implementation + * <code>DefaultTextDoubleClickStrategy</code>. + * + * @see ITextViewer + * @see org.eclipse.swt.events.MouseListener#mouseDoubleClick(MouseEvent) + */ +public interface ITextDoubleClickStrategy { + + /** + * The mouse has been double clicked on the given text viewer. + * + * @param viewer the viewer into which has been double clicked + */ + void doubleClicked(ITextViewer viewer); +} diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/ITextHover.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/ITextHover.java new file mode 100644 index 000000000..727903192 --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/ITextHover.java @@ -0,0 +1,46 @@ +package org.eclipse.jface.text; + +/* + * (c) Copyright IBM Corp. 2000, 2001. + * All Rights Reserved. + */ + + +/** + * Provides a hover popup which appears on top of the text viewer with + * relevant display information. If the text hover does not provide information + * no hover popup is shown. Any implementer of this interface must be capable of + * operating in a non-UI thread.<p> + * Clients may implement this interface. + * + * @see ITextViewer + */ +public interface ITextHover { + + /** + * Returns the text which should be presented if a hover popup is shown + * for the specified hover region. The hover region has the same semantics + * as the region returned by <code>getHoverRegion</code>. If the returned + * string is <code>null</code> or empty no hover popup will be shown. + * + * @param textViewer the viewer on which the hover popup should be shown + * @param hoverRegion the text range in the viewer which is used to determine + * the hover display information + * @return the hover popup display information + */ + String getHoverInfo(ITextViewer textViewer, IRegion hoverRegion); + + /** + * Returns the text region which should serve as the source of information + * to compute the hover popup display information. The popup has been requested + * for the given offset.<p> + * For example, if hover information can be provided on a per method basis in a + * source viewer, the offset should be used to find the enclosing method and the + * source range of the method should be returned. + * + * @param textViewer the viewer on which the hover popup should be shown + * @param offset the offset for which the hover request has been issued + * @return the hover region used to compute the hover display information + */ + IRegion getHoverRegion(ITextViewer textViewer, int offset); +}
\ No newline at end of file diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/ITextInputListener.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/ITextInputListener.java new file mode 100644 index 000000000..016b9ace0 --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/ITextInputListener.java @@ -0,0 +1,34 @@ +package org.eclipse.jface.text; + +/* + * (c) Copyright IBM Corp. 2000, 2001. + * All Rights Reserved. + */ + + +/** + * Text input listeners registered with an text viewer are informed + * if the document serving as the text viewer's model is replaced. + * Clients may implement this interface. + * + * @see ITextViewer + * @see IDocument + */ +public interface ITextInputListener { + + /** + * Called before the input document is replaced. + * + * @param oldInput the text viewer's previous input document + * @param newInput the text viewer's new input document + */ + void inputDocumentAboutToBeChanged(IDocument oldInput, IDocument newInput); + + /** + * Called after the input document has been replaced. + * + * @param oldInput the text viewer's previous input document + * @param newInput the text viewer's new input document + */ + void inputDocumentChanged(IDocument oldInput, IDocument newInput); +}
\ No newline at end of file diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/ITextListener.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/ITextListener.java new file mode 100644 index 000000000..ba6006111 --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/ITextListener.java @@ -0,0 +1,33 @@ +package org.eclipse.jface.text; + +/* + * (c) Copyright IBM Corp. 2000, 2001. + * All Rights Reserved. + */ + + +/** + * Text listeners registered with an text viewer are informed about + * all text viewer modifications by means of text events. A text event + * describes a change as a replace operation.<p> + * The changes described in the event are the changes applied to the text viewer's + * widget (i.e. its visual representation) and not those applied to the text viewer's + * document. The text event can be asked to return the according document + * event. If a text listener receives a text event, it is guaranteed that + * both the document and the viewer's visual representation are in sync.<p> + * Clients may implement this interface. + * + * @see ITextViewer + * @see TextEvent + * @see DocumentEvent + */ +public interface ITextListener { + + /** + * The visual representation of a text viewer this listener is registered with + * has been changed. + * + * @param event the description of the change + */ + void textChanged(TextEvent event); +} diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/ITextOperationTarget.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/ITextOperationTarget.java new file mode 100644 index 000000000..9ae0600c1 --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/ITextOperationTarget.java @@ -0,0 +1,95 @@ +package org.eclipse.jface.text; + +/* + * (c) Copyright IBM Corp. 2000, 2001. + * All Rights Reserved. + */ + + +/** + * Defines the target for a text operation. + */ +public interface ITextOperationTarget { + + + /** + * Text operation code for undoing the last edit command. + */ + static final int UNDO= 1; + + /** + * Text operation code for redoing the last undone edit command. + */ + static final int REDO= 2; + + /** + * Text operation code for moving the selected text to the clipboard. + */ + static final int CUT= 3; + + /** + * Text operation code for copying the selected text to the clipboard. + */ + static final int COPY= 4; + + /** + * Text operation code for inserting the clipboard content at the + * current position. + */ + static final int PASTE= 5; + + /** + * Text operation code for deleting the selected text or if selection + * is empty the character at the right of the current position. + */ + static final int DELETE= 6; + + /** + * Text operation code for selecting the complete text. + */ + static final int SELECT_ALL= 7; + + /** + * Text operation code for shifting the selected text block to the right. + */ + static final int SHIFT_RIGHT= 8; + + /** + * Text operation code for unshifting the selected text block to the left. + */ + static final int SHIFT_LEFT= 9; + + /** + * Text operation code for printing the complete text. + */ + static final int PRINT= 10; + + /** + * Text operation code for prefixing the selected text block. + */ + static final int PREFIX= 11; + + /** + * Text operation code for removing the prefix from the selected text block. + */ + static final int STRIP_PREFIX= 12; + + + /** + * Returns whether the operation specified by the given operation code + * can be performed. + * + * @param operation the operation code + * @return <code>true</code> if the specified operation can be performed + */ + boolean canDoOperation(int operation); + + /** + * Performs the operation specified by the operation code on the target. + * <code>doOperation</code> must only be called if <code>canDoOperation</code> + * returns <code>true</code>. + * + * @param operation the operation code + */ + void doOperation(int operation); +} diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/ITextOperationTargetExtension.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/ITextOperationTargetExtension.java new file mode 100644 index 000000000..e96e370e8 --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/ITextOperationTargetExtension.java @@ -0,0 +1,31 @@ +/********************************************************************** +Copyright (c) 2000, 2002 IBM Corp. and others. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Common Public License v1.0 +which accompanies this distribution, and is available at +http://www.eclipse.org/legal/cpl-v10.html + +Contributors: + IBM Corporation - Initial implementation +**********************************************************************/ + +package org.eclipse.jface.text; + + +/** + * Extension interface to <code>ITextOperationTarget</code>. Allows a client to control + * the enable state of operations provided by this target. + * + * @since 2.0 + */ +public interface ITextOperationTargetExtension { + + /** + * Enables/disabled the given text operation. + * + * @param operation the operation to enable/disable + * @param enable <code>true</code> to enable the operation otherwise <code>false</code> + */ + void enableOperation(int operation, boolean enable); +} + diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/ITextSelection.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/ITextSelection.java new file mode 100644 index 000000000..7967c4d52 --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/ITextSelection.java @@ -0,0 +1,76 @@ +package org.eclipse.jface.text; + +/* + * (c) Copyright IBM Corp. 2000, 2001. + * All Rights Reserved. + */ + + +import org.eclipse.jface.viewers.ISelection; + + +/** + * This interface represents a textual selection. A text selection is + * a range of characters. Although a text selection is a snapshot taken + * at a particular point in time, it must not copy the line information + * and the selected text from the selection provider.<p> + * If, for example, the selection provider is a source viewer, and a text + * selection is created for the range [5, 10], the line formation for + * the 5th character must not be determined and remembered at the point + * of creation. It can rather be determined at the point, when + * <code>getStartLine</code> is called. If the source viewer range [0, 15] + * has been changed in the meantime between the creation of the text + * selection object and the invocation of <code>getStartLine</code>, the returned + * line number may differ from the line number of the 5th character at the + * point of creation of the text selection object.<p> The contract of this + * interface is that weak in order to allow for efficient implementations.<p> + * Clients may implement this interface or use the default implementation provided + * by <code>TextSelection</code>. + */ +public interface ITextSelection extends ISelection { + + /** + * Returns the offset of the selected text. + * + * @return the offset of the selected text + */ + int getOffset(); + + /** + * Returns the length of the selected text. + * + * @return the length of the selected text + */ + int getLength(); + + /** + * Returns number of the line containing the offset of the selected text. + * If the underlying text has been changed between the creation of this + * selection object and the call of this method, the value returned might + * differ from what it would have been at the point of creation. + * + * @return the start line of this selection or -1 if there is no valid line information + */ + int getStartLine(); + + /** + * Returns the number of the line containing the last character of the selected text. + * If the underlying text has been changed between the creation of this + * selection object and the call of this method, the value returned might + * differ from what it would have been at the point of creation. + * + * @return the end line of this selection or -1 if there is no valid line information + */ + int getEndLine(); + + /** + * Returns the selected text. + * If the underlying text has been changed between the creation of this + * selection object and the call of this method, the value returned might + * differ from what it would have been at the point of creation. + * + * @return the selected text or <code>null</code> if there is no valid text information + */ + String getText(); +} + diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/ITextViewer.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/ITextViewer.java new file mode 100644 index 000000000..4cf7376eb --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/ITextViewer.java @@ -0,0 +1,432 @@ +/********************************************************************** +Copyright (c) 2000, 2002 IBM Corp. and others. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Common Public License v1.0 +which accompanies this distribution, and is available at +http://www.eclipse.org/legal/cpl-v10.html + +Contributors: + IBM Corporation - Initial implementation +**********************************************************************/ + +package org.eclipse.jface.text; + + +import org.eclipse.swt.custom.StyledText; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Point; + +import org.eclipse.jface.viewers.ISelectionProvider; + + +/** + * A text viewer turns a text widget into a document-based text widget. + * It supports the following kinds of listeners: + * <ul> + * <li> viewport listeners to inform about changes of the viewer's viewport + * <li> text listeners to inform about changes of the document and the subsequent viewer change + * <li> text input listeners to inform about changes of the viewer's input document. + * </ul> + * A text viewer supports a set of plug-ins which define its behavior: + * <ul> + * <li> undo manager + * <li> double click behavior + * <li> auto indentation + * <li> text hover + * </ul> + * Installed plug-ins are not automatically activated. Plug-ins must be activated with the + * <code>activatePlugins</code> call. Most plug-ins can be defined per content type. + * Content types are derived from the partitioning of the text viewer's input document.<p> + * A text viewer also provides the concept of event consumption. Events handled by the + * viewer can be filtered and processed by a dynamic event consumer.<p> + * A text viewer provides several text editing functions, some of them are configurable, + * through a text operation target interface. It also supports a presentation mode + * in which it only shows specified sections of its document. The viewer's presentation mode + * does not affect any client of the viewer other than text listeners.<p> + * Clients may implement this interface or use the standard implementation + * <code>TextViewer</code>. + * + * @see IDocument + * @see ITextInputListener + * @see IViewportListener + * @see ITextListener + * @see IEventConsumer + */ +public interface ITextViewer { + + + /* ---------- widget --------- */ + + /** + * Returns this viewer's SWT control, <code>null</code> if the control is disposed. + * + * @return the SWT control + */ + StyledText getTextWidget(); + + + /* --------- plugins --------- */ + + /** + * Sets this viewer's undo manager. + * + * @param undoManager the new undo manager. <code>null</code> is a valid argument. + */ + void setUndoManager(IUndoManager undoManager); + + /** + * Sets this viewer's text double click strategy for the given content type. + * + * @param strategy the new double click strategy. <code>null</code> is a valid argument. + * @param contentType the type for which the strategy is registered + */ + void setTextDoubleClickStrategy(ITextDoubleClickStrategy strategy, String contentType); + + /** + * Sets this viewer's auto indent strategy for the given content type. + * + * @param strategy the new auto indent strategy. <code>null</code> is a valid argument. + * @param contentType the type for which the strategy is registered + */ + void setAutoIndentStrategy(IAutoIndentStrategy strategy, String contentType); + + /** + * Sets this viewer's text hover for the given content type. + * + * @param textViewerHover the new hover. <code>null</code> is a valid argument. + * @param contentType the type for which the hover is registered + */ + void setTextHover(ITextHover textViewerHover, String contentType); + + /** + * Activates the installed plug-ins. If the plug-ins are already activated + * this call has no effect. + */ + void activatePlugins(); + + /** + * Resets the installed plug-ins. If plug-ins change their state or + * behavior over the course of time, this method causes them to be set + * back to their initial state and behavior. E.g., if an <code>IUndoManager</code> + * has been installed on this text viewer, the manager's list of remembered + * text editing operations is removed. + */ + void resetPlugins(); + + + + /* ---------- listeners ------------- */ + + /** + * Adds the given viewport listener to this viewer. The listener + * is informed about all changes to the visible area of this viewer. + * If the listener is already registered with this viewer, this call + * has no effect. + * + * @param listener the listener to be added + */ + void addViewportListener(IViewportListener listener); + + /** + * Removes the given listener from this viewer's set of viewport listeners. + * If the listener is not registered with this viewer, this call has + * no effect. + * + * @param listener the listener to be removed + */ + void removeViewportListener(IViewportListener listener); + + /** + * Adds a text listener to this viewer. If the listener is already registered + * with this viewer, this call has no effect. + * + * @param listener the listener to be added + */ + void addTextListener(ITextListener listener); + + /** + * Removes the given listener from this viewer's set of text listeners. + * If the listener is not registered with this viewer, this call has + * no effect. + * + * @param listener the listener to be removed + */ + void removeTextListener(ITextListener listener); + + /** + * Adds a text input listener to this viewer. If the listener is already registered + * with this viewer, this call has no effect. + * + * @param listener the listener to be added + */ + void addTextInputListener(ITextInputListener listener); + + /** + * Removes the given listener from this viewer's set of text input listeners. + * If the listener is not registered with this viewer, this call has + * no effect. + * + * @param listener the listener to be removed + */ + void removeTextInputListener(ITextInputListener listener); + + + + /* -------------- model manipulation ------------- */ + + /** + * Sets the given document as the text viewer's model and updates the + * presentation accordingly. An approriate <code>TextEvent</code> is + * issued. This text event does not carry a related document event. + * + * @param document the viewer's new input document + */ + void setDocument(IDocument document); + + /** + * Returns the text viewer's input document. + * + * @return the viewer's input document + */ + IDocument getDocument(); + + + /* -------------- event handling ----------------- */ + + /** + * Registers an event consumer with this viewer. + * + * @param consumer the viewer's event consumer. <code>null</code> is a valid argument. + */ + void setEventConsumer(IEventConsumer consumer); + + /** + * Sets the editable mode. + * + * @param editable the editable mode + */ + void setEditable(boolean editable); + + /** + * Returns whether the shown text can be manipulated. + * + * @return the viewer's editable mode + */ + boolean isEditable(); + + + /* ----------- visible region support ------------- */ + + /** + * Sets the given document as this viewer's model and + * makes the specified region visible in the presentation. An approriate + * <code>TextEvent</code> is issued. The text event does not carry a + * related document event. This method is a convenience method for + * <code>setDocument(document);setVisibleRegion(offset, length)</code>. + * + * @param document the new input document + * @param visibleRegionOffset the offset of the visible region + * @param visibleRegionLength the length of the visible region + */ + void setDocument(IDocument document, int visibleRegionOffset, int visibleRegionLength); + + /** + * Sets the region of this viewer's document which will be visible in the presentation. + * + * @param offset the offset of the visible region + * @param length the length of the visible region + */ + void setVisibleRegion(int offset, int length); + + /** + * Resets the region of this viewer's document which is visible in the presentation. + * Afterwards, the whole document is presented again. + */ + void resetVisibleRegion(); + + /** + * Returns the current visible region of this viewer's document. + * The result may differ from the argument passed to <code>setVisibleRegion</code> + * if the document has been modified since then. + * + * @return this viewer's current visible region + */ + IRegion getVisibleRegion(); + + /** + * Returns whether a given range overlaps with the visible region of this viewer's document. + * + * @return <code>true</code> if the specified range overlaps with the visible region + */ + boolean overlapsWithVisibleRegion(int offset, int length); + + + + /* ------------- presentation manipulation ----------- */ + + /** + * Applies the color information encoded in the given text presentation. + * <code>controlRedraw</code> tells this viewer whether it should take care of + * redraw management or not. If, e.g., this call is one in a sequence of multiple + * coloring calls, it is more appropriate to explicitly control redrawing at the + * beginning and the end of the sequence. + * + * @param presentation the presentation to be applied to this viewer + * @param controlRedraw indicates whether this viewer should manage redraws + */ + void changeTextPresentation(TextPresentation presentation, boolean controlRedraw); + + /** + * Marks the currently applied text presentation as invalid. It is the viewer's + * responsibility to take any action it can to repair the text presentation. + * + * @since 2.0 + */ + void invalidateTextPresentation(); + + /** + * Applies the given color to this viewer's selection. + * + * @param color the color to be applied + */ + void setTextColor(Color color); + + /** + * Applies the given color to the specified section of this viewer. + * <code>controlRedraw</code> tells this viewer whether it should take care of + * redraw management or not. + * + * @param color the color to be applied + * @param offset the offset of the range to be colored + * @param length the length of the range to be colored + * @param controlRedraw indicates whether this viewer should manage redraws + */ + void setTextColor(Color color, int offset, int length, boolean controlRedraw); + + + /* --------- target handling and configuration ------------ */ + + /** + * Returns the text operation target of this viewer. + * + * @return the text operation target of this viewer + */ + ITextOperationTarget getTextOperationTarget(); + + /** + * Returns the find/replace operation target of this viewer. + * + * @return the find/replace operation target of this viewer + */ + IFindReplaceTarget getFindReplaceTarget(); + + /** + * Sets the string that is used as prefix when lines of the given + * content type are prefixed by the prefix text operation. + * Sets the strings that are used as prefixes when lines of the given content type + * are prefixed using the prefix text operation. The prefixes are considered equivalent. + * Inserting a prefix always inserts the defaultPrefixes[0]. + * Removing a prefix removes all of the specified prefixes. + * + * @param defaultPrefixes the prefixes to be used + * @param contentType the content type for which the prefixes are specified + * @since 2.0 + */ + void setDefaultPrefixes(String[] defaultPrefixes, String contentType); + + /** + * Sets the strings that are used as prefixes when lines of the given content type + * are shifted using the shift text operation. The prefixes are considered equivalent. + * Thus "\t" and " " can both be used as prefix characters. + * Shift right always inserts the indentPrefixes[0]. + * Shift left removes all of the specified prefixes. + * + * @param indentPrefixes the prefixes to be used + * @param contentType the content type for which the prefixes are specified + */ + void setIndentPrefixes(String[] indentPrefixes, String contentType); + + + + /* --------- selection handling -------------- */ + + /** + * Sets the selection to the specified range. + * + * @param offset the offset of the selection range + * @param length the length of the selection range + */ + void setSelectedRange(int offset, int length); + + /** + * Returns the range of the current selection in coordinates of this viewer's document. + * + * @return the current selection + */ + Point getSelectedRange(); + + /** + * Returns a selection provider dedicated to this viewer. Subsequent + * calls to this method return always the same selection provider. + * + * @return this viewer's selection provider + */ + ISelectionProvider getSelectionProvider(); + + + /* ------------- appearance manipulation --------------- */ + + /** + * Ensures that the given range is visible. + * + * @param offset the offset of the range to be revealed + * @param length the length of the range to be revealed + */ + void revealRange(int offset, int length); + + /** + * Scrolls the widget so the the given index is the line + * with the smallest line number of all visible lines. + * + * @param index the line which should become the top most line + */ + void setTopIndex(int index); + + /** + * Returns the visible line with the smallest line number. + * + * @return the number of the top most visible line + */ + int getTopIndex(); + + /** + * Returns the document offset of the upper left corner of this viewer's viewport. + * + * @return the upper left corner offset + */ + int getTopIndexStartOffset(); + + /** + * Returns the visible line with the highest line number. + * + * @return the number of the bottom most line + */ + int getBottomIndex(); + + /** + * Returns the document offset of the lower right + * corner of this viewer's viewport. This is the visible character + * with the highest character position. If the content of this viewer + * is shorter, the position of the last character of the content is returned. + * + * @return the lower right corner offset + */ + int getBottomIndexEndOffset(); + + /** + * Returns the vertical offset of the first visible line. + * + * @return the vertical offset of the first visible line + */ + int getTopInset(); +}
\ No newline at end of file diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/ITextViewerExtension.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/ITextViewerExtension.java new file mode 100644 index 000000000..ccc6f1314 --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/ITextViewerExtension.java @@ -0,0 +1,101 @@ +/********************************************************************** +Copyright (c) 2000, 2002 IBM Corp. and others. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Common Public License v1.0 +which accompanies this distribution, and is available at +http://www.eclipse.org/legal/cpl-v10.html + +Contributors: + IBM Corporation - Initial implementation +**********************************************************************/ + +package org.eclipse.jface.text; + + +import org.eclipse.swt.custom.VerifyKeyListener; +import org.eclipse.swt.widgets.Control; + + +/** + * Extension interface for <code>ITextViewer</code>. Extends <code>ITextViewer</code> with + * <ul> + * <li> a replacement of the event consumer mechanism (methods dealing with <code>VerifyKeyListener</code>) + * <li> access to the control of this viewer + * <li> marked region support a la emacs + * <li> control of the viewer's redraw behavior (@see #setRedraw) + * <li> access to the viewer's rewrite target + * </ul> + * + * @since 2.0 + */ +public interface ITextViewerExtension { + + /** + * Inserts the verify key listener at the beginning of the viewer's + * list of verify key listeners. If the listener is already registered + * with the viewer this call moves the listener to the beginnng of + * the list. + * + * @param listener the listener to be inserted + */ + void prependVerifyKeyListener(VerifyKeyListener listener); + + /** + * Appends a verify key listener to the viewer's list of verify + * key listeners. If the listener is already registered with the viewer + * this call moves the listener to the end of the list. + * + * @param listener the listener to be added + */ + void appendVerifyKeyListener(VerifyKeyListener listener); + + /** + * Removes the verify key listener from the viewer's list of verify key listeners. + * If the listener is not registered with this viewer, this call has no effect. + * + * @param listener the listener to be removed + */ + void removeVerifyKeyListener(VerifyKeyListener listener); + + /** + * Returns the control of this viewer. + * + * @return the control of this viewer + */ + Control getControl(); + + /** + * Sets or clears the mark. If offset is <code>-1</code>, the mark is cleared. + * If a mark is set and the selection is empty, cut and copy actions performed on this + * text viewer peform on the region limited by the positions of the mark and the cursor. + * + * @param offset the offset of the mark + */ + void setMark(int offset); + + /** + * Returns the mark position, <code>-1</code> if mark is not set. + * + * @return the mark position or <code>-1</code> if no mark is set + */ + int getMark(); + + /** + * Enables/disables the redrawing of this text viewer. This temporarily disconnects + * the viewer from its underlying StyledText widget. While being disconnected only + * the viewer's selection may be changed using <code>setSelectedRange</code>. + * Any direct manipulation of the widget as well as calls to methods that change the viewer's + * presentation state (such as enabling the segmented view) are not allowed. + * When redrawing is disabled the viewer does not send out any selection or + * view port change notification. When redrawing is enabled again, a selection + * change notification is sent out for the selected range and this range is revealed. + */ + void setRedraw(boolean redraw); + + /** + * Returns the viewer's rewrite target. + * + * @return the viewer's rewrite target + */ + IRewriteTarget getRewriteTarget(); +} diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/IUndoManager.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/IUndoManager.java new file mode 100644 index 000000000..1a372371d --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/IUndoManager.java @@ -0,0 +1,86 @@ +package org.eclipse.jface.text; + +/* + * (c) Copyright IBM Corp. 2000, 2001. + * All Rights Reserved. + */ + + +/** + * An undo manager is connected to at most one text viewer. + * It monitors the text viewer and keeps a history of the + * changes applied to the viewer. The undo manager groups those + * changes into user interactions which on an undo request are + * rolled back in one atomic change. <p> + * Clients may implement this interface or use the standard + * implementation <code>DefaultUndoManager</code>. + */ +public interface IUndoManager { + + /** + * Connects this undo manager to the given text viewer. + * + * @param viewer the viewer the undo manager is connected to + */ + void connect(ITextViewer viewer); + + /** + * Disconnects this undo manager from its text viewer. + * If this undo manager hasn't been connected before this + * operation has no effect. + */ + void disconnect(); + + /** + * Signals the undo manager that all subsequent changes until + * <code>endCompoundChange</code> is called are to be undone in one piece. + */ + void beginCompoundChange(); + + /** + * Signals the undo manager that the sequence of changes which started with + * <code>beginCompoundChange</code> has been finished. All subsequent changes + * are considered to be individually undoable. + */ + void endCompoundChange(); + + /** + * Resets the history of the undo manager. After that call, + * there aren't any undoable or redoable text changes. + */ + void reset(); + + /** + * The given parameter determines the maximal length of the history + * remembered by the undo manager. + * + * @param undoLevel the length of this undo manager's history + */ + void setMaximalUndoLevel(int undoLevel); + + /** + * Returns whether at least one text change can be rolled back. + * + * @return <code>true</code> if at least one text change can be rolled back + */ + boolean undoable(); + + /** + * Returns whether at least one text change can be repeated. A text change + * can be repeated only if it was executed and rolled back. + * + * @return <code>true</code> if at least on text change can be repeated + */ + boolean redoable(); + + /** + * Rolls back the most recently executed text change. + */ + void undo(); + + /** + * Repeats the most recently rolled back text change. + */ + void redo(); + +}
\ No newline at end of file diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/IViewportListener.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/IViewportListener.java new file mode 100644 index 000000000..8677db812 --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/IViewportListener.java @@ -0,0 +1,24 @@ +package org.eclipse.jface.text; + +/* + * (c) Copyright IBM Corp. 2000, 2001. + * All Rights Reserved. + */ + + +/** + * Registered with a text viewer, viewport listeners are + * informed about changes of a text viewer's viewport. The view port is that + * portion of the viewer's document which is visible in the viewer. <p> + * Clients may implement this interface. + * + * @see ITextViewer + */ +public interface IViewportListener { + + /** + * Informs about viewport changes. The given vertical position + * is the new vertical scrolling offset measured in pixels. + */ + void viewportChanged(int verticalOffset); +} diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/IWidgetTokenKeeper.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/IWidgetTokenKeeper.java new file mode 100644 index 000000000..a11f3668d --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/IWidgetTokenKeeper.java @@ -0,0 +1,34 @@ +/********************************************************************** +Copyright (c) 2000, 2002 IBM Corp. and others. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Common Public License v1.0 +which accompanies this distribution, and is available at +http://www.eclipse.org/legal/cpl-v10.html + +Contributors: + IBM Corporation - Initial implementation +**********************************************************************/ + +package org.eclipse.jface.text; + +/** + * A widget token keeper may require a widget token from an + * <code>IWidgetTokenOwner</code> and release the token + * to the owner after usage. A widget token owner may request + * the token from the token keeper. The keeper may deny that. + * + * @since 2.0 + */ +public interface IWidgetTokenKeeper { + + /** + * The given widget token owner requests the widget token back from + * this token keeper. Returns <code>true</code> if the token is released + * by this token keeper. Note, the keeper must not call + * <code>releaseWidgetToken(IWidgetTokenKeeper)</code> explicitly. + * + * @param owner the token owner + * @return <code>true</code> if token has been released <code>false</code> otherwise + */ + boolean requestWidgetToken(IWidgetTokenOwner owner); +} diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/IWidgetTokenOwner.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/IWidgetTokenOwner.java new file mode 100644 index 000000000..6c82bc7fb --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/IWidgetTokenOwner.java @@ -0,0 +1,45 @@ +/********************************************************************** +Copyright (c) 2000, 2002 IBM Corp. and others. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Common Public License v1.0 +which accompanies this distribution, and is available at +http://www.eclipse.org/legal/cpl-v10.html + +Contributors: + IBM Corporation - Initial implementation +**********************************************************************/ + +package org.eclipse.jface.text; + + +/** + * A widget token must be aquired in order to display + * information in a temporary window. The intent behind this concept is that + * only one temporary window should be presented at any moment in time and + * also to avoid overlapping temporary windows. + * + * @since 2.0 + */ +public interface IWidgetTokenOwner { + + /** + * Requests the widget token from this token owner. Returns + * <code>true</code> if the token has been aquired or is + * already owned by the requester. This method is non-blocking. + * + * @param requester the token requester + * @return <code>true</code> if requester aquires the token, + * <code>false</code> otherwise + */ + boolean requestWidgetToken(IWidgetTokenKeeper requester); + + /** + * The given token keeper releases the token to this + * token owner. If the token has previously not been held + * by the given token keeper, nothing happens. This + * method is non-blocking. + * + * @param tokenKeeper the token keeper + */ + void releaseWidgetToken(IWidgetTokenKeeper tokenKeeper); +} diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/JFaceTextMessages.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/JFaceTextMessages.java new file mode 100644 index 000000000..62940e97f --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/JFaceTextMessages.java @@ -0,0 +1,26 @@ +/* + * (c) Copyright IBM Corp. 2000, 2001. + * All Rights Reserved. + */ +package org.eclipse.jface.text; + +import java.util.MissingResourceException; +import java.util.ResourceBundle; + +class JFaceTextMessages { + + private static final String RESOURCE_BUNDLE= "org.eclipse.jface.text.JFaceTextMessages";//$NON-NLS-1$ + + private static ResourceBundle fgResourceBundle= ResourceBundle.getBundle(RESOURCE_BUNDLE); + + private JFaceTextMessages() { + } + + public static String getString(String key) { + try { + return fgResourceBundle.getString(key); + } catch (MissingResourceException e) { + return "!" + key + "!";//$NON-NLS-2$ //$NON-NLS-1$ + } + } +} diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/JFaceTextMessages.properties b/org.eclipse.jface.text/src/org/eclipse/jface/text/JFaceTextMessages.properties new file mode 100644 index 000000000..295194358 --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/JFaceTextMessages.properties @@ -0,0 +1,28 @@ +######################################### +# (c) Copyright IBM Corp. 2000, 2001. +# All Rights Reserved. +######################################### + + +TextViewer.error.bad_location.WidgetCommand.setEvent=TextViewer.WidgetCommand.setEvent: BadLocationException +TextViewer.error.bad_location.findAndSelect=TextViewer.findAndSelect: BadLocationException +TextViewer.error.bad_location.getBottomIndex=TextViewer.getBottomIndex: BadLocationException +TextViewer.error.bad_location.getBottomIndexEndOffset=TextViewer.getBottomIndexEndOffset: BadLocationException +TextViewer.error.bad_location.getFirstCompleteLineOfRegion=TextViewer.getFirstCompleteLineOfRegion: BadLocationException +TextViewer.error.bad_location.getTopIndex=TextViewer.getTopIndex: BadLocationException +TextViewer.error.bad_location.getTopIndexStartOffset=TextViewer.getTopIndexStartOffset: BadLocationException +TextViewer.error.bad_location.selectContentTypePlugin=TextViewer.selectContentTypePlugin: BadLocationException +TextViewer.error.bad_location.setTopIndex_1=TextViewer.setTopIndex: BadLocationException +TextViewer.error.bad_location.setTopIndex_2=TextViewer.setTopIndex: BadLocationException +TextViewer.error.bad_location.shift_1=TextViewer.shift: BadLocationException +TextViewer.error.bad_location.shift_2=TextViewer.shift: BadLocationException +TextViewer.error.bad_location.verifyText=TextViewer.verifyText: BadLocationException +TextViewer.error.invalid_range=Invalid range argument +TextViewer.error.invalid_visible_region_1=Invalid visible region argument +TextViewer.error.invalid_visible_region_2=Invalid visible region argument + +InfoPopup.info_delay_timer_name=AdditionalInfo Delay + +ContentAssistant.assist_delay_timer_name=AutoAssist Delay + +AbstractHoverInformationControlManager.hover.restarter=Hover Restart Delay diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/MarkSelection.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/MarkSelection.java new file mode 100644 index 000000000..83cb0b5e1 --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/MarkSelection.java @@ -0,0 +1,69 @@ +/********************************************************************** +Copyright (c) 2000, 2002 IBM Corp. and others. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Common Public License v0.5 +which accompanies this distribution, and is available at +http://www.eclipse.org/legal/cpl-v05.html + +Contributors: + IBM Corporation - Initial implementation +**********************************************************************/ + +package org.eclipse.jface.text; + + +/** + * Default implementation of <code>IMarkSelection</code>. + * @since 2.0 + */ +public class MarkSelection implements IMarkSelection { + + /** The marked document. */ + private final IDocument fDocument; + /** The offset of the mark selection. */ + private final int fOffset; + /** The length of the mark selection. */ + private final int fLength; + + /** + * Creates a MarkSelection. + * + * @param document the marked document + * @param offset the offset of the mark + * @param length the length of the mark, may be negative if caret before offset + */ + public MarkSelection(IDocument document, int offset, int length) { + fDocument= document; + fOffset= offset; + fLength= length; + } + + /* + * @see IMarkSelection#getDocument() + */ + public IDocument getDocument() { + return fDocument; + } + + /* + * @see IMarkSelection#getOffset() + */ + public int getOffset() { + return fOffset; + } + + /* + * @see IMarkSelection#getLength() + */ + public int getLength() { + return fLength; + } + + /* + * @see ISelection#isEmpty() + */ + public boolean isEmpty() { + return fLength == 0; + } + +} diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/PropagatingFontFieldEditor.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/PropagatingFontFieldEditor.java new file mode 100644 index 000000000..e31916724 --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/PropagatingFontFieldEditor.java @@ -0,0 +1,132 @@ +/********************************************************************** +Copyright (c) 2000, 2002 IBM Corp. and others. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Common Public License v1.0 +which accompanies this distribution, and is available at +http://www.eclipse.org/legal/cpl-v10.html + +Contributors: + IBM Corporation - Initial implementation +**********************************************************************/ + +package org.eclipse.jface.text; + + +import org.eclipse.swt.graphics.FontData; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; + +import org.eclipse.jface.preference.FontFieldEditor; +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.jface.preference.PreferenceConverter; +import org.eclipse.jface.util.IPropertyChangeListener; +import org.eclipse.jface.util.PropertyChangeEvent; + + +/** + * This font field editor implements chaining between a source preference + * store and a target preference store. Any time the source preference + * store changes, the change is propagated to the target store. Propagation + * means that the actual value stored in the source store is set as default + * value in the target store. If the target store does not contain a value + * other than the default value, the new default value is immediately + * effective. + * + * @see FontFieldEditor + * @since 2.0 + */ +public class PropagatingFontFieldEditor extends FontFieldEditor { + + /** The editor's parent widget */ + private Composite fParent; + /** The representation of the default font choice */ + private String fDefaultFontLabel; + + /** + * Creates a new font field editor with the given parameters. + * + * @param name the editor's name + * @param labelText the text shown as editor description + * @param parent the editor's parent widget + * @param defaultFontLabel the label shown in the editor value field when the default value should be taken + */ + public PropagatingFontFieldEditor(String name, String labelText, Composite parent, String defaultFontLabel) { + super(name, labelText, parent); + fParent= parent; + fDefaultFontLabel= defaultFontLabel == null ? "" : defaultFontLabel; //$NON-NLS-1$ + } + + /* + * @see FontFieldEditor#doLoad() + */ + protected void doLoad() { + if (getPreferenceStore().isDefault(getPreferenceName())) + loadDefault(); + super.doLoad(); + checkForDefault(); + } + + /* + * @see FontFieldEditor#doLoadDefault() + */ + protected void doLoadDefault() { + super.doLoadDefault(); + checkForDefault(); + } + + /** + * Checks whether this editor presents the default value "inheritated" + * from the workbench rather than its own font. + */ + private void checkForDefault() { + if (presentsDefaultValue()) { + Control c= getValueControl(fParent); + if (c instanceof Label) + ((Label) c).setText(fDefaultFontLabel); + } + } + + /** + * Propagates the font set in the source store to the + * target store using the given keys. + * + * @param source the store from which to read the text font + * @param sourceKey the key under which the font can be found + * @param target the store to which to propagate the font + * @param targetKey the key under which to store the font + */ + private static void propagateFont(IPreferenceStore source, String sourceKey, IPreferenceStore target, String targetKey) { + FontData fd= PreferenceConverter.getFontData(source, sourceKey); + if (fd != null) { + boolean isDefault= target.isDefault(targetKey); // save old state! + PreferenceConverter.setDefault(target, targetKey, fd); + if (isDefault) { + // restore old state + target.setToDefault(targetKey); + } + } + } + + /** + * Starts the propagation of the font preference stored in the source preference + * store under the source key to the target preference store using the target + * preference key. + * + * @param source the source preference store + * @param sourceKey the key to be used in the source preference store + * @param target the target preference store + * @param targetKey the key to be used in the target preference store + */ + public static void startPropagate(final IPreferenceStore source, final String sourceKey, final IPreferenceStore target, final String targetKey) { + source.addPropertyChangeListener(new IPropertyChangeListener() { + public void propertyChange(PropertyChangeEvent event) { + if (sourceKey.equals(event.getProperty())) + propagateFont(source, sourceKey, target, targetKey); + } + }); + + propagateFont(source, sourceKey, target, targetKey); + } +} + diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/TextAttribute.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/TextAttribute.java new file mode 100644 index 000000000..1a1e1fff9 --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/TextAttribute.java @@ -0,0 +1,121 @@ +/********************************************************************** +Copyright (c) 2000, 2002 IBM Corp. and others. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Common Public License v1.0 +which accompanies this distribution, and is available at +http://www.eclipse.org/legal/cpl-v10.html + +Contributors: + IBM Corporation - Initial implementation +**********************************************************************/ + +package org.eclipse.jface.text; + + +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Color; + + +/** + * Description of textual attributes such as color and style. + * Text attributes are considered value objects. + */ +public class TextAttribute { + + /** Foreground color */ + private Color foreground; + + /** Background color */ + private Color background; + + /** The text style */ + private int style; + + /** + * Creates a text attribute with the given colors and style. + * + * @param foreground the foreground color + * @param background the background color + * @param style the style + */ + public TextAttribute(Color foreground, Color background, int style) { + this.foreground= foreground; + this.background= background; + this.style= style; + } + + /** + * Creates a text attribute for the given foreground color, no background color and + * with the SWT normal style. + * + * @param foreground the foreground color + */ + public TextAttribute(Color foreground) { + this(foreground, null, SWT.NORMAL); + } + + /* + * @see Object#equals + */ + public boolean equals(Object object) { + + if (object == this) + return true; + + if (!(object instanceof TextAttribute)) + return false; + + TextAttribute a= (TextAttribute) object; + return (a.style == style && equals(a.foreground, foreground) && equals(a.background, background)); + } + + /** + * Returns whether the two given objects are equal. + * + * @param o1 the first object, can be <code>null</code> + * @param o2 the second object, can be <code>null</code> + * @return <code>true</code> if the given objects are equals + * @since 2.0 + */ + private boolean equals(Object o1, Object o2) { + if (o1 != null) + return o1.equals(o2); + return (o2 == null); + } + + /* + * @see Object#hashCode + */ + public int hashCode() { + int foregroundHash= foreground == null ? 0 : foreground.hashCode(); + int backgroundHash= background == null ? 0 : background.hashCode(); + return (foregroundHash << 24) | (backgroundHash << 16) | style; + } + + /** + * Returns the attribute's foreground color. + * + * @return the attribute's foreground color + */ + public Color getForeground() { + return foreground; + } + + /** + * Returns the attribute's background color. + * + * @return the attribute's background color + */ + public Color getBackground() { + return background; + } + + /** + * Returns the attribute's style. + * + * @return the attribute's style + */ + public int getStyle() { + return style; + } +}
\ No newline at end of file diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/TextEvent.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/TextEvent.java new file mode 100644 index 000000000..22c1d3009 --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/TextEvent.java @@ -0,0 +1,119 @@ +/********************************************************************** +Copyright (c) 2000, 2002 IBM Corp. and others. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Common Public License v1.0 +which accompanies this distribution, and is available at +http://www.eclipse.org/legal/cpl-v10.html + +Contributors: + IBM Corporation - Initial implementation +**********************************************************************/ + +package org.eclipse.jface.text; + + +/** + * This event is sent to implementers of <code>ITextListener</code>. It represents a + * change applied to text viewer. The change is specified as a replace command using + * offset, length, inserted text, and replaced text. The text viewer issues a text event + * after the viewer has been changed either in response to a change of the viewer's document + * or when the viewer's visual content has been changed. In the first case, the text event + * also carries the original document event. Depending on the viewer's presentation mode, + * the text event coordinates are different from the document event's coordinates. + * Client's other than text viewer's don't create instances of this class. + * + * @see ITextListener + * @see ITextViewer + * @see DocumentEvent + */ +public class TextEvent { + + /** Start offset of the change */ + private int fOffset; + /** The length of the change */ + private int fLength; + /** Inserted text */ + private String fText; + /** Replaced text */ + private String fReplacedText; + /** The original document event, may by null */ + private DocumentEvent fDocumentEvent; + /** + * The redraw state of the viewer issuing this event + * @since 2.0 + */ + private boolean fViewerRedrawState; + + /** + * Creates a new <code>TextEvent</code> based on the specification. + * + * @param offset the offset + * @param length the length + * @param replacedText the replaced text + * @param event the associated document event or <code>null</code> if none + * @param viewerRedrawState the redraw state of the viewer + */ + protected TextEvent(int offset, int length, String text, String replacedText, DocumentEvent event, boolean viewerRedrawState) { + fOffset= offset; + fLength= length; + fText= text; + fReplacedText= replacedText; + fDocumentEvent= event; + fViewerRedrawState= viewerRedrawState; + } + + /** + * Returns the offset of the event. + * + * @return the offset of the event + */ + public int getOffset() { + return fOffset; + } + + /** + * Returns the length of the event. + * + * @return the length of the event + */ + public int getLength() { + return fLength; + } + + /** + * Returns the text of the event. + * + * @return the text of the event + */ + public String getText() { + return fText; + } + + /** + * Returns the text replaced by this event. + * + * @return the text replaced by this event + */ + public String getReplacedText() { + return fReplacedText; + } + + /** + * Returns the corresponding document event that caused the viewer change + * + * @return the corresponding document event, <code>null</code> if a visual change only + */ + public DocumentEvent getDocumentEvent() { + return fDocumentEvent; + } + + /** + * Returns the viewer's redraw state. + * + * @return <code>true</code> if the viewer's redraw state is <code>true</code> + * @since 2.0 + */ + public boolean getViewerRedrawState() { + return fViewerRedrawState; + } +}
\ No newline at end of file diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/TextPresentation.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/TextPresentation.java new file mode 100644 index 000000000..1c20dbb68 --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/TextPresentation.java @@ -0,0 +1,398 @@ +/********************************************************************** +Copyright (c) 2000, 2002 IBM Corp. and others. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Common Public License v1.0 +which accompanies this distribution, and is available at +http://www.eclipse.org/legal/cpl-v10.html + +Contributors: + IBM Corporation - Initial implementation +**********************************************************************/ + +package org.eclipse.jface.text; + + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.NoSuchElementException; + +import org.eclipse.swt.custom.StyleRange; +import org.eclipse.swt.custom.StyledText; + + +/** + * Describes the presentation styles for a section of an indexed text + * such as a document or string. A text presentation defines a default style + * for the whole section and in addition style differences for individual + * subsections. Text presentations can be narrowed down to a particular + * result window. All methods are result window aware, i.e. ranges outside + * the result window are always ignored.<p> + * All iterators provided by a text presentation assume that they enumerate + * non overlapping, consequtive ranges inside the default range. Thus, all + * these iterators do not include the default range. The default style range + * must be explicitly asked for using <code>getDefaultStyleRange</code>. + */ +public class TextPresentation { + + /** + * Applies the given presentation to the given text widget. Helper method. + * + * @param presentation the style information + * @param the widget to which to apply the style information + * @since 2.0 + */ + public static void applyTextPresentation(TextPresentation presentation, StyledText text) { + + StyleRange[] ranges= new StyleRange[presentation.getDenumerableRanges()]; + + int i= 0; + Iterator e= presentation.getAllStyleRangeIterator(); + while (e.hasNext()) + ranges[i++]= (StyleRange) e.next(); + + text.setStyleRanges(ranges); + } + + + + + /** + * Enumerates all the <code>StyleRange</code>s included in the presentation. + */ + class FilterIterator implements Iterator { + + protected int fIndex; + protected int fLength; + protected boolean fSkipDefaults; + protected IRegion fWindow; + + /** + * <code>skipDefaults</code> tells the enumeration to skip all those style ranges + * which define the same style as the presentation's default style range. + */ + protected FilterIterator(boolean skipDefaults) { + + fSkipDefaults= skipDefaults; + + fWindow= fResultWindow; + fIndex= getFirstIndexInWindow(fWindow); + fLength= getFirstIndexAfterWindow(fWindow); + + if (fSkipDefaults) + computeIndex(); + } + + /* + * @see Iterator#next + */ + public Object next() { + try { + StyleRange r= (StyleRange) fRanges.get(fIndex++); + return createWindowRelativeRange(fWindow, r); + } catch (ArrayIndexOutOfBoundsException x) { + throw new NoSuchElementException(); + } finally { + if (fSkipDefaults) + computeIndex(); + } + } + + /* + * @see Iterator#hasNext + */ + public boolean hasNext() { + return fIndex < fLength; + } + + /* + * @see Iterator#remove + */ + public void remove() { + throw new UnsupportedOperationException(); + } + + /** + * Returns whether the given object should be skipped. + * + * @return <code>true</code> if teh object should be skipped by the iterator + */ + protected boolean skip(Object o) { + StyleRange r= (StyleRange) o; + return r.similarTo(fDefaultRange); + } + + /** + * Computes the index of the styled range that is the next to be enumerated. + */ + protected void computeIndex() { + while (fIndex < fLength && skip(fRanges.get(fIndex))) + ++ fIndex; + } + }; + + /** The syle information for the range covered by the whole presentation */ + private StyleRange fDefaultRange; + /** The member ranges of the presentation */ + private ArrayList fRanges= new ArrayList(); + /** A clipping region against which the presentation can be clipped when asked for results */ + private IRegion fResultWindow; + + + /** + * Creates a new empty text presentation. + */ + public TextPresentation() { + } + + /** + * Sets the result window for this presentation. When dealing with + * this presentation all ranges which are outside the result window + * are ignored. For example, the size of the presentation is 0 + * when there is no range inside the window even if there are ranges + * outside the window. All methods are aware of the result window. + * + * @param resultWindow the result window + */ + public void setResultWindow(IRegion resultWindow) { + fResultWindow= resultWindow; + } + + /** + * Set the default style range of this presentation. + * The default style range defines the overall area covered + * by this presentation and its style information. + * + * @param range the range decribing the default region + */ + public void setDefaultStyleRange(StyleRange range) { + fDefaultRange= range; + } + + /** + * Returns this presentation's default style range. The returned <code>StyleRange</code> + * is relative to the start of the result window. + * + * @return this presentation's default style range + */ + public StyleRange getDefaultStyleRange() { + return createWindowRelativeRange(fResultWindow, fDefaultRange); + } + + /** + * Add the given range to the presentation. The range must be a + * subrange of the presentation's default range. + * + * @param range the range to be added + */ + public void addStyleRange(StyleRange range) { + checkConsistency(range); + fRanges.add(range); + } + + /** + * Checks whether the given range is a subrange of the presentation's + * default style range. + * + * @param range the range to be checked + * @exception IllegalArgumentAxception if range is not a subrange of the presentation's default range + */ + private void checkConsistency(StyleRange range) { + + if (range == null) + throw new IllegalArgumentException(); + + if (fDefaultRange != null) { + + if (range.start < fDefaultRange.start) + range.start= fDefaultRange.start; + + int defaultEnd= fDefaultRange.start + fDefaultRange.length; + int end= range.start + range.length; + if (end > defaultEnd) + range.length -= (defaultEnd - end); + } + } + + /** + * Returns the index of the first range which overlaps with the + * specified window. + * + * @param window the window to be used for searching + * @return the index of the first range overlapping with the window + */ + private int getFirstIndexInWindow(IRegion window) { + int i= 0; + if (window != null) { + int start= window.getOffset(); + while (i < fRanges.size()) { + StyleRange r= (StyleRange) fRanges.get(i++); + if (r.start + r.length > start) { + -- i; + break; + } + } + } + return i; + } + + /** + * Returns the index of the first range which comes after the specified window and does + * not overlap with this window. + * + * @param window the window to be used for searching + * @return the index of the first range behind the window and not overlapping with the window + */ + private int getFirstIndexAfterWindow(IRegion window) { + int i= fRanges.size(); + if (window != null) { + int end= window.getOffset() + window.getLength(); + while (i > 0) { + StyleRange r= (StyleRange) fRanges.get(--i); + if (r.start < end) { + ++ i; + break; + } + } + } + return i; + } + + /** + * Returns a style range which is relative to the specified window and + * appropriately clipped if necessary. The original style range is not + * modified. + * + * @param window the reference window + * @param range the absolute range + * @return the window relative range based on the absolute range + */ + private StyleRange createWindowRelativeRange(IRegion window, StyleRange range) { + if (window == null || range == null) + return range; + + int start= range.start - window.getOffset(); + if (start < 0) + start= 0; + + int rangeEnd= range.start + range.length; + int windowEnd= window.getOffset() + window.getLength(); + int end= (rangeEnd > windowEnd ? windowEnd : rangeEnd); + end -= window.getOffset(); + + StyleRange newRange= (StyleRange) range.clone(); + newRange.start= start; + newRange.length= end - start; + return newRange; + } + + + /** + * Returns an iterator which enumerates all style ranged which define a style + * different from the presentation's default style range. The default style range + * is not enumerated. + * + * @return a style range interator + */ + public Iterator getNonDefaultStyleRangeIterator() { + return new FilterIterator(fDefaultRange != null); + } + + /** + * Returns an iterator which enumerates all style ranges of this presentation + * except the default style range. The returned <code>StyleRange</code>s + * are relative to the start of the presentation's result window. + * + * @return a style range iterator + */ + public Iterator getAllStyleRangeIterator() { + return new FilterIterator(false); + } + + /** + * Returns whether this collection contains any style range including + * the default style range. + * + * @return <code>true</code> if there is no style range in this presentation + */ + public boolean isEmpty() { + return (fDefaultRange == null && getDenumerableRanges() == 0); + } + + /** + * Returns the number of style ranges in the presentation not counting the default + * style range. + * + * @return the number of style ranges in the presentation excluding the default style range + */ + public int getDenumerableRanges() { + int size= getFirstIndexAfterWindow(fResultWindow) - getFirstIndexInWindow(fResultWindow); + return (size < 0 ? 0 : size); + } + + /** + * Returns the style range with the smallest offset ignoring the default style range or null + * if the presentation is empty. + * + * @return the style range with the smalled offset different from the default style range + */ + public StyleRange getFirstStyleRange() { + try { + + StyleRange range= (StyleRange) fRanges.get(getFirstIndexInWindow(fResultWindow)); + return createWindowRelativeRange(fResultWindow, range); + + } catch (NoSuchElementException x) { + } + + return null; + } + + /** + * Returns the style range with the highest offset ignoring the default style range. + * + * @return the style range with the highest offset different from the default style range + */ + public StyleRange getLastStyleRange() { + try { + + StyleRange range= (StyleRange) fRanges.get(getFirstIndexAfterWindow(fResultWindow) - 1); + return createWindowRelativeRange(fResultWindow, range); + + } catch (NoSuchElementException x) { + } + + return null; + } + + /** + * Returns the coverage of this presentation as clipped by the presentation's + * result window. + * + * @return the coverage of this presentation + */ + public IRegion getCoverage() { + + if (fDefaultRange != null) { + StyleRange range= getDefaultStyleRange(); + return new Region(range.start, range.length); + } + + StyleRange first= getFirstStyleRange(); + StyleRange last= getLastStyleRange(); + + if (first == null || last == null) + return null; + + return new Region(first.start, last.start - first. start + last.length); + } + + /** + * Clears this presentation by resetting all applied changes. + * @since 2.0 + */ + public void clear() { + fDefaultRange= null; + fResultWindow= null; + fRanges.clear(); + } +}
\ No newline at end of file diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/TextSelection.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/TextSelection.java new file mode 100644 index 000000000..a4b60a41e --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/TextSelection.java @@ -0,0 +1,175 @@ +package org.eclipse.jface.text; + + +/* + * (c) Copyright IBM Corp. 2000, 2001. + * All Rights Reserved. + */ + + +/** + * Standard implementation of <code>ITextSelection</code>. + * Makes atvantage of the weak contract of correctness of its + * interface. If generated from a selection provider, it only + * remembers its offset and length and computes the remaining + * information on request. + */ +public class TextSelection implements ITextSelection { + + private final static ITextSelection NULL= new TextSelection(); + + /** + * Returns a shared instance of an empty text selection. + */ + public static ITextSelection emptySelection() { + return NULL; + } + + /** Document which delivers the data of the selection */ + private IDocument fDocument; + /** Offset of the selection */ + private int fOffset; + /** Length of the selection */ + private int fLength; + + + /** + * Creates an empty text selection. + */ + private TextSelection() { + this(null, -1, -1); + } + + /** + * Creates a text selection for the given range. This + * selection object describes generically a text range and + * is intended to be an argument for the <code>setSelection</code> + * method of selection providers. + * + * @param offset the offset of the range + * @param length the length of the range + */ + public TextSelection(int offset, int length) { + this(null, offset, length); + } + + /** + * Creates a text selection for the given range of the given document. + * This selection object is created by selection providers in responds + * <code>getSelection</code>. + * + * @param document the document whose text range is selected in a viewer + * @param offset the offset of the selected range + * @param length the length of the selected range + */ + public TextSelection(IDocument document, int offset, int length) { + fDocument= document; + fOffset= offset; + fLength= length; + } + + /** + * Returns true if the offset and length are smaller than 0. + * A selection of length 0, is a valid text selection as it + * describes, e.g., the cursor position in a viewer. + */ + /* + * @see ISelection#isEmpty + */ + public boolean isEmpty() { + return fOffset < 0 || fLength < 0; + } + + /* + * @see ITextSelection#getOffset + */ + public int getOffset() { + return fOffset; + } + + /* + * @see ITextSelection#getLength + */ + public int getLength() { + return fLength; + } + + /* + * @see ITextSelection#getStartLine + */ + public int getStartLine() { + + try { + if (fDocument != null) + return fDocument.getLineOfOffset(fOffset); + } catch (BadLocationException x) { + } + + return -1; + } + + /* + * @see ITextSelection#getEndLine + */ + public int getEndLine() { + try { + if (fDocument != null) + return fDocument.getLineOfOffset(fOffset + fLength - 1); + } catch (BadLocationException x) { + } + + return -1; + } + + /* + * @see ITextSelection#getText + */ + public String getText() { + try { + if (fDocument != null) + return fDocument.get(fOffset, fLength); + } catch (BadLocationException x) { + } + + return null; + } + + /* + * @see java.lang.Object#equals(Object) + */ + public boolean equals(Object obj) { + if (obj == this) + return true; + + if (obj == null || getClass() != obj.getClass()) + return false; + + TextSelection s= (TextSelection) obj; + boolean sameRange= (s.fOffset == fOffset && s.fLength == fLength); + if (sameRange) { + + if (s.fDocument == null && fDocument == null) + return true; + if (s.fDocument == null || fDocument == null) + return false; + + try { + String sContent= s.fDocument.get(fOffset, fLength); + String content= fDocument.get(fOffset, fLength); + return sContent.equals(content); + } catch (BadLocationException x) { + } + } + + return false; + } + + /* + * @see java.lang.Object#hashCode() + */ + public int hashCode() { + int low= fDocument != null ? fDocument.hashCode() : 0; + return (fOffset << 24) | (fLength << 16) | low; + } +} + diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/TextViewer.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/TextViewer.java new file mode 100644 index 000000000..6563014ad --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/TextViewer.java @@ -0,0 +1,3432 @@ +/********************************************************************** +Copyright (c) 2000, 2002 IBM Corp. and others. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Common Public License v1.0 +which accompanies this distribution, and is available at +http://www.eclipse.org/legal/cpl-v10.html + +Contributors: + IBM Corporation - Initial implementation +**********************************************************************/ + +package org.eclipse.jface.text; + + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.LineBackgroundEvent; +import org.eclipse.swt.custom.LineBackgroundListener; +import org.eclipse.swt.custom.StyleRange; +import org.eclipse.swt.custom.StyledText; +import org.eclipse.swt.custom.ST; +import org.eclipse.swt.custom.VerifyKeyListener; +import org.eclipse.swt.events.ControlEvent; +import org.eclipse.swt.events.ControlListener; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.events.DisposeListener; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.KeyListener; +import org.eclipse.swt.events.MouseAdapter; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.MouseMoveListener; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.events.TraverseEvent; +import org.eclipse.swt.events.TraverseListener; +import org.eclipse.swt.events.VerifyEvent; +import org.eclipse.swt.events.VerifyListener; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.printing.PrintDialog; +import org.eclipse.swt.printing.Printer; +import org.eclipse.swt.printing.PrinterData; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.ScrollBar; + +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.ISelectionProvider; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.jface.viewers.Viewer; + + + +/** + * SWT based implementation of <code>ITextViewer</code>. Once the viewer and its SWT control + * have been created the viewer can only indirectly be disposed by disposing its SWT control.<p> + * Clients are supposed to instantiate a text viewer and subsequently to communicate with it + * exclusively using the <code>ITextViewer</code> interface or any of the implemented extension + * interfaces. <p> + * A text viewer serves as text operation target. It only partially supports the external control of + * the enable state of its text operations. A text viewer is also a widget token owner. Anything that + * wants to display an overlay window on top of a text viewer should implement the + * <code>IWidgetTokenKeeper</code> interface and participate in the widget token negotiation between + * the text viewer and all its potential widget token keepers.<p> + * Clients should no subclass this class as it is rather likely that subclasses will be broken by + * future releases. + * + * @see ITextViewer + */ +public class TextViewer extends Viewer implements + ITextViewer, ITextViewerExtension, + ITextOperationTarget, ITextOperationTargetExtension, + IWidgetTokenOwner { + + /** Internal flag to indicate the debug state. */ + public static boolean TRACE_ERRORS= false; + + /** + * Represents a replace command that brings the text viewer's text widget + * back in sync with text viewer's document after the document has been changed. + */ + protected class WidgetCommand { + + public DocumentEvent event; + public int start, length; + public String text, preservedText; + + /** + * Translates a document event into the presentation coordinates of this text viewer. + * + * @param e the event to be translated + */ + public void setEvent(DocumentEvent e) { + + event= e; + + start= e.getOffset(); + length= e.getLength(); + text= e.getText(); + + if (length != 0) { + try { + preservedText= e.getDocument().get(e.getOffset(), e.getLength()); + } catch (BadLocationException x) { + preservedText= null; + if (TRACE_ERRORS) + System.out.println(JFaceTextMessages.getString("TextViewer.error.bad_location.WidgetCommand.setEvent")); //$NON-NLS-1$ + } + } else + preservedText= null; + } + }; + + /** + * Connects a text double click strategy to this viewer's text widget. + * Calls the double click strategy when the mouse has been double clicked + * inside the text editor. + */ + class TextDoubleClickStrategyConnector extends MouseAdapter { + + /** Internal flag to remember that a double clicked occurred. */ + private boolean fDoubleClicked= false; + + public TextDoubleClickStrategyConnector() { + } + + /* + * @see MouseListener#mouseDoubleClick(MouseEvent) + */ + public void mouseDoubleClick(MouseEvent e) { + fDoubleClicked= true; + } + + /* + * @see MouseListener#mouseUp(MouseEvent) + */ + public void mouseUp(MouseEvent e) { + if (fDoubleClicked) { + fDoubleClicked= false; + ITextDoubleClickStrategy s= (ITextDoubleClickStrategy) selectContentTypePlugin(getSelectedRange().x, fDoubleClickStrategies); + if (s != null) + s.doubleClicked(TextViewer.this); + } + } + }; + + /** + * Monitors the area of the viewer's document that is visible in the viewer. + * If the area might have changed, it informs the text viewer about this + * potential change and its origin. The origin is internally used for optimization + * purposes. + */ + class ViewportGuard extends MouseAdapter + implements ControlListener, KeyListener, MouseMoveListener, SelectionListener { + + /* + * @see ControlListener#controlResized(ControlEvent) + */ + public void controlResized(ControlEvent e) { + updateViewportListeners(RESIZE); + } + + /* + * @see ControlListener#controlMoved(ControlEvent) + */ + public void controlMoved(ControlEvent e) { + } + + /* + * @see KeyListener#keyReleased + */ + public void keyReleased(KeyEvent e) { + updateViewportListeners(KEY); + } + + /* + * @see KeyListener#keyPressed + */ + public void keyPressed(KeyEvent e) { + updateViewportListeners(KEY); + } + + /* + * @see MouseListener#mouseUp + */ + public void mouseUp(MouseEvent e) { + if (fTextWidget != null) + fTextWidget.removeMouseMoveListener(this); + updateViewportListeners(MOUSE_END); + } + + /* + * @see MouseListener#mouseDown + */ + public void mouseDown(MouseEvent e) { + if (fTextWidget != null) + fTextWidget.addMouseMoveListener(this); + } + + /* + * @see MouseMoveListener#mouseMove + */ + public void mouseMove(MouseEvent e) { + updateViewportListeners(MOUSE); + } + + /* + * @see SelectionListener#widgetSelected + */ + public void widgetSelected(SelectionEvent e) { + updateViewportListeners(SCROLLER); + } + + /* + * @see SelectionListener#widgetDefaultSelected + */ + public void widgetDefaultSelected(SelectionEvent e) {} + }; + + /** + * This position updater is used to keep the selection during text shift operations. + */ + static class ShiftPositionUpdater extends DefaultPositionUpdater { + + /** + * Creates the position updater for the given category. + * + * @param category the category this updater takes care of + */ + protected ShiftPositionUpdater(String category) { + super(category); + } + + /** + * If an insertion happens at the selection's start offset, + * the position is extended rather than shifted. + */ + protected void adaptToInsert() { + + int myStart= fPosition.offset; + int myEnd= fPosition.offset + fPosition.length -1; + myEnd= Math.max(myStart, myEnd); + + int yoursStart= fOffset; + int yoursEnd= fOffset + fReplaceLength -1; + yoursEnd= Math.max(yoursStart, yoursEnd); + + if (myEnd < yoursStart) + return; + + if (myStart <= yoursStart) { + fPosition.length += fReplaceLength; + return; + } + + if (myStart > yoursStart) + fPosition.offset += fReplaceLength; + } + }; + + /** + * Internal document listener. + */ + class DocumentListener implements IDocumentListener { + + /* + * @see IDocumentListener#documentAboutToBeChanged + */ + public void documentAboutToBeChanged(DocumentEvent e) { + if (e.getDocument() == getVisibleDocument()) + fWidgetCommand.setEvent(e); + } + + /* + * @see IDocumentListener#documentChanged + */ + public void documentChanged(final DocumentEvent e) { + if (fWidgetCommand.event == e) + updateTextListeners(fWidgetCommand); + } + }; + + + /** + * Internal verify listener. + */ + class TextVerifyListener implements VerifyListener { + + /** + * Indicates whether verify events are forwarded or ignored. + * @since 2.0 + */ + private boolean fForward= true; + + /** + * Tells the listener to forward received events. + * + * @param forward <code>true</code> if forwarding should be enabled. + * @since 2.0 + */ + public void forward(boolean forward) { + fForward= forward; + } + + /* + * @see VerifyListener#verifyText(VerifyEvent) + */ + public void verifyText(VerifyEvent e) { + if (fForward) + handleVerifyEvent(e); + } + }; + + /** + * The viewer's manager reponsible for registered verify key listeners. + * Uses batches rather than robust iterators because of performance issues. + * + * @since 2.0 + */ + class VerifyKeyListenersManager implements VerifyKeyListener { + + /** + * Represents a batched addListener/removeListener command. + */ + class Batch { + /** The index at which to insert the listener. */ + int index; + /** The listener to be inserted. */ + VerifyKeyListener listener; + + /** + * Creates a new batch containing the given listener for the given index. + * + * @param l the listener to be added + * @param i the index at which to insert the listener + */ + public Batch(VerifyKeyListener l, int i) { + listener= l; + index= i; + } + }; + + /** List of registed verify key listeners. */ + private List fListeners= new ArrayList(); + /** List of pending batches. */ + private List fBatched= new ArrayList(); + /** The currently active iterator. */ + private Iterator fIterator; + + /* + * @see VerifyKeyListener#verifyKey(VerifyEvent) + */ + public void verifyKey(VerifyEvent event) { + if (fListeners.isEmpty()) + return; + + fIterator= fListeners.iterator(); + while (fIterator.hasNext() && event.doit) { + VerifyKeyListener listener= (VerifyKeyListener) fIterator.next(); + listener.verifyKey(event); + } + fIterator= null; + + processBatchedRequests(); + } + + /** + * Processes the pending batched requests. + */ + private void processBatchedRequests() { + if (!fBatched.isEmpty()) { + Iterator e= fBatched.iterator(); + while (e.hasNext()) { + Batch batch= (Batch) e.next(); + insertListener(batch.listener, batch.index); + } + fBatched.clear(); + } + } + + /** + * Returns the number of registered verify key listeners. + * + * @return the number of registered verify key listeners + */ + public int numberOfListeners() { + return fListeners.size(); + } + + /** + * Inserts the given listener at the given index or moves it + * to that index. + * + * @param listener the listener to be inserted + * @param index the index of the listener or -1 for remove + */ + public void insertListener(VerifyKeyListener listener, int index) { + + if (index == -1) { + removeListener(listener); + } else if (listener != null) { + + if (fIterator != null) { + + fBatched.add(new Batch(listener, index)); + + } else { + + int idx= -1; + + // find index based on identity + int size= fListeners.size(); + for (int i= 0; i < size; i++) { + if (listener == fListeners.get(i)) { + idx= i; + break; + } + } + + // move or add it + if (idx != index) { + + if (idx != -1) + fListeners.remove(idx); + + if (index > fListeners.size()) + fListeners.add(listener); + else + fListeners.add(index, listener); + } + + if (size == 0) // checking old size, i.e. current size == size + 1 + install(); + } + } + } + + /** + * Removes the given listener. + * + * @param listener the listener to be removed + */ + public void removeListener(VerifyKeyListener listener) { + if (listener == null) + return; + + if (fIterator != null) { + + fBatched.add(new Batch(listener, -1)); + + } else { + + int size= fListeners.size(); + for (int i= 0; i < size; i++) { + if (listener == fListeners.get(i)) { + fListeners.remove(i); + if (size == 1) // checking old size, i.e. current size == size - 1 + uninstall(); + return; + } + } + } + } + + /** + * Installs this manager. + */ + private void install() { + StyledText textWidget= getTextWidget(); + if (textWidget != null && !textWidget.isDisposed()) + textWidget.addVerifyKeyListener(this); + } + + /** + * Uninstalls this manager. + */ + private void uninstall() { + StyledText textWidget= getTextWidget(); + if (textWidget != null && !textWidget.isDisposed()) + textWidget.removeVerifyKeyListener(this); + } + }; + + + /** + * Reification of a range in which a find replace operation is performed. This range is visually + * highlighted in the viewer as long as the replace operation is in progress. + * + * @since 2.0 + */ + class FindReplaceRange implements LineBackgroundListener, ITextListener, IPositionUpdater { + + /** Internal name for the position category used to update the range. */ + private final static String RANGE_CATEGORY= "org.eclipse.jface.text.TextViewer.find.range"; //$NON-NLS-1$ + + /** The highlight color of this range. */ + private Color fHighlightColor; + /** The region describing this range's extend. */ + private IRegion fRange; + /** The position used to lively update this range's extent. */ + private Position fPosition; + + /** Creates a new find/replace range with the given extent. + * + * @param range the extent of this range + */ + public FindReplaceRange(IRegion range) { + setRange(range); + } + + /** + * Sets the extent of this range. + * + * @param range the extent of this range + */ + public void setRange(IRegion range) { + fPosition= new Position(range.getOffset(), range.getLength()); + } + + /** + * Returns the extent of this range. + * + * @return the extent of this range + */ + public IRegion getRange() { + return new Region(fPosition.getOffset(), fPosition.getLength()); + } + + /** + * Sets the highlight color of this range. Causes the range to be redrawn. + * + * @param color the highlight color + */ + public void setHighlightColor(Color color) { + fHighlightColor= color; + paint(); + } + + /* + * @see LineBackgroundListener#lineGetBackground(LineBackgroundEvent) + * @since 2.0 + */ + public void lineGetBackground(LineBackgroundEvent event) { + /* Don't use cached line information because of patched redrawing events. */ + + if (fTextWidget != null) { + int offset= event.lineOffset + TextViewer.this.getVisibleRegionOffset(); + + if (fPosition.includes(offset)) + event.lineBackground= fHighlightColor; + } + } + + /** + * Installs this range. The range registers itself as background + * line painter and text listener. Also, it creates a category with the + * viewer's document to maintain its own extent. + */ + public void install() { + TextViewer.this.addTextListener(this); + fTextWidget.addLineBackgroundListener(this); + + IDocument document= TextViewer.this.getDocument(); + try { + document.addPositionCategory(RANGE_CATEGORY); + document.addPosition(RANGE_CATEGORY, fPosition); + document.addPositionUpdater(this); + } catch (BadPositionCategoryException e) { + // should not happen + } catch (BadLocationException e) { + // should not happen + } + + paint(); + } + + /** + * Uninstalls this range. + * @see #install() + */ + public void uninstall() { + + // http://bugs.eclipse.org/bugs/show_bug.cgi?id=19612 + + IDocument document= TextViewer.this.getDocument(); + if (document != null) { + document.removePositionUpdater(this); + document.removePosition(fPosition); + } + + if (fTextWidget != null && !fTextWidget.isDisposed()) + fTextWidget.removeLineBackgroundListener(this); + + TextViewer.this.removeTextListener(this); + + clear(); + } + + /** + * Clears the highlighting of this range. + */ + private void clear() { + if (fTextWidget != null && !fTextWidget.isDisposed()) + fTextWidget.redraw(); + } + + /** + * Paints the highlighting of this range. + */ + private void paint() { + int offset= fPosition.getOffset() - TextViewer.this.getVisibleRegionOffset(); + int length= fPosition.getLength(); + + int count= fTextWidget.getCharCount(); + if (offset + length >= count) { + length= count - offset; // clip + + Point upperLeft= fTextWidget.getLocationAtOffset(offset); + Point lowerRight= fTextWidget.getLocationAtOffset(offset + length); + int width= fTextWidget.getClientArea().width; + int height= fTextWidget.getLineHeight() + lowerRight.y - upperLeft.y; + fTextWidget.redraw(upperLeft.x, upperLeft.y, width, height, false); + } + + fTextWidget.redrawRange(offset, length, true); + } + + /* + * @see ITextListener#textChanged(TextEvent) + * @since 2.0 + */ + public void textChanged(TextEvent event) { + if (event.getViewerRedrawState()) + paint(); + } + + /* + * @see IPositionUpdater#update(DocumentEvent) + * @since 2.0 + */ + public void update(DocumentEvent event) { + int offset= event.getOffset(); + int length= event.getLength(); + int delta= event.getText().length() - length; + + if (offset < fPosition.getOffset()) + fPosition.setOffset(fPosition.getOffset() + delta); + else if (offset < fPosition.getOffset() + fPosition.getLength()) + fPosition.setLength(fPosition.getLength() + delta); + } + }; + + /** + * This viewer's find/replace target. + */ + class FindReplaceTarget implements IFindReplaceTarget, IFindReplaceTargetExtension { + + /** The range for this target. */ + private FindReplaceRange fRange; + /** The highlight color of the range of this target. */ + private Color fScopeHighlightColor; + /** The document partitioner remembered in case of a "Replace All". */ + private IDocumentPartitioner fRememberedPartitioner; + + /* + * @see IFindReplaceTarget#getSelectionText() + */ + public String getSelectionText() { + Point s= TextViewer.this.getSelectedRange(); + if (s.x > -1 && s.y > -1) { + try { + IDocument document= TextViewer.this.getDocument(); + return document.get(s.x, s.y); + } catch (BadLocationException x) { + } + } + return null; + } + + /* + * @see IFindReplaceTarget#replaceSelection(String) + */ + public void replaceSelection(String text) { + Point s= TextViewer.this.getSelectedRange(); + if (s.x > -1 && s.y > -1) { + try { + IDocument document= TextViewer.this.getDocument(); + document.replace(s.x, s.y, text); + if (text != null && text.length() > 0) + TextViewer.this.setSelectedRange(s.x, text.length()); + } catch (BadLocationException x) { + } + } + } + + /* + * @see IFindReplaceTarget#isEditable() + */ + public boolean isEditable() { + return TextViewer.this.isEditable(); + } + + /* + * @see IFindReplaceTarget#getSelection() + */ + public Point getSelection() { + Point point= TextViewer.this.getSelectedRange(); + point.x -= TextViewer.this.getVisibleRegionOffset(); + return point; + } + + /* + * @see IFindReplaceTarget#findAndSelect(int, String, boolean, boolean, boolean) + */ + public int findAndSelect(int offset, String findString, boolean searchForward, boolean caseSensitive, boolean wholeWord) { + if (offset != -1) + offset += TextViewer.this.getVisibleRegionOffset(); + + if (fRange != null) { + IRegion range= fRange.getRange(); + offset= TextViewer.this.findAndSelectInRange(offset, findString, searchForward, caseSensitive, wholeWord, range.getOffset(), range.getLength()); + } else { + offset= TextViewer.this.findAndSelect(offset, findString, searchForward, caseSensitive, wholeWord); + } + + if (offset != -1) + offset -= TextViewer.this.getVisibleRegionOffset(); + + return offset; + } + + /* + * @see IFindReplaceTarget#canPerformFind() + */ + public boolean canPerformFind() { + return TextViewer.this.canPerformFind(); + } + + /* + * @see IFindReplaceTargetExtension#beginSession() + * @since 2.0 + */ + public void beginSession() { + fRange= null; + } + + /* + * @see IFindReplaceTargetExtension#endSession() + * @since 2.0 + */ + public void endSession() { + if (fRange != null) { + fRange.uninstall(); + fRange= null; + } + } + + /* + * @see IFindReplaceTargetExtension#getScope() + * @since 2.0 + */ + public IRegion getScope() { + return fRange == null ? null : fRange.getRange(); + } + + /* + * @see IFindReplaceTargetExtension#getLineSelection() + * @since 2.0 + */ + public Point getLineSelection() { + Point point= TextViewer.this.getSelectedRange(); + + try { + IDocument document= TextViewer.this.getDocument(); + + // beginning of line + int line= document.getLineOfOffset(point.x); + int offset= document.getLineOffset(line); + + // end of line + line= document.getLineOfOffset(point.x + point.y); + int length= document.getLineOffset(line) + document.getLineLength(line) - offset; + + return new Point(offset, length); + + } catch (BadLocationException e) { + // should not happen + return null; + } + } + + /* + * @see IFindReplaceTargetExtension#setSelection(int, int) + * @since 2.0 + */ + public void setSelection(int offset, int length) { + TextViewer.this.setSelectedRange(offset /*+ TextViewer.this.getVisibleRegionOffset()*/, length); + } + + /* + * @see IFindReplaceTargetExtension#setScope(IRegion) + * @since 2.0 + */ + public void setScope(IRegion scope) { + if (fRange != null) + fRange.uninstall(); + + if (scope == null) { + fRange= null; + return; + } + + fRange= new FindReplaceRange(scope); + fRange.setHighlightColor(fScopeHighlightColor); + fRange.install(); + } + + /* + * @see IFindReplaceTargetExtension#setScopeHighlightColor(Color) + * @since 2.0 + */ + public void setScopeHighlightColor(Color color) { + if (fRange != null) + fRange.setHighlightColor(color); + fScopeHighlightColor= color; + } + + /* + * @see IFindReplaceTargetExtension#setReplaceAllMode(boolean) + * @since 2.0 + */ + public void setReplaceAllMode(boolean replaceAll) { + + // http://bugs.eclipse.org/bugs/show_bug.cgi?id=18232 + + if (replaceAll) { + + TextViewer.this.setRedraw(false); + TextViewer.this.startSequentialRewriteMode(false); + + if (fUndoManager != null) + fUndoManager.beginCompoundChange(); + + IDocument document= TextViewer.this.getDocument(); + fRememberedPartitioner= document.getDocumentPartitioner(); + if (fRememberedPartitioner != null) { + fRememberedPartitioner.disconnect(); + document.setDocumentPartitioner(null); + } + + } else { + + TextViewer.this.setRedraw(true); + TextViewer.this.stopSequentialRewriteMode(); + + if (fUndoManager != null) + fUndoManager.endCompoundChange(); + + if (fRememberedPartitioner != null) { + IDocument document= TextViewer.this.getDocument(); + fRememberedPartitioner.connect(document); + document.setDocumentPartitioner(fRememberedPartitioner); + } + } + } + }; + + + /** + * The viewer's rewrite target. + * @since 2.0 + */ + class RewriteTarget implements IRewriteTarget { + + /* + * @see org.eclipse.jface.text.IRewriteTarget#beginCompoundChange() + */ + public void beginCompoundChange() { + if (fUndoManager != null) + fUndoManager.beginCompoundChange(); + } + + /* + * @see org.eclipse.jface.text.IRewriteTarget#endCompoundChange() + */ + public void endCompoundChange() { + if (fUndoManager != null) + fUndoManager.endCompoundChange(); + } + + /* + * @see org.eclipse.jface.text.IRewriteTarget#getDocument() + */ + public IDocument getDocument() { + return TextViewer.this.getDocument(); + } + + /* + * @see org.eclipse.jface.text.IRewriteTarget#setRedraw(boolean) + */ + public void setRedraw(boolean redraw) { + TextViewer.this.setRedraw(redraw); + } + }; + + + /** ID for originators of view port changes */ + protected static final int SCROLLER= 1; + protected static final int MOUSE= 2; + protected static final int MOUSE_END= 3; + protected static final int KEY= 4; + protected static final int RESIZE= 5; + protected static final int INTERNAL= 6; + + /** Internal name of the position category used selection preservation during shift */ + protected static final String SHIFTING= "__TextViewer_shifting"; //$NON-NLS-1$ + + /** The viewer's text widget */ + private StyledText fTextWidget; + /** The viewer's input document */ + private IDocument fDocument; + /** The viewer's visible document */ + private IDocument fVisibleDocument; + /** The viewer's document adapter */ + private IDocumentAdapter fDocumentAdapter; + /** The child document manager */ + private ChildDocumentManager fChildDocumentManager; + /** The text viewer's double click strategies connector */ + private TextDoubleClickStrategyConnector fDoubleClickStrategyConnector; + /** + * The text viewer's hovering controller + * @since 2.0 + */ + private AbstractHoverInformationControlManager fTextHoverManager; + /** The text viewer's viewport guard */ + private ViewportGuard fViewportGuard; + /** Caches the graphical coordinate of the first visible line */ + private int fTopInset= 0; + /** The most recent document modification as widget command */ + private WidgetCommand fWidgetCommand= new WidgetCommand(); + /** The SWT control's scrollbars */ + private ScrollBar fScroller; + /** Document listener */ + private DocumentListener fDocumentListener= new DocumentListener(); + /** Verify listener */ + private TextVerifyListener fVerifyListener= new TextVerifyListener(); + /** The most recent widget modification as document command */ + private DocumentCommand fDocumentCommand= new DocumentCommand(); + /** The viewer's find/replace target */ + private IFindReplaceTarget fFindReplaceTarget; + /** + * The viewer widget token keeper + * @since 2.0 + */ + private IWidgetTokenKeeper fWidgetTokenKeeper; + /** + * The viewer's manager of verify key listeners + * @since 2.0 + */ + private VerifyKeyListenersManager fVerifyKeyListenersManager= new VerifyKeyListenersManager(); + /** + * The mark position. + * @since 2.0 + */ + private Position fMarkPosition; + /** + * The mark position category. + * @since 2.0 + */ + private final String MARK_POSITION_CATEGORY="__mark_category_" + hashCode(); + /** + * The mark position updater + * @since 2.0 + */ + private final IPositionUpdater fMarkPositionUpdater= new DefaultPositionUpdater(MARK_POSITION_CATEGORY); + /** + * The flag indicating the redraw behavior + * @since 2.0 + */ + private int fRedrawCounter= 0; + /** + * The selection when working in non-redraw state + * @since 2.0 + */ + private Point fDocumentSelection; + /** + * The viewer's rewrite target + * @since 2.0 + */ + private IRewriteTarget fRewriteTarget; + + + /** Should the auto indent strategies ignore the next edit operation */ + protected boolean fIgnoreAutoIndent= false; + /** The strings a line is prefixed with on SHIFT_RIGHT and removed from each line on SHIFT_LEFT */ + protected Map fIndentChars; + /** The string a line is prefixed with on PREFIX and removed from each line on STRIP_PREFIX */ + protected Map fDefaultPrefixChars; + /** The text viewer's text double click strategies */ + protected Map fDoubleClickStrategies; + /** The text viewer's undo manager */ + protected IUndoManager fUndoManager; + /** The text viewer's auto indent strategies */ + protected Map fAutoIndentStrategies; + /** The text viewer's text hovers */ + protected Map fTextHovers; + /** + * The creator of the text hover control + * @since 2.0 + */ + protected IInformationControlCreator fHoverControlCreator; + /** All registered viewport listeners> */ + protected List fViewportListeners; + /** The last visible vertical position of the top line */ + protected int fLastTopPixel; + /** All registered text listeners */ + protected List fTextListeners; + /** All registered text input listeners */ + protected List fTextInputListeners; + /** The text viewer's event consumer */ + protected IEventConsumer fEventConsumer; + /** Indicates whether the viewer's text presentation should be replaced are modified. */ + protected boolean fReplaceTextPresentation= false; + + + //---- Construction and disposal ------------------ + + + /** + * Internal use only + */ + protected TextViewer() { + } + + /** + * Create a new text viewer with the given SWT style bits. + * The viewer is ready to use but does not have any plug-in installed. + * + * @param parent the parent of the viewer's control + * @param styles the SWT style bits for the viewer's control + */ + public TextViewer(Composite parent, int styles) { + createControl(parent, styles); + } + + /** + * Factory method to create the text widget to be used as the viewer's text widget. + * + * @return the text widget to be used + */ + protected StyledText createTextWidget(Composite parent, int styles) { + return new StyledText(parent, styles); + } + + /** + * Factory method to create the document adapter to be used by this viewer. + * + * @return the document adapter to be used + */ + protected IDocumentAdapter createDocumentAdapter() { + return new DocumentAdapter(); + } + + /** + * Creates the viewer's SWT control. The viewer's text widget either is + * the control or is a child of the control. + * + * @param parent the parent of the viewer's control + * @param styles the SWT style bits for the viewer's control + */ + protected void createControl(Composite parent, int styles) { + + fTextWidget= createTextWidget(parent, styles); + fTextWidget.addDisposeListener( + new DisposeListener() { + public void widgetDisposed(DisposeEvent e) { + setDocument(null); + handleDispose(); + fTextWidget= null; + } + } + ); + + fTextWidget.setFont(parent.getFont()); + fTextWidget.setDoubleClickEnabled(false); + + /* + * Disable SWT Shift+TAB traversal in this viewer + * 1GIYQ9K: ITPUI:WINNT - StyledText swallows Shift+TAB + */ + fTextWidget.addTraverseListener(new TraverseListener() { + public void keyTraversed(TraverseEvent e) { + if ((SWT.SHIFT == e.stateMask) && ('\t' == e.character)) + e.doit = false; + } + }); + + // where does the first line start + fTopInset= -fTextWidget.computeTrim(0, 0, 0, 0).y; + + fVerifyListener.forward(true); + fTextWidget.addVerifyListener(fVerifyListener); + + fTextWidget.addSelectionListener(new SelectionListener() { + public void widgetDefaultSelected(SelectionEvent event) { + selectionChanged(event.x, event.y - event.x); + } + public void widgetSelected(SelectionEvent event) { + selectionChanged(event.x, event.y - event.x); + } + }); + + initializeViewportUpdate(); + } + + /* + * @see Viewer#getControl + */ + public Control getControl() { + return fTextWidget; + } + + /* + * @see ITextViewer#activatePlugin + */ + public void activatePlugins() { + + if (fDoubleClickStrategies != null && !fDoubleClickStrategies.isEmpty() && fDoubleClickStrategyConnector == null) { + fDoubleClickStrategyConnector= new TextDoubleClickStrategyConnector(); + fTextWidget.addMouseListener(fDoubleClickStrategyConnector); + } + + if (fTextHovers != null && !fTextHovers.isEmpty() && fHoverControlCreator != null && fTextHoverManager == null) { + fTextHoverManager= new TextViewerHoverManager(this, fHoverControlCreator); + fTextHoverManager.install(this.getTextWidget()); + } + + if (fUndoManager != null) { + fUndoManager.connect(this); + fUndoManager.reset(); + } + } + + /* + * @see ITextViewer#resetPlugins() + */ + public void resetPlugins() { + if (fUndoManager != null) + fUndoManager.reset(); + } + + /** + * Frees all resources allocated by this viewer. Internally called when the viewer's + * control has been disposed. + */ + protected void handleDispose() { + + removeViewPortUpdate(); + fViewportGuard= null; + + if (fViewportListeners != null) { + fViewportListeners.clear(); + fViewportListeners= null; + } + + if (fTextListeners != null) { + fTextListeners.clear(); + fTextListeners= null; + } + + if (fAutoIndentStrategies != null) { + fAutoIndentStrategies.clear(); + fAutoIndentStrategies= null; + } + + if (fUndoManager != null) { + fUndoManager.disconnect(); + fUndoManager= null; + } + + if (fDoubleClickStrategies != null) { + fDoubleClickStrategies.clear(); + fDoubleClickStrategies= null; + } + + if (fTextHovers != null) { + fTextHovers.clear(); + fTextHovers= null; + } + + fDoubleClickStrategyConnector= null; + + if (fTextHoverManager != null) { + fTextHoverManager.dispose(); + fTextHoverManager= null; + } + + if (fDocumentListener != null) + fDocumentListener= null; + + if (fVisibleDocument instanceof ChildDocument) { + ChildDocument child = (ChildDocument) fVisibleDocument; + child.removeDocumentListener(fDocumentListener); + getChildDocumentManager().freeChildDocument(child); + } + + if (fDocumentAdapter != null) { + fDocumentAdapter.setDocument(null); + fDocumentAdapter= null; + } + + fVisibleDocument= null; + fDocument= null; + fChildDocumentManager= null; + fScroller= null; + } + + + //---- simple getters and setters + + /** + * Returns viewer's text widget. + */ + public StyledText getTextWidget() { + return fTextWidget; + } + + /* + * @see ITextViewer#setAutoIndentStrategy + */ + public void setAutoIndentStrategy(IAutoIndentStrategy strategy, String contentType) { + + if (strategy != null) { + if (fAutoIndentStrategies == null) + fAutoIndentStrategies= new HashMap(); + fAutoIndentStrategies.put(contentType, strategy); + } else if (fAutoIndentStrategies != null) + fAutoIndentStrategies.remove(contentType); + } + + /* + * @see ITextViewer#setEventConsumer + */ + public void setEventConsumer(IEventConsumer consumer) { + fEventConsumer= consumer; + } + + /* + * @see ITextViewer#setIndentPrefixes + */ + public void setIndentPrefixes(String[] indentPrefixes, String contentType) { + + int i= -1; + boolean ok= (indentPrefixes != null); + while (ok && ++i < indentPrefixes.length) + ok= (indentPrefixes[i] != null); + + if (ok) { + + if (fIndentChars == null) + fIndentChars= new HashMap(); + + fIndentChars.put(contentType, indentPrefixes); + + } else if (fIndentChars != null) + fIndentChars.remove(contentType); + } + + /* + * @see ITextViewer#getTopInset + */ + public int getTopInset() { + return fTopInset; + } + + /* + * @see ITextViewer#isEditable + */ + public boolean isEditable() { + if (fTextWidget == null) + return false; + return fTextWidget.getEditable(); + } + + /* + * @see ITextViewer#setEditable + */ + public void setEditable(boolean editable) { + if (fTextWidget != null) + fTextWidget.setEditable(editable); + } + + /* + * @see ITextViewer#setDefaultPrefixes + * @since 2.0 + */ + public void setDefaultPrefixes(String[] defaultPrefixes, String contentType) { + + if (defaultPrefixes != null && defaultPrefixes.length > 0) { + if (fDefaultPrefixChars == null) + fDefaultPrefixChars= new HashMap(); + fDefaultPrefixChars.put(contentType, defaultPrefixes); + } else if (fDefaultPrefixChars != null) + fDefaultPrefixChars.remove(contentType); + } + + /* + * @see ITextViewer#setUndoManager + */ + public void setUndoManager(IUndoManager undoManager) { + fUndoManager= undoManager; + } + + /* + * @see ITextViewer#setTextHover + */ + public void setTextHover(ITextHover hover, String contentType) { + + if (hover != null) { + if (fTextHovers == null) + fTextHovers= new HashMap(); + fTextHovers.put(contentType, hover); + } else if (fTextHovers != null) + fTextHovers.remove(contentType); + } + + /** + * Returns the text hover for a given offset. + * + * @param offset the offset for which to return the text hover + * @return the text hover for the given offset + */ + protected ITextHover getTextHover(int offset) { + return (ITextHover) selectContentTypePlugin(offset, fTextHovers); + } + + /** + * Returns the text hovering controller of this viewer. + * + * @return the text hovering controller of this viewer + * @since 2.0 + */ + protected AbstractInformationControlManager getTextHoveringController() { + return fTextHoverManager; + } + + /** + * Sets the creator for the hover controls. + * + * @param creator the hover control creator + * @since 2.0 + */ + public void setHoverControlCreator(IInformationControlCreator creator) { + fHoverControlCreator= creator; + } + + /* + * @see IWidgetTokenOwner#requestWidgetToken(IWidgetTokenKeeper) + * @since 2.0 + */ + public boolean requestWidgetToken(IWidgetTokenKeeper requester) { + if (fTextWidget != null) { + if (fWidgetTokenKeeper != null) { + if (fWidgetTokenKeeper == requester) + return true; + if (fWidgetTokenKeeper.requestWidgetToken(this)) { + fWidgetTokenKeeper= requester; + return true; + } + } else { + fWidgetTokenKeeper= requester; + return true; + } + } + return false; + } + + /* + * @see IWidgetTokenOwner#releaseWidgetToken(IWidgetTokenKeeper) + * @since 2.0 + */ + public void releaseWidgetToken(IWidgetTokenKeeper tokenKeeper) { + if (fWidgetTokenKeeper == tokenKeeper) + fWidgetTokenKeeper= null; + } + + + //---- Selection + + /* + * @see ITextViewer#getSelectedRange + */ + public Point getSelectedRange() { + + if (!redraws()) + return new Point(fDocumentSelection.x, fDocumentSelection.y); + + if (fTextWidget != null) { + Point p= fTextWidget.getSelectionRange(); + int offset= getVisibleRegionOffset(); + return new Point(p.x + offset, p.y); + } + + return new Point(-1, -1); + } + + /* + * @see ITextViewer#setSelectedRange + */ + public void setSelectedRange(int offset, int length) { + + if (!redraws()) { + fDocumentSelection.x= offset; + fDocumentSelection.y= length; + return; + } + + if (fTextWidget == null) + return; + + int end= offset + length; + + IDocument document= getVisibleDocument(); + if (document == null) + return; + + if (document instanceof ChildDocument) { + Position p= ((ChildDocument) document).getParentDocumentRange(); + if (p.overlapsWith(offset, length)) { + + if (offset < p.getOffset()) + offset= p.getOffset(); + offset -= p.getOffset(); + + int e= p.getOffset() + p.getLength(); + if (end > e) + end= e; + end -= p.getOffset(); + + } else + return; + } + + length= end - offset; + + int[] selectionRange= new int[] { offset, length }; + validateSelectionRange(selectionRange); + if (selectionRange[0] >= 0 && selectionRange[1] >= 0) { + fTextWidget.setSelectionRange(selectionRange[0], selectionRange[1]); + selectionChanged(selectionRange[0], selectionRange[1]); + } + } + + /** + * Validates and adapts the given selection range if it is not a valid + * widget selection. The widget selection is invalid if it starts or ends + * inside a multi-character line delimiter. If so, the selection is adapted to + * start <b>after</b> the divided line delimiter and to end <b>before</b> + * the divided line delimiter. The parameter passed in is changed in-place + * when being adapted. An adaptation to <code>[-1, -1]</code> indicates + * that the selection range could not be validated. + * Subclasses may reimplement this method. + * + * @param selectionRange selectionRange[0] is the offset, selectionRange[1] + * the length of the selection to validate. + * @since 2.0 + */ + protected void validateSelectionRange(int[] selectionRange) { + + IDocument document= getVisibleDocument(); + int documentLength= document.getLength(); + + int offset= selectionRange[0]; + int length= selectionRange[1]; + + + if (offset <0) + offset= 0; + + if (offset > documentLength) + offset= documentLength; + + int delta= (offset + length) - documentLength; + if (delta > 0) + length -= delta; + + try { + + int lineNumber= document.getLineOfOffset(offset); + IRegion lineInformation= document.getLineInformation(lineNumber); + + int lineEnd= lineInformation.getOffset() + lineInformation.getLength(); + delta= offset - lineEnd; + if (delta > 0) { + // in the middle of a multi-character line delimiter + offset= lineEnd; + String delimiter= document.getLineDelimiter(lineNumber); + if (delimiter != null) + offset += delimiter.length(); + } + + int end= offset + length; + lineInformation= document.getLineInformationOfOffset(end); + lineEnd= lineInformation.getOffset() + lineInformation.getLength(); + delta= end - lineEnd; + if (delta > 0) { + // in the middle of a multi-character line delimiter + length -= delta; + } + + } catch (BadLocationException x) { + selectionRange[0]= -1; + selectionRange[1]= -1; + return; + } + + selectionRange[0]= offset; + selectionRange[1]= length; + } + + /* + * @see Viewer#setSelection(ISelection) + */ + public void setSelection(ISelection selection, boolean reveal) { + if (selection instanceof ITextSelection) { + ITextSelection s= (ITextSelection) selection; + setSelectedRange(s.getOffset(), s.getLength()); + if (reveal) + revealRange(s.getOffset(), s.getLength()); + } + } + + /* + * @see Viewer#getSelection() + */ + public ISelection getSelection() { + Point p= getSelectedRange(); + if (p.x == -1 || p.y == -1) + return TextSelection.emptySelection(); + + return new TextSelection(getDocument(), p.x, p.y); + } + + /* + * @see ITextViewer#getSelectionProvider + */ + public ISelectionProvider getSelectionProvider() { + return this; + } + + /** + * Sends out a text selection changed event to all registered listeners. + * + * @param offset the offset of the newly selected range in the visible document + * @param length the length of the newly selected range in the visible document + */ + protected void selectionChanged(int offset, int length) { + if (redraws()) { + ISelection selection= new TextSelection(getDocument(), getVisibleRegionOffset() + offset, length); + SelectionChangedEvent event= new SelectionChangedEvent(this, selection); + fireSelectionChanged(event); + } + } + + /** + * Sends out a mark selection changed event to all registered listeners. + * + * @param offset the offset of the mark selection in the visible document, the offset is <code>-1</code> if the mark was cleared + * @param length the length of the mark selection, may be negative if the caret is before the mark. + * @since 2.0 + */ + protected void markChanged(int offset, int length) { + if (redraws()) { + if (offset != -1) + offset += getVisibleRegionOffset(); + ISelection selection= new MarkSelection(getDocument(), offset, length); + SelectionChangedEvent event= new SelectionChangedEvent(this, selection); + fireSelectionChanged(event); + } + } + + + //---- Text listeners + + /* + * @see ITextViewer#addTextListener + */ + public void addTextListener(ITextListener listener) { + if (fTextListeners == null) + fTextListeners= new ArrayList(); + + if (!fTextListeners.contains(listener)) + fTextListeners.add(listener); + } + + /* + * @see ITextViewer#removeTextListener + */ + public void removeTextListener(ITextListener listener) { + if (fTextListeners != null) { + fTextListeners.remove(listener); + if (fTextListeners.size() == 0) + fTextListeners= null; + } + } + + /** + * Informs all registered text listeners about the change specified by the + * widget command. This method does not use a robust iterator. + * + * @param cmd the widget command translated into a text event sent to all text listeners + */ + protected void updateTextListeners(WidgetCommand cmd) { + + if (fTextListeners != null) { + + DocumentEvent event= cmd.event; + if (event instanceof ChildDocumentEvent) + event= ((ChildDocumentEvent) event).getParentEvent(); + + TextEvent e= new TextEvent(cmd.start, cmd.length, cmd.text, cmd.preservedText, event, redraws()); + for (int i= 0; i < fTextListeners.size(); i++) { + ITextListener l= (ITextListener) fTextListeners.get(i); + l.textChanged(e); + } + } + } + + //---- Text input listeners + + /* + * @see ITextViewer#addTextInputListener + */ + public void addTextInputListener(ITextInputListener listener) { + if (fTextInputListeners == null) + fTextInputListeners= new ArrayList(); + + if (!fTextInputListeners.contains(listener)) + fTextInputListeners.add(listener); + } + + /* + * @see ITextViewer#removeTextInputListener + */ + public void removeTextInputListener(ITextInputListener listener) { + if (fTextInputListeners != null) { + fTextInputListeners.remove(listener); + if (fTextInputListeners.size() == 0) + fTextInputListeners= null; + } + } + + /** + * Informs all registered text input listeners about the forthcoming input change, + * This method does not use a robust iterator. + * + * @param oldInput the old input document + * @param newInput the new input document + */ + protected void fireInputDocumentAboutToBeChanged(IDocument oldInput, IDocument newInput) { + if (fTextInputListeners != null) { + for (int i= 0; i < fTextInputListeners.size(); i++) { + ITextInputListener l= (ITextInputListener) fTextInputListeners.get(i); + l.inputDocumentAboutToBeChanged(oldInput, newInput); + } + } + } + + /** + * Informs all registered text input listeners about the sucessful input change, + * This method does not use a robust iterator. + * + * @param oldInput the old input document + * @param newInput the new input document + */ + protected void fireInputDocumentChanged(IDocument oldInput, IDocument newInput) { + if (fTextInputListeners != null) { + for (int i= 0; i < fTextInputListeners.size(); i++) { + ITextInputListener l= (ITextInputListener) fTextInputListeners.get(i); + l.inputDocumentChanged(oldInput, newInput); + } + } + } + + //---- Document + + /* + * @see Viewer#getInput + */ + public Object getInput() { + return getDocument(); + } + + /* + * @see ITextViewer#getDocument + */ + public IDocument getDocument() { + return fDocument; + } + + /* + * @see Viewer#setInput + */ + public void setInput(Object input) { + + IDocument document= null; + if (input instanceof IDocument) + document= (IDocument) input; + + setDocument(document); + } + + /* + * @see ITextViewer#setDocument(IDocument) + */ + public void setDocument(IDocument document) { + + fReplaceTextPresentation= true; + fireInputDocumentAboutToBeChanged(fDocument, document); + + IDocument oldDocument= fDocument; + fDocument= document; + + setVisibleDocument(fDocument); + + inputChanged(fDocument, oldDocument); + + fireInputDocumentChanged(oldDocument, fDocument); + fReplaceTextPresentation= false; + } + + /* + * @see ITextViewer#setDocument(IDocument, int int) + */ + public void setDocument(IDocument document, int visibleRegionOffset, int visibleRegionLength) { + + fReplaceTextPresentation= true; + fireInputDocumentAboutToBeChanged(fDocument, document); + + IDocument oldDocument= fDocument; + fDocument= document; + + try { + int line= fDocument.getLineOfOffset(visibleRegionOffset); + int offset= fDocument.getLineOffset(line); + int length= (visibleRegionOffset - offset) + visibleRegionLength; + setVisibleDocument(getChildDocumentManager().createChildDocument(fDocument, offset, length)); + } catch (BadLocationException x) { + throw new IllegalArgumentException(JFaceTextMessages.getString("TextViewer.error.invalid_visible_region_1")); //$NON-NLS-1$ + } + + inputChanged(fDocument, oldDocument); + + fireInputDocumentChanged(oldDocument, fDocument); + fReplaceTextPresentation= false; + } + + //---- Viewports + + /** + * Initializes all listeners and structures required to set up viewport listeners. + */ + private void initializeViewportUpdate() { + + if (fViewportGuard != null) + return; + + if (fTextWidget != null) { + + fViewportGuard= new ViewportGuard(); + fLastTopPixel= -1; + + fTextWidget.addKeyListener(fViewportGuard); + fTextWidget.addMouseListener(fViewportGuard); + + fScroller= fTextWidget.getVerticalBar(); + if (fScroller != null) + fScroller.addSelectionListener(fViewportGuard); + } + } + + /** + * Removes all listeners and structures required to set up viewport listeners. + */ + private void removeViewPortUpdate() { + + if (fTextWidget != null) { + + fTextWidget.removeKeyListener(fViewportGuard); + fTextWidget.removeMouseListener(fViewportGuard); + + if (fScroller != null && !fScroller.isDisposed()) { + fScroller.removeSelectionListener(fViewportGuard); + fScroller= null; + } + + fViewportGuard= null; + } + } + + /* + * @see ITextViewer#addViewportListener + */ + public void addViewportListener(IViewportListener listener) { + + if (fViewportListeners == null) { + fViewportListeners= new ArrayList(); + initializeViewportUpdate(); + } + + if (!fViewportListeners.contains(listener)) + fViewportListeners.add(listener); + } + + /* + * @see ITextViewer#removeViewportListener + */ + public void removeViewportListener(IViewportListener listener) { + if (fViewportListeners != null) + fViewportListeners.remove(listener); + } + + /** + * Checks whether the viewport changed and if so informs all registered + * listeners about the change. + * + * @param origin describes under which circumstances this method has been called. + * + * @see IViewportListener + */ + protected void updateViewportListeners(int origin) { + + if (redraws()) { + int topPixel= fTextWidget.getTopPixel(); + if (topPixel >= 0 && topPixel != fLastTopPixel) { + if (fViewportListeners != null) { + for (int i= 0; i < fViewportListeners.size(); i++) { + IViewportListener l= (IViewportListener) fViewportListeners.get(i); + l.viewportChanged(topPixel); + } + } + fLastTopPixel= topPixel; + } + } + } + + //---- scrolling and revealing + + /* + * @see ITextViewer#getTopIndex + */ + public int getTopIndex() { + + if (fTextWidget != null) { + + int top= fTextWidget.getTopIndex(); + + int offset= getVisibleRegionOffset(); + if (offset > 0) { + try { + top += getDocument().getLineOfOffset(offset); + } catch (BadLocationException x) { + if (TRACE_ERRORS) + System.out.println(JFaceTextMessages.getString("TextViewer.error.bad_location.getTopIndex")); //$NON-NLS-1$ + return -1; + } + } + + return top; + } + + return -1; + } + + /* + * @see ITextViewer#setTopIndex + */ + public void setTopIndex(int index) { + + if (fTextWidget != null) { + + int offset= getVisibleRegionOffset(); + if (offset > 0) { + try { + index -= getDocument().getLineOfOffset(offset); + } catch (BadLocationException x) { + if (TRACE_ERRORS) + System.out.println(JFaceTextMessages.getString("TextViewer.error.bad_location.setTopIndex_1")); //$NON-NLS-1$ + return; + } + } + + if (index >= 0) { + + int lines= getVisibleLinesInViewport(); + if (lines > -1 ) { + IDocument d= getVisibleDocument(); + int last= d.getNumberOfLines() - lines; + if (last > 0 && index > last) + index= last; + + fTextWidget.setTopIndex(index); + updateViewportListeners(INTERNAL); + + } else + fTextWidget.setTopIndex(index); + } + } + } + + /** + * Returns the viewport height in lines. The actual visible lines can be fewer if the + * document is shorter than the viewport. + * + * @return the viewport height in lines + */ + protected int getVisibleLinesInViewport() { + if (fTextWidget != null) { + Rectangle clArea= fTextWidget.getClientArea(); + if (!clArea.isEmpty()) + return clArea.height / fTextWidget.getLineHeight(); + } + return -1; + } + + /* + * @see ITextViewer#getBottomIndex + */ + public int getBottomIndex() { + + if (fTextWidget == null) + return -1; + + IRegion r= getVisibleRegion(); + + try { + + IDocument d= getDocument(); + int startLine= d.getLineOfOffset(r.getOffset()); + int endLine= d.getLineOfOffset(r.getOffset() + r.getLength() - 1); + int lines= getVisibleLinesInViewport(); + + if (startLine + lines < endLine) + return getTopIndex() + lines - 1; + + return endLine; + + } catch (BadLocationException x) { + if (TRACE_ERRORS) + System.out.println(JFaceTextMessages.getString("TextViewer.error.bad_location.getBottomIndex")); //$NON-NLS-1$ + } + + return -1; + } + + /* + * @see ITextViewer#getTopIndexStartOffset + */ + public int getTopIndexStartOffset() { + + if (fTextWidget != null) { + int top= fTextWidget.getTopIndex(); + try { + top= getVisibleDocument().getLineOffset(top); + return top + getVisibleRegionOffset(); + } catch (BadLocationException ex) { + if (TRACE_ERRORS) + System.out.println(JFaceTextMessages.getString("TextViewer.error.bad_location.getTopIndexStartOffset")); //$NON-NLS-1$ + } + } + + return -1; + } + + /* + * @see ITextViewer#getBottomIndexEndOffset + */ + public int getBottomIndexEndOffset() { + try { + + IRegion line= getDocument().getLineInformation(getBottomIndex()); + int bottomEndOffset= line.getOffset() + line.getLength() - 1; + + IRegion region= getVisibleRegion(); + int visibleRegionEndOffset= region.getOffset() + region.getLength() - 1; + return visibleRegionEndOffset < bottomEndOffset ? visibleRegionEndOffset : bottomEndOffset; + + } catch (BadLocationException ex) { + if (TRACE_ERRORS) + System.out.println(JFaceTextMessages.getString("TextViewer.error.bad_location.getBottomIndexEndOffset")); //$NON-NLS-1$ + return getDocument().getLength() - 1; + } + } + + /* + * @see ITextViewer#revealRange + */ + public void revealRange(int start, int length) { + + if (fTextWidget == null || !redraws()) + return; + + int end= start + length; + + IDocument document= getVisibleDocument(); + if (document == null) + return; + + Position p= (document instanceof ChildDocument) + ? ((ChildDocument) document).getParentDocumentRange() + : new Position(0, document.getLength()); + + if (p.overlapsWith(start, length)) { + + if (start < p.getOffset()) + start= p.getOffset(); + start -= p.getOffset(); + + int e= p.getOffset() + p.getLength(); + if (end > e) + end= e; + end -= p.getOffset(); + + } else { + // http://dev.eclipse.org/bugs/show_bug.cgi?id=15159 + start= start < p.getOffset() ? 0 : p.getLength(); + end= start; + } + + internalRevealRange(start, end); + } + + /** + * Reveals the given range of the visible document. + * + * @param start the start offset of the range + * @param end the end offset of the range + */ + protected void internalRevealRange(int start, int end) { + + try { + + IDocument doc= getVisibleDocument(); + + int startLine= doc.getLineOfOffset(start); + int endLine= doc.getLineOfOffset(end); + + int top= fTextWidget.getTopIndex(); + if (top > -1) { + + // scroll vertically + + int lines= getVisibleLinesInViewport(); + int bottom= top + lines; + + // two lines at the top and the bottom should always be left + // if window is smaller than 5 lines, always center position is chosen + int bufferZone= 2; + + if (startLine >= top + bufferZone + && startLine <= bottom - bufferZone + && endLine >= top + bufferZone + && endLine <= bottom - bufferZone) { + + // do not scroll at all as it is already visible + + } else { + + int delta= Math.max(0, lines - (endLine - startLine)); + fTextWidget.setTopIndex(startLine - delta/3); + updateViewportListeners(INTERNAL); + } + + // scroll horizontally + + if (endLine < startLine) { + endLine += startLine; + startLine= endLine - startLine; + endLine -= startLine; + } + + int startPixel= -1; + int endPixel= -1; + + if (endLine > startLine) { + // reveal the beginning of the range in the start line + IRegion line= doc.getLineInformation(startLine); + startPixel= getWidthInPixels(line.getOffset(), start - line.getOffset()); + endPixel= getWidthInPixels(line.getOffset(), line.getLength()); + } else { + int lineStart= doc.getLineOffset(startLine); + startPixel= getWidthInPixels(lineStart, start - lineStart); + endPixel= getWidthInPixels(lineStart, end - lineStart); + } + + int visibleStart= fTextWidget.getHorizontalPixel(); + int visibleEnd= visibleStart + fTextWidget.getClientArea().width; + + // scroll only if not yet visible + if (startPixel < visibleStart || visibleEnd < endPixel) { + + // set buffer zone to 10 pixels + bufferZone= 10; + + int newOffset= visibleStart; + if (startPixel < visibleStart) + newOffset= startPixel; + else if (endPixel - startPixel + bufferZone < visibleEnd - visibleStart) + newOffset= visibleStart + (endPixel - visibleEnd + bufferZone); + else + newOffset= startPixel; + + fTextWidget.setHorizontalIndex(newOffset / getAverageCharWidth()); + } + + } + } catch (BadLocationException e) { + throw new IllegalArgumentException(JFaceTextMessages.getString("TextViewer.error.invalid_range")); //$NON-NLS-1$ + } + } + + /** + * Returns the width of the text when being drawed into this viewer's widget. + * + * @param the string to messure + * @return the width of the presentation of the given string + * @deprecated use <code>getWidthInPixels(int, int)</code> instead + */ + final protected int getWidthInPixels(String text) { + GC gc= new GC(fTextWidget); + gc.setFont(fTextWidget.getFont()); + Point extent= gc.textExtent(text); + gc.dispose(); + return extent.x; + } + + /** + * Returns the width of the representation of a text range in the + * visible region of the viewer's document as drawn in this viewer's + * widget. + * + * @param offset the offset of the text range in the visible region + * @param length the length of the text range in the visible region + * @return the width of the presentation of the specified text range + * @since 2.0 + */ + final protected int getWidthInPixels(int offset, int length) { + + Point left= fTextWidget.getLocationAtOffset(offset); + Point right= new Point(left.x, left.y); + + int end= offset + length; + for (int i= offset +1; i <= end; i++) { + + Point p= fTextWidget.getLocationAtOffset(i); + + if (left.x > p.x) + left.x= p.x; + + if (right.x < p.x) + right.x= p.x; + } + + return right.x - left.x; + } + + /** + * Returns the average character width of this viewer's widget. + * + * @return the average character width of this viewer's widget + */ + final protected int getAverageCharWidth() { + GC gc= new GC(fTextWidget); + gc.setFont(fTextWidget.getFont()); + int increment= gc.getFontMetrics().getAverageCharWidth(); + gc.dispose(); + return increment; + } + + /* + * @see Viewer#refresh + */ + public void refresh() { + setDocument(getDocument()); + } + + //---- visible range support + + /** + * Returns the child document manager + * + * @return the child document manager + */ + private ChildDocumentManager getChildDocumentManager() { + if (fChildDocumentManager == null) + fChildDocumentManager= new ChildDocumentManager(); + return fChildDocumentManager; + } + + /** + * Invalidates the current presentation by sending an initialization + * event to all text listener. + * @since 2.0 + */ + public final void invalidateTextPresentation() { + if (fVisibleDocument != null) { + fWidgetCommand.start= 0; + fWidgetCommand.length= 0; + fWidgetCommand.text= fVisibleDocument.get(); + fWidgetCommand.event= null; + updateTextListeners(fWidgetCommand); + } + } + + /** + * Initializes the text widget with the visual document and + * invalidates the overall presentation. + */ + private void initializeWidgetContents() { + + if (fTextWidget != null && fVisibleDocument != null) { + + // set widget content + if (fDocumentAdapter == null) + fDocumentAdapter= createDocumentAdapter(); + + fDocumentAdapter.setDocument(fVisibleDocument); + fTextWidget.setContent(fDocumentAdapter); + + // invalidate presentation + invalidateTextPresentation(); + } + } + + /** + * Sets this viewer's visible document. The visible document represents the + * visible region of the viewer's input document. + * + * @param document the visible document + */ + private void setVisibleDocument(IDocument document) { + + if (fVisibleDocument != null && fDocumentListener != null) + fVisibleDocument.removeDocumentListener(fDocumentListener); + + fVisibleDocument= document; + + initializeWidgetContents(); + resetPlugins(); + + if (fVisibleDocument != null && fDocumentListener != null) + fVisibleDocument.addDocumentListener(fDocumentListener); + } + + /** + * Returns the viewer's visible document. + * + * @return the viewer's visible document + */ + protected IDocument getVisibleDocument() { + return fVisibleDocument; + } + + /** + * Returns the offset of the visible region. + * + * @return the offset of the visible region + */ + protected int getVisibleRegionOffset() { + + IDocument document= getVisibleDocument(); + if (document instanceof ChildDocument) { + ChildDocument cdoc= (ChildDocument) document; + return cdoc.getParentDocumentRange().getOffset(); + } + + return 0; + } + + /* + * @see ITextViewer#getVisibleRegion + */ + public IRegion getVisibleRegion() { + + IDocument document= getVisibleDocument(); + if (document instanceof ChildDocument) { + Position p= ((ChildDocument) document).getParentDocumentRange(); + return new Region(p.getOffset(), p.getLength()); + } + + return new Region(0, document == null ? 0 : document.getLength()); + } + + /* + * @see ITextViewer#setVisibleRegion + */ + public void setVisibleRegion(int start, int length) { + + IRegion region= getVisibleRegion(); + if (start == region.getOffset() && length == region.getLength()) { + // nothing to change + return; + } + + ChildDocument child= null; + IDocument parent= getVisibleDocument(); + + if (parent instanceof ChildDocument) { + child= (ChildDocument) parent; + parent= child.getParentDocument(); + } + + try { + + int line= parent.getLineOfOffset(start); + int offset= parent.getLineOffset(line); + length += (start - offset); + + if (child != null) { + child.setParentDocumentRange(offset, length); + } else { + child= getChildDocumentManager().createChildDocument(parent, offset, length); + } + + setVisibleDocument(child); + + } catch (BadLocationException x) { + throw new IllegalArgumentException(JFaceTextMessages.getString("TextViewer.error.invalid_visible_region_2")); //$NON-NLS-1$ + } + } + + /* + * @see ITextViewer#resetVisibleRegion + */ + public void resetVisibleRegion() { + IDocument document= getVisibleDocument(); + if (document instanceof ChildDocument) { + ChildDocument child = (ChildDocument) document; + setVisibleDocument(child.getParentDocument()); + getChildDocumentManager().freeChildDocument(child); + } + } + + /* + * @see ITextViewer#overlapsWithVisibleRegion + */ + public boolean overlapsWithVisibleRegion(int start, int length) { + IDocument document= getVisibleDocument(); + if (document instanceof ChildDocument) { + ChildDocument cdoc= (ChildDocument) document; + return cdoc.getParentDocumentRange().overlapsWith(start, length); + } else if (document != null) { + int size= document.getLength(); + return (start >= 0 && length >= 0 && start + length <= size); + } + return false; + } + + + + //-------------------------------------- + + /* + * @see ITextViewer#setTextDoubleClickStrategy + */ + public void setTextDoubleClickStrategy(ITextDoubleClickStrategy strategy, String contentType) { + + if (strategy != null) { + if (fDoubleClickStrategies == null) + fDoubleClickStrategies= new HashMap(); + fDoubleClickStrategies.put(contentType, strategy); + } else if (fDoubleClickStrategies != null) + fDoubleClickStrategies.remove(contentType); + } + + /** + * Selects from the given map the one which is registered under + * the content type of the partition in which the given offset is located. + * + * @param plugins the map from which to choose + * @param offset the offset for which to find the plugin + * @return the plugin registered under the offset's content type + */ + protected Object selectContentTypePlugin(int offset, Map plugins) { + try { + return selectContentTypePlugin(getDocument().getContentType(offset), plugins); + } catch (BadLocationException x) { + if (TRACE_ERRORS) + System.out.println(JFaceTextMessages.getString("TextViewer.error.bad_location.selectContentTypePlugin")); //$NON-NLS-1$ + } + return null; + } + + /** + * Selects from the given <code>plugins</code> this one which is registered for + * the given content <code>type</code>. + */ + private Object selectContentTypePlugin(String type, Map plugins) { + + if (plugins == null) + return null; + + return plugins.get(type); + } + + /** + * Hook called on receipt of a <code>VerifyEvent</code>. The event has + * been translated into a <code>DocumentCommand</code> which can now be + * manipulated by interested parties. By default, the hook forwards the command + * to the installed <code>IAutoIndentStrategy</code>. + * + * @param command the document command representing the verify event + */ + protected void customizeDocumentCommand(DocumentCommand command) { + if (!fIgnoreAutoIndent) { + IAutoIndentStrategy s= (IAutoIndentStrategy) selectContentTypePlugin(command.offset, fAutoIndentStrategies); + if (s != null) + s.customizeDocumentCommand(getDocument(), command); + } + fIgnoreAutoIndent= false; + } + + /** + * @see VerifyListener#verifyText + */ + protected void handleVerifyEvent(VerifyEvent e) { + + if (fEventConsumer != null) { + fEventConsumer.processEvent(e); + if (!e.doit) + return; + } + + int offset= getVisibleRegionOffset(); + fDocumentCommand.setEvent(e, offset); + customizeDocumentCommand(fDocumentCommand); + if (!fDocumentCommand.fillEvent(e, offset)) { + try { + fVerifyListener.forward(false); + getDocument().replace(fDocumentCommand.offset, fDocumentCommand.length, fDocumentCommand.text); + if (fTextWidget != null) { + int caretOffset= fDocumentCommand.offset + (fDocumentCommand.text == null ? 0 : fDocumentCommand.text.length()) - offset; + fTextWidget.setCaretOffset(caretOffset); + } + } catch (BadLocationException x) { + if (TRACE_ERRORS) + System.out.println(JFaceTextMessages.getString("TextViewer.error.bad_location.verifyText")); //$NON-NLS-1$ + } finally { + fVerifyListener.forward(true); + } + } + } + + //---- text manipulation + + /** + * Returns whether the marked region of this viewer is empty. + * + * @return <code>true</code> if the marked region of this viewer is empty, otherwise <code>false</code> + */ + private boolean isMarkedRegionEmpty() { + + if (fTextWidget == null) + return true; + + IRegion region= getVisibleRegion(); + int offset= region.getOffset(); + int length= region.getLength(); + + return + fMarkPosition == null || + fMarkPosition.isDeleted() || + fMarkPosition.offset < offset || + fMarkPosition.offset > offset + length; + } + + /* + * @see ITextViewer#canDoOperation + */ + public boolean canDoOperation(int operation) { + + if (fTextWidget == null || !redraws()) + return false; + + switch (operation) { + case CUT: + return isEditable() &&(fTextWidget.getSelectionCount() > 0 || !isMarkedRegionEmpty()); + case COPY: + return fTextWidget.getSelectionCount() > 0 || !isMarkedRegionEmpty(); + case DELETE: + case PASTE: + return isEditable(); + case SELECT_ALL: + return true; + case SHIFT_RIGHT: + case SHIFT_LEFT: + return isEditable() && fIndentChars != null && areMultipleLinesSelected(); + case PREFIX: + case STRIP_PREFIX: + return isEditable() && fDefaultPrefixChars != null; + case UNDO: + return fUndoManager != null && fUndoManager.undoable(); + case REDO: + return fUndoManager != null && fUndoManager.redoable(); + case PRINT: + return isPrintable(); + } + + return false; + } + + /* + * @see ITextViewer#doOperation + */ + public void doOperation(int operation) { + + if (fTextWidget == null || !redraws()) + return; + + switch (operation) { + + case UNDO: + if (fUndoManager != null) { + fIgnoreAutoIndent= true; + fUndoManager.undo(); + } + break; + case REDO: + if (fUndoManager != null) { + fIgnoreAutoIndent= true; + fUndoManager.redo(); + } + break; + case CUT: + if (fTextWidget.getSelectionCount() == 0) + copyMarkedRegion(true); + else + fTextWidget.cut(); + break; + case COPY: + if (fTextWidget.getSelectionCount() == 0) + copyMarkedRegion(false); + else + fTextWidget.copy(); + break; + case PASTE: + fIgnoreAutoIndent= true; + fTextWidget.paste(); + break; + case DELETE: + deleteText(); + break; + case SELECT_ALL: + setSelectedRange(getVisibleRegionOffset(), getVisibleDocument().getLength()); + break; + case SHIFT_RIGHT: + shift(false, true, false); + break; + case SHIFT_LEFT: + shift(false, false, false); + break; + case PREFIX: + shift(true, true, true); + break; + case STRIP_PREFIX: + shift(true, false, true); + break; + case PRINT: + print(); + break; + } + } + + /* + * @see ITextOperationTargetExtension#enableOperation(int, boolean) + * @since 2.0 + */ + public void enableOperation(int operation, boolean enable) { + /* + * No-op by default. + * Will be changed to regularily disable the known operations. + */ + } + + /** + * Copies/cuts the marked region. + * + * @param delete <code>true</code> if the region should be deleted rather than copied. + * @since 2.0 + */ + private void copyMarkedRegion(boolean delete) { + + if (fTextWidget == null) + return; + + IRegion region= getVisibleRegion(); + int offset= region.getOffset(); + int length= region.getLength(); + + if (fMarkPosition == null || fMarkPosition.isDeleted() || + fMarkPosition.offset < offset || fMarkPosition.offset > offset + length) + return; + + int markOffset= fMarkPosition.offset - offset; + + Point selection= fTextWidget.getSelection(); + if (selection.x <= markOffset) + fTextWidget.setSelection(selection.x, markOffset); + else + fTextWidget.setSelection(markOffset, selection.x); + + if (delete) { + fTextWidget.cut(); + } else { + fTextWidget.copy(); + fTextWidget.setSelection(selection.x); // restore old cursor position + } + } + + /** + * Deletes the current selection. If the selection has the length 0 + * the selection is automatically extended to the right - either by 1 + * or by the length of line delimiter if at the end of a line. + * + * @deprecated use <code>StyledText.invokeAction</code> instead + */ + protected void deleteText() { + fTextWidget.invokeAction(ST.DELETE_NEXT); + } + + /** + * A block is selected if the character preceding the start of the + * selection is a new line character. + * + * @return <code>true</code> if a block is selected + */ + protected boolean isBlockSelected() { + + Point s= getSelectedRange(); + if (s.y == 0) + return false; + + try { + + IDocument document= getDocument(); + int line= document.getLineOfOffset(s.x); + int start= document.getLineOffset(line); + return (s.x == start); + + } catch (BadLocationException x) { + } + + return false; + } + + /** + * Returns <code>true</code> if one line is completely selected or if multiple lines are selected. + * Being completely selected means that all characters except the new line characters are + * selected. + * + * @return <code>true</code> if one or multiple lines are selected + * @since 2.0 + */ + protected boolean areMultipleLinesSelected() { + Point s= getSelectedRange(); + if (s.y == 0) + return false; + + try { + + IDocument document= getDocument(); + int startLine= document.getLineOfOffset(s.x); + int endLine= document.getLineOfOffset(s.x + s.y); + IRegion line= document.getLineInformation(startLine); + return startLine != endLine || (s.x == line.getOffset() && s.y == line.getLength()); + + } catch (BadLocationException x) { + } + + return false; + } + + /** + * Returns the index of the first line whose start offset is in the given text range. + * + * @param region the text range in characters where to find the line + * @return the first line whose start index is in the given range, -1 if there is no such line + */ + private int getFirstCompleteLineOfRegion(IRegion region) { + + try { + + IDocument d= getDocument(); + + int startLine= d.getLineOfOffset(region.getOffset()); + + int offset= d.getLineOffset(startLine); + if (offset >= region.getOffset()) + return startLine; + + offset= d.getLineOffset(startLine + 1); + return (offset > region.getOffset() + region.getLength() ? -1 : startLine + 1); + + } catch (BadLocationException x) { + if (TRACE_ERRORS) + System.out.println(JFaceTextMessages.getString("TextViewer.error.bad_location.getFirstCompleteLineOfRegion")); //$NON-NLS-1$ + } + + return -1; + } + + + /** + * Creates a region describing the text block (something that starts at + * the beginning of a line) completely containing the current selection. + * + * @param selection the selection to use + * @return the region describing the text block comprising the given selection + * @since 2.0 + */ + private IRegion getTextBlockFromSelection(Point selection) { + + try { + IDocument document= getDocument(); + IRegion line= document.getLineInformationOfOffset(selection.x); + int length= selection.y == 0 ? line.getLength() : selection.y + (selection.x - line.getOffset()); + return new Region(line.getOffset(), length); + + } catch (BadLocationException x) { + } + + return null; + } + + /** + * Shifts a text block to the right or left using the specified set of prefix characters. + * The prefixes must start at the beginnig of the line. + * + * @param useDefaultPrefixes says whether the configured default or indent prefixes should be used + * @param right says whether to shift to the right or the left + * + * @deprecated use shift(boolean, boolean, boolean) instead + */ + protected void shift(boolean useDefaultPrefixes, boolean right) { + shift(useDefaultPrefixes, right, false); + } + + /** + * Shifts a text block to the right or left using the specified set of prefix characters. + * If white space should be ignored the prefix characters must not be at the beginning of + * the line when shifting to the left. There may be whitespace in front of the prefixes. + * + * @param useDefaultPrefixes says whether the configured default or indent prefixes should be used + * @param right says whether to shift to the right or the left + * @param ignoreWhitespace says whether whitepsace in front of prefixes is allowed + * @since 2.0 + */ + protected void shift(boolean useDefaultPrefixes, boolean right, boolean ignoreWhitespace) { + + if (fUndoManager != null) + fUndoManager.beginCompoundChange(); + + setRedraw(false); + startSequentialRewriteMode(true); + + IDocument d= getDocument(); + IDocumentPartitioner partitioner= null; + + try { + + Point selection= getSelectedRange(); + IRegion block= getTextBlockFromSelection(selection); + ITypedRegion[] regions= d.computePartitioning(block.getOffset(), block.getLength()); + + int lineCount= 0; + int[] lines= new int[regions.length * 2]; // [startline, endline, startline, endline, ...] + for (int i= 0, j= 0; i < regions.length; i++, j+= 2) { + // start line of region + lines[j]= getFirstCompleteLineOfRegion(regions[i]); + // end line of region + int offset= regions[i].getOffset() + regions[i].getLength() - 1; + lines[j + 1]= (lines[j] == -1 ? -1 : d.getLineOfOffset(offset)); + lineCount += lines[j + 1] - lines[j] + 1; + } + + if (lineCount >= 20) { + partitioner= d.getDocumentPartitioner(); + if (partitioner != null) { + partitioner.disconnect(); + d.setDocumentPartitioner(null); + } + } + + // Remember the selection range. + IPositionUpdater positionUpdater= new ShiftPositionUpdater(SHIFTING); + Position rememberedSelection= new Position(selection.x, selection.y); + d.addPositionCategory(SHIFTING); + d.addPositionUpdater(positionUpdater); + try { + d.addPosition(SHIFTING, rememberedSelection); + } catch (BadPositionCategoryException ex) { + // should not happen + } + + // Perform the shift operation. + Map map= (useDefaultPrefixes ? fDefaultPrefixChars : fIndentChars); + for (int i= 0, j= 0; i < regions.length; i++, j += 2) { + String[] prefixes= (String[]) selectContentTypePlugin(regions[i].getType(), map); + if (prefixes != null && prefixes.length > 0 && lines[j] >= 0 && lines[j + 1] >= 0) { + if (right) + shiftRight(lines[j], lines[j + 1], prefixes[0]); + else + shiftLeft(lines[j], lines[j + 1], prefixes, ignoreWhitespace); + } + } + + // Restore the selection. + setSelectedRange(rememberedSelection.getOffset(), rememberedSelection.getLength()); + + try { + d.removePositionUpdater(positionUpdater); + d.removePositionCategory(SHIFTING); + } catch (BadPositionCategoryException ex) { + // should not happen + } + + } catch (BadLocationException x) { + if (TRACE_ERRORS) + System.out.println(JFaceTextMessages.getString("TextViewer.error.bad_location.shift_1")); //$NON-NLS-1$ + + } finally { + + if (partitioner != null) { + partitioner.connect(d); + d.setDocumentPartitioner(partitioner); + } + + stopSequentialRewriteMode(); + setRedraw(true); + + if (fUndoManager != null) + fUndoManager.endCompoundChange(); + } + } + + /** + * Shifts the specified lines to the right inserting the given prefix + * at the beginning of each line + * + * @param prefix the prefix to be inserted + * @param startLine the first line to shift + * @param endLine the last line to shift + * @since 2.0 + */ + private void shiftRight(int startLine, int endLine, String prefix) { + + try { + + IDocument d= getDocument(); + while (startLine <= endLine) { + d.replace(d.getLineOffset(startLine++), 0, prefix); + } + + } catch (BadLocationException x) { + if (TRACE_ERRORS) + System.out.println("TextViewer.shiftRight: BadLocationException"); //$NON-NLS-1$ + } + } + + /** + * Shifts the specified lines to the right or to the left. On shifting to the right + * it insert <code>prefixes[0]</code> at the beginning of each line. On shifting to the + * left it tests whether each of the specified lines starts with one of the specified + * prefixes and if so, removes the prefix. + * + * @param prefixes the prefixes to be used for shifting + * @param right if <code>true</code> shift to the right otherwise to the left + * @param startLine the first line to shift + * @param endLine the last line to shift + * @since 2.0 + */ + private void shiftLeft(int startLine, int endLine, String[] prefixes, boolean ignoreWhitespace) { + + IDocument d= getDocument(); + + try { + + IRegion[] occurrences= new IRegion[endLine - startLine + 1]; + + // find all the first occurrences of prefix in the given lines + for (int i= 0; i < occurrences.length; i++) { + + IRegion line= d.getLineInformation(startLine + i); + String text= d.get(line.getOffset(), line.getLength()); + + int index= -1; + int[] found= TextUtilities.indexOf(prefixes, text, 0); + if (found[0] != -1) { + if (ignoreWhitespace) { + String s= d.get(line.getOffset(), found[0]); + s= s.trim(); + if (s.length() == 0) + index= line.getOffset() + found[0]; + } else if (found[0] == 0) + index= line.getOffset(); + } + + if (index > -1) { + // remember where prefix is in line, so that it can be removed + int length= prefixes[found[1]].length(); + if (length == 0 && !ignoreWhitespace && line.getLength() > 0) { + // found a non-empty line which cannot be shifted + return; + } else + occurrences[i]= new Region(index, length); + } else { + // found a line which cannot be shifted + return; + } + } + + // ok - change the document + int decrement= 0; + for (int i= 0; i < occurrences.length; i++) { + IRegion r= occurrences[i]; + d.replace(r.getOffset() - decrement, r.getLength(), ""); //$NON-NLS-1$ + decrement += r.getLength(); + } + + } catch (BadLocationException x) { + if (TRACE_ERRORS) + System.out.println("TextViewer.shiftLeft: BadLocationException"); //$NON-NLS-1$ + } + } + + /** + * Returns whether the shown text can be printed. + * + * @return the viewer's printable mode + */ + protected boolean isPrintable() { + /* + * 1GK7Q10: ITPUI:WIN98 - internal error after invoking print at editor view + * Changed from returning true to testing the length of the printer queue + */ + PrinterData[] printerList= Printer.getPrinterList(); + return (printerList != null && printerList.length > 0); + } + + /** + * Brings up a print dialog and calls <code>printContents(Printer)</code> which + * performs the actual print. + * + * Subclasses may override. + */ + protected void print() { + + final PrintDialog dialog= new PrintDialog(fTextWidget.getShell(), SWT.PRIMARY_MODAL); + final PrinterData data= dialog.open(); + + if (data != null) { + + final Printer printer= new Printer(data); + final Runnable styledTextPrinter= fTextWidget.print(printer); + + Thread printingThread= new Thread("Printing") { //$NON-NLS-1$ + public void run() { + styledTextPrinter.run(); + printer.dispose(); + } + }; + printingThread.start(); + } + } + + + //------ find support + + /** + * @see IFindReplaceTarget#canPerformFind + */ + protected boolean canPerformFind() { + IDocument d= getVisibleDocument(); + return (fTextWidget != null && d != null && d.getLength() > 0); + } + + /** + * @see IFindReplaceTarget#findAndSelect(int, String, boolean, boolean, boolean) + */ + protected int findAndSelect(int startPosition, String findString, boolean forwardSearch, boolean caseSensitive, boolean wholeWord) { + if (fTextWidget == null) + return -1; + + try { + int offset= (startPosition == -1 ? startPosition : startPosition - getVisibleRegionOffset()); + int pos= getVisibleDocument().search(offset, findString, forwardSearch, caseSensitive, wholeWord); + if (pos > -1) { + int length= findString.length(); + if (redraws()) { + fTextWidget.setSelectionRange(pos, length); + internalRevealRange(pos, pos + length); + selectionChanged(pos, length); + } else { + setSelectedRange(pos + getVisibleRegionOffset(), length); + } + } + return pos + getVisibleRegionOffset(); + } catch (BadLocationException x) { + if (TRACE_ERRORS) + System.out.println(JFaceTextMessages.getString("TextViewer.error.bad_location.findAndSelect")); //$NON-NLS-1$ + } + + return -1; + } + + /** + * @see IFindReplaceTarget#findAndSelect(int, String, boolean, boolean, boolean) + * @since 2.0 + */ + private int findAndSelectInRange(int startPosition, String findString, boolean forwardSearch, boolean caseSensitive, boolean wholeWord, int rangeOffset, int rangeLength) { + if (fTextWidget == null) + return -1; + + try { + int offset; + if (forwardSearch && (startPosition == -1 || startPosition < rangeOffset)) { + offset= rangeOffset; + } else if (!forwardSearch && (startPosition == -1 || startPosition > rangeOffset + rangeLength)) { + offset= rangeOffset + rangeLength; + } else { + offset= startPosition; + } + offset -= getVisibleRegionOffset(); + + int pos= getVisibleDocument().search(offset, findString, forwardSearch, caseSensitive, wholeWord); + + int length = findString.length(); + if (pos != -1 && (pos + getVisibleRegionOffset() < rangeOffset || pos + getVisibleRegionOffset() + length > rangeOffset + rangeLength)) + pos= -1; + + if (pos > -1) { + if (redraws()) { + fTextWidget.setSelectionRange(pos, length); + internalRevealRange(pos, pos + length); + selectionChanged(pos, length); + } else { + setSelectedRange(pos + getVisibleRegionOffset(), length); + } + } + return pos + getVisibleRegionOffset(); + } catch (BadLocationException x) { + if (TRACE_ERRORS) + System.out.println(JFaceTextMessages.getString("TextViewer.error.bad_location.findAndSelect")); //$NON-NLS-1$ + } + + return -1; + } + + //---------- text presentation support + + /* + * @see ITextViewer#setTextColor + */ + public void setTextColor(Color color) { + if (color != null) + setTextColor(color, 0, getDocument().getLength(), true); + } + + /* + * @see ITextViewer#setTextColor + */ + public void setTextColor(Color color, int start, int length, boolean controlRedraw) { + + if (fTextWidget != null) { + + if (controlRedraw) + fTextWidget.setRedraw(false); + + StyleRange s= new StyleRange(); + s.foreground= color; + s.start= start; + s.length= length; + + fTextWidget.setStyleRange(s); + + if (controlRedraw) + fTextWidget.setRedraw(true); + } + } + + /** + * Adds the given presentation to the viewer's style information. + * + * @param presentation the presentation to be added + */ + private void addPresentation(TextPresentation presentation) { + + StyleRange range= presentation.getDefaultStyleRange(); + if (range != null) { + + fTextWidget.setStyleRange(range); + Iterator e= presentation.getNonDefaultStyleRangeIterator(); + while (e.hasNext()) { + range= (StyleRange) e.next(); + fTextWidget.setStyleRange(range); + } + + } else { + + Iterator e= presentation.getAllStyleRangeIterator(); + + // use optimized StyledText + StyleRange[] ranges= new StyleRange[presentation.getDenumerableRanges()]; + for (int i= 0; i < ranges.length; i++) + ranges[i]= (StyleRange) e.next(); + + IRegion region= presentation.getCoverage(); + fTextWidget.replaceStyleRanges(region.getOffset(), region.getLength(), ranges); + } + } + + /** + * Returns the visible region if it is not equal to the whole document. + * Otherwise returns <code>null</code>. + * + * @return the viewer's visible region if smaller than input document, otherwise <code>null</code> + */ + protected IRegion internalGetVisibleRegion() { + + IDocument document= getVisibleDocument(); + if (document instanceof ChildDocument) { + Position p= ((ChildDocument) document).getParentDocumentRange(); + return new Region(p.getOffset(), p.getLength()); + } + + return null; + } + + /* + * @see ITextViewer#changeTextPresentation + */ + public void changeTextPresentation(TextPresentation presentation, boolean controlRedraw) { + + if (presentation == null) + return; + + presentation.setResultWindow(internalGetVisibleRegion()); + if (presentation.isEmpty() || fTextWidget == null) + return; + + if (controlRedraw) + fTextWidget.setRedraw(false); + + if (fReplaceTextPresentation) + TextPresentation.applyTextPresentation(presentation, fTextWidget); + else + addPresentation(presentation); + + if (controlRedraw) + fTextWidget.setRedraw(true); + } + + /* + * @see ITextViewer#getFindReplaceTarget() + */ + public IFindReplaceTarget getFindReplaceTarget() { + if (fFindReplaceTarget == null) + fFindReplaceTarget= new FindReplaceTarget(); + return fFindReplaceTarget; + } + + /* + * @see ITextViewer#getTextOperationTarget() + */ + public ITextOperationTarget getTextOperationTarget() { + return this; + } + + /* + * @see ITextViewerExtension#appendVerifyKeyListener(VerifyKeyListener) + * @since 2.0 + */ + public void appendVerifyKeyListener(VerifyKeyListener listener) { + int index= fVerifyKeyListenersManager.numberOfListeners(); + fVerifyKeyListenersManager.insertListener(listener, index); + } + + /* + * @see ITextViewerExtension#prependVerifyKeyListener(VerifyKeyListener) + * @since 2.0 + */ + public void prependVerifyKeyListener(VerifyKeyListener listener) { + fVerifyKeyListenersManager.insertListener(listener, 0); + + } + + /* + * @see ITextViewerExtension#removeVerifyKeyListener(VerifyKeyListener) + * @since 2.0 + */ + public void removeVerifyKeyListener(VerifyKeyListener listener) { + fVerifyKeyListenersManager.removeListener(listener); + } + + /* + * @see ITextViewerExtension#getMark() + * @since 2.0 + */ + public int getMark() { + return fMarkPosition == null || fMarkPosition.isDeleted() + ? -1 + : fMarkPosition.getOffset(); + } + + /* + * @see ITextViewerExtension#setMark(int) + * @since 2.0 + */ + public void setMark(int offset) { + + // clear + if (offset == -1) { + if (fMarkPosition != null && !fMarkPosition.isDeleted()) { + + IDocument document= getDocument(); + if (document != null) + document.removePosition(fMarkPosition); + } + + fMarkPosition= null; + + markChanged(-1, 0); + + // set + } else { + if (fMarkPosition == null) { + + IDocument document= getDocument(); + if (document == null) + return; + + if (offset < 0 || offset > document.getLength()) + return; + + try { + Position position= new Position(offset); + document.addPosition(MARK_POSITION_CATEGORY, position); + fMarkPosition= position; + + } catch (BadLocationException e) { + return; + } catch (BadPositionCategoryException e) { + return; + } + + } else { + + IDocument document= getDocument(); + if (document == null) { + fMarkPosition= null; + return; + } + + if (offset < 0 || offset > document.getLength()) + return; + + fMarkPosition.setOffset(offset); + fMarkPosition.undelete(); + } + + markChanged(fMarkPosition.offset - getVisibleRegionOffset(), 0); + } + } + + /* + * @see Viewer#inputChanged(Object, Object) + * @since 2.0 + */ + protected void inputChanged(Object newInput, Object oldInput) { + + IDocument oldDocument= (IDocument) oldInput; + if (oldDocument != null) { + if (fMarkPosition != null && !fMarkPosition.isDeleted()) + oldDocument.removePosition(fMarkPosition); + + try { + oldDocument.removePositionUpdater(fMarkPositionUpdater); + oldDocument.removePositionCategory(MARK_POSITION_CATEGORY); + + } catch (BadPositionCategoryException e) { + } + } + + fMarkPosition= null; + + super.inputChanged(newInput, oldInput); + + IDocument newDocument= (IDocument) newInput; + if (newDocument != null) { + newDocument.addPositionCategory(MARK_POSITION_CATEGORY); + newDocument.addPositionUpdater(fMarkPositionUpdater); + } + } + + /** + * Informs all text listeners about the change of the viewer's redraw state. + * @since 2.0 + */ + private void fireRedrawChanged() { + fWidgetCommand.start= 0; + fWidgetCommand.length= 0; + fWidgetCommand.text= null; + fWidgetCommand.event= null; + updateTextListeners(fWidgetCommand); + } + + /** + * Enables the redrawing of this text viewer. Subclasses may extend. + * @since 2.0 + */ + protected void enabledRedrawing() { + if (fDocumentAdapter instanceof IDocumentAdapterExtension) { + IDocumentAdapterExtension extension= (IDocumentAdapterExtension) fDocumentAdapter; + StyledText textWidget= getTextWidget(); + if (textWidget != null && !textWidget.isDisposed()) { + int topPixel= textWidget.getTopPixel(); + extension.resumeForwardingDocumentChanges(); + if (topPixel > -1) { + try { + textWidget.setTopPixel(topPixel); + } catch (IllegalArgumentException x) { + // changes don't allow for the previous top pixel + } + } + } + } + + setSelectedRange(fDocumentSelection.x, fDocumentSelection.y); + revealRange(fDocumentSelection.x, fDocumentSelection.y); + + if (fTextWidget != null && !fTextWidget.isDisposed()) + fTextWidget.setRedraw(true); + + fireRedrawChanged(); + } + + /** + * Disables the redrawing of this text viewer. Subclasses may extend. + * @since 2.0 + */ + protected void disableRedrawing() { + + fDocumentSelection= getSelectedRange(); + + if (fDocumentAdapter instanceof IDocumentAdapterExtension) { + IDocumentAdapterExtension extension= (IDocumentAdapterExtension) fDocumentAdapter; + extension.stopForwardingDocumentChanges(); + } + + if (fTextWidget != null && !fTextWidget.isDisposed()) + fTextWidget.setRedraw(false); + + fireRedrawChanged(); + } + + /* + * @see ITextViewerExtension#setRedraw(boolean) + * @since 2.0 + */ + public final void setRedraw(boolean redraw) { + if (!redraw) { + if (fRedrawCounter == 0) + disableRedrawing(); + ++ fRedrawCounter; + } else { + -- fRedrawCounter; + if (fRedrawCounter == 0) + enabledRedrawing(); + } + } + + /** + * Returns whether this viewer redraws itself. + * + * @return <code>true</code> if this viewer redraws itself + * @since 2.0 + */ + protected final boolean redraws() { + return fRedrawCounter <= 0; + } + + /** + * Starts the sequential rewrite mode of the viewer's document. + * @since 2.0 + */ + protected final void startSequentialRewriteMode(boolean normalized) { + IDocument document= getDocument(); + if (document instanceof IDocumentExtension) { + IDocumentExtension extension= (IDocumentExtension) document; + extension.startSequentialRewrite(normalized); + } + } + + /** + * Sets the sequential rewrite mode of the viewer's document. + * @since 2.0 + */ + protected final void stopSequentialRewriteMode() { + IDocument document= getDocument(); + if (document instanceof IDocumentExtension) { + IDocumentExtension extension= (IDocumentExtension) document; + extension.stopSequentialRewrite(); + } + } + + /* + * @see org.eclipse.jface.text.ITextViewerExtension#getRewriteTarget() + * @since 2.0 + */ + public IRewriteTarget getRewriteTarget() { + if (fRewriteTarget == null) + fRewriteTarget= new RewriteTarget(); + return fRewriteTarget; + } +} diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/TextViewerHoverManager.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/TextViewerHoverManager.java new file mode 100644 index 000000000..426bdb67d --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/TextViewerHoverManager.java @@ -0,0 +1,300 @@ +/********************************************************************** +Copyright (c) 2000, 2002 IBM Corp. and others. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Common Public License v1.0 +which accompanies this distribution, and is available at +http://www.eclipse.org/legal/cpl-v10.html + +Contributors: + IBM Corporation - Initial implementation +**********************************************************************/ + +package org.eclipse.jface.text; + + +import org.eclipse.swt.custom.StyledText; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.widgets.Display; + + +/** + * This manager controls the layout, content, and visibility of an information + * control in reaction to mouse hover events issued by the text widget of a + * text viewer. It overrides <code>computeInformation</code>, so that the + * computation is performed in a dedicated background thread. This implies + * that the used <code>ITextHover</code> objects must be capable of + * operating in a non-UI thread. + * + * @since 2.0 + */ +class TextViewerHoverManager extends AbstractHoverInformationControlManager implements IWidgetTokenKeeper { + + /** The text viewer */ + private TextViewer fTextViewer; + /** The hover information computation thread */ + private Thread fThread; + /** The stopper of the computation thread */ + private ITextListener fStopper; + /** Internal monitor */ + private Object fMutex= new Object(); + + + /** + * Creates a new text viewer hover manager specific for the given text viewer. + * The manager uses the given information control creator. + * + * @param textViewer the viewer for which the controller is created + * @param creator the information control creator + */ + public TextViewerHoverManager(TextViewer textViewer, IInformationControlCreator creator) { + super(creator); + fTextViewer= textViewer; + fStopper= new ITextListener() { + public void textChanged(TextEvent event) { + synchronized (fMutex) { + if (fThread != null) { + fThread.interrupt(); + fThread= null; + } + } + } + }; + } + + /** + * Determines all necessary details and delegates the computation into + * a background thread. + */ + protected void computeInformation() { + + Point location= getHoverEventLocation(); + int offset= computeOffsetAtLocation(location.x, location.y); + if (offset == -1) { + setInformation(null, null); + return; + } + + final ITextHover hover= fTextViewer.getTextHover(offset); + if (hover == null) { + setInformation(null, null); + return; + } + + final IRegion region= hover.getHoverRegion(fTextViewer, offset); + if (region == null) { + setInformation(null, null); + return; + } + + final Rectangle area= computeArea(region); + if (area == null || area.isEmpty()) { + setInformation(null, null); + return; + } + + if (fThread != null) { + setInformation(null, null); + return; + } + + fThread= new Thread() { + public void run() { + // http://bugs.eclipse.org/bugs/show_bug.cgi?id=17693 + try { + + if (fThread != null) { + String information= hover.getHoverInfo(fTextViewer, region); + setInformation(information, area); + } else { + setInformation(null, null); + } + + } finally { + synchronized (fMutex) { + if (fTextViewer != null) + fTextViewer.removeTextListener(fStopper); + fThread= null; + } + } + } + }; + + fThread.setDaemon(true); + fThread.setPriority(Thread.MIN_PRIORITY); + synchronized (fMutex) { + fTextViewer.addTextListener(fStopper); + fThread.start(); + } + } + + /** + * As computation is done in the background, this method is + * also called in the background thread. Delegates the control + * flow back into the ui thread, in order to allow displaying the + * information in the information control. + */ + protected void presentInformation() { + if (fTextViewer == null) + return; + + StyledText textWidget= fTextViewer.getTextWidget(); + if (textWidget != null && !textWidget.isDisposed()) { + Display display= textWidget.getDisplay(); + if (display == null) + return; + + display.asyncExec(new Runnable() { + public void run() { + doPresentInformation(); + } + }); + } + } + + /* + * @see AbstractInformationControlManager#presentInformation() + */ + protected void doPresentInformation() { + super.presentInformation(); + } + + /** + * Computes the document offset underlying the given text widget coordinates. + * This method uses a linear search as it cannot make any assumption about + * how the document is actually presented in the widget. (Covers cases such + * as bidi text.) + * + * @param x the x coordinate inside the text widget + * @param y the y coordinate inside the text widget + * @return the document offset corresponding to the given point + */ + private int computeOffsetAtLocation(int x, int y) { + + StyledText styledText= fTextViewer.getTextWidget(); + IDocument document= fTextViewer.getVisibleDocument(); + + if (document == null) + return -1; + + int line= (y + styledText.getTopPixel()) / styledText.getLineHeight(); + int lineCount= document.getNumberOfLines(); + + if (line > lineCount - 1) + line= lineCount - 1; + + if (line < 0) + line= 0; + + try { + + IRegion lineInfo= document.getLineInformation(line); + int low= lineInfo.getOffset(); + int high= low + lineInfo.getLength(); + + int lookup= styledText.getLocationAtOffset(low).x; + int guess= low; + int guessDelta= Math.abs(lookup - x); + + for (int i= low + 1; i < high; i++) { + lookup= styledText.getLocationAtOffset(i).x; + int delta= Math.abs(lookup - x); + if (delta < guessDelta) { + guess= i; + guessDelta= delta; + } + } + + return guess + fTextViewer.getVisibleRegionOffset(); + + } catch (BadLocationException e) { + } + + return -1; + } + + /** + * Determines graphical area covered by the given text region. + * + * @param region the region whose graphical extend must be computed + * @return the graphical extend of the given region + */ + private Rectangle computeArea(IRegion region) { + + StyledText styledText= fTextViewer.getTextWidget(); + + IRegion visibleRegion= fTextViewer.getVisibleRegion(); + int start= region.getOffset() - visibleRegion.getOffset(); + int end= start + region.getLength(); + if (end > visibleRegion.getLength()) + end= visibleRegion.getLength(); + + Point upperLeft= styledText.getLocationAtOffset(start); + Point lowerRight= new Point(upperLeft.x, upperLeft.y); + + for (int i= start +1; i < end; i++) { + + Point p= styledText.getLocationAtOffset(i); + + if (upperLeft.x > p.x) + upperLeft.x= p.x; + + if (upperLeft.y > p.y) + upperLeft.y= p.y; + + if (lowerRight.x < p.x) + lowerRight.x= p.x; + + if (lowerRight.y < p.y) + lowerRight.y= p.y; + } + + lowerRight.x += fTextViewer.getAverageCharWidth(); + lowerRight.y += styledText.getLineHeight(); + + int width= lowerRight.x - upperLeft.x; + int height= lowerRight.y - upperLeft.y; + return new Rectangle(upperLeft.x, upperLeft.y, width, height); + } + + /* + * @see AbstractInformationControlManager#showInformationControl(Rectangle) + */ + protected void showInformationControl(Rectangle subjectArea) { + if (fTextViewer != null && fTextViewer.requestWidgetToken(this)) + super.showInformationControl(subjectArea); + } + + /* + * @see AbstractInformationControlManager#hideInformationControl() + */ + protected void hideInformationControl() { + try { + super.hideInformationControl(); + } finally { + if (fTextViewer != null) + fTextViewer.releaseWidgetToken(this); + } + } + + /* + * @see AbstractInformationControlManager#handleInformationControlDisposed() + */ + protected void handleInformationControlDisposed() { + try { + super.handleInformationControlDisposed(); + } finally { + if (fTextViewer != null) + fTextViewer.releaseWidgetToken(this); + } + } + + /* + * @see IWidgetTokenKeeper#requestWidgetToken(IWidgetTokenOwner) + */ + public boolean requestWidgetToken(IWidgetTokenOwner owner) { + super.hideInformationControl(); + return true; + } +} + diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/AdditionalInfoController.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/AdditionalInfoController.java new file mode 100644 index 000000000..c1f642abb --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/AdditionalInfoController.java @@ -0,0 +1,232 @@ +/********************************************************************** +Copyright (c) 2000, 2002 IBM Corp. and others. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Common Public License v1.0 +which accompanies this distribution, and is available at +http://www.eclipse.org/legal/cpl-v10.html + +Contributors: + IBM Corporation - Initial implementation +**********************************************************************/ + +package org.eclipse.jface.text.contentassist; + + +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.events.DisposeListener; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.TableItem; + +import org.eclipse.jface.text.AbstractInformationControlManager; +import org.eclipse.jface.text.IInformationControl; +import org.eclipse.jface.text.IInformationControlCreator; +import org.eclipse.jface.util.Assert; + + + +/** + * Displays the additional information available for a completion proposal. + * + * @since 2.0 + */ +class AdditionalInfoController extends AbstractInformationControlManager implements Runnable { + + /** + * Internal table selection listener. + */ + private class TableSelectionListener implements SelectionListener { + + /* + * @see SelectionListener#widgetSelected(SelectionEvent) + */ + public void widgetSelected(SelectionEvent e) { + handleTableSelectionChanged(); + } + + /* + * @see SelectionListener#widgetDefaultSelected(SelectionEvent) + */ + public void widgetDefaultSelected(SelectionEvent e) { + } + }; + + + private Table fProposalTable; + private Thread fThread; + private boolean fIsReset= false; + + private Object fMutex= new Object(); + private Object fStartSignal; + + private SelectionListener fSelectionListener= new TableSelectionListener(); + private int fDelay; + + + /** + * Creates a new additional information controller. + * + * @param creator the information control creator to be used by this controller + * @param delay time in milliseconds after which additional info should be displayed + */ + AdditionalInfoController(IInformationControlCreator creator, int delay) { + super(creator); + fDelay= delay; + setAnchor(ANCHOR_RIGHT); + setFallbackAnchors(new Anchor[] { ANCHOR_LEFT, ANCHOR_BOTTOM, ANCHOR_RIGHT }); + } + + /* + * @see AbstractInformationControlManager#install(Control) + */ + public void install(Control control) { + + if (fProposalTable == control) { + // already installed + return; + } + + super.install(control); + + Assert.isTrue(control instanceof Table); + fProposalTable= (Table) control; + fProposalTable.addSelectionListener(fSelectionListener); + fThread= new Thread(this, JFaceTextMessages.getString("InfoPopup.info_delay_timer_name")); //$NON-NLS-1$ + + fStartSignal= new Object(); + synchronized (fStartSignal) { + fThread.start(); + try { + // wait until thread is ready + fStartSignal.wait(); + } catch (InterruptedException x) { + } + } + } + + /* + * @see AbstractInformationControlManager#disposeInformationControl() + */ + public void disposeInformationControl() { + + if (fThread != null) { + fThread.interrupt(); + fThread= null; + } + + if (fProposalTable != null && !fProposalTable.isDisposed()) { + fProposalTable.removeSelectionListener(fSelectionListener); + fProposalTable= null; + } + + super.disposeInformationControl(); + } + + /* + * @see java.lang.Runnable#run() + */ + public void run() { + try { + while (true) { + + synchronized (fMutex) { + + if (fStartSignal != null) { + synchronized (fStartSignal) { + fStartSignal.notifyAll(); + fStartSignal= null; + } + } + + // Wait for a selection event to occur. + fMutex.wait(); + + while (true) { + fIsReset= false; + // Delay before showing the popup. + fMutex.wait(fDelay); + if (!fIsReset) + break; + } + } + + if (fProposalTable != null && !fProposalTable.isDisposed()) { + fProposalTable.getDisplay().asyncExec(new Runnable() { + public void run() { + if (!fIsReset) + showInformation(); + } + }); + } + + } + } catch (InterruptedException e) { + } + + fThread= null; + } + + /** + *Handles a change of the line selected in the associated selector. + */ + public void handleTableSelectionChanged() { + + if (fProposalTable != null && !fProposalTable.isDisposed() && fProposalTable.isVisible()) { + synchronized (fMutex) { + fIsReset= true; + fMutex.notifyAll(); + } + } + } + + /* + * @see AbstractInformationControlManager#computeInformation() + */ + protected void computeInformation() { + + if (fProposalTable == null || fProposalTable.isDisposed()) + return; + + TableItem[] selection= fProposalTable.getSelection(); + if (selection != null && selection.length > 0) { + + TableItem item= selection[0]; + + // compute information + String information= null; + Object d= item.getData(); + if (d instanceof ICompletionProposal) { + ICompletionProposal p= (ICompletionProposal) d; + information= p.getAdditionalProposalInfo(); + } + + // compute subject area + setMargins(4, -1); + Rectangle area= fProposalTable.getBounds(); + area.x= 0; // subject area is the whole subject control + area.y= 0; + + // set information & subject area + setInformation(information, area); + } + } + + /* + * @see org.eclipse.jface.text.AbstractInformationControlManager#computeSizeConstraints(Control, IInformationControl) + */ + protected Point computeSizeConstraints(Control subjectControl, IInformationControl informationControl) { + Point sizeConstraint= super.computeSizeConstraints(subjectControl, informationControl); + Point size= subjectControl.getSize(); + if (sizeConstraint.x < size.x) + sizeConstraint.x= size.x; + if (sizeConstraint.y < size.y) + sizeConstraint.y= size.y; + return sizeConstraint; + } +} + + diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/CompletionProposal.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/CompletionProposal.java new file mode 100644 index 000000000..a33cf7f02 --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/CompletionProposal.java @@ -0,0 +1,120 @@ +package org.eclipse.jface.text.contentassist; + +/* + * (c) Copyright IBM Corp. 2000, 2001. + * All Rights Reserved. + */ + + +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Point; + +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.util.Assert; + + + +/** + * The standard implementation of the <code>ICompletionProposal</code> interface. + */ +public final class CompletionProposal implements ICompletionProposal { + + private String fDisplayString; + private String fReplacementString; + private int fReplacementOffset; + private int fReplacementLength; + private int fCursorPosition; + private Image fImage; + private IContextInformation fContextInformation; + private String fAdditionalProposalInfo; + + /** + * Creates a new completion proposal based on the provided information. The replacement string is + * considered being the display string too. All remaining fields are set to <code>null</code>. + * + * @param replacementString the actual string to be inserted into the document + * @param replacementOffset the offset of the text to be replaced + * @param replacementLength the length of the text to be replaced + * @param cursorPosition the position of the cursor following the insert relative to replacementOffset + */ + public CompletionProposal(String replacementString, int replacementOffset, int replacementLength, int cursorPosition) { + this(replacementString, replacementOffset, replacementLength, cursorPosition, null, null, null, null); + } + + /** + * Creates a new completion proposal. All fields are initialized based on the provided information. + * + * @param replacementString the actual string to be inserted into the document + * @param replacementOffset the offset of the text to be replaced + * @param replacementLength the length of the text to be replaced + * @param cursorPosition the position of the cursor following the insert relative to replacementOffset + * @param image the image to display for this proposal + * @param displayString the string to be displayed for the proposal + * @param contentInformation the context information associated with this proposal + * @param additionalProposalInfo the additional information associated with this proposal + */ + public CompletionProposal(String replacementString, int replacementOffset, int replacementLength, int cursorPosition, Image image, String displayString, IContextInformation contextInformation, String additionalProposalInfo) { + Assert.isNotNull(replacementString); + Assert.isTrue(replacementOffset >= 0); + Assert.isTrue(replacementLength >= 0); + Assert.isTrue(cursorPosition >= 0); + + fReplacementString= replacementString; + fReplacementOffset= replacementOffset; + fReplacementLength= replacementLength; + fCursorPosition= cursorPosition; + fImage= image; + fDisplayString= displayString; + fContextInformation= contextInformation; + fAdditionalProposalInfo= additionalProposalInfo; + } + + /* + * @see ICompletionProposal#apply + */ + public void apply(IDocument document) { + try { + document.replace(fReplacementOffset, fReplacementLength, fReplacementString); + } catch (BadLocationException x) { + // ignore + } + } + + /* + * @see ICompletionProposal#getSelection + */ + public Point getSelection(IDocument document) { + return new Point(fReplacementOffset + fCursorPosition, 0); + } + + /* + * @see ICompletionProposal#getContextInformation() + */ + public IContextInformation getContextInformation() { + return fContextInformation; + } + + /* + * @see ICompletionProposal#getImage() + */ + public Image getImage() { + return fImage; + } + + /* + * @see ICompletionProposal#getDisplayString() + */ + public String getDisplayString() { + if (fDisplayString != null) + return fDisplayString; + return fReplacementString; + } + + /* + * @see ICompletionProposal#getAdditionalProposalInfo() + */ + public String getAdditionalProposalInfo() { + return fAdditionalProposalInfo; + } +} diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/CompletionProposalPopup.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/CompletionProposalPopup.java new file mode 100644 index 000000000..da95fcc30 --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/CompletionProposalPopup.java @@ -0,0 +1,603 @@ +/********************************************************************** +Copyright (c) 2000, 2002 IBM Corp. and others. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Common Public License v1.0 +which accompanies this distribution, and is available at +http://www.eclipse.org/legal/cpl-v10.html + +Contributors: + IBM Corporation - Initial implementation +**********************************************************************/ + +package org.eclipse.jface.text.contentassist; + + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.BusyIndicator; +import org.eclipse.swt.custom.StyledText; +import org.eclipse.swt.events.ControlEvent; +import org.eclipse.swt.events.ControlListener; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.events.VerifyEvent; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.TableItem; + +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.ITextViewer; + + + +/** + * This class is used to present proposals to the user. If additional + * information exists for a proposal, then selecting that proposal + * will result in the information being displayed in a secondary + * window. + * + * @see org.eclipse.jface.text.contentassist.ICompletionProposal + */ +class CompletionProposalPopup implements IContentAssistListener { + + private ITextViewer fViewer; + private ContentAssistant fContentAssistant; + private AdditionalInfoController fAdditionalInfoController; + + private PopupCloser fPopupCloser= new PopupCloser(); + private Shell fProposalShell; + private Table fProposalTable; + private boolean fInserting= false; + + private long fInvocationCounter= 0; + private ICompletionProposal[] fFilteredProposals; + private ICompletionProposal[] fComputedProposals; + private int fInvocationOffset; + private int fFilterOffset; + + private String fLineDelimiter; + + + /** + * Creates a new completion proposal popup for the given elements. + * + * @param contentAssistant the content assistant feeding this popup + * @param viewer the viewer on top of which this popup appears + * @param infoController the info control collaborating with this popup + * @since 2.0 + */ + public CompletionProposalPopup(ContentAssistant contentAssistant, ITextViewer viewer, AdditionalInfoController infoController) { + fContentAssistant= contentAssistant; + fViewer= viewer; + fAdditionalInfoController= infoController; + } + + /** + * Computes and presents completion proposals. The flag indicates whether this call has + * be made out of an auto activation context. + * + * @param autoActivated <code>true</code> if auto activation context + * @return an error message or <code>null</code> in case of no error + */ + public String showProposals(final boolean autoActivated) { + final StyledText styledText= fViewer.getTextWidget(); + BusyIndicator.showWhile(styledText.getDisplay(), new Runnable() { + public void run() { + + + fInvocationOffset= fViewer.getSelectedRange().x; + fComputedProposals= computeProposals(fInvocationOffset); + + + int count= (fComputedProposals == null ? 0 : fComputedProposals.length); + if (count == 0) { + + if (!autoActivated) + styledText.getDisplay().beep(); + + } else { + + if (count == 1 && !autoActivated && fContentAssistant.isAutoInserting()) + + insertProposal(fComputedProposals[0], (char) 0, fInvocationOffset); + + else { + + if (fLineDelimiter == null) + fLineDelimiter= styledText.getLineDelimiter(); + + createProposalSelector(); + setProposals(fComputedProposals); + displayProposals(); + } + } + } + }); + + return getErrorMessage(); + } + + /** + * Returns the completion proposal available at the given offset of the + * viewer's document. Delegates the work to the content assistant. + * + * @param offset the offset + * @return the completion proposals available at this offset + */ + private ICompletionProposal[] computeProposals(int offset) { + return fContentAssistant.computeCompletionProposals(fViewer, offset); + } + + /** + * Returns the error message. + * + * @return the error message + */ + private String getErrorMessage() { + return fContentAssistant.getErrorMessage(); + } + + /** + * Creates the proposal selector. + */ + private void createProposalSelector() { + if (Helper.okToUse(fProposalShell)) + return; + + Control control= fViewer.getTextWidget(); + fProposalShell= new Shell(control.getShell(), SWT.ON_TOP | SWT.RESIZE ); + fProposalTable= new Table(fProposalShell, SWT.H_SCROLL | SWT.V_SCROLL); + + fProposalTable.setLocation(0, 0); + fAdditionalInfoController.setSizeConstraints(50, 10, true, false); + + GridLayout layout= new GridLayout(); + layout.marginWidth= 0; + layout.marginHeight= 0; + fProposalShell.setLayout(layout); + + GridData data= new GridData(GridData.FILL_BOTH); + data.heightHint= fProposalTable.getItemHeight() * 10; + data.widthHint= 300; + fProposalTable.setLayoutData(data); + + fProposalShell.pack(); + + fProposalShell.addControlListener(new ControlListener() { + + public void controlMoved(ControlEvent e) {} + + public void controlResized(ControlEvent e) { + // resets the cached resize constraints + fAdditionalInfoController.setSizeConstraints(50, 10, true, false); + } + }); + + + fProposalShell.setBackground(control.getDisplay().getSystemColor(SWT.COLOR_BLACK)); + + Color c= fContentAssistant.getProposalSelectorBackground(); + if (c == null) + c= control.getDisplay().getSystemColor(SWT.COLOR_INFO_BACKGROUND); + fProposalTable.setBackground(c); + + c= fContentAssistant.getProposalSelectorForeground(); + if (c == null) + c= control.getDisplay().getSystemColor(SWT.COLOR_INFO_FOREGROUND); + fProposalTable.setForeground(c); + + fProposalTable.addSelectionListener(new SelectionListener() { + public void widgetSelected(SelectionEvent e) { + } + + public void widgetDefaultSelected(SelectionEvent e) { + selectProposal(); + } + }); + + fPopupCloser.install(fContentAssistant, fProposalTable); + + fProposalTable.setHeaderVisible(false); + fContentAssistant.addToLayout(this, fProposalShell, ContentAssistant.LayoutManager.LAYOUT_PROPOSAL_SELECTOR, fContentAssistant.getSelectionOffset()); + } + + /** + * Returns the proposal selected in the proposal selector. + * + * @return the selected proposal + * @since 2.0 + */ + private ICompletionProposal getSelectedProposal() { + int i= fProposalTable.getSelectionIndex(); + if (i < 0 || i >= fFilteredProposals.length) + return null; + return fFilteredProposals[i]; + } + + /** + * Takes the selected proposal and applies it. + * @since 2.0 + */ + private void selectProposal() { + ICompletionProposal p= getSelectedProposal(); + hide(); + if (p != null) + insertProposal(p, (char) 0, fViewer.getSelectedRange().x); + } + + /** + * Applies the given proposal at the given offset. The given character is the + * one that triggered the insertion of this proposal. + * + * @param p the completion proposal + * @param trigger the trigger character + * @param offset the offset + * + * @since 2.0 + */ + private void insertProposal(ICompletionProposal p, char trigger, int offset) { + + fInserting= true; + + try { + IDocument document= fViewer.getDocument(); + + if (p instanceof ICompletionProposalExtension) { + ICompletionProposalExtension e= (ICompletionProposalExtension) p; + e.apply(document, trigger, offset); + } else { + p.apply(document); + } + + Point selection= p.getSelection(document); + if (selection != null) { + fViewer.setSelectedRange(selection.x, selection.y); + fViewer.revealRange(selection.x, selection.y); + } + + IContextInformation info= p.getContextInformation(); + if (info != null) { + + int position; + if (p instanceof ICompletionProposalExtension) { + ICompletionProposalExtension e= (ICompletionProposalExtension) p; + position= e.getContextInformationPosition(); + } else { + if (selection == null) + selection= fViewer.getSelectedRange(); + position= selection.x + selection.y; + } + + fContentAssistant.showContextInformation(info, position); + } + + } finally { + fInserting= false; + } + + } + + /** + * Returns whether this popup has the focus. + * @return <code>true</code> if the popup has the focus + */ + public boolean hasFocus() { + if (Helper.okToUse(fProposalShell)) + return (fProposalShell.isFocusControl() || fProposalTable.isFocusControl()); + + return false; + } + + /** + * Hides this popup. + */ + public void hide() { + if (Helper.okToUse(fProposalShell)) { + + fContentAssistant.removeContentAssistListener(this, ContentAssistant.PROPOSAL_SELECTOR); + + fPopupCloser.uninstall(); + fProposalShell.setVisible(false); + fProposalShell.dispose(); + fProposalShell= null; + } + + fFilteredProposals= null; + } + + /** + *Returns whether this popup is active. It is active if the propsal selector is visible. + * @return <code>true</code> if this popup is active + */ + public boolean isActive() { + return fProposalShell != null && !fProposalShell.isDisposed(); + } + + /** + * Initializes the proposal selector with these given proposals. + * + * @param proposals the proposals + */ + private void setProposals(ICompletionProposal[] proposals) { + if (Helper.okToUse(fProposalTable)) { + + fFilteredProposals= proposals; + + fProposalTable.setRedraw(false); + fProposalTable.removeAll(); + + TableItem item; + ICompletionProposal p; + for (int i= 0; i < proposals.length; i++) { + p= proposals[i]; + item= new TableItem(fProposalTable, SWT.NULL); + if (p.getImage() != null) + item.setImage(p.getImage()); + item.setText(p.getDisplayString()); + item.setData(p); + } + + Point currentLocation= fProposalShell.getLocation(); + Point newLocation= getLocation(); + if ((newLocation.x < currentLocation.x && newLocation.y == currentLocation.y) || newLocation.y < currentLocation.y) + fProposalShell.setLocation(newLocation); + + selectProposal(0); + fProposalTable.setRedraw(true); + } + } + + /** + * Returns the graphical location at which this popup should be made visible. + * @return the location of this popup + */ + private Point getLocation() { + StyledText text= fViewer.getTextWidget(); + int caret= text.getCaretOffset(); + Point p= text.getLocationAtOffset(caret); + p= new Point(p.x, p.y + text.getLineHeight()); + return text.toDisplay(p); + } + + /** + *Displays this popup and install the additional info controller, so that additional info + * is displayed when a proposal is selected and additional info is available. + */ + private void displayProposals() { + if (fContentAssistant.addContentAssistListener(this, ContentAssistant.PROPOSAL_SELECTOR)) { + fProposalShell.setVisible(true); + if (fAdditionalInfoController != null) { + fAdditionalInfoController.install(fProposalTable); + fAdditionalInfoController.handleTableSelectionChanged(); + } + } + } + + /* + * @see IContentAssistListener#verifyKey(VerifyEvent) + */ + public boolean verifyKey(VerifyEvent e) { + if (!Helper.okToUse(fProposalShell)) + return true; + + char key= e.character; + if (key == 0) { + int newSelection= fProposalTable.getSelectionIndex(); + int visibleRows= (fProposalTable.getSize().y / fProposalTable.getItemHeight()) - 1; + switch (e.keyCode) { + + case SWT.ARROW_LEFT : + case SWT.ARROW_RIGHT : + filterProposal(); + return true; + + case SWT.ARROW_UP : + newSelection -= 1; + if (newSelection < 0) + newSelection= fProposalTable.getItemCount() - 1; + break; + + case SWT.ARROW_DOWN : + newSelection += 1; + if (newSelection > fProposalTable.getItemCount() - 1) + newSelection= 0; + break; + + case SWT.PAGE_DOWN : + newSelection += visibleRows; + if (newSelection >= fProposalTable.getItemCount()) + newSelection= fProposalTable.getItemCount() - 1; + break; + + case SWT.PAGE_UP : + newSelection -= visibleRows; + if (newSelection < 0) + newSelection= 0; + break; + + case SWT.HOME : + newSelection= 0; + break; + + case SWT.END : + newSelection= fProposalTable.getItemCount() - 1; + break; + + case SWT.CTRL : + case SWT.SHIFT : + return true; + + default : + hide(); + return true; + } + + selectProposal(newSelection); + + e.doit= false; + return false; + + } else { + + switch (key) { + case 0x1B : // Esc + e.doit= false; + hide(); + break; + + case 0x0D : // Enter + e.doit= false; + selectProposal(); + break; + + default: + + if ('\t' == key) { + e.doit= false; + fProposalShell.setFocus(); + return false; + } + + ICompletionProposal p= getSelectedProposal(); + if (p instanceof ICompletionProposalExtension) { + ICompletionProposalExtension t= (ICompletionProposalExtension) p; + char[] triggers= t.getTriggerCharacters(); + if (contains(triggers, key)) { + e.doit= false; + hide(); + insertProposal(p, key, fViewer.getSelectedRange().x); + } + } + } + } + + return true; + } + + /** + * Selects the entry with the given index in the proposal selector and feeds + * the selection to the additional info controller. + * + * @param index the index in the list + * @since 2.0 + */ + private void selectProposal(int index) { + fProposalTable.setSelection(index); + fProposalTable.showSelection(); + if (fAdditionalInfoController != null) + fAdditionalInfoController.handleTableSelectionChanged(); + } + + /** + * Returns whether the given character is contained in the given array of + * characters. + * + * @param characters the list of characters + * @param c the character to look for in the list + * @return <code>true</code> if character belongs to the list + * @since 2.0 + */ + private boolean contains(char[] characters, char c) { + + if (characters == null) + return false; + + for (int i= 0; i < characters.length; i++) { + if (c == characters[i]) + return true; + } + + return false; + } + + /* + * @see IEventConsumer#processEvent(VerifyEvent) + */ + public void processEvent(VerifyEvent e) { + if (!fInserting) + filterProposal(); + } + + /** + * Filters the displayed proposal based on the given cursor position and the + * offset of the original invocation of the content assistant. + */ + private void filterProposal() { + ++ fInvocationCounter; + Control control= fViewer.getTextWidget(); + control.getDisplay().asyncExec(new Runnable() { + long fCounter= fInvocationCounter; + public void run() { + + if (fCounter != fInvocationCounter) return; + + int offset= fViewer.getSelectedRange().x; + ICompletionProposal[] proposals= (offset == -1 ? null : computeFilteredProposals(offset)); + fFilterOffset= offset; + + if (proposals != null && proposals.length > 0) + setProposals(proposals); + else + hide(); + } + }); + } + + /** + * Computes the subset of already computed propsals that are still valid for + * the given offset. + * + * @param offset the offset + * @return the set of filtered proposals + * @since 2.0 + */ + private ICompletionProposal[] computeFilteredProposals(int offset) { + + if (offset == fInvocationOffset) + return fComputedProposals; + + if (offset < fInvocationOffset) { + fInvocationOffset= offset; + fComputedProposals= computeProposals(fInvocationOffset); + return fComputedProposals; + } + + ICompletionProposal[] proposals= fComputedProposals; + if (offset > fFilterOffset) + proposals= fFilteredProposals; + + if (proposals == null) + return null; + + IDocument document= fViewer.getDocument(); + int length= proposals.length; + List filtered= new ArrayList(length); + for (int i= 0; i < length; i++) { + if (proposals[i] instanceof ICompletionProposalExtension) { + + ICompletionProposalExtension p= (ICompletionProposalExtension) proposals[i]; + if (p.isValidFor(document, offset)) + filtered.add(p); + + } else { + // restore original behavior + fInvocationOffset= offset; + fComputedProposals= computeProposals(fInvocationOffset); + return fComputedProposals; + } + } + + ICompletionProposal[] p= new ICompletionProposal[filtered.size()]; + filtered.toArray(p); + return p; + } +} + + diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/ContentAssistant.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/ContentAssistant.java new file mode 100644 index 000000000..f174ac291 --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/ContentAssistant.java @@ -0,0 +1,1377 @@ +/********************************************************************** +Copyright (c) 2000, 2002 IBM Corp. and others. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Common Public License v1.0 +which accompanies this distribution, and is available at +http://www.eclipse.org/legal/cpl-v10.html + +Contributors: + IBM Corporation - Initial implementation +**********************************************************************/ + +package org.eclipse.jface.text.contentassist; + + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.SWTError; +import org.eclipse.swt.custom.StyledText; +import org.eclipse.swt.custom.VerifyKeyListener; +import org.eclipse.swt.events.ControlEvent; +import org.eclipse.swt.events.ControlListener; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.events.DisposeListener; +import org.eclipse.swt.events.FocusEvent; +import org.eclipse.swt.events.FocusListener; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.MouseListener; +import org.eclipse.swt.events.VerifyEvent; +import org.eclipse.swt.graphics.Color; +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.Event; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Widget; + +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IEventConsumer; +import org.eclipse.jface.text.IInformationControlCreator; +import org.eclipse.jface.text.ITextViewer; +import org.eclipse.jface.text.ITextViewerExtension; +import org.eclipse.jface.text.IViewportListener; +import org.eclipse.jface.text.IWidgetTokenKeeper; +import org.eclipse.jface.text.IWidgetTokenOwner; + +import org.eclipse.jface.util.Assert; + + + +/** + * The standard implementation of the <code>IContentAssistant</code> interface. + * Usually, clients instantiate this class and configure it before using it. + */ +public class ContentAssistant implements IContentAssistant, IWidgetTokenKeeper { + + /** + * A generic closer class used to monitor various + * interface events in order to determine whether + * content-assist should be terminated and all + * associated windows closed. + */ + class Closer implements ControlListener, MouseListener, FocusListener, DisposeListener, IViewportListener { + + /** + * Installs this closer on it's viewer's text widget. + */ + protected void install() { + Control w= fViewer.getTextWidget(); + if (Helper.okToUse(w)) { + + Control shell= w.getShell(); + shell.addControlListener(this); + + w.addMouseListener(this); + w.addFocusListener(this); + + /* + * 1GGYYWK: ITPJUI:ALL - Dismissing editor with code assist up causes lots of Internal Errors + */ + w.addDisposeListener(this); + } + + fViewer.addViewportListener(this); + } + + /** + * Uninstalls this closer from the viewer's text widget. + */ + protected void uninstall() { + Control w= fViewer.getTextWidget(); + if (Helper.okToUse(w)) { + + Control shell= w.getShell(); + if (Helper.okToUse(shell)) + shell.removeControlListener(this); + + w.removeMouseListener(this); + w.removeFocusListener(this); + + /* + * 1GGYYWK: ITPJUI:ALL - Dismissing editor with code assist up causes lots of Internal Errors + */ + w.removeDisposeListener(this); + } + + fViewer.removeViewportListener(this); + } + + /* + * @see ControlListener#controlResized(ControlEvent) + */ + public void controlResized(ControlEvent e) { + hide(); + } + + /* + * @see ControlListener#controlMoved(ControlEvent) + */ + public void controlMoved(ControlEvent e) { + hide(); + } + + /* + * @see MouseListener#mouseDown(MouseEvent) + */ + public void mouseDown(MouseEvent e) { + hide(); + } + + /* + * @see MouseListener#mouseUp(MouseEvent) + */ + public void mouseUp(MouseEvent e) { + } + + /* + * @see MouseListener#mouseDoubleClick(MouseEvent) + */ + public void mouseDoubleClick(MouseEvent e) { + hide(); + } + + /* + * @see FocusListener#focusGained(FocusEvent) + */ + public void focusGained(FocusEvent e) { + } + + /* + * @see FocusListener#focusLost(FocusEvent) + */ + public void focusLost(FocusEvent e) { + Control control= fViewer.getTextWidget(); + Display d= control.getDisplay(); + d.asyncExec(new Runnable() { + public void run() { + if (!fProposalPopup.hasFocus() && !fContextInfoPopup.hasFocus()) { + hide(); + } + } + }); + } + + /* + * @seeDisposeListener#widgetDisposed(DisposeEvent) + */ + public void widgetDisposed(DisposeEvent e) { + /* + * 1GGYYWK: ITPJUI:ALL - Dismissing editor with code assist up causes lots of Internal Errors + */ + hide(); + } + + /* + * @see IViewportListener#viewportChanged(int) + */ + public void viewportChanged(int topIndex) { + hide(); + } + + /** + * Hides any open popups. + */ + protected void hide() { + fProposalPopup.hide(); + fContextInfoPopup.hide(); + } + }; + + /** + * An implementation of <code>IContentAssistListener</code>, this class is + * used to monitor key events in support of automatic activation + * of the content assistant. If enabled, the implementation utilizes a + * thread to watch for input characters matching the activation + * characters specified by the content assist processor, and if + * detected, will wait the indicated delay interval before + * activating the content assistant. + */ + class AutoAssistListener implements VerifyKeyListener, Runnable { + + private Thread fThread; + private boolean fIsReset= false; + private Object fMutex= new Object(); + private int fShowStyle; + + private final static int SHOW_PROPOSALS= 1; + private final static int SHOW_CONTEXT_INFO= 2; + + protected AutoAssistListener() { + } + + protected void start(int showStyle) { + fShowStyle= showStyle; + fThread= new Thread(this, JFaceTextMessages.getString("ContentAssistant.assist_delay_timer_name")); //$NON-NLS-1$ + fThread.start(); + } + + public void run() { + try { + while (true) { + synchronized (fMutex) { + if (fAutoActivationDelay != 0) + fMutex.wait(fAutoActivationDelay); + if (fIsReset) { + fIsReset= false; + continue; + } + } + showAssist(fShowStyle); + break; + } + } catch (InterruptedException e) { + } + fThread= null; + } + + protected void reset(int showStyle) { + synchronized (fMutex) { + fShowStyle= showStyle; + fIsReset= true; + fMutex.notifyAll(); + } + } + + protected void stop() { + if (fThread != null) { + fThread.interrupt(); + } + } + + private boolean contains(char[] characters, char character) { + if (characters != null) { + for (int i= 0; i < characters.length; i++) { + if (character == characters[i]) + return true; + } + } + return false; + } + + public void verifyKey(VerifyEvent e) { + + int showStyle; + int pos= fViewer.getSelectedRange().x; + char[] activation= getCompletionProposalAutoActivationCharacters(fViewer.getDocument(), pos); + + if (contains(activation, e.character) && !fProposalPopup.isActive()) + showStyle= SHOW_PROPOSALS; + else { + activation= getContextInformationAutoActivationCharacters(fViewer.getDocument(), pos); + if (contains(activation, e.character) && !fContextInfoPopup.isActive()) + showStyle= SHOW_CONTEXT_INFO; + else { + if (fThread != null && fThread.isAlive()) + stop(); + return; + } + } + + if (fThread != null && fThread.isAlive()) + reset(showStyle); + else + start(showStyle); + } + + protected void showAssist(final int showStyle) { + Control control= fViewer.getTextWidget(); + Display d= control.getDisplay(); + if (d != null) { + try { + d.syncExec(new Runnable() { + public void run() { + if (showStyle == SHOW_PROPOSALS) + fProposalPopup.showProposals(true); + else if (showStyle == SHOW_CONTEXT_INFO) + fContextInfoPopup.showContextProposals(true); + } + }); + } catch (SWTError e) { + } + } + } + }; + + /** + * The laypout manager layouts the various + * windows associated with the content assistant based on the + * settings of the content assistant. + */ + class LayoutManager implements Listener { + + // Presentation types. + public final static int LAYOUT_PROPOSAL_SELECTOR= 0; + public final static int LAYOUT_CONTEXT_SELECTOR= 1; + public final static int LAYOUT_CONTEXT_INFO_POPUP= 2; + + int fContextType= LAYOUT_CONTEXT_SELECTOR; + Shell[] fShells= new Shell[3]; + Object[] fPopups= new Object[3]; + + protected void add(Object popup, Shell shell, int type, int offset) { + Assert.isNotNull(popup); + Assert.isTrue(shell != null && !shell.isDisposed()); + checkType(type); + + if (fShells[type] != shell) { + if (fShells[type] != null) + fShells[type].removeListener(SWT.Dispose, this); + shell.addListener(SWT.Dispose, this); + fShells[type]= shell; + } + + fPopups[type]= popup; + if (type == LAYOUT_CONTEXT_SELECTOR || type == LAYOUT_CONTEXT_INFO_POPUP) + fContextType= type; + + layout(type, offset); + adjustListeners(type); + } + + protected void checkType(int type) { + Assert.isTrue(type == LAYOUT_PROPOSAL_SELECTOR || + type == LAYOUT_CONTEXT_SELECTOR || type == LAYOUT_CONTEXT_INFO_POPUP); + } + + public void handleEvent(Event event) { + Widget source= event.widget; + source.removeListener(SWT.Dispose, this); + + int type= getShellType(source); + checkType(type); + fShells[type]= null; + + switch (type) { + case LAYOUT_PROPOSAL_SELECTOR: + if (fContextType == LAYOUT_CONTEXT_SELECTOR && + Helper.okToUse(fShells[LAYOUT_CONTEXT_SELECTOR])) { + // Restore event notification to the tip popup. + addContentAssistListener((IContentAssistListener) fPopups[LAYOUT_CONTEXT_SELECTOR], CONTEXT_SELECTOR); + } + break; + + case LAYOUT_CONTEXT_SELECTOR: + if (Helper.okToUse(fShells[LAYOUT_PROPOSAL_SELECTOR])) { + if (fProposalPopupOrientation == PROPOSAL_STACKED) + layout(LAYOUT_PROPOSAL_SELECTOR, getSelectionOffset()); + // Restore event notification to the proposal popup. + addContentAssistListener((IContentAssistListener) fPopups[LAYOUT_PROPOSAL_SELECTOR], PROPOSAL_SELECTOR); + } + fContextType= LAYOUT_CONTEXT_INFO_POPUP; + break; + + case LAYOUT_CONTEXT_INFO_POPUP: + if (Helper.okToUse(fShells[LAYOUT_PROPOSAL_SELECTOR])) { + if (fContextInfoPopupOrientation == CONTEXT_INFO_BELOW) + layout(LAYOUT_PROPOSAL_SELECTOR, getSelectionOffset()); + } + fContextType= LAYOUT_CONTEXT_SELECTOR; + break; + } + } + + protected int getShellType(Widget shell) { + for (int i=0; i<fShells.length; i++) { + if (fShells[i] == shell) + return i; + } + return -1; + } + + protected void layout(int type, int offset) { + switch (type) { + case LAYOUT_PROPOSAL_SELECTOR: + layoutProposalSelector(offset); + break; + case LAYOUT_CONTEXT_SELECTOR: + layoutContextSelector(offset); + break; + case LAYOUT_CONTEXT_INFO_POPUP: + layoutContextInfoPopup(offset); + break; + } + } + + protected void layoutProposalSelector(int offset) { + if (fContextType == LAYOUT_CONTEXT_INFO_POPUP && + fContextInfoPopupOrientation == CONTEXT_INFO_BELOW && + Helper.okToUse(fShells[LAYOUT_CONTEXT_INFO_POPUP])) { + // Stack proposal selector beneath the tip box. + Shell shell= fShells[LAYOUT_PROPOSAL_SELECTOR]; + Shell parent= fShells[LAYOUT_CONTEXT_INFO_POPUP]; + shell.setLocation(getStackedLocation(shell, parent)); + } else if (fContextType != LAYOUT_CONTEXT_SELECTOR || + !Helper.okToUse(fShells[LAYOUT_CONTEXT_SELECTOR])) { + // There are no other presentations to be concerned with, + // so place the proposal selector beneath the cursor line. + Shell shell= fShells[LAYOUT_PROPOSAL_SELECTOR]; + shell.setLocation(getBelowLocation(shell, offset)); + } else { + switch (fProposalPopupOrientation) { + case PROPOSAL_REMOVE: { + // Remove the tip selector and place the + // proposal selector beneath the cursor line. + fShells[LAYOUT_CONTEXT_SELECTOR].dispose(); + Shell shell= fShells[LAYOUT_PROPOSAL_SELECTOR]; + shell.setLocation(getBelowLocation(shell, offset)); + break; + } + case PROPOSAL_OVERLAY: { + // Overlay the tip selector with the proposal selector. + Shell shell= fShells[LAYOUT_PROPOSAL_SELECTOR]; + shell.setLocation(getBelowLocation(shell, offset)); + break; + } + case PROPOSAL_STACKED: { + // Stack the proposal selector beneath the tip selector. + Shell shell= fShells[LAYOUT_PROPOSAL_SELECTOR]; + Shell parent= fShells[LAYOUT_CONTEXT_SELECTOR]; + shell.setLocation(getStackedLocation(shell, parent)); + break; + } + } + } + } + + protected void layoutContextSelector(int offset) { + // Always place the context selector beneath the cursor line. + Shell shell= fShells[LAYOUT_CONTEXT_SELECTOR]; + shell.setLocation(getBelowLocation(shell, offset)); + + if (Helper.okToUse(fShells[LAYOUT_PROPOSAL_SELECTOR])) { + switch (fProposalPopupOrientation) { + case PROPOSAL_REMOVE: + // Remove the proposal selector. + fShells[LAYOUT_PROPOSAL_SELECTOR].dispose(); + break; + + case PROPOSAL_OVERLAY: + // The proposal selector has been overlayed by the tip selector. + break; + + case PROPOSAL_STACKED: { + // Stack the proposal selector beneath the tip selector. + shell= fShells[LAYOUT_PROPOSAL_SELECTOR]; + Shell parent= fShells[LAYOUT_CONTEXT_SELECTOR]; + shell.setLocation(getStackedLocation(shell, parent)); + break; + } + } + } + } + + protected void layoutContextInfoPopup(int offset) { + switch (fContextInfoPopupOrientation) { + case CONTEXT_INFO_ABOVE: { + // Place the popup above the cursor line. + Shell shell= fShells[LAYOUT_CONTEXT_INFO_POPUP]; + shell.setLocation(getAboveLocation(shell, offset)); + break; + } + case CONTEXT_INFO_BELOW: { + // Place the popup beneath the cursor line. + Shell parent= fShells[LAYOUT_CONTEXT_INFO_POPUP]; + parent.setLocation(getBelowLocation(parent, offset)); + if (Helper.okToUse(fShells[LAYOUT_PROPOSAL_SELECTOR])) { + // Stack the proposal selector beneath the context info popup. + Shell shell= fShells[LAYOUT_PROPOSAL_SELECTOR]; + shell.setLocation(getStackedLocation(shell, parent)); + } + break; + } + } + } + + protected void shiftLeftLocation(Point location, Rectangle shellBounds, Rectangle displayBounds) { + if (location.x + shellBounds.width > displayBounds.width) + location.x= displayBounds.width - shellBounds.width; + } + + protected void shiftDownLocation(Point location, Rectangle shellBounds, Rectangle displayBounds) { + if (location.y < displayBounds.y) + location.y= displayBounds.y; + } + + protected void shiftUpLocation(Point location, Rectangle shellBounds, Rectangle displayBounds) { + if (location.y + shellBounds.height > displayBounds.height) + location.y= displayBounds.height - shellBounds.height; + } + + protected Point getAboveLocation(Shell shell, int offset) { + StyledText text= fViewer.getTextWidget(); + Point location= text.getLocationAtOffset(offset); + location= text.toDisplay(location); + + Rectangle shellBounds= shell.getBounds(); + Rectangle displayBounds= shell.getDisplay().getClientArea(); + + location.y=location.y - shellBounds.height; + + shiftLeftLocation(location, shellBounds, displayBounds); + shiftDownLocation(location, shellBounds, displayBounds); + + return location; + } + + protected Point getBelowLocation(Shell shell, int offset) { + StyledText text= fViewer.getTextWidget(); + Point location= text.getLocationAtOffset(offset); + location= text.toDisplay(location); + + Rectangle shellBounds= shell.getBounds(); + Rectangle displayBounds= shell.getDisplay().getClientArea(); + + location.y= location.y + text.getLineHeight(); + shiftLeftLocation(location, shellBounds, displayBounds); + shiftUpLocation(location, shellBounds, displayBounds); + + return location; + } + + protected Point getStackedLocation(Shell shell, Shell parent) { + Point p= parent.getLocation(); + Point size= parent.getSize(); + p.x += size.x / 4; + p.y += size.y; + + p= parent.toDisplay(p); + + Rectangle shellBounds= shell.getBounds(); + Rectangle displayBounds= shell.getDisplay().getClientArea(); + shiftLeftLocation(p, shellBounds, displayBounds); + shiftUpLocation(p, shellBounds, displayBounds); + + return p; + } + + protected void adjustListeners(int type) { + switch (type) { + case LAYOUT_PROPOSAL_SELECTOR: + if (fContextType == LAYOUT_CONTEXT_SELECTOR && + Helper.okToUse(fShells[LAYOUT_CONTEXT_SELECTOR])) + // Disable event notification to the tip selector. + removeContentAssistListener((IContentAssistListener) fPopups[LAYOUT_CONTEXT_SELECTOR], CONTEXT_SELECTOR); + break; + case LAYOUT_CONTEXT_SELECTOR: + if (Helper.okToUse(fShells[LAYOUT_PROPOSAL_SELECTOR])) + // Disable event notification to the proposal selector. + removeContentAssistListener((IContentAssistListener) fPopups[LAYOUT_PROPOSAL_SELECTOR], PROPOSAL_SELECTOR); + break; + case LAYOUT_CONTEXT_INFO_POPUP: + break; + } + } + }; + + /** + * Internal key listener and event consumer. + */ + class InternalListener implements VerifyKeyListener, IEventConsumer { + + /** + * Verifies key events by notifying the registered listeners. + * Each listener is allowed to indicate that the event has been + * handled and should not be further processed. + * + * @param event the verify event + * @see VerifyKeyListener#verifyKey + */ + public void verifyKey(VerifyEvent e) { + IContentAssistListener[] listeners= (IContentAssistListener[]) fListeners.clone(); + for (int i= 0; i < listeners.length; i++) { + if (listeners[i] != null) { + if (!listeners[i].verifyKey(e) || !e.doit) + return; + } + } + } + + /* + * @see IEventConsumer#processEvent + */ + public void processEvent(VerifyEvent event) { + + installKeyListener(); + + IContentAssistListener[] listeners= (IContentAssistListener[])fListeners.clone(); + for (int i= 0; i < listeners.length; i++) { + if (listeners[i] != null) { + listeners[i].processEvent(event); + if (!event.doit) + return; + } + } + } + }; + + + // Content-Assist Listener types + final static int CONTEXT_SELECTOR= 0; + final static int PROPOSAL_SELECTOR= 1; + final static int CONTEXT_INFO_POPUP= 2; + + private IInformationControlCreator fInformationControlCreator; + private int fAutoActivationDelay= 500; + private boolean fIsAutoActivated= false; + private boolean fIsAutoInserting= false; + private int fProposalPopupOrientation= PROPOSAL_OVERLAY; + private int fContextInfoPopupOrientation= CONTEXT_INFO_ABOVE; + private Map fProcessors; + + private Color fContextInfoPopupBackground; + private Color fContextInfoPopupForeground; + private Color fContextSelectorBackground; + private Color fContextSelectorForeground; + private Color fProposalSelectorBackground; + private Color fProposalSelectorForeground; + + private ITextViewer fViewer; + private String fLastErrorMessage; + + private Closer fCloser; + private LayoutManager fLayoutManager; + private AutoAssistListener fAutoAssistListener; + private InternalListener fInternalListener; + private CompletionProposalPopup fProposalPopup; + private ContextInformationPopup fContextInfoPopup; + + private boolean fKeyListenerHooked= false; + private IContentAssistListener[] fListeners= new IContentAssistListener[4]; + + /** + * Creates a new content assistant. The content assistant is not automatically activated, + * overlays the completion proposals with context information list if necessary, and + * shows the context information above the location at which it was activated. If auto + * activation will be enabled, without further configuration steps, this content assistant + * is activated after a 500 ms delay. + */ + public ContentAssistant() { + } + + /** + * Registers a given content assist processor for a particular content type. + * If there is already a processor registered for this type, the new processor + * is registered instead of the old one. + * + * @param processor the content assist processor to register, or <code>null</code> to remove an existing one + * @param contentType the content type under which to register + */ + public void setContentAssistProcessor(IContentAssistProcessor processor, String contentType) { + + Assert.isNotNull(contentType); + + if (fProcessors == null) + fProcessors= new HashMap(); + + if (processor == null) + fProcessors.remove(contentType); + else + fProcessors.put(contentType, processor); + } + + /* + * @see IContentAssistant#getContentAssistProcessor + */ + public IContentAssistProcessor getContentAssistProcessor(String contentType) { + if (fProcessors == null) + return null; + + return (IContentAssistProcessor) fProcessors.get(contentType); + } + + /** + * Enables the content assistant's auto activation mode. + * + * @param enabled indicates whether auto activation is enabled or not + */ + public void enableAutoActivation(boolean enabled) { + fIsAutoActivated= enabled; + manageAutoActivation(fIsAutoActivated); + } + + /** + * Enables the content assistant's auto insertion mode. If enabled, + * the content assistant inserts a proposal automatically if it is + * the only proposal. In the case of ambiguities, the user must + * make the choice. + * + * @param enabled indicates whether auto insertion is enabled or not + * @since 2.0 + */ + public void enableAutoInsert(boolean enabled) { + fIsAutoInserting= enabled; + } + + /** + * Returns whether this content assistant is in the auto insertion + * mode or not. + * + * @return <code>true</code> if in auto insertion mode + * @since 2.0 + */ + boolean isAutoInserting() { + return fIsAutoInserting; + } + + /** + * Installs and uninstall the listeners needed for autoactivation. + * @param start <code>true</code> if listeners must be installed, + * <code>false</code> if they must be removed + * @since 2.0 + */ + private void manageAutoActivation(boolean start) { + if (start) { + + if (fViewer != null && fAutoAssistListener == null) { + fAutoAssistListener= new AutoAssistListener(); + if (fViewer instanceof ITextViewerExtension) { + ITextViewerExtension extension= (ITextViewerExtension) fViewer; + extension.appendVerifyKeyListener(fAutoAssistListener); + } else { + StyledText textWidget= fViewer.getTextWidget(); + if (Helper.okToUse(textWidget)) + textWidget.addVerifyKeyListener(fAutoAssistListener); + } + } + + } else if (fAutoAssistListener != null) { + + if (fViewer instanceof ITextViewerExtension) { + ITextViewerExtension extension= (ITextViewerExtension) fViewer; + extension.removeVerifyKeyListener(fAutoAssistListener); + } else { + StyledText textWidget= fViewer.getTextWidget(); + if (Helper.okToUse(textWidget)) + textWidget.removeVerifyKeyListener(fAutoAssistListener); + } + + fAutoAssistListener= null; + } + } + + /** + * Sets the delay after which the content assistant is automatically invoked + * if the cursor is behind an auto activation character. + * + * @param delay the auto activation delay + */ + public void setAutoActivationDelay(int delay) { + fAutoActivationDelay= delay; + } + + /** + * Sets the proposal popups' orientation. + * The following values may be used: + * <ul> + * <li>PROPOSAL_OVERLAY<p> + * proposal popup windows should overlay each other + * </li> + * <li>PROPOSAL_REMOVE<p> + * any currently shown proposal popup should be closed + * </li> + * <li>PROPOSAL_STACKED<p> + * proposal popup windows should be vertical stacked, with no overlap, + * beneath the line containing the current cursor location + * </li> + * </ul> + * + * @param orientation the popup's orientation + */ + public void setProposalPopupOrientation(int orientation) { + fProposalPopupOrientation= orientation; + } + + /** + * Sets the context information popup's orientation. + * The following values may be used: + * <ul> + * <li>CONTEXT_ABOVE<p> + * context information popup should always appear above the line containing + * the current cursor location + * </li> + * <li>CONTEXT_BELOW<p> + * context information popup should always appear below the line containing + * the current cursor location + * </li> + * </ul> + * + * @param orientation the popup's orientation + */ + public void setContextInformationPopupOrientation(int orientation) { + fContextInfoPopupOrientation= orientation; + } + + /** + * Sets the context information popup's background color. + * + * @param background the background color + */ + public void setContextInformationPopupBackground(Color background) { + fContextInfoPopupBackground= background; + } + + /** + * Returns the background of the context information popup. + * + * @return the background of the context information popup + * @since 2.0 + */ + Color getContextInformationPopupBackground() { + return fContextInfoPopupBackground; + } + + /** + * Sets the context information popup's foreground color. + * + * @param foreground the foreground color + * @since 2.0 + */ + public void setContextInformationPopupForeground(Color foreground) { + fContextInfoPopupForeground= foreground; + } + + /** + * Returns the foreground of the context information popup. + * + * @return the foreground of the context information popup + * @since 2.0 + */ + Color getContextInformationPopupForeground() { + return fContextInfoPopupForeground; + } + + /** + * Sets the proposal selector's background color. + * + * @param background the background color + * @since 2.0 + */ + public void setProposalSelectorBackground(Color background) { + fProposalSelectorBackground= background; + } + + /** + * Returns the background of the proposal selector. + * + * @return the background of the proposal selector + * @since 2.0 + */ + Color getProposalSelectorBackground() { + return fProposalSelectorBackground; + } + + /** + * Sets the proposal's foreground color. + * + * @param foreground the foreground color + * @since 2.0 + */ + public void setProposalSelectorForeground(Color foreground) { + fProposalSelectorForeground= foreground; + } + + /** + * Returns the foreground of the proposal selector. + * + * @return the foreground of the proposal selector + * @since 2.0 + */ + Color getProposalSelectorForeground() { + return fProposalSelectorForeground; + } + + /** + * Sets the context selector's background color. + * + * @param background the background color + * @since 2.0 + */ + public void setContextSelectorBackground(Color background) { + fContextSelectorBackground= background; + } + + /** + * Returns the background of the context selector. + * + * @return the background of the context selector + * @since 2.0 + */ + Color getContextSelectorBackground() { + return fContextSelectorBackground; + } + + /** + * Sets the context selector's foreground color. + * + * @param foreground the foreground color + * @since 2.0 + */ + public void setContextSelectorForeground(Color foreground) { + fContextSelectorForeground= foreground; + } + + /** + * Returns the foreground of the context selector. + * + * @return the foreground of the context selector + * @since 2.0 + */ + Color getContextSelectorForeground() { + return fContextSelectorForeground; + } + + /** + * Sets the information control creator for the additional information control. + * + * @param creator the information control creator for the additional information control + * @since 2.0 + */ + public void setInformationControlCreator(IInformationControlCreator creator) { + fInformationControlCreator= creator; + } + + /* + * @see IContentAssist#install + */ + public void install(ITextViewer textViewer) { + Assert.isNotNull(textViewer); + + fViewer= textViewer; + + fLayoutManager= new LayoutManager(); + fInternalListener= new InternalListener(); + + AdditionalInfoController controller= null; + if (fInformationControlCreator != null) + controller= new AdditionalInfoController(fInformationControlCreator, Math.round(fAutoActivationDelay * 1.5f)); + + fContextInfoPopup= new ContextInformationPopup(this, fViewer); + fProposalPopup= new CompletionProposalPopup(this, fViewer, controller); + + manageAutoActivation(fIsAutoActivated); + } + + /* + * @see IContentAssist#uninstall + */ + public void uninstall() { + + if (fProposalPopup != null) + fProposalPopup.hide(); + + if (fContextInfoPopup != null) + fContextInfoPopup.hide(); + + manageAutoActivation(false); + + fViewer= null; + } + + /** + * Adds the given shell of the specified type to the layout. + * Valid types are defined by <code>LayoutManager</code>. + * + * @param popup a content assist popup + * @param shell the shell of the content-assist popup + * @param type the type of popup + * @param visibleOffset the offset at which to layout the popup relative to the offset of the viewer's visible region + * @since 2.0 + */ + void addToLayout(Object popup, Shell shell, int type, int visibleOffset) { + fLayoutManager.add(popup, shell, type, visibleOffset); + } + + /** + * Layouts the registered popup of the given type relative to the + * given offset. The offset is relative to the offset of the viewer's visible region. + * Valid types are defined by <code>LayoutManager</code>. + * + * @param type the type of popup to layout + * @param visibleOffset the offset at which to layout relative to the offset of the viewer's visible region + * @since 2.0 + */ + void layout(int type, int visibleOffset) { + fLayoutManager.layout(type, visibleOffset); + } + + /** + * Notifies the controller that a popup has lost focus. + * + * @param e the focus event + */ + void popupFocusLost(FocusEvent e) { + fCloser.focusLost(e); + } + + /** + * Returns the offset of the selection relative to the offset of the visible region. + * + * @return the offset of the selection relative to the offset of the visible region + * @since 2.0 + */ + int getSelectionOffset() { + StyledText text= fViewer.getTextWidget(); + return text.getSelectionRange().x; + } + + /** + * Returns whether the widget token could be acquired. + * The following are valid listener types: + * <ul> + * <li>AUTO_ASSIST + * <li>CONTEXT_SELECTOR + * <li>PROPOSAL_SELECTOR + * <li>CONTEXT_INFO_POPUP + * <ul> + * @param type the listener type for which to acquire + * @return <code>true</code> if the widget token could be acquired + * @since 2.0 + */ + private boolean acquireWidgetToken(int type) { + switch (type) { + case CONTEXT_SELECTOR: + case PROPOSAL_SELECTOR: + if (fViewer instanceof IWidgetTokenOwner) { + IWidgetTokenOwner owner= (IWidgetTokenOwner) fViewer; + return owner.requestWidgetToken(this); + } + return false; + } + return true; + } + + /** + * Registers a content assist listener. + * The following are valid listener types: + * <ul> + * <li>AUTO_ASSIST + * <li>CONTEXT_SELECTOR + * <li>PROPOSAL_SELECTOR + * <li>CONTEXT_INFO_POPUP + * <ul> + * Returns whether the listener could be added successfully. A listener + * can not be added if the widget token could not be acquired. + * + * @param listener the listener to register + * @param type the type of listener + * @return <code>true</code> if the listener could be added + */ + boolean addContentAssistListener(IContentAssistListener listener, int type) { + + if (acquireWidgetToken(type)) { + + fListeners[type]= listener; + + if (getNumberOfListeners() == 1) { + fCloser= new Closer(); + fCloser.install(); + fViewer.setEventConsumer(fInternalListener); + installKeyListener(); + } + return true; + } + + return false; + } + + /** + * Installs a key listener on the text viewer's widget. + */ + private void installKeyListener() { + if (!fKeyListenerHooked) { + StyledText text= fViewer.getTextWidget(); + if (Helper.okToUse(text)) { + + if (fViewer instanceof ITextViewerExtension) { + ITextViewerExtension e= (ITextViewerExtension) fViewer; + e.prependVerifyKeyListener(fInternalListener); + } else { + text.addVerifyKeyListener(fInternalListener); + } + + fKeyListenerHooked= true; + } + } + } + + /** + * Releases the previously acquired widget token if the token + * is no longer necessary. + * The following are valid listener types: + * <ul> + * <li>AUTO_ASSIST + * <li>CONTEXT_SELECTOR + * <li>PROPOSAL_SELECTOR + * <li>CONTEXT_INFO_POPUP + * <ul> + * + * @param type the listener type + * @since 2.0 + */ + private void releaseWidgetToken(int type) { + if (fListeners[CONTEXT_SELECTOR] == null && fListeners[PROPOSAL_SELECTOR] == null) { + if (fViewer instanceof IWidgetTokenOwner) { + IWidgetTokenOwner owner= (IWidgetTokenOwner) fViewer; + owner.releaseWidgetToken(this); + } + } + } + + /** + * Unregisters a content assist listener. + * + * @param listener the listener to unregister + * @param type the type of listener + * + * @see #addContentAssistListener + */ + void removeContentAssistListener(IContentAssistListener listener, int type) { + fListeners[type]= null; + + if (getNumberOfListeners() == 0) { + + if (fCloser != null) { + fCloser.uninstall(); + fCloser= null; + } + + uninstallKeyListener(); + fViewer.setEventConsumer(null); + } + + releaseWidgetToken(type); + } + + /** + * Uninstall the key listener from the text viewer's widget. + */ + private void uninstallKeyListener() { + if (fKeyListenerHooked) { + StyledText text= fViewer.getTextWidget(); + if (Helper.okToUse(text)) { + + if (fViewer instanceof ITextViewerExtension) { + ITextViewerExtension e= (ITextViewerExtension) fViewer; + e.removeVerifyKeyListener(fInternalListener); + } else { + text.removeVerifyKeyListener(fInternalListener); + } + + fKeyListenerHooked= false; + } + } + } + + /** + * Returns the number of listeners. + * + * @return the number of listeners + * @since 2.0 + */ + private int getNumberOfListeners() { + int count= 0; + for (int i= 0; i <= CONTEXT_INFO_POPUP; i++) { + if (fListeners[i] != null) + ++ count; + } + return count; + } + + /* + * @see IContentAssist#showPossibleCompletions + */ + public String showPossibleCompletions() { + return fProposalPopup.showProposals(false); + } + + /* + * @see IContentAssist#showContextInformation + */ + public String showContextInformation() { + return fContextInfoPopup.showContextProposals(false); + } + + /** + * Requests that the specified context information to be shown. + * + * @param contextInformation the context information to be shown + * @param position the position to which the context information refers to + * @since 2.0 + */ + void showContextInformation(IContextInformation contextInformation, int position) { + fContextInfoPopup.showContextInformation(contextInformation, position); + } + + /** + * Returns the current content assist error message. + * + * @return an error message or <code>null</code> if no error has occurred + */ + String getErrorMessage() { + return fLastErrorMessage; + } + + /** + * Returns the content assist processor for the content + * type of the specified document position. + * + * @param document the document + * @param position a position within the document + * @return a content-assist processor or <code>null</code> if none exists + */ + private IContentAssistProcessor getProcessor(IDocument document, int position) { + try { + String type= document.getContentType(position); + return getContentAssistProcessor(type); + } catch (BadLocationException x) { + } + + return null; + } + + /** + * Returns an array of completion proposals computed based on + * the specified document position. The position is used to + * determine the appropriate content assist processor to invoke. + * + * @param viewer the viewer for which to compute the prosposals + * @param position a document position + * @return an array of completion proposals + * + * @see IContentAssistProcessor#computeCompletionProposals + */ + ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int position) { + fLastErrorMessage= null; + + ICompletionProposal[] result= null; + + IContentAssistProcessor p= getProcessor(viewer.getDocument(), position); + if (p != null) { + result= p.computeCompletionProposals(viewer, position); + fLastErrorMessage= p.getErrorMessage(); + } + + return result; + } + + /** + * Returns an array of context information objects computed based + * on the specified document position. The position is used to determine + * the appropriate content assist processor to invoke. + * + * @param viewer the viewer for which to compute the context information + * @param position a document position + * @return an array of context information objects + * + * @see IContentAssistProcessor#computeContextInformation + */ + IContextInformation[] computeContextInformation(ITextViewer viewer, int position) { + fLastErrorMessage= null; + + IContextInformation[] result= null; + + IContentAssistProcessor p= getProcessor(viewer.getDocument(), position); + if (p != null) { + result= p.computeContextInformation(viewer, position); + fLastErrorMessage= p.getErrorMessage(); + } + + return result; + } + + /** + * Returns the context information validator that should be used to + * determine when the currently displayed context information should + * be dismissed. The position is used to determine the appropriate + * content assist processor to invoke. + * + * @param document the document + * @param position a document position + * @return an validator + * + * @see IContentAssistProcessor#getContextInformationValidator + */ + IContextInformationValidator getContextInformationValidator(IDocument document, int position) { + IContentAssistProcessor p= getProcessor(document, position); + if (p != null) + return p.getContextInformationValidator(); + return null; + } + + /** + * Returns the context information presenter that should be used to + * display context information. The position is used to determine the appropriate + * content assist processor to invoke. + * + * @param document the document + * @param position a document position + * @return a presenter + * @since 2.0 + */ + IContextInformationPresenter getContextInformationPresenter(IDocument document, int position) { + IContextInformationValidator validator= getContextInformationValidator(document, position); + if (validator instanceof IContextInformationPresenter) + return (IContextInformationPresenter) validator; + return null; + } + + /** + * Returns the characters which when typed by the user should automatically + * initiate proposing completions. The position is used to determine the + * appropriate content assist processor to invoke. + * + * @param document the document + * @param position a document position + * @return the auto activation characters + * + * @see IContentAssistProcessor#getCompletionProposalAutoActivationCharacters + */ + private char[] getCompletionProposalAutoActivationCharacters(IDocument document, int position) { + IContentAssistProcessor p= getProcessor(document, position); + if (p != null) + return p.getCompletionProposalAutoActivationCharacters(); + return null; + } + + /** + * Returns the characters which when typed by the user should automatically + * initiate the presentation of context information. The position is used + * to determine the appropriate content assist processor to invoke. + * + * @param document the document + * @param position a document position + * @return the auto activation characters + * + * @see IContentAssistProcessor#getContextInformationAutoActivationCharacters + */ + private char[] getContextInformationAutoActivationCharacters(IDocument document, int position) { + IContentAssistProcessor p= getProcessor(document, position); + if (p != null) + return p.getContextInformationAutoActivationCharacters(); + return null; + } + + /* + * @see org.eclipse.jface.text.IWidgetTokenKeeper#requestWidgetToken(IWidgetTokenOwner) + * @since 2.0 + */ + public boolean requestWidgetToken(IWidgetTokenOwner owner) { + return false; + } +} + diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/ContextInformation.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/ContextInformation.java new file mode 100644 index 000000000..8dbc83dd1 --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/ContextInformation.java @@ -0,0 +1,86 @@ +package org.eclipse.jface.text.contentassist; + +/* + * (c) Copyright IBM Corp. 2000, 2001. + * All Rights Reserved. + */ + + +import org.eclipse.swt.graphics.Image; + +import org.eclipse.jface.util.Assert; + + +/** + * A default implementation of the <code>IContextInformation</code> interface. + */ +public final class ContextInformation implements IContextInformation { + + private String fContextDisplayString; + private String fInformationDisplayString; + private Image fImage; + + /** + * Creates a new context information without an image. + * + * @param contextDisplayString the string to be used when presenting the context + * @param informationDisplayString the string to be displayed when presenting the context information + */ + public ContextInformation(String contextDisplayString, String informationDisplayString) { + this(null, contextDisplayString, informationDisplayString); + } + + /** + * Creates a new context information with an image. + * + * @param image the image to display when presenting the context information + * @param contextDisplayString the string to be used when presenting the context + * @param informationDisplayString the string to be displayed when presenting the context information, + * may not be <code>null</code> + */ + public ContextInformation(Image image, String contextDisplayString, String informationDisplayString) { + + Assert.isNotNull(informationDisplayString); + + fImage= image; + fContextDisplayString= contextDisplayString; + fInformationDisplayString= informationDisplayString; + } + + /* + * @see IContextInformation#equals + */ + public boolean equals(Object object) { + if (object instanceof IContextInformation) { + IContextInformation contextInformation= (IContextInformation) object; + boolean equals= fInformationDisplayString.equalsIgnoreCase(contextInformation.getInformationDisplayString()); + if (fContextDisplayString != null) + equals= equals && fContextDisplayString.equalsIgnoreCase(contextInformation.getContextDisplayString()); + return equals; + } + return false; + } + + /* + * @see IContextInformation#getInformationDisplayString() + */ + public String getInformationDisplayString() { + return fInformationDisplayString; + } + + /* + * @see IContextInformation#getImage() + */ + public Image getImage() { + return fImage; + } + + /* + * @see IContextInformation#getContextDisplayString() + */ + public String getContextDisplayString() { + if (fContextDisplayString != null) + return fContextDisplayString; + return fInformationDisplayString; + } +} diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/ContextInformationPopup.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/ContextInformationPopup.java new file mode 100644 index 000000000..540024c1f --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/ContextInformationPopup.java @@ -0,0 +1,626 @@ +/********************************************************************** +Copyright (c) 2000, 2002 IBM Corp. and others. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Common Public License v1.0 +which accompanies this distribution, and is available at +http://www.eclipse.org/legal/cpl-v10.html + +Contributors: + IBM Corporation - Initial implementation +**********************************************************************/ + +package org.eclipse.jface.text.contentassist; +
import java.util.Iterator; + +import java.util.Stack; +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.BusyIndicator; +import org.eclipse.swt.custom.StyleRange; +import org.eclipse.swt.custom.StyledText; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.events.VerifyEvent; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.TableItem; + +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.ITextViewer; +import org.eclipse.jface.text.TextPresentation; +import org.eclipse.jface.text.contentassist.ContentAssistant.LayoutManager; + + +/** + * This class is used to present context information to the user. + * If multiple contexts are valid at the current cursor location, + * a list is presented from which the user may choose one context. + * Once the user makes their choice, or if there was only a single + * possible context, the context information is shown in a tooltip like popup. <p> + * If the tooltip is visible and the user wants to see context information of + * a context embedded into the one for which context information is displayed, + * context information for the embedded context is shown. As soon as the + * cursor leaves the embedded context area, the context information for + * the embedding context is shown again. + * + * @see IContextInformation + * @see IContextInformationValidator + */ +class ContextInformationPopup implements IContentAssistListener { + + + + /** + * Represents the state necessary for embedding contexts. + * @since 2.0 + */ + static class ContextFrame { + public int fBeginOffset; + public int fOffset; + public int fVisibleOffset; + public IContextInformation fInformation; + public IContextInformationValidator fValidator; + public IContextInformationPresenter fPresenter; + }; + + private ITextViewer fViewer; + private ContentAssistant fContentAssistant; + private int fListenerCount= 0; + + private PopupCloser fPopupCloser= new PopupCloser(); + private Shell fContextSelectorShell; + private Table fContextSelectorTable; + private IContextInformation[] fContextSelectorInput; + private String fLineDelimiter= null; + + private Shell fContextInfoPopup; + private StyledText fContextInfoText; + private TextPresentation fTextPresentation; + + private Stack fContextFrameStack= new Stack(); + + + /** + * Creates a new context information popup. + * + * @param contentAssistant the content assist for computing the context information + * @param viewer the viewer on top of which the context information is shown + */ + public ContextInformationPopup(ContentAssistant contentAssistant, ITextViewer viewer) { + fContentAssistant= contentAssistant; + fViewer= viewer; + } + + /** + * Shows all possible contexts for the given cursor position of the viewer. + * + * @param autoActivated <code>true</code> if auto activated + * @return a potential error message or <code>null</code> in case of no error + */ + public String showContextProposals(final boolean autoActivated) { + final StyledText styledText= fViewer.getTextWidget(); + BusyIndicator.showWhile(styledText.getDisplay(), new Runnable() { + public void run() { + + int position= fViewer.getSelectedRange().x; + + IContextInformation[] contexts= computeContextInformation(position); + int count = (contexts == null ? 0 : contexts.length); + if (count == 1) { + + // Show context information directly + internalShowContextInfo(contexts[0], position); + + } else if (count > 0) { + // Precise context must be selected + + if (fLineDelimiter == null) + fLineDelimiter= styledText.getLineDelimiter(); + + createContextSelector(); + setContexts(contexts); + displayContextSelector(); + hideContextInfoPopup(); + + } else if (!autoActivated) { + styledText.getDisplay().beep(); + } + } + }); + + return getErrorMessage(); + } + + /** + * Displays the given context information for the given offset. + * + * @param info the context information + * @param position the offset + * @since 2.0 + */ + public void showContextInformation(final IContextInformation info, final int position) { + Control control= fViewer.getTextWidget(); + BusyIndicator.showWhile(control.getDisplay(), new Runnable() { + public void run() { + internalShowContextInfo(info, position); + hideContextSelector(); + } + }); + } + + /** + * Displays the given context information for the given offset. + * + * @param info the context information + * @param position the offset + * @since 2.0 + */ + + private void internalShowContextInfo(IContextInformation information, int offset) { + + IContextInformationValidator validator= fContentAssistant.getContextInformationValidator(fViewer.getDocument(), offset); + + if (validator != null) { + ContextFrame current= new ContextFrame(); + current.fInformation= information; + current.fBeginOffset= (information instanceof IContextInformationExtension) ? ((IContextInformationExtension) information).getContextInformationPosition() : offset; + if (current.fBeginOffset == -1) current.fBeginOffset= offset; + current.fOffset= offset; + current.fVisibleOffset= fViewer.getTextWidget().getSelectionRange().x - (offset - current.fBeginOffset); + current.fValidator= validator; + current.fPresenter= fContentAssistant.getContextInformationPresenter(fViewer.getDocument(), offset); + + fContextFrameStack.push(current); + + internalShowContextFrame(current, fContextFrameStack.size() == 1); + } + } + + /** + * Shows the given context frame. + * + * @param frame the frane to display + * @param initial <code>true</code> if this is the first frame to be displayed + * @since 2.0 + */ + private void internalShowContextFrame(ContextFrame frame, boolean initial) { + + frame.fValidator.install(frame.fInformation, fViewer, frame.fOffset); + + if (frame.fPresenter != null) { + if (fTextPresentation == null) + fTextPresentation= new TextPresentation(); + frame.fPresenter.install(frame.fInformation, fViewer, frame.fBeginOffset); + frame.fPresenter.updatePresentation(frame.fOffset, fTextPresentation); + } + + createContextInfoPopup(); + + fContextInfoText.setText(frame.fInformation.getInformationDisplayString()); + if (fTextPresentation != null) + TextPresentation.applyTextPresentation(fTextPresentation, fContextInfoText); + resize(); + + if (initial) { + if (fContentAssistant.addContentAssistListener(this, ContentAssistant.CONTEXT_INFO_POPUP)) { + fContentAssistant.addToLayout(this, fContextInfoPopup, ContentAssistant.LayoutManager.LAYOUT_CONTEXT_INFO_POPUP, frame.fVisibleOffset); + fContextInfoPopup.setVisible(true); + } + } else { + fContentAssistant.layout(ContentAssistant.LayoutManager.LAYOUT_CONTEXT_INFO_POPUP, frame.fVisibleOffset); + } + } + + /** + * Computes all possible context information for the given offset. + * + * @param position the offset + * @return all possible context information for the given offset + * @since 2.0 + */ + private IContextInformation[] computeContextInformation(int position) { + return fContentAssistant.computeContextInformation(fViewer, position); + } + + /** + *Returns the error message generated while computing context information. + * + * @return the error message + */ + private String getErrorMessage() { + return fContentAssistant.getErrorMessage(); + } + + /** + * Creates the context information popup. This is the tooltip like overlay window. + */ + private void createContextInfoPopup() { + if (Helper.okToUse(fContextInfoPopup)) + return; + + Control control= fViewer.getTextWidget(); + Display display= control.getDisplay(); + + fContextInfoPopup= new Shell(control.getShell(), SWT.NO_TRIM | SWT.ON_TOP); + fContextInfoPopup.setBackground(display.getSystemColor(SWT.COLOR_BLACK)); + + fContextInfoText= new StyledText(fContextInfoPopup, SWT.MULTI | SWT.READ_ONLY); + + Color c= fContentAssistant.getContextInformationPopupBackground(); + if (c == null) + c= display.getSystemColor(SWT.COLOR_INFO_BACKGROUND); + fContextInfoText.setBackground(c); + + c= fContentAssistant.getContextInformationPopupForeground(); + if (c == null) + c= display.getSystemColor(SWT.COLOR_INFO_FOREGROUND); + fContextInfoText.setForeground(c); + } + + /** + * Resizes the context information popup. + * @since 2.0 + */ + private void resize() { + Point size= fContextInfoText.computeSize(SWT.DEFAULT, SWT.DEFAULT, true); + size.x += 3; + fContextInfoText.setSize(size); + fContextInfoText.setLocation(1,1); + size.x += 2; + size.y += 2; + fContextInfoPopup.setSize(size); + } + + /** + *Hides the context information popup. + */ + private void hideContextInfoPopup() { + + if (Helper.okToUse(fContextInfoPopup)) { + + int size= fContextFrameStack.size(); + if (size > 0) { + fContextFrameStack.pop(); + -- size; + } + + if (size > 0) { + ContextFrame current= (ContextFrame) fContextFrameStack.peek(); + internalShowContextFrame(current, false); + } else { + + fContentAssistant.removeContentAssistListener(this, ContentAssistant.CONTEXT_INFO_POPUP); + + fContextInfoPopup.setVisible(false); + fContextInfoPopup.dispose(); + fContextInfoPopup= null; + + if (fTextPresentation != null) { + fTextPresentation.clear(); + fTextPresentation= null; + } + } + } + } + + /** + * Creates the context selector in case the user has the choice between multiple valid contexts + * at a given offset. + */ + private void createContextSelector() { + if (Helper.okToUse(fContextSelectorShell)) + return; + + Control control= fViewer.getTextWidget(); + fContextSelectorShell= new Shell(control.getShell(), SWT.NO_TRIM | SWT.ON_TOP); + fContextSelectorTable= new Table(fContextSelectorShell, SWT.H_SCROLL | SWT.V_SCROLL); + + int height= fContextSelectorTable.getItemHeight() * 10; + fContextSelectorShell.setSize(302, height + 2); + fContextSelectorTable.setSize(300, height); + fContextSelectorTable.setLocation(1, 1); + + fContextSelectorShell.setBackground(control.getDisplay().getSystemColor(SWT.COLOR_BLACK)); + + Color c= fContentAssistant.getContextSelectorBackground(); + if (c == null) + c= control.getDisplay().getSystemColor(SWT.COLOR_INFO_BACKGROUND); + fContextSelectorTable.setBackground(c); + + c= fContentAssistant.getContextSelectorForeground(); + if (c == null) + c= control.getDisplay().getSystemColor(SWT.COLOR_INFO_FOREGROUND); + fContextSelectorTable.setForeground(c); + + fContextSelectorTable.addSelectionListener(new SelectionListener() { + public void widgetSelected(SelectionEvent e) { + } + + public void widgetDefaultSelected(SelectionEvent e) { + insertSelectedContext(); + hideContextSelector(); + } + }); + + fPopupCloser.install(fContentAssistant, fContextSelectorTable); + + fContextSelectorTable.setHeaderVisible(false); + fContentAssistant.addToLayout(this, fContextSelectorShell, ContentAssistant.LayoutManager.LAYOUT_CONTEXT_SELECTOR, fContentAssistant.getSelectionOffset()); + } + + /** + * Causes the context information of the context selected in the context selector + * to be displayed in the context information popup. + */ + private void insertSelectedContext() { + int i= fContextSelectorTable.getSelectionIndex(); + + if (i < 0 || i >= fContextSelectorInput.length) + return; + + int position= fViewer.getSelectedRange().x; + internalShowContextInfo(fContextSelectorInput[i], position); + } + + /** + * Sets the contexts in the context selector to the given set. + * + * @param contexts teh possible contexts + */ + private void setContexts(IContextInformation[] contexts) { + if (Helper.okToUse(fContextSelectorTable)) { + + fContextSelectorInput= contexts; + + fContextSelectorTable.setRedraw(false); + fContextSelectorTable.removeAll(); + + TableItem item; + IContextInformation t; + for (int i= 0; i < contexts.length; i++) { + t= contexts[i]; + item= new TableItem(fContextSelectorTable, SWT.NULL); + if (t.getImage() != null) + item.setImage(t.getImage()); + item.setText(t.getContextDisplayString()); + } + + fContextSelectorTable.select(0); + fContextSelectorTable.setRedraw(true); + } + } + + /** + * Displays the context selector. + */ + private void displayContextSelector() { + if (fContentAssistant.addContentAssistListener(this, ContentAssistant.CONTEXT_SELECTOR)) + fContextSelectorShell.setVisible(true); + } + + /** + * Hodes the context selector. + */ + private void hideContextSelector() { + if (Helper.okToUse(fContextSelectorShell)) { + fContentAssistant.removeContentAssistListener(this, ContentAssistant.CONTEXT_SELECTOR); + + fPopupCloser.uninstall(); + fContextSelectorShell.setVisible(false); + fContextSelectorShell.dispose(); + fContextSelectorShell= null; + } + } + + /** + *Returns whether the context selector has the focus. + * @return <code>true</code> if teh context selector has the focus + */ + public boolean hasFocus() { + if (Helper.okToUse(fContextSelectorShell)) + return (fContextSelectorShell.isFocusControl() || fContextSelectorTable.isFocusControl()); + + return false; + } + + /** + * Hides context selector and context information popup. + */ + public void hide() { + hideContextSelector(); + hideContextInfoPopup(); + } + + /** + * Returns whether this context information popup is active. I.e., either + * a context selector or context information is displayed. + * + * @return <code>true</code> if the context selector is active + */ + public boolean isActive() { + return (Helper.okToUse(fContextInfoPopup) || Helper.okToUse(fContextSelectorShell)); + } + + /* + * @see IContentAssistListener#verifyKey(VerifyEvent) + */ + public boolean verifyKey(VerifyEvent e) { + if (Helper.okToUse(fContextSelectorShell)) + return contextSelectorKeyPressed(e); + if (Helper.okToUse(fContextInfoPopup)) + return contextInfoPopupKeyPressed(e); + return true; + } + + /** + * Processes a key stroke in the context selector. + * + * @param e the verify event describing the key stroke + * @return <code>true</code> if processing can be stopped + */ + private boolean contextSelectorKeyPressed(VerifyEvent e) { + + char key= e.character; + if (key == 0) { + + int change; + int visibleRows= (fContextSelectorTable.getSize().y / fContextSelectorTable.getItemHeight()) - 1; + int selection= fContextSelectorTable.getSelectionIndex(); + + switch (e.keyCode) { + + case SWT.ARROW_UP: + change= (fContextSelectorTable.getSelectionIndex() > 0 ? -1 : 0); + break; + + case SWT.ARROW_DOWN: + change= (fContextSelectorTable.getSelectionIndex() < fContextSelectorTable.getItemCount() - 1 ? 1 : 0); + break; + + case SWT.PAGE_DOWN : + change= visibleRows; + if (selection + change >= fContextSelectorTable.getItemCount()) + change= fContextSelectorTable.getItemCount() - selection; + break; + + case SWT.PAGE_UP : + change= -visibleRows; + if (selection + change < 0) + change= -selection; + break; + + case SWT.HOME : + change= -selection; + break; + + case SWT.END : + change= fContextSelectorTable.getItemCount() - selection; + break; + + case SWT.CTRL: + case SWT.SHIFT: + return true; + default: + hideContextSelector(); + return true; + } + + fContextSelectorTable.setSelection(selection + change); + fContextSelectorTable.showSelection(); + e.doit= false; + return false; + + } else if ('\t' == key) { + // switch focus to selector shell + e.doit= false; + fContextSelectorShell.setFocus(); + return false; + } else if (key == 0x1B) { + // terminate on Esc + hideContextSelector(); + } + + return true; + } + + /** + * Processes a key stroke while the info popup is up. + * + * @param e the verify event describing the key stroke + * @return <code>true</code> if processing can be stopped + */ + private boolean contextInfoPopupKeyPressed(KeyEvent e) { + + char key= e.character; + if (key == 0) { + + switch (e.keyCode) { + + case SWT.ARROW_LEFT: + case SWT.ARROW_RIGHT: + validateContextInformation(); + break; + case SWT.CTRL: + case SWT.SHIFT: + break; + default: + hideContextInfoPopup(); + break; + } + + } else if (key == 0x1B) { + // terminate on Esc + hideContextInfoPopup(); + } else { + validateContextInformation(); + } + return true; + } + + /* + * @see IEventConsumer#processEvent(VerifyEvent) + */ + public void processEvent(VerifyEvent event) { + if (Helper.okToUse(fContextSelectorShell)) + contextSelectorProcessEvent(event); + if (Helper.okToUse(fContextInfoPopup)) + contextInfoPopupProcessEvent(event); + } + + /** + * Processes a key stroke in the context selector. + * + * @param e the verify event describing the key stroke + */ + private void contextSelectorProcessEvent(VerifyEvent e) { + + if (e.start == e.end && e.text != null && e.text.equals(fLineDelimiter)) { + e.doit= false; + insertSelectedContext(); + } + + hideContextSelector(); + } + + /** + * Processes a key stroke while the info popup is up. + * + * @param e the verify event describing the key stroke + */ + private void contextInfoPopupProcessEvent(VerifyEvent e) { + if (e.start != e.end && (e.text == null || e.text.length() == 0)) + validateContextInformation(); + } + + /** + * Validates the context information for the viewer's actual cursor position. + */ + private void validateContextInformation() { + /* + * Post the code in the event queue in order to ensure that the + * action described by this verify key event has already beed executed. + * Otherwise, we'd validate the context information based on the + * pre-key-stroke state. + */ + fContextInfoPopup.getDisplay().asyncExec(new Runnable() { + + private ContextFrame fFrame= (ContextFrame) fContextFrameStack.peek(); + + public void run() { + if (Helper.okToUse(fContextInfoPopup) && fFrame == fContextFrameStack.peek()) { + int offset= fViewer.getSelectedRange().x; + if (fFrame.fValidator == null || !fFrame.fValidator.isContextInformationValid(offset)) { + hideContextInfoPopup(); + } else if (fFrame.fPresenter != null && fFrame.fPresenter.updatePresentation(offset, fTextPresentation)) { + TextPresentation.applyTextPresentation(fTextPresentation, fContextInfoText); + resize(); + } + } + } + }); + } +}
\ No newline at end of file diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/ContextInformationValidator.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/ContextInformationValidator.java new file mode 100644 index 000000000..79def403c --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/ContextInformationValidator.java @@ -0,0 +1,53 @@ +package org.eclipse.jface.text.contentassist; + +/* + * (c) Copyright IBM Corp. 2000, 2001. + * All Rights Reserved. + */ + + +import org.eclipse.jface.text.ITextViewer; + + +/** + * A default implementation of the <code>IContextInfomationValidator</code> interface. + * This implementation determines whether the information is valid by asking the content + * assist processor for all context information objects for the current position. If the + * currently displayed information is in the result set, the context information is + * considered valid. + */ +public final class ContextInformationValidator implements IContextInformationValidator { + + private IContentAssistProcessor fProcessor; + private IContextInformation fContextInformation; + private ITextViewer fViewer; + + /** + * Creates a new context information validator which is ready to be installed on + * a particular context information. + */ + public ContextInformationValidator(IContentAssistProcessor processor) { + fProcessor= processor; + } + + /* + * @see IContextInformationValidator#install + */ + public void install(IContextInformation contextInformation, ITextViewer viewer, int position) { + fContextInformation= contextInformation; + fViewer= viewer; + } + + /* + * @see IContentAssistTipCloser#isContextInformationValid + */ + public boolean isContextInformationValid(int position) { + IContextInformation[] infos= fProcessor.computeContextInformation(fViewer, position); + if (infos != null && infos.length > 0) { + for (int i= 0; i < infos.length; i++) + if (fContextInformation.equals(infos[i])) + return true; + } + return false; + } +}
\ No newline at end of file diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/Helper.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/Helper.java new file mode 100644 index 000000000..7f7468882 --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/Helper.java @@ -0,0 +1,26 @@ +package org.eclipse.jface.text.contentassist; + +/* + * (c) Copyright IBM Corp. 2000, 2001. + * All Rights Reserved. + */ + + +import org.eclipse.swt.widgets.Widget; + + +/** + * Helper class for testing widget state. + */ +class Helper { + + /** + * Returns whether the widget is <code>null</code> or disposed. + * + * @param widget the widget to check + * @return <code>true</code> if the widget is neither <code>null</code> nor disposed + */ + public static boolean okToUse(Widget widget) { + return (widget != null && !widget.isDisposed()); + } +}
\ No newline at end of file diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/ICompletionProposal.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/ICompletionProposal.java new file mode 100644 index 000000000..01c1babda --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/ICompletionProposal.java @@ -0,0 +1,76 @@ +package org.eclipse.jface.text.contentassist; + +/* + * (c) Copyright IBM Corp. 2000, 2001. + * All Rights Reserved. + */ + + +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Point; + +import org.eclipse.jface.text.IDocument; + + +/** + * The interface of completion proposals generated by content assist processors. + * A completion proposal contains information used to present the proposed completion + * to the user, to insert the completion should the user select it, and to present + * context information for the choosen completion once it has been inserted.<p> + * The interface can be implemented by clients. By default, clients use + * <code>CompletionProposal</code> as the standard implementer of this interface. + * + * @see IContentAssistProcessor + */ +public interface ICompletionProposal { + + /** + * Inserts the proposed completion into the given document. + * + * @param document the document into which to insert the proposed completion + */ + void apply(IDocument document); + + /** + * Returns the new selection after the proposal has been applied to + * the given document in absolute document coordinates. If it returns + * <code>null</code>, no new selection is set. + * + * @param document the document into which the proposed completion has been inserted + * @return the new selection in absolute document coordinates + */ + Point getSelection(IDocument document); + + /** + * Returns optional additional information about the proposal. + * The additional information will be presented to assist the user + * in deciding if the selected proposal is the desired choice. + * + * @return the additional information or <code>null</code> + */ + String getAdditionalProposalInfo(); + + /** + * Returns the string to be displayed in the list of completion proposals. + * + * @return the string to be displayed + */ + String getDisplayString(); + + /** + * Returns the image to be displayed in the list of completion proposals. + * The image would typically be shown to the left of the display string. + * + * @return the image to be shown or <code>null</code> if no image is desired + */ + Image getImage(); + + /** + * Returns optional context information associated with this proposal. + * The context information will automatically be shown if the proposal + * has been applied. + * + * @return the context information for this proposal or <code>null</code> + */ + IContextInformation getContextInformation(); +} diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/ICompletionProposalExtension.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/ICompletionProposalExtension.java new file mode 100644 index 000000000..160e833d6 --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/ICompletionProposalExtension.java @@ -0,0 +1,66 @@ +/********************************************************************** +Copyright (c) 2000, 2002 IBM Corp. and others. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Common Public License v1.0 +which accompanies this distribution, and is available at +http://www.eclipse.org/legal/cpl-v10.html + +Contributors: + IBM Corporation - Initial implementation +**********************************************************************/ + +package org.eclipse.jface.text.contentassist; + + +import org.eclipse.jface.text.IDocument; + + +/** + * Extension interface to <code>ICompletionProposal</code>. + * Add the following functions: + * <ul> + * <li> handling of trigger characters other then ENTER + * <li> completion proposal validation for a given offset + * <li> freely positionable context information + * </ul> + *
* @since 2.0 + */ +public interface ICompletionProposalExtension { + + /** + * Applies the proposed completion to the given document. The insertion + * has been triggered by entering the given character at the given offset. + * This method assumes that <code>isValidFor</code> returns + * <code>true</code> if called for <code>offset</code>. + * + * @param document the document into which to insert the proposed completion + * @param trigger the trigger to apply the completion + * @param offset the offset at which the trigger has been activated + */ + void apply(IDocument document, char trigger, int offset); + + /** + * Returns whether this completion proposal is valid for the given + * position in the given document. + * + * @param document the document for which the proposal is tested + * @param offset the offset for which the proposal is tested + */ + boolean isValidFor(IDocument document, int offset); + + /** + * Returns the characters which trigger the application of this completion proposal. + * + * @return the completion characters for this completion proposal or <code>null</code> + * if no completion other than the new line character is possible + */ + char[] getTriggerCharacters(); + + /** + * Returns the position to which the computed context information refers to or + * <code>-1</code> if no context information can be provided by this completion proposal. + * + * @return the position to which the context information refers to or <code>-1</code> for no information + */ + int getContextInformationPosition(); +} diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/IContentAssistListener.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/IContentAssistListener.java new file mode 100644 index 000000000..5b3166fc0 --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/IContentAssistListener.java @@ -0,0 +1,29 @@ +package org.eclipse.jface.text.contentassist; + +/* + * (c) Copyright IBM Corp. 2000, 2001. + * All Rights Reserved. + */ + + +import org.eclipse.swt.events.VerifyEvent; + +import org.eclipse.jface.text.IEventConsumer; + + + +/** + * An interface whereby listeners can not only receive key events, + * but can also consume them to prevent subsequent listeners from + * processing the event. + */ +interface IContentAssistListener extends IEventConsumer { + + /** + * Verifies the key event. + * + * @return <code>true</code> if processing should be continued by additional listeners + * @see org.eclipse.swt.custom.VerifyKeyListener#verifyKey + */ + public boolean verifyKey(VerifyEvent event); +}
\ No newline at end of file diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/IContentAssistProcessor.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/IContentAssistProcessor.java new file mode 100644 index 000000000..3a0783f80 --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/IContentAssistProcessor.java @@ -0,0 +1,84 @@ +package org.eclipse.jface.text.contentassist; + +/* + * (c) Copyright IBM Corp. 2000, 2001. + * All Rights Reserved. + */ + + +import org.eclipse.jface.text.ITextViewer; + + + +/** + * A content assist processor proposes completions and + * computes context information for a particular content type. + * A content assist processor is an <code>IContentAssistant</code>-plug-ins. + * This interface must be implemented by clients. Implementers should be + * registered with a content assistant in order to get involved in the + * assisting process. +*/ +public interface IContentAssistProcessor { + + /** + * Returns a list of completion proposals based on the + * specified location within the document that corresponds + * to the current cursor position within the text viewer. + * + * @param viewer the viewer whose document is used to compute the proposals + * @param documentPosition an offset within the document for which completions should be computed + * @return an array of completion proposals or <code>null</code> if no proposals are possible + */ + ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int documentOffset); + + /** + * Returns information about possible contexts based on the + * specified location within the document that corresponds + * to the current cursor position within the text viewer. + * + * @param viewer the viewer whose document is used to compute the possible contexts + * @param documentPosition an offset within the document for which context information should be computed + * @return an array of context information objects or <code>null</code> if no context could be found + */ + IContextInformation[] computeContextInformation(ITextViewer viewer, int documentOffset); + + /** + * Returns the characters which when entered by the user should + * automatically trigger the presentation of possible completions. + * + * @return the auto activation characters for completion proposal or <code>null</code> + * if no auto activation is desired + */ + char[] getCompletionProposalAutoActivationCharacters(); + + /** + * Returns the characters which when entered by the user should + * automatically trigger the presentation of context information. + * + * @return the auto activation characters for presenting context information + * or <code>null</code> if no auto activation is desired + */ + char[] getContextInformationAutoActivationCharacters(); + + /** + * Returns the reason why this content assist processor + * was unable to produce any completion proposals or context information. + * + * @return an error message or <code>null</code> if no error occurred + */ + String getErrorMessage(); + + /** + * Returns a validator used to determine when displayed context information + * should be dismissed. May only return <code>null</code> if the processor is + * incapable of computing context information. <p> + * + * Because of http://dev.eclipse.org/bugs/show_bug.cgi?id=13926 the object returned + * by this method should also implement <code>IContextInformationPresenter</code>. + * @see IContextInformationPresenter + * + * @return a context information validator, or <code>null</code> if the processor + * is incapable of computing context information + */ + IContextInformationValidator getContextInformationValidator(); +} diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/IContentAssistant.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/IContentAssistant.java new file mode 100644 index 000000000..74fcd27e9 --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/IContentAssistant.java @@ -0,0 +1,85 @@ +package org.eclipse.jface.text.contentassist; + +/* + * (c) Copyright IBM Corp. 2000, 2001. + * All Rights Reserved. + */ + +import org.eclipse.jface.text.ITextViewer; + + +/** + * An <code>IContentAssistant</code> provides support on interactive content completion. + * The content assistant is a <code>ITextViewer</code> add-on. Its + * purpose is to propose, display, and insert completions of the content + * of the text viewer's document at the viewer's cursor position. In addition + * to handle completions, a content assistant can also be requested to provide + * context information. Context information is shown in a tooltip like popup. + * As it is not always possible to determine the exact context at a given + * document offset, a content assistant displays the possible contexts and requests + * the user to choose the one whose information should be displayed.<p> + * A content assistant has a list of <code>IContentAssistProcessor</code> + * objects each of which is registered for a particular document content + * type. The content assistant uses the processors to react on the request + * of completing documents or presenting context information.<p> + * The interface can be implemented by clients. By default, clients use + * <code>ContentAssistant</code> as the standard implementer of this interface. + * + * @see ITextViewer + * @see IContentAssistProcessor + */ + + public interface IContentAssistant { + + //------ proposal popup orientation styles ------------ + /** The context info list will overlay the list of completion proposals. */ + public final static int PROPOSAL_OVERLAY= 10; + /** The completion proposal list will be removed before the context info list will be shown. */ + public final static int PROPOSAL_REMOVE= 11; + /** The context info list will be presented without hiding or overlapping the completion proposal list. */ + public final static int PROPOSAL_STACKED= 12; + + //------ context info box orientation styles ---------- + /** Context info will be shown above the location it has been requested for without hiding the location. */ + public final static int CONTEXT_INFO_ABOVE= 20; + /** Context info will be shown below the location it has been requested for without hiding the location. */ + public final static int CONTEXT_INFO_BELOW= 21; + + + /** + * Installs content assist support on the given text viewer. + * + * @param textViewer the text viewer on which content assist will work + */ + void install(ITextViewer textViewer); + + /** + * Uninstalls content assist support from the text viewer it has + * previously be installed on. + */ + void uninstall(); + + /** + * Shows all possible completions of the content at the viewer's cursor position. + * + * @return an optional error message if no proposals can be computed + */ + String showPossibleCompletions(); + + /** + * Shows context information for the content at the viewer's cursor position. + * + * @return an optional error message if no context information can be computed + */ + String showContextInformation(); + + /** + * Returns the content assist processor to be used for the given content type. + * + * @param contentType the type of the content for which this + * content assistant is to be requested + * @return an instance content assist processor or + * <code>null</code> if none exists for the specified content type + */ + IContentAssistProcessor getContentAssistProcessor(String contentType); +}
\ No newline at end of file diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/IContextInformation.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/IContextInformation.java new file mode 100644 index 000000000..bbf4efec1 --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/IContextInformation.java @@ -0,0 +1,57 @@ +package org.eclipse.jface.text.contentassist; + +/* + * (c) Copyright IBM Corp. 2000, 2001. + * All Rights Reserved. + */ + + +import org.eclipse.swt.graphics.Image; + + +/** + * The inferface of context information presented to the user and + * generated by content assist processors. + * The interface can be implemented by clients. By default, clients use + * <code>ContextInformation</code> as the standard implementer of this interface. + * + * @see IContentAssistProcessor + */ +public interface IContextInformation { + + /** + * Returns the string to be displayed in the list of contexts. + * This method is used to supply a unique presentation for + * situations where the context is ambiguous. These strings are + * used to allow the user to select the specific context. + * + * @return the string to be displayed for the context + */ + String getContextDisplayString(); + + /** + * Returns the image for this context information. + * The image will be shown to the left of the display string. + * + * @return the image to be shown or <code>null</code> if no image is desired + */ + Image getImage(); + + /** + * Returns the string to be displayed in the tooltip like information popup. + * + * @return the string to be displayed + */ + String getInformationDisplayString(); + + /** + * Compares the given object with this receiver. Two context informations are + * equal if there information display strings and their context display strings + * are equal. + * + * @see Object#equals + */ + boolean equals(Object object); +} + + diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/IContextInformationExtension.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/IContextInformationExtension.java new file mode 100644 index 000000000..d34e2435c --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/IContextInformationExtension.java @@ -0,0 +1,29 @@ +/********************************************************************** +Copyright (c) 2000, 2002 IBM Corp. and others. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Common Public License v1.0 +which accompanies this distribution, and is available at +http://www.eclipse.org/legal/cpl-v10.html + +Contributors: + IBM Corporation - Initial implementation +**********************************************************************/ + +package org.eclipse.jface.text.contentassist; + + +/** + * Extension interface for <code>IContextInformation</code>. + * Adds the functionality of freely positionable context information. + * + * @since 2.0 + */ +public interface IContextInformationExtension { + + /** + * Returns the start offset of the range for which this context information is valid. + * + * @return the start offset of the range for which this context information is valid + */ + int getContextInformationPosition(); +} diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/IContextInformationPresenter.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/IContextInformationPresenter.java new file mode 100644 index 000000000..d0b878310 --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/IContextInformationPresenter.java @@ -0,0 +1,48 @@ +/********************************************************************** +Copyright (c) 2000, 2002 IBM Corp. and others. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Common Public License v1.0 +which accompanies this distribution, and is available at +http://www.eclipse.org/legal/cpl-v10.html + +Contributors: + IBM Corporation - Initial implementation +**********************************************************************/ + +package org.eclipse.jface.text.contentassist; + + +import org.eclipse.jface.text.ITextViewer; +import org.eclipse.jface.text.TextPresentation; + + +/** + * A context information presenter determines the presentation + * of context information depending on a given document position. + * The interface can be implemented by clients. + * + * @since 2.0 + */ +public interface IContextInformationPresenter { + + /** + * Installs this presenter for the given context information. + * + * @param info the context information which this presenter should style + * @param viewer the text viewer on which the information is presented + * @param documentPosition the document position for which the information has been computed + */ + void install(IContextInformation info, ITextViewer viewer, int documentPosition); + + /** + * Updates the given presentation of the given context information + * at the given document position. Returns whether update changed the + * presentation. + * + * @param information the context information to be styled + * @param documentPosition the current position within the document + * @param presentation the presentation to be updated + * @return <code>true</code> if the given presentation has been changed + */ + boolean updatePresentation(int documentPosition, TextPresentation presentation); +}
\ No newline at end of file diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/IContextInformationValidator.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/IContextInformationValidator.java new file mode 100644 index 000000000..661023c92 --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/IContextInformationValidator.java @@ -0,0 +1,38 @@ +package org.eclipse.jface.text.contentassist; + +/* + * (c) Copyright IBM Corp. 2000, 2001. + * All Rights Reserved. + */ + + +import org.eclipse.jface.text.ITextViewer; + + +/** + * A context information validator is used to determine if + * a displayed context information is still valid or should + * be dismissed. The interface can be implemented by clients. <p> + * + * @see IContextInformationPresenter + */ +public interface IContextInformationValidator { + + /** + * Installs this validator for the given context information. + * + * @param info the context information which this validator should check + * @param viewer the text viewer on which the information is presented + * @param documentPosition the document position for which the information has been computed + */ + void install(IContextInformation info, ITextViewer viewer, int documentPosition); + + /** + * Returns whether the information this validator is installed on is still valid + * at the given document position. + * + * @param documentPosition the current position within the document + * @return <code>true</code> if the information also valid at the given document position + */ + boolean isContextInformationValid(int documentPosition); +}
\ No newline at end of file diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/JFaceTextMessages.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/JFaceTextMessages.java new file mode 100644 index 000000000..5b93bed49 --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/JFaceTextMessages.java @@ -0,0 +1,26 @@ +/* + * (c) Copyright IBM Corp. 2000, 2001. + * All Rights Reserved. + */ +package org.eclipse.jface.text.contentassist; + +import java.util.MissingResourceException; +import java.util.ResourceBundle; + +class JFaceTextMessages { + + private static final String RESOURCE_BUNDLE= "org.eclipse.jface.text.JFaceTextMessages";//$NON-NLS-1$ + + private static ResourceBundle fgResourceBundle= ResourceBundle.getBundle(RESOURCE_BUNDLE); + + private JFaceTextMessages() { + } + + public static String getString(String key) { + try { + return fgResourceBundle.getString(key); + } catch (MissingResourceException e) { + return "!" + key + "!";//$NON-NLS-2$ //$NON-NLS-1$ + } + } +} diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/PopupCloser.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/PopupCloser.java new file mode 100644 index 000000000..41f264a12 --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/PopupCloser.java @@ -0,0 +1,72 @@ +package org.eclipse.jface.text.contentassist; + +/* + * (c) Copyright IBM Corp. 2000, 2001. + * All Rights Reserved. + */ + +import org.eclipse.swt.events.FocusEvent; +import org.eclipse.swt.events.FocusListener; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.ScrollBar; +import org.eclipse.swt.widgets.Table; + + +/** + * A generic closer class used to monitor various + * interface events in order to determine whether + * a content assist should be terminated and all + * associated windows be closed. + */ +class PopupCloser implements FocusListener, SelectionListener { + + private ContentAssistant fContentAssistant; + private Table fTable; + private ScrollBar fScrollbar; + private boolean fScrollbarClicked= false; + + public void install(ContentAssistant contentAssistant, Table table) { + fContentAssistant= contentAssistant; + fTable= table; + if (Helper.okToUse(fTable)) { + fTable.addFocusListener(this); + fScrollbar= fTable.getVerticalBar(); + if (fScrollbar != null) + fScrollbar.addSelectionListener(this); + } + } + + public void uninstall() { + if (Helper.okToUse(fTable)) { + fTable.removeFocusListener(this); + if (fScrollbar != null) + fScrollbar.removeSelectionListener(this); + } + } + + // SelectionListener + public void widgetSelected(SelectionEvent e) { + fScrollbarClicked= true; + } + + public void widgetDefaultSelected(SelectionEvent e) { + fScrollbarClicked= true; + } + + // FocusListener + public void focusGained(FocusEvent e) { + } + + public void focusLost(final FocusEvent e) { + fScrollbarClicked= false; + Display d= fTable.getDisplay(); + d.asyncExec(new Runnable() { + public void run() { + if (Helper.okToUse(fTable) && !fTable.isFocusControl() && !fScrollbarClicked) + fContentAssistant.popupFocusLost(e); + } + }); + } +}
\ No newline at end of file diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/package.html b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/package.html new file mode 100644 index 000000000..2d94dfb32 --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/contentassist/package.html @@ -0,0 +1,25 @@ +<!doctype html public "-//w3c//dtd html 4.0 transitional//en"> +<html> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> + <meta name="Author" content="IBM"> + <meta name="GENERATOR" content="Mozilla/4.51 [en] (WinNT; I) [Netscape]"> + <title>Package-level Javadoc</title> +</head> +<body> +Provides a content assist add-on for an <tt>ITextViewer</tt>. +Content assist supports the user in writing by proposing context +sensitive completions at a given document position. A completion can also +be a incomplete in itself and content assist provides means to deal with +nested completions. +<h2> +Package Specification</h2> +<tt>IContentAssistant</tt> defines the concept of the content assist add-on. +It collaborates with content type specific completion processors (<tt>IContentAssistProcessor</tt>) +in order to generate completion proposals (<tt>ICompletionProposal</tt>) +valid at the current document position. The package provides a default +implementation <tt>ContentAssistant</tt> which completely defines and implements +the UI and the control flow for content assist. +<br> +</body> +</html> diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/formatter/ContentFormatter.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/formatter/ContentFormatter.java new file mode 100644 index 000000000..6ac7520ff --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/formatter/ContentFormatter.java @@ -0,0 +1,654 @@ +package org.eclipse.jface.text.formatter; + +/* + * (c) Copyright IBM Corp. 2000, 2001. + * All Rights Reserved. + */ + +import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.BadPositionCategoryException;
import org.eclipse.jface.text.DefaultPositionUpdater;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentListener;
import org.eclipse.jface.text.IPositionUpdater;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITypedRegion;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.TypedPosition;
import org.eclipse.jface.util.Assert; + + +/** + * Standard implementation of <code>IContentFormatter</code>. + * The formatter supports two operation modi: partition aware and + * partition unaware. <p> + * In the partition aware mode, the formatter determines the + * partitioning of the document region to be formatted. For each + * partition it determines all document positions which are affected + * when text changes are applied to the partition. Those which overlap + * with the partition are remembered as character positions. These + * character positions are passed over to the formatting strategy + * registered for the partition's content type. The formatting strategy + * returns a string containing the formatted document partition as well + * as the adapted character positions. The formatted partition replaces + * the old content of the partition. The remembered document postions + * are updated with the adapted character positions. In addition, all + * other document positions are accordingly adapted to the formatting + * changes.<p> + * In the partition unaware mode, the document's partitioning is ignored + * and the document is considered consisting of only one partition of + * the content type <code>IDocument.DEFAULT_CONTENT_TYPE</code>. The + * formatting process is similar to the partition aware mode, with the + * exception of having only one partition.<p> + * Usually, clients instantiate this class and configure it before using it. + * + * @see IContentFormatter + * @see IDocument + * @see ITypedRegion + * @see Position + */ +public class ContentFormatter implements IContentFormatter { + + /** + * Defines a reference to either the offset or the end offset of + * a particular position. + */ + static class PositionReference implements Comparable { + + /** The referenced position */ + protected Position fPosition; + /** The reference to either the offset or the end offset */ + protected boolean fRefersToOffset; + /** The original category of the referenced position */ + protected String fCategory; + + protected PositionReference(Position position, boolean refersToOffset, String category) { + fPosition= position; + fRefersToOffset= refersToOffset; + fCategory= category; + } + + /** + * Returns the offset of the referenced position. + */ + protected int getOffset() { + return fPosition.getOffset(); + } + + /** + * Manipulates the offset of the referenced position. + */ + protected void setOffset(int offset) { + fPosition.setOffset(offset); + } + + /** + * Returns the length of the referenced position. + */ + protected int getLength() { + return fPosition.getLength(); + } + + /** + * Manipulates the length of the referenced position. + */ + protected void setLength(int length) { + fPosition.setLength(length); + } + + /** + * Returns whether this reference points to the offset or endoffset + * of the references position. + */ + protected boolean refersToOffset() { + return fRefersToOffset; + } + + /** + * Returns the category of the referenced position. + */ + protected String getCategory() { + return fCategory; + } + + /** + * Returns the referenced position. + */ + protected Position getPosition() { + return fPosition; + } + + /** + * Returns the referenced character position + */ + protected int getCharacterPosition() { + if (fRefersToOffset) + return getOffset(); + return getOffset() + getLength(); + } + + /** + * @see Comparable#compareTo(Object) + */ + public int compareTo(Object obj) { + + if (obj instanceof PositionReference) { + PositionReference r= (PositionReference) obj; + return getCharacterPosition() - r.getCharacterPosition(); + } + + throw new ClassCastException(); + } + }; + + /** + * The position updater used to adapt all to update the + * remembered partitions. + * + * @see IPositionUpdater + * @see DefaultPositionUpdater + */ + class NonDeletingPositionUpdater extends DefaultPositionUpdater { + + protected NonDeletingPositionUpdater(String category) { + super(category); + } + + /* + * @see DefaultPositionUpdater#notDeleted() + */ + protected boolean notDeleted() { + return true; + } + }; + + /** + * The position updater which runs as first updater on the document's positions. + * Used to remove all affected positions from their categories to avoid them + * from being regularily updated. + * + * @see IPositionUpdater + */ + class RemoveAffectedPositions implements IPositionUpdater { + /** + * @see IPositionUpdater#update(DocumentEvent) + */ + public void update(DocumentEvent event) { + removeAffectedPositions(event.getDocument()); + } + }; + + /** + * The position updater which runs as last updater on the document's positions. + * Used to update all affected positions and adding them back to their + * original categories. + * + * @see IPositionUpdater + */ + class UpdateAffectedPositions implements IPositionUpdater { + + private int[] fPositions; + private int fOffset; + + public UpdateAffectedPositions(int[] positions, int offset) { + fPositions= positions; + fOffset= offset; + } + + /** + * @see IPositionUpdater#update(DocumentEvent) + */ + public void update(DocumentEvent event) { + updateAffectedPositions(event.getDocument(), fPositions, fOffset); + } + }; + + + /** Internal position category used for the formatter partitioning */ + private final static String PARTITIONING= "__formatter_partitioning"; //$NON-NLS-1$ + + /** The map of <code>IFormattingStrategy</code> objects */ + private Map fStrategies; + /** The indicator of whether the formatter operates in partition aware mode or not */ + private boolean fIsPartitionAware= true; + + /** The partition information managing document position categories */ + private String[] fPartitionManagingCategories; + /** The list of references to offset and end offset of all overlapping positions */ + private List fOverlappingPositionReferences; + /** Position updater used for partitioning positions */ + private IPositionUpdater fPartitioningUpdater; + + + + /** + * Creates a new content formatter. The content formatter operates by default + * in the partition-aware mode. There are no preconfigured formatting strategies. + */ + public ContentFormatter() { + } + + /** + * Registers a strategy for a particular content type. If there is already a strategy + * registered for this type, the new strategy is registered instead of the old one. + * If the given content type is <code>null</code> the given strategy is registered for + * all content types as is called only once per formatting session. + * + * @param strategy the formatting strategy to register, or <code>null</code> to remove an existing one + * @param contentType the content type under which to register, or <code>null</code> for all content types + */ + public void setFormattingStrategy(IFormattingStrategy strategy, String contentType) { + + Assert.isNotNull(contentType); + + if (fStrategies == null) + fStrategies= new HashMap(); + + if (strategy == null) + fStrategies.remove(contentType); + else + fStrategies.put(contentType, strategy); + } + + /** + * Informs this content formatter about the names of those position categories + * which are used to manage the document's partitioning information and thus should + * be ignored when this formatter updates positions. + * + * @param categories the categories to be ignored + */ + public void setPartitionManagingPositionCategories(String[] categories) { + fPartitionManagingCategories= categories; + } + + /** + * Sets the formatter's operation mode. + * + * @param enable indicates whether the formatting process should be partition ware + */ + public void enablePartitionAwareFormatting(boolean enable) { + fIsPartitionAware= enable; + } + + /* + * @see IContentFormatter#getFormattingStrategy + */ + public IFormattingStrategy getFormattingStrategy(String contentType) { + + Assert.isNotNull(contentType); + + if (fStrategies == null) + return null; + + return (IFormattingStrategy) fStrategies.get(contentType); + } + + /* + * @see IContentFormatter#format + */ + public void format(IDocument document, IRegion region) { + if (fIsPartitionAware) + formatPartitions(document, region); + else + formatRegion(document, region); + } + + /** + * Determines the partitioning of the given region of the document. + * Informs for each partition about the start, the process, and the + * termination of the formatting session. + */ + private void formatPartitions(IDocument document, IRegion region) { + + addPartitioningUpdater(document); + + try { + + TypedPosition[] ranges= getPartitioning(document, region); + if (ranges != null) { + start(ranges, getIndentation(document, region.getOffset())); + format(document, ranges); + stop(ranges); + } + + } catch (BadLocationException x) { + } + + removePartitioningUpdater(document); + } + + /** + * Informs for the given region about the start, the process, and + * the termination of the formatting session. + */ + private void formatRegion(IDocument document, IRegion region) { + + IFormattingStrategy strategy= getFormattingStrategy(IDocument.DEFAULT_CONTENT_TYPE); + if (strategy != null) { + strategy.formatterStarts(getIndentation(document, region.getOffset())); + format(document, strategy, new TypedPosition(region.getOffset(), region.getLength(), IDocument.DEFAULT_CONTENT_TYPE)); + strategy.formatterStops(); + } + } + + /** + * Returns the partitioning of the given region of the specified document. + * As one partition after the other will be formatted and formatting will + * probably change the length of the formatted partition, it must be kept + * track of the modifications in order to submit the correct partition to all + * formatting strategies. For this, all partitions are remembered as positions + * in a dedicated position category. (As formatting stratgies might rely on each + * other, calling them in reversed order is not an option.) + * + * @param document the document + * @param region the region for which the partitioning must be determined + * @return the partitioning of the specified region + * @exception BadLocationException of region is invalid in the document + */ + private TypedPosition[] getPartitioning(IDocument document, IRegion region) throws BadLocationException { + + ITypedRegion[] regions= document.computePartitioning(region.getOffset(), region.getLength()); + TypedPosition[] positions= new TypedPosition[regions.length]; + + for (int i= 0; i < regions.length; i++) { + positions[i]= new TypedPosition(regions[i]); + try { + document.addPosition(PARTITIONING, positions[i]); + } catch (BadPositionCategoryException x) { + // should not happen + } + } + + return positions; + } + + /** + * Fires <code>formatterStarts</code> to all formatter strategies + * which will be involved in the forthcoming formatting process. + * + * @param regions the partitioning of the document to be formatted + * @param indentation the initial indentation + */ + private void start(TypedPosition[] regions, String indentation) { + for (int i= 0; i < regions.length; i++) { + IFormattingStrategy s= getFormattingStrategy(regions[i].getType()); + if (s != null) + s.formatterStarts(indentation); + } + } + + /** + * Formats one partition after the other using the formatter strategy registered for + * the partition's content type. + * + * @param document to document to be formatted + * @param ranges the partitioning of the document region to be formatted + */ + private void format(final IDocument document, TypedPosition[] ranges) { + for (int i= 0; i < ranges.length; i++) { + IFormattingStrategy s= getFormattingStrategy(ranges[i].getType()); + if (s != null) { + format(document, s, ranges[i]); + } + } + } + + /** + * Formats the given region of the document using the specified formatting + * strategy. In order to maintain positions correctly, first all affected + * positions determined, after all document listeners have been informed about + * the upcoming change, the affected positions are removed to avoid that they + * are regularily updated. After all position updaters have run, the affected + * positions are updated with the formatter's information and added back to + * their categories, right before the first document listener is informed about + * that a change happend. + * + * @param document the document to be formatted + * @param strategy the strategy to be used + * @param region the region to be formatted + */ + private void format(final IDocument document, IFormattingStrategy strategy, TypedPosition region) { + try { + + final int offset= region.getOffset(); + int length= region.getLength(); + + String content= document.get(offset, length); + final int[] positions= getAffectedPositions(document, offset, length); + String formatted= strategy.format(content, isLineStart(document, offset), getIndentation(document, offset), positions); + + IPositionUpdater first= new RemoveAffectedPositions(); + document.insertPositionUpdater(first, 0); + IPositionUpdater last= new UpdateAffectedPositions(positions, offset); + document.addPositionUpdater(last); + + document.replace(offset, length, formatted); + + document.removePositionUpdater(first); + document.removePositionUpdater(last); + + } catch (BadLocationException x) { + // should not happen + } + } + + /** + * Fires <code>formatterStops</code> to all formatter strategies which were + * involved in the formatting process which is about to terminate. + * + * @param regions the partitioning of the document which has been formatted + */ + private void stop(TypedPosition[] regions) { + for (int i= 0; i < regions.length; i++) { + IFormattingStrategy s= getFormattingStrategy(regions[i].getType()); + if (s != null) + s.formatterStops(); + } + } + + /** + * Installs those updaters which the formatter needs to keep + * track of the partitions. + * + * @param document the document to be formatted + */ + private void addPartitioningUpdater(IDocument document) { + fPartitioningUpdater= new NonDeletingPositionUpdater(PARTITIONING); + document.addPositionCategory(PARTITIONING); + document.addPositionUpdater(fPartitioningUpdater); + } + + /** + * Removes the formatter's internal position updater and category. + * + * @param document the document that has been formatted + */ + private void removePartitioningUpdater(IDocument document) { + + try { + + document.removePositionUpdater(fPartitioningUpdater); + document.removePositionCategory(PARTITIONING); + fPartitioningUpdater= null; + + } catch (BadPositionCategoryException x) { + // should not happen + } + } + + /** + * Determines whether the given document position category should be ignored + * by this formatter's position updating. + * + * @param category the category to check + * @return <code>true</code> if the category should be ignored, <code>false</code> otherwise + */ + private boolean ignoreCategory(String category) { + + if (PARTITIONING.equals(category)) + return true; + + if (fPartitionManagingCategories != null) { + for (int i= 0; i < fPartitionManagingCategories.length; i++) { + if (fPartitionManagingCategories[i].equals(category)) + return true; + } + } + + return false; + } + + /** + * Determines all embracing, overlapping, and follow up positions + * for the given region of the document. + * + * @param document the document to be formatted + * @param offset the offset of the document region to be formatted + * @param length the length of the document to be formatted + */ + private void determinePositionsToUpdate(IDocument document, int offset, int length) { + + String[] categories= document.getPositionCategories(); + if (categories != null) { + for (int i= 0; i < categories.length; i++) { + + if (ignoreCategory(categories[i])) + continue; + + try { + + Position[] positions= document.getPositions(categories[i]); + + for (int j= 0; j < positions.length; j++) { + + Position p= (Position) positions[j]; + if (p.overlapsWith(offset, length)) { + + if (offset < p.getOffset()) + fOverlappingPositionReferences.add(new PositionReference(p, true, categories[i])); + + if (p.getOffset() + p.getLength() < offset + length) + fOverlappingPositionReferences.add(new PositionReference(p, false, categories[i])); + } + } + + } catch (BadPositionCategoryException x) { + // can not happen + } + } + } + } + + /** + * Returns all offset and the end offset of all positions overlapping with the + * specified document range. + * + * @param document the document to be formatted + * @param offset the offset of the document region to be formatted + * @param length the length of the document to be formatted + * @return all character positions of the interleaving positions + */ + private int[] getAffectedPositions(IDocument document, int offset, int length) { + + fOverlappingPositionReferences= new ArrayList(); + + determinePositionsToUpdate(document, offset, length); + + Collections.sort(fOverlappingPositionReferences); + + int[] positions= new int[fOverlappingPositionReferences.size()]; + for (int i= 0; i < positions.length; i++) { + PositionReference r= (PositionReference) fOverlappingPositionReferences.get(i); + positions[i]= r.getCharacterPosition() - offset; + } + + return positions; + } + + /** + * Removes the affected positions from their categories to avoid + * that they are invalidly updated. + * + * @param document the document + */ + private void removeAffectedPositions(IDocument document) { + int size= fOverlappingPositionReferences.size(); + for (int i= 0; i < size; i++) { + PositionReference r= (PositionReference) fOverlappingPositionReferences.get(i); + try { + document.removePosition(r.getCategory(), r.getPosition()); + } catch (BadPositionCategoryException x) { + // can not happen + } + } + } + + /** + * Updates all the overlapping positions. Note, all other positions are + * automatically updated by their document position updaters. + * + * @param document the document to has been formatted + * @param positions the adapted character positions to be used to update the document positions + * @param offset the offset of the document region that has been formatted + */ + private void updateAffectedPositions(IDocument document, int[] positions, int offset) { + + if (positions.length == 0) + return; + + Map added= new HashMap(positions.length * 2); + + for (int i= 0; i < positions.length; i++) { + + PositionReference r= (PositionReference) fOverlappingPositionReferences.get(i); + + if (r.refersToOffset()) + r.setOffset(offset + positions[i]); + else + r.setLength((offset + positions[i]) - r.getOffset()); + + if (added.get(r.getPosition()) == null) { + try { + document.addPosition(r.getCategory(), r.getPosition()); + added.put(r.getPosition(), r.getPosition()); + } catch (BadPositionCategoryException x) { + // can not happen + } catch (BadLocationException x) { + // should not happen + } + } + + } + + fOverlappingPositionReferences= null; + } + + /** + * Returns the indentation of the line of the given offset. + * + * @param document the document + * @param offset the offset + * @return the indentation of the line of the offset + */ + private String getIndentation(IDocument document, int offset) { + + try { + int start= document.getLineOfOffset(offset); + start= document.getLineOffset(start); + + int end= start; + char c= document.getChar(end); + while ('\t' == c || ' ' == c) + c= document.getChar(++end); + + return document.get(start, end - start); + } catch (BadLocationException x) { + } + + return ""; //$NON-NLS-1$ + } + + /** + * Determines whether the offset is the beginning of a line in the given document. + * + * @param document the document + * @param offset the offset + * @return <code>true</code> if offset is the beginning of a line + * @exception BadLocationException if offset is invalid in document + */ + private boolean isLineStart(IDocument document, int offset) throws BadLocationException { + int start= document.getLineOfOffset(offset); + start= document.getLineOffset(start); + return (start == offset); + } +} diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/formatter/IContentFormatter.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/formatter/IContentFormatter.java new file mode 100644 index 000000000..329e8c92d --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/formatter/IContentFormatter.java @@ -0,0 +1,49 @@ +package org.eclipse.jface.text.formatter; + +/* + * (c) Copyright IBM Corp. 2000, 2001. + * All Rights Reserved. + */ + +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IRegion; + + +/** + * The interface of a document content formatter. The formatter formats ranges + * within documents. The documents are modified by the formatter.<p> + * The content formatter is assumed to determine the partitioning of the document + * range to be formatted. For each partition, the formatter determines based + * on the partition's content type the formatting strategy to be used. Before + * the first strategy is activated all strategies are informed about the + * start of the formatting process. After that, the formatting strategies are + * activated in the sequence defined by the partitioning of the document range to be + * formatted. It is assumed that a strategy must be finished before the next strategy + * can be activated. After the last strategy has been finished, all strategies are + * informed about the termination of the formatting process.<p> + * The interface can be implemented by clients. By default, clients use <code>ContentFormatter</code> + * as the standard implementer of this interface. + * + * @see IDocument + * @see IFormattingStrategy + */ +public interface IContentFormatter { + + /** + * Formats the given region of the specified document.The formatter may safely + * assume that it is the only subject that modifies the document at this point in time. + * + * @param document the document to be formatted + * @param region the region within the document to be formatted + */ + void format(IDocument document, IRegion region); + + /** + * Returns the formatting strategy registered for the given content type. + * + * @param contentType the content type for which to look up the formatting strategy + * @return the formatting strategy for the given content type, or + * <code>null</code> if there is no such strategy + */ + IFormattingStrategy getFormattingStrategy(String contentType); +} diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/formatter/IFormattingStrategy.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/formatter/IFormattingStrategy.java new file mode 100644 index 000000000..5568ef785 --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/formatter/IFormattingStrategy.java @@ -0,0 +1,51 @@ +package org.eclipse.jface.text.formatter; + + +/* + * (c) Copyright IBM Corp. 2000, 2001. + * All Rights Reserved. + */ + +import java.util.List; + +/** + * An formatting strategy is assumed to be specialized on formatting text + * of a particular content type. Each formatting process calls the strategy's + * methods in the following sequence: + * <ul> + * <li><code>formatterStarts</code> + * <li><code>format</code> + * <li><code>formatterStops</code> + * </ul> + * This interface must be implemented by clients. Implementers should be registered with + * a content formatter in order get involved in the formatting process. + */ +public interface IFormattingStrategy { + + /** + * Informs the strategy about the start of a formatting process in which it will + * participate. + * + * @param initialIndentation the indent string of the first line at which the + * overall formatting process starts. + */ + void formatterStarts(String initialIndentation); + + /** + * Formats the given string. During the formatting process this strategy must update + * the given character positions according to the changes applied to the given string. + * + * @param content the initial string to be formatted + * @param isLineStart indicates whether the beginning of content is a line start in its document + * @param indentation the indentation string to be used + * @param positions the character positions to be updated + * @return the formatted string + */ + String format(String content, boolean isLineStart, String indentation, int[] positions); + + /** + * Informs the strategy that the formatting process in which it has participated + * has been finished. + */ + void formatterStops(); +}
\ No newline at end of file diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/formatter/package.html b/org.eclipse.jface.text/src/org/eclipse/jface/text/formatter/package.html new file mode 100644 index 000000000..33f16a7de --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/formatter/package.html @@ -0,0 +1,21 @@ +<!doctype html public "-//w3c//dtd html 4.0 transitional//en"> +<html> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> + <meta name="Author" content="IBM"> + <meta name="GENERATOR" content="Mozilla/4.51 [en] (WinNT; I) [Netscape]"> + <title>Package-level Javadoc</title> +</head> +<body> +Provides a text formatter add-on for an <tt>ITextViewer</tt>. +A text formatter changes the formatting of document region while +preserving and correctly updating the positions of the document. +<h2> +Package Specification</h2> +<tt>IContentFormatter</tt> defines the concept of a text formatter. It +collaborates with content type specific formatting stratgies (<tt>IFormattingStrategy</tt>) +which for a given document region format the subregion with the matching +content type. The package contains a default implementation of <tt>IContentFormatter</tt> +(<tt>ContentFormatter</tt>). +</body> +</html> diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/information/IInformationPresenter.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/information/IInformationPresenter.java new file mode 100644 index 000000000..74375c750 --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/information/IInformationPresenter.java @@ -0,0 +1,66 @@ +/********************************************************************** +Copyright (c) 2000, 2002 IBM Corp. and others. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Common Public License v1.0 +which accompanies this distribution, and is available at +http://www.eclipse.org/legal/cpl-v10.html + +Contributors: + IBM Corporation - Initial implementation +**********************************************************************/ + +package org.eclipse.jface.text.information; + + +import org.eclipse.jface.text.ITextViewer; + + +/** + * An information presenter shows information available at the text viewer's + * current document position. An <code>IInformationPresenter</code> is a + * <code>ITextViewer</code> add-on.<p> + * An information presenters has a list of <code>IInformationProvider</code> objects + * each of which is registered for a particular document content type. + * The presenter uses the strategy objects to retrieve the information to present.<p> + * The interface can be implemented by clients. By default, clients use + * <code>InformationPresenter</code> as the standard implementer of this interface. + * + * @see ITextViewer + * @see IInformationProvider + * @since 2.0 + */ +public interface IInformationPresenter { + + /** + * Installs the information presenter on the given text viewer. After this method has been + * finished, the presenter is operational. I.e., the method <code>showInformation</code> + * can be called until <code>uninstall</code> is called. + * + * @param textViewer the viewer on which the presenter is installed + */ + void install(ITextViewer textViewer); + + /** + * Removes the information presenter from the text viewer it has previously been + * installed on. + */ + void uninstall(); + + /** + * Shows information related to the cursor position of the text viewer + * this information presenter is installed on. + * + * @return an optional error message if + */ + void showInformation(); + + /** + * Returns the information provider to be used for the given content type. + * + * @param contentType the type of the content for which information will be requested + * @return an information provider or + * <code>null</code> if none exists for the specified content type + */ + IInformationProvider getInformationProvider(String contentType); +} + diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/information/IInformationProvider.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/information/IInformationProvider.java new file mode 100644 index 000000000..e9be43fda --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/information/IInformationProvider.java @@ -0,0 +1,54 @@ +/********************************************************************** +Copyright (c) 2000, 2002 IBM Corp. and others. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Common Public License v1.0 +which accompanies this distribution, and is available at +http://www.eclipse.org/legal/cpl-v10.html + +Contributors: + IBM Corporation - Initial implementation +**********************************************************************/ + +package org.eclipse.jface.text.information; + + +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.ITextViewer; + + +/** + * Provides information related to the content of a text viewer.<p> + * Clients may implement this interface. + * + * @see ITextViewer + * @since 2.0 + */ +public interface IInformationProvider { + + /** + * Returns the region of the text viewer's document close to the given + * offset that contains a subject about which information can be provided.<p> + * For example, if information can be provided on a per code block basis, + * the offset should be used to find the enclosing code block and the source + * range of the block should be returned. + * + * @param textViewer the text viewer in which informationhas been requested + * @param offset the offset at which information has been requested + * @return the region of the text viewer's document containing the information subject + */ + IRegion getSubject(ITextViewer textViewer, int offset); + + /** + * Returns the information about the given subject or <code>null</code> if + * no information is available. It depends on the concrete configuration in which + * format the information is to be provided. For example, information presented + * in an information control displaying HTML, should be provided in HTML. + * + * @param textViewer the viewer in whose document the subject is contained + * @param subject the text region constituting the information subject + * @return the information about the subject + * @see IInformationPresenter + */ + String getInformation(ITextViewer textViewer, IRegion subject); +} + diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/information/InformationPresenter.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/information/InformationPresenter.java new file mode 100644 index 000000000..f8f7590c2 --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/information/InformationPresenter.java @@ -0,0 +1,394 @@ +/********************************************************************** +Copyright (c) 2000, 2002 IBM Corp. and others. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Common Public License v1.0 +which accompanies this distribution, and is available at +http://www.eclipse.org/legal/cpl-v10.html + +Contributors: + IBM Corporation - Initial implementation +**********************************************************************/ + +package org.eclipse.jface.text.information; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.swt.custom.StyledText; +import org.eclipse.swt.events.ControlEvent; +import org.eclipse.swt.events.ControlListener; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.events.DisposeListener; +import org.eclipse.swt.events.FocusEvent; +import org.eclipse.swt.events.FocusListener; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.KeyListener; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.MouseListener; +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.jface.text.AbstractInformationControlManager; +import org.eclipse.jface.text.IInformationControl; +import org.eclipse.jface.text.IInformationControlCreator; +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.ITextViewer; +import org.eclipse.jface.text.IViewportListener; +import org.eclipse.jface.text.IWidgetTokenKeeper; +import org.eclipse.jface.text.IWidgetTokenOwner; + +import org.eclipse.jface.util.Assert; + +/** + * Standard implementation of <code>IInformationPresenter</code>. + * This implementation extends <code>AbstractInformationControlManager</code>. + * The information control is made visible on request by calling + * <code>showInformation</code>.<p> + * Usually, clients instantiate this class and configure it before using it. The configuration + * must be consistent: This means the used <code>IInformationControlCreator</code> + * must create an information control expecting information in the same format the configured + * <code>IInformationProvider</code>s use to encode the information they provide. + * + * @since 2.0 + */ +public class InformationPresenter extends AbstractInformationControlManager implements IInformationPresenter, IWidgetTokenKeeper { + + /** + * Internal information control closer. Listens to several events issued by its subject control + * and closes the information control when necessary. + */ + class Closer implements IInformationControlCloser, ControlListener, MouseListener, + FocusListener, IViewportListener, KeyListener { + + /** The subject control */ + private Control fSubjectControl; + /** The information control */ + private IInformationControl fInformationControl; + /** Indicates whether this closer is active */ + private boolean fIsActive= false; + + /* + * @see IInformationControlCloser#setSubjectControl(Control) + */ + public void setSubjectControl(Control control) { + fSubjectControl= control; + } + + /* + * @see IInformationControlCloser#setInformationControl(IInformationControl) + */ + public void setInformationControl(IInformationControl control) { + fInformationControl= control; + } + + /* + * @see IInformationControlCloser#start(Rectangle) + */ + public void start(Rectangle informationArea) { + + if (fIsActive) + return; + fIsActive= true; + + if (fSubjectControl != null && ! fSubjectControl.isDisposed()) { + fSubjectControl.addControlListener(this); + fSubjectControl.addMouseListener(this); + fSubjectControl.addFocusListener(this); + fSubjectControl.addKeyListener(this); + } + + if (fInformationControl != null) + fInformationControl.addFocusListener(this); + + fTextViewer.addViewportListener(this); + } + + /* + * @see IInformationControlCloser#stop() + */ + public void stop() { + + if (!fIsActive) + return; + fIsActive= false; + + fTextViewer.removeViewportListener(this); + + if (fInformationControl != null) + fInformationControl.removeFocusListener(this); + + hideInformationControl(); + + if (fSubjectControl != null && !fSubjectControl.isDisposed()) { + fSubjectControl.removeControlListener(this); + fSubjectControl.removeMouseListener(this); + fSubjectControl.removeFocusListener(this); + fSubjectControl.removeKeyListener(this); + } + } + + /* + * @see ControlListener#controlResized(ControlEvent) + */ + public void controlResized(ControlEvent e) { + stop(); + } + + /* + * @see ControlListener#controlMoved(ControlEvent) + */ + public void controlMoved(ControlEvent e) { + stop(); + } + + /* + * @see MouseListener#mouseDown(MouseEvent) + */ + public void mouseDown(MouseEvent e) { + stop(); + } + + /* + * @see MouseListener#mouseUp(MouseEvent) + */ + public void mouseUp(MouseEvent e) { + } + + /* + * @see MouseListener#mouseDoubleClick(MouseEvent) + */ + public void mouseDoubleClick(MouseEvent e) { + stop(); + } + + /* + * @see FocusListener#focusGained(FocusEvent) + */ + public void focusGained(FocusEvent e) { + } + + /* + * @see FocusListener#focusLost(FocusEvent) + */ + public void focusLost(FocusEvent e) { + Display d= fSubjectControl.getDisplay(); + d.asyncExec(new Runnable() { + public void run() { + if ( !fInformationControl.isFocusControl()) + stop(); + } + }); + } + + /* + * @see IViewportListenerListener#viewportChanged(int) + */ + public void viewportChanged(int topIndex) { + stop(); + } + + /* + * @see KeyListener#keyPressed(KeyEvent) + */ + public void keyPressed(KeyEvent e) { + stop(); + } + + /* + * @see KeyListener#keyReleased(KeyEvent) + */ + public void keyReleased(KeyEvent e) { + } + }; + + + /** The text viewer this information presenter works on */ + private ITextViewer fTextViewer; + /** The map of <code>IInformationProvider</code> objects */ + private Map fProviders; + + + /** + * Creates a new information presenter that uses the given information control creator. + * The presenter is not installed on any text viewer yet. By default, an information + * control closer is set that closes the information control in the event of key strokes, + * resizing, moves, focus changes, mouse clicks, and disposal - all of those applied to + * the information control's parent control. Also, the setup ensures that the information + * control when made visible will request thel focus. + * + * @param creator the information control creator to be used + */ + public InformationPresenter(IInformationControlCreator creator) { + super(creator); + setCloser(new Closer()); + takesFocusWhenVisible(true); + } + + /** + * Registers a given information provider for a particular content type. + * If there is already a provider registered for this type, the new provider + * is registered instead of the old one. + * + * @param provider the information provider to register, or <code>null</code> to remove an existing one + * @param contentType the content type under which to register + */ + public void setInformationProvider(IInformationProvider provider, String contentType) { + + Assert.isNotNull(contentType); + + if (fProviders == null) + fProviders= new HashMap(); + + if (provider == null) + fProviders.remove(contentType); + else + fProviders.put(contentType, provider); + } + + /* + * @see IInformationPresenter#getInformationProvider(String) + */ + public IInformationProvider getInformationProvider(String contentType) { + if (fProviders == null) + return null; + + return (IInformationProvider) fProviders.get(contentType); + } + + /* + * @see AbstractInformationControlManager#computeInformation() + */ + protected void computeInformation() { + int offset= fTextViewer.getSelectedRange().x; + if (offset == -1) + return; + + + IInformationProvider provider= null; + try { + IDocument document= fTextViewer.getDocument(); + String type= document.getContentType(offset); + provider= getInformationProvider(type); + } catch (BadLocationException x) { + } + if (provider == null) + return; + + IRegion subject= provider.getSubject(fTextViewer, offset); + if (subject == null) + return; + + setInformation(provider.getInformation(fTextViewer, subject), computeArea(subject)); + } + + /** + * Determines the graphical area covered by the given text region. + * + * @param region the region whose graphical extend must be computed + * @return the graphical extend of the given region + */ + private Rectangle computeArea(IRegion region) { + + StyledText styledText= fTextViewer.getTextWidget(); + + IRegion visible= fTextViewer.getVisibleRegion(); + int start= region.getOffset() - visible.getOffset(); + int end= start + region.getLength(); + + Point upperLeft= styledText.getLocationAtOffset(start); + Point lowerRight= new Point(upperLeft.x, upperLeft.y); + + for (int i= start +1; i < end; i++) { + + Point p= styledText.getLocationAtOffset(i); + + if (upperLeft.x > p.x) + upperLeft.x= p.x; + + if (upperLeft.y > p.y) + upperLeft.y= p.y; + + if (lowerRight.x < p.x) + lowerRight.x= p.x; + + if (lowerRight.y < p.y) + lowerRight.y= p.y; + } + + GC gc= new GC(styledText); + lowerRight.x += gc.getFontMetrics().getAverageCharWidth(); + lowerRight.y += styledText.getLineHeight(); + gc.dispose(); + + int width= lowerRight.x - upperLeft.x; + int height= lowerRight.y - upperLeft.y; + return new Rectangle(upperLeft.x, upperLeft.y, width, height); + } + + /* + * @see IInformationPresenter#install(ITextViewer) + */ + public void install(ITextViewer textViewer) { + fTextViewer= textViewer; + install(fTextViewer.getTextWidget()); + } + + /* + * @see IInformationPresenter#uninstall() + */ + public void uninstall() { + dispose(); + } + + /* + * @see AbstractInformationControlManager#showInformationControl(Rectangle) + */ + protected void showInformationControl(Rectangle subjectArea) { + if (fTextViewer instanceof IWidgetTokenOwner) { + IWidgetTokenOwner owner= (IWidgetTokenOwner) fTextViewer; + if (owner.requestWidgetToken(this)) + super.showInformationControl(subjectArea); + } + } + + /* + * @see AbstractInformationControlManager#hideInformationControl() + */ + protected void hideInformationControl() { + try { + super.hideInformationControl(); + } finally { + if (fTextViewer instanceof IWidgetTokenOwner) { + IWidgetTokenOwner owner= (IWidgetTokenOwner) fTextViewer; + owner.releaseWidgetToken(this); + } + } + } + + /* + * @see AbstractInformationControlManager#handleInformationControlDisposed() + */ + protected void handleInformationControlDisposed() { + try { + super.handleInformationControlDisposed(); + } finally { + if (fTextViewer instanceof IWidgetTokenOwner) { + IWidgetTokenOwner owner= (IWidgetTokenOwner) fTextViewer; + owner.releaseWidgetToken(this); + } + } + } + + /* + * @see org.eclipse.jface.text.IWidgetTokenKeeper#requestWidgetToken(IWidgetTokenOwner) + */ + public boolean requestWidgetToken(IWidgetTokenOwner owner) { + return false; + } +} + diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/information/package.html b/org.eclipse.jface.text/src/org/eclipse/jface/text/information/package.html new file mode 100644 index 000000000..2c20da020 --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/information/package.html @@ -0,0 +1,22 @@ +<!doctype html public "-//w3c//dtd html 4.0 transitional//en"> +<html> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> + <meta name="Author" content="IBM"> + <meta name="GENERATOR" content="Mozilla/4.76 [en] (Windows NT 5.0; U) [Netscape]"> + <title>Package-level Javadoc</title> +</head> +<body> +Provides an information provider add-on for an <tt>ITextViewer</tt>. +An information provider presents information for a certain subject in a +specific information control. An information control usually is a floating +window. +<h2> +Package Specification</h2> +<tt>IInformationPresenter</tt> defines the concept of an information provider. +It collaborates with content type specific information providers (<tt>IInformationProvider</tt>) +which determine for a certain offset in a text viewer an information subject +and the information available about this subject.. The package contains +a default implementation of <tt>IInformationPresenter</tt> (<tt>InformationPresenter</tt>). +</body> +</html> diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/package.html b/org.eclipse.jface.text/src/org/eclipse/jface/text/package.html new file mode 100644 index 000000000..5561c3748 --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/package.html @@ -0,0 +1,82 @@ +<!doctype html public "-//w3c//dtd html 4.0 transitional//en"> +<html> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> + <meta name="Author" content="IBM"> + <meta name="GENERATOR" content="Mozilla/4.51 [en] (WinNT; I) [Netscape]"> + <title>Package-level Javadoc</title> +</head> +<body> +Provides a framework for creating, manipulating, displaying +and editing text documents. +<h2> +Package Specification</h2> +The packages is divided into two parts. The first part defines and implements +the notion of text documents, whereas the second part defines and implements +a viewer for text documents. +<h3> +Text Model</h3> +<tt>IDocument</tt> is the major text model abstraction. It provides content +management, position management using position categories, document partition +management, searching, and change notification. In order to be notified +about document changes, an object must implements <tt>IDocumentListener</tt> +and must be registered with the document. Position updating in responds +to a document change is performed by implementers of <tt>IDocumentPositionUpdater</tt>. +Partition updating in responds to a document change is performed by implements +of <tt>IDocumentPartitioner</tt>. In order to be notified about document +partition changes, objects must implement <tt>IDocumentParititoningListener</tt> +and must be registered with the document. +<p>The package contains default implementations for document position updaters +and for documents. <tt>AbstractDocument</tt> uses <tt>ITextStorage</tt> +for storing and managing its content and <tt>ILineTracker</tt> to maintain +a line structure of its content. As defaults a gap text implementation +of <tt>ITextStore</tt> is provided, together with a line tracker understanding +the three standard line delimiters ("\r", "\n", "\r\n") and a line +tracker which can be freely configured to consider any given set of strings +as valid line delimiters. +<h3> +Text Viewer</h3> +<tt>ITextViewer</tt> defines the concept of a document based, editiable +viewer. <tt>ITextViewer</tt> offers the following functionality: +<ul> +<li> +present a document</li> + +<li> +event consumption (<tt>IEventConsumer</tt>)</li> + +<li> +viewport tracking and notification (<tt>IIViewportListener</tt>)</li> + +<li> +change notification (<tt>ITextListener</tt>, <tt>ITextInputListener</tt>)</li> + +<li> +listeners (combined view/model notification, input document)</li> + +<li> +standard text editing functions plus text hover support</li> + +<li> +visual region support</li> +</ul> +An ITextViewer supports the following plugins +<ul> +<li> +<tt>IUndoManager</tt> for the undo/redo mechanism</li> + +<li> +<tt>IDoubleClickStrategy</tt> for partition type specific behavior on mouse +double click</li> + +<li> +<tt>IAutoIndentStrategy</tt> for content type specific behavior on +inserting a line break</li> + +<li> +<tt>ITextHover</tt> for content type specific behavior when overing over +text</li> +</ul> +The package provides default implementations for all these interfaces. +</body> +</html> diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/presentation/IPresentationDamager.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/presentation/IPresentationDamager.java new file mode 100644 index 000000000..3f2ba767b --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/presentation/IPresentationDamager.java @@ -0,0 +1,54 @@ +package org.eclipse.jface.text.presentation; + +/* + * (c) Copyright IBM Corp. 2000, 2001. + * All Rights Reserved. + */ + + +import org.eclipse.jface.text.DocumentEvent; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.ITypedRegion; + + +/** + * Presentation damagers are used by a presentation reconciler to determine the + * region of the document's presentation which must be rebuilt because of the + * occurrence of a document change. A presentation damager is assumed to be + * specific for a particular document content type. A presentation damager is + * expected to return a damage region which is a valid input for a presentation repairer. + * I.e. having access to the damage region only the repairer must be able to derive + * all the information neede to sucessfully repair this region.<p> + * This interface must either be implemented by clients or clients use the rule-based + * default implementation <code>RuleBasedDamagerRepairer</code>. Implementers should be + * registered with a presentation reconciler in order get involved in the reconciling + * process. + * + * @see IPresentationReconciler + * @see IDocument + * @see DocumentEvent + * @see IPresentationRepairer + */ +public interface IPresentationDamager { + + /** + * Tells the presentation damager on which document it will work. + * + * @param document the damager's working document + */ + void setDocument(IDocument document); + + /** + * Returns the damage in the document's presentation caused by the given document change. + * The damage is restricted to the specified partition for which the presentation damager is + * responsible. The damage may also depend on whether the document change also caused changes + * of the document's partitioning. + * + * @param partition the partition inside which the damage must be determined + * @param event the event describing the change whose damage must be determined + * @param documentPartitioningChange indicates whether the given change changed the document's partitioning + * @return the computed damage + */ + IRegion getDamageRegion(ITypedRegion partition, DocumentEvent event, boolean documentPartitioningChanged); +}
\ No newline at end of file diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/presentation/IPresentationReconciler.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/presentation/IPresentationReconciler.java new file mode 100644 index 000000000..962e4b3ea --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/presentation/IPresentationReconciler.java @@ -0,0 +1,70 @@ +package org.eclipse.jface.text.presentation; + +/* + * (c) Copyright IBM Corp. 2000, 2001. + * All Rights Reserved. + */ + +import org.eclipse.jface.text.ITextViewer; + + +/** + * An <code>IPresentationReconciler</code> defines and maintains the representation of a + * text viewer's document in the presence of changes applied to the document. + * An <code>IPresentationReconciler</code> is a <code>ITextViewer</code> add-on.<p> + * The presentation reconciler keeps track of changes applied to the text viewer. It sends + * each change to presentation damagers which are registered for the content types of the + * regions in which the change occurred. The presentation reconciler passes the computed + * damage to presentation repairers which construct text presentations. Those text presentation + * when applied to the presentation reconciler's text viewer bring the document's presentation + * in sync with the document's content and thus repair the damage. A presentation damager + * is expected to return damage which is a valid input for a presentation repairer registered + * for the same content type as the damager.<p> + * A presentation reconciler should always be configured with damager/repairer pairs. I.e. + * for each damager there should be a corresponding repairer.<p> + * The interface can be implemented by clients. By default, clients use + * <code>PresentationReconciler</code> as the standard implementer of this interface. + * + * @see ITextViewer + * @see IPresentationDamager + * @see IPresentationRepairer + * @see org.eclipse.jface.text.TextPresentation + */ +public interface IPresentationReconciler { + + /** + * Installs this presentation reconciler on the given text viewer. After + * this method has been finished, the reconciler is operational. I.e., it + * works without requesting further client actions until <code>uninstall</code> + * is called. + * + * @param textViewer the viewer on which this presentation reconciler is installed + */ + void install(ITextViewer viewer); + + /** + * Removes the reconciler from the text viewer it has previously been + * installed on. + */ + void uninstall(); + + /** + * Returns the presentation damager registered with this presentation reconciler + * for the specified content type. + * + * @param contentType the content type for which to determine the damager + * @return the presentation damager registered for the given content type, or + * <code>null</code> if there is no such strategy + */ + IPresentationDamager getDamager(String contentType); + + /** + * Returns the presentation repairer registered with this presentation reconciler + * for the specified content type. + * + * @param contentType the content type for which to determine the repairer + * @return the presentation repairer registered for the given content type, or + * <code>null</code> if there is no such strategy + */ + IPresentationRepairer getRepairer(String contentType); +} diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/presentation/IPresentationRepairer.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/presentation/IPresentationRepairer.java new file mode 100644 index 000000000..8217dcf95 --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/presentation/IPresentationRepairer.java @@ -0,0 +1,50 @@ +package org.eclipse.jface.text.presentation; + +/* + * (c) Copyright IBM Corp. 2000, 2001. + * All Rights Reserved. + */ + + +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.ITypedRegion; +import org.eclipse.jface.text.TextPresentation; + + +/** + * Presentation repairers are used by a presentation reconciler + * to rebuild a damaged region in a document's presentation. A presentation repairer + * is assumed to be specific for a particular document content type. The presentation + * repairer gets the region which it should repair and constructs a "repair description" + * The presentation repairer adds the individual steps of this sequence into the + * text presentation it gets passed in.<p> + * This interface must either be implemented by clients or clients use the rule-based + * default implementation <code>RuleBasedDamagerRepairer</code>. Implementers should be + * registered with a presentation reconciler in order get involved in the reconciling + * process. + * + * @see IPresentationReconciler + * @see IDocument + * @see org.eclipse.swt.custom.StyleRange + * @see TextPresentation + */ +public interface IPresentationRepairer { + + + /** + * Tells the presentation repairer on which document it will work. + * + * @param document the damager's working document + */ + void setDocument(IDocument document); + + /** + * Fills the given presentation with the style ranges which when applied to the + * presentation reconciler's text viewer repair the presentational damage described by + * the given region. + * + * @param presentation the text presentation to be filled by this repairer + * @param damage the damage to be repaired + */ + void createPresentation(TextPresentation presentation, ITypedRegion damage); +} diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/presentation/PresentationReconciler.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/presentation/PresentationReconciler.java new file mode 100644 index 000000000..6981d4285 --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/presentation/PresentationReconciler.java @@ -0,0 +1,465 @@ +/********************************************************************** +Copyright (c) 2000, 2002 IBM Corp. and others. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Common Public License v1.0 +which accompanies this distribution, and is available at +http://www.eclipse.org/legal/cpl-v10.html + +Contributors: + IBM Corporation - Initial implementation +**********************************************************************/ + +package org.eclipse.jface.text.presentation; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.BadPositionCategoryException; +import org.eclipse.jface.text.DefaultPositionUpdater; +import org.eclipse.jface.text.DocumentEvent; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IDocumentListener; +import org.eclipse.jface.text.IDocumentPartitioningListener; +import org.eclipse.jface.text.IDocumentPartitioningListenerExtension; +import org.eclipse.jface.text.IPositionUpdater; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.ITextInputListener; +import org.eclipse.jface.text.ITextListener; +import org.eclipse.jface.text.ITextViewer; +import org.eclipse.jface.text.ITypedRegion; +import org.eclipse.jface.text.Region; +import org.eclipse.jface.text.TextEvent; +import org.eclipse.jface.text.TextPresentation; +import org.eclipse.jface.text.TypedPosition; +import org.eclipse.jface.util.Assert; + + + +/** + * Standard implementation of <code>IPresentationReconciler</code>. + * This implementation assumes that the tasks performed by its + * presentation damagers and repairers are lightweight and of low cost. + * This presentation reconciler runs in the UI thread and always repairs + * the complete damage caused by a document change rather than just the + * portion overlapping with the viewer's viewport.<p> + * Usually, clients instantiate this class and configure it before using it. + */ +public class PresentationReconciler implements IPresentationReconciler { + + /** Prefix of the name of the position category for tracking damage regions. */ + protected final static String TRACKED_PARTITION= "__reconciler_tracked_partition"; //$NON-NLS-1$ + + + /** + * Internal listener class. + */ + class InternalListener implements + ITextInputListener, IDocumentListener, ITextListener, + IDocumentPartitioningListener, IDocumentPartitioningListenerExtension { + + /** Set to <code>true</code> if between a document about to be changed and a changed event. */ + private boolean fDocumentChanging= false; + + /* + * @see ITextInputListener#inputDocumentAboutToBeChanged + */ + public void inputDocumentAboutToBeChanged(IDocument oldDocument, IDocument newDocument) { + if (oldDocument != null) { + try { + + fViewer.removeTextListener(this); + oldDocument.removeDocumentListener(this); + oldDocument.removeDocumentPartitioningListener(this); + + oldDocument.removePositionUpdater(fPositionUpdater); + oldDocument.removePositionCategory(fPositionCategory); + + } catch (BadPositionCategoryException x) { + // should not happend for former input documents; + } + } + } + + /* + * @see ITextInputListener#inputDocumenChanged + */ + public void inputDocumentChanged(IDocument oldDocument, IDocument newDocument) { + + fDocumentChanging= false; + + if (newDocument != null) { + + newDocument.addPositionCategory(fPositionCategory); + newDocument.addPositionUpdater(fPositionUpdater); + + newDocument.addDocumentPartitioningListener(this); + newDocument.addDocumentListener(this); + fViewer.addTextListener(this); + + setDocumentToDamagers(newDocument); + setDocumentToRepairers(newDocument); + processDamage(new Region(0, newDocument.getLength()), newDocument); + } + } + + /* + * @see IDocumentPartitioningListener#documentPartitioningChanged + */ + public void documentPartitioningChanged(IDocument document) { + if (!fDocumentChanging) + processDamage(new Region(0, document.getLength()), document); + else + fDocumentPartitioningChanged= true; + } + + /* + * @see IDocumentPartitioningListenerExtension#documentPartitioningChanged + * @since 2.0 + */ + public void documentPartitioningChanged(IDocument document, IRegion changedRegion) { + if (!fDocumentChanging) { + processDamage(new Region(changedRegion.getOffset(), changedRegion.getLength()), document); + } else { + fDocumentPartitioningChanged= true; + fChangedDocumentPartitions= changedRegion; + } + } + + /* + * @see IDocumentListener#documentAboutToBeChanged + */ + public void documentAboutToBeChanged(DocumentEvent e) { + + fDocumentChanging= true; + + try { + int offset= e.getOffset() + e.getLength(); + fRememberedPosition= new TypedPosition(e.getDocument().getPartition(offset)); + e.getDocument().addPosition(fPositionCategory, fRememberedPosition); + } catch (BadLocationException x) { + // can not happen + } catch (BadPositionCategoryException x) { + // should not happen on input elements + } + } + + /* + * @see IDocumentListener#documentChanged + */ + public void documentChanged(DocumentEvent e) { + try { + e.getDocument().removePosition(fPositionCategory, fRememberedPosition); + } catch (BadPositionCategoryException x) { + // can not happen on input documents + } + + fDocumentChanging= false; + } + + /* + * @see ITextListener#textChanged + */ + public void textChanged(TextEvent e) { + + if (!e.getViewerRedrawState()) + return; + + DocumentEvent de= e.getDocumentEvent(); + + if (de == null) { + + IDocument d= fViewer.getDocument(); + processDamage(new Region(0, d.getLength()), d); + + } else { + + IRegion damage= getDamage(de); + if (damage != null) + processDamage(damage, de.getDocument()); + } + + fDocumentPartitioningChanged= false; + fChangedDocumentPartitions= null; + } + }; + + /** The map of presentation damagers. */ + private Map fDamagers; + /** The map of presentation repairers. */ + private Map fRepairers; + /** The target viewer. */ + private ITextViewer fViewer; + /** The internal listener. */ + private InternalListener fInternalListener= new InternalListener(); + /** The name of the position category to track damage regions. */ + private String fPositionCategory; + /** The position updated for the damage regions' position category. */ + private IPositionUpdater fPositionUpdater; + /** The positions representing the damage regions. */ + private TypedPosition fRememberedPosition; + /** Flag indicating the receipt of a partitioning changed notification. */ + private boolean fDocumentPartitioningChanged= false; + /** The range covering the changed parititoning. */ + private IRegion fChangedDocumentPartitions= null; + + + /** + * Creates a new presentation reconciler. There are no damagers or repairers + * registered with this reconciler. + */ + public PresentationReconciler() { + super(); + fPositionCategory= TRACKED_PARTITION + hashCode(); + fPositionUpdater= new DefaultPositionUpdater(fPositionCategory); + } + + /** + * Registers a given presentation damager for a particular content type. + * If there is already a damager registered for this type, the new damager + * is registered instead of the old one. + * + * @param damager the presentation damager to register, or <code>null</code> to remove an existing one + * @param contentType the content type under which to register + */ + public void setDamager(IPresentationDamager damager, String contentType) { + + Assert.isNotNull(contentType); + + if (fDamagers == null) + fDamagers= new HashMap(); + + if (damager == null) + fDamagers.remove(contentType); + else + fDamagers.put(contentType, damager); + } + + /** + * Registers a given presentation repairer for a particular content type. + * If there is already a repairer registered for this type, the new repairer + * is registered instead of the old one. + * + * @param repairer the presentation repairer to register, or <code>null</code> to remove an existing one + * @param contentType the content type under which to register + */ + public void setRepairer(IPresentationRepairer repairer, String contentType) { + + Assert.isNotNull(contentType); + + if (fRepairers == null) + fRepairers= new HashMap(); + + if (repairer == null) + fRepairers.remove(contentType); + else + fRepairers.put(contentType, repairer); + } + + /* + * @see IPresentationReconciler#install + */ + public void install(ITextViewer viewer) { + Assert.isNotNull(viewer); + + fViewer= viewer; + fViewer.addTextInputListener(fInternalListener); + } + + /* + * @see IPresentationReconciler#uninstall + */ + public void uninstall() { + fViewer.removeTextInputListener(fInternalListener); + } + + /* + * @see IPresentationReconciler#getDamager + */ + public IPresentationDamager getDamager(String contentType) { + + if (fDamagers == null) + return null; + + return (IPresentationDamager) fDamagers.get(contentType); + } + + /* + * @see IPresentationReconciler#getRepairer + */ + public IPresentationRepairer getRepairer(String contentType) { + + if (fRepairers == null) + return null; + + return (IPresentationRepairer) fRepairers.get(contentType); + } + + /** + * Informs all registed damagers about the document on which they will work. + * + * @param document the document on which to work + */ + private void setDocumentToDamagers(IDocument document) { + if (fDamagers != null) { + Iterator e= fDamagers.values().iterator(); + while (e.hasNext()) { + IPresentationDamager damager= (IPresentationDamager) e.next(); + damager.setDocument(document); + } + } + } + + /** + * Informs all registed repairers about the document on which they will work. + * + * @param document the document on which to work + */ + private void setDocumentToRepairers(IDocument document) { + if (fRepairers != null) { + Iterator e= fRepairers.values().iterator(); + while (e.hasNext()) { + IPresentationRepairer repairer= (IPresentationRepairer) e.next(); + repairer.setDocument(document); + } + } + } + + /** + * Constructs a "repair description" for the given damage and returns + * this description as a text presentation. For this, it queries the + * partitioning of the damage region and asks for each partition an + * appropriate presentation repairer to construct the "repair description" + * for this partition. + * + * @param damage the damage to be repaired + * @param document the document whose presentation must be repaired + * @return the presentation repair descritption as text presentation + */ + private TextPresentation createPresentation(IRegion damage, IDocument document) { + try { + + TextPresentation presentation= new TextPresentation(); + + ITypedRegion[] partitioning= document.computePartitioning(damage.getOffset(), damage.getLength()); + for (int i= 0; i < partitioning.length; i++) { + ITypedRegion r= partitioning[i]; + IPresentationRepairer repairer= getRepairer(r.getType()); + if (repairer != null) + repairer.createPresentation(presentation, r); + } + + return presentation; + + } catch (BadLocationException x) { + } + + return null; + } + + + /** + * Checks for the first and the last affected partition and calls their damagers. + * Invalidates everything from the start of the damage for the first partition + * until the end of the damage for the last partition. + * + * @param e the event describing the document change + * @return the damaged caused by the change + */ + private IRegion getDamage(DocumentEvent e) { + + IRegion damage= null; + + try { + + ITypedRegion partition= e.getDocument().getPartition(e.getOffset()); + IPresentationDamager damager= getDamager(partition.getType()); + if (damager == null) + return null; + + IRegion r= damager.getDamageRegion(partition, e, fDocumentPartitioningChanged); + + if (!fDocumentPartitioningChanged) { + damage= r; + } else { + + int damageEnd= getDamageEndOffset(e); + + int parititionDamageEnd= -1; + if (fChangedDocumentPartitions != null) + parititionDamageEnd= fChangedDocumentPartitions.getOffset() + fChangedDocumentPartitions.getLength(); + + int end= Math.max(damageEnd, parititionDamageEnd); + + damage= end == -1 ? r : new Region(r.getOffset(), end - r.getOffset()); + } + + } catch (BadLocationException x) { + } + + return damage; + } + + /** + * Returns the end offset of the damage. If a partition has been splitted by + * the given document event also the second half of the original + * partition must be considered. This is achieved by using the remembered + * partition range. + * + * @param e the event describing the change + * @return the damage end offset (excluding) + * @exception BadLocationException if method accesses invalid offset + */ + private int getDamageEndOffset(DocumentEvent e) throws BadLocationException { + + IDocument d= e.getDocument(); + + int length= 0; + if (e.getText() != null) { + length= e.getText().length(); + if (length > 0) + -- length; + } + + ITypedRegion partition= d.getPartition(e.getOffset() + length); + int endOffset= partition.getOffset() + partition.getLength(); + if (endOffset == e.getOffset()) + return -1; + + int end= fRememberedPosition.getOffset() + fRememberedPosition.getLength(); + if (endOffset < end) + partition= d.getPartition(end); + + IPresentationDamager damager= getDamager(partition.getType()); + if (damager == null) + return -1; + + IRegion r= damager.getDamageRegion(partition, e, fDocumentPartitioningChanged); + + return r.getOffset() + r.getLength(); + } + + /** + * Processes the given damage. + * @param damage the damage to be repaired + * @param document the document whose presentation must be repaired + */ + private void processDamage(IRegion damage, IDocument document) { + if (damage != null && damage.getLength() > 0) { + TextPresentation p= createPresentation(damage, document); + if (p != null && !p.isEmpty()) + applyTextRegionCollection(p); + } + } + + /** + * Applies the given text presentation to the text viewer the presentation + * reconciler is installed on. + * + * @param presentation the text presentation to be applied to the text viewer + */ + private void applyTextRegionCollection(TextPresentation presentation) { + fViewer.changeTextPresentation(presentation, false); + } +} diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/presentation/package.html b/org.eclipse.jface.text/src/org/eclipse/jface/text/presentation/package.html new file mode 100644 index 000000000..9574694de --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/presentation/package.html @@ -0,0 +1,24 @@ +<!doctype html public "-//w3c//dtd html 4.0 transitional//en"> +<html> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> + <meta name="Author" content="IBM"> + <meta name="GENERATOR" content="Mozilla/4.51 [en] (WinNT; I) [Netscape]"> + <title>Package-level Javadoc</title> +</head> +<body> +Provides a presentation reconciler add-on for an <tt>ITextViewer</tt>. +A presentation reconciler keep the presentation (styles and colors) in +sync with the content of the document serving as the <tt>ITextViewer</tt>'s +input. +<h2> +Package Specification</h2> +<tt>IPresentationReconciler</tt> defines the concept of a presentation +reconciler. It collaborates with content type specific presentation damagers +(<tt>IPresentationDamager</tt>) which for a given document change determine +the region of the presentation which must be rebuild, and content type +specific presentation repairers (<tt>IPresentationRepairer</tt>) which +construct for a given damage region the document presentation. The package +contains a default implementation of <tt>IPresentationReconciler </tt>(<tt>PresentationReconciler</tt>). +</body> +</html> diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/reconciler/AbstractReconciler.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/reconciler/AbstractReconciler.java new file mode 100644 index 000000000..36c16479c --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/reconciler/AbstractReconciler.java @@ -0,0 +1,468 @@ +/********************************************************************** +Copyright (c) 2000, 2002 IBM Corp. and others. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Common Public License v1.0 +which accompanies this distribution, and is available at +http://www.eclipse.org/legal/cpl-v10.html + +Contributors: + IBM Corporation - Initial implementation +**********************************************************************/ + +package org.eclipse.jface.text.reconciler; + + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import org.eclipse.swt.widgets.Listener; + +import org.eclipse.core.runtime.IProgressMonitor; + +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.DocumentEvent; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IDocumentListener; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.ITextInputListener; +import org.eclipse.jface.text.ITextViewer; +import org.eclipse.jface.text.ITypedRegion; +import org.eclipse.jface.text.Region; +import org.eclipse.jface.text.TypedRegion; +import org.eclipse.jface.util.Assert; + + + +/** + * Abstract implementation of <code>IReconciler</code>. The reconciler + * listens to input document changes as well as changes of + * the input document of the text viewer it is installed on. Depending on + * its configuration it manages the received change notifications in a + * queue folding neighboring or overlapping changes together. The reconciler + * processes the dirty regions as a background activity after having waited for further + * changes for the configured duration of time. A reconciler is started using its + * <code>install</code> method. As a first step <code>initialProcess</code> is + * executed in the background. Then, the reconciling thread waits for changes that + * need to be reconciled. A reconciler can be resumed by calling <code>forceReconciling</code> + * independent from the existence of actual changes. This mechanism is for subclasses only. + * It is the clients responsibility to stop a reconciler using its <code>uninstall</code> + * method. Unstopped reconcilers do not free their resources.<p> + * It is subclass responsibility to specify how dirty regions are processed. + * + * @see IReconciler + * @see IDocumentListener + * @see ITextInputListener + * @see DirtyRegion + * @since 2.0 + */ +abstract public class AbstractReconciler implements IReconciler { + + + /** + * Background thread for the reconciling activity. + */ + class BackgroundThread extends Thread { + + /** Has the reconciler been canceled */ + private boolean fCanceled= false; + /** Has the reconciler been reset */ + private boolean fReset= false; + /** Has a change been applied */ + private boolean fIsDirty= false; + /** Is a reconciling strategy active */ + private boolean fIsActive= false; + + /** + * Creates a new background thread. The thread + * runs with minimal priority. + * + * @param name the thread's name + */ + public BackgroundThread(String name) { + super(name); + setPriority(Thread.MIN_PRIORITY); + setDaemon(true); + } + + /** + * Returns whether a reconciling strategy is active right now. + * + * @return <code>true</code> if a activity is active + */ + public boolean isActive() { + return fIsActive; + } + + /** + * Cancels the background thread. + */ + public void cancel() { + fCanceled= true; + synchronized (fDirtyRegionQueue) { + fDirtyRegionQueue.notifyAll(); + } + } + + /** + * Reset the background thread as the text viewer has been changed, + */ + public void reset() { + + if (fDelay > 0) { + + synchronized (this) { + fIsDirty= true; + fReset= true; + } + + } else { + + synchronized(this) { + fIsDirty= true; + } + + synchronized (fDirtyRegionQueue) { + fDirtyRegionQueue.notifyAll(); + } + } + + // http://bugs.eclipse.org/bugs/show_bug.cgi?id=19525 + reconcilerReset(); + + } + + /** + * The background activity. Waits until there is something in the + * queue managing the changes that have been applied to the text viewer. + * Removes the first change from the queue and process it.<p> + * Calls <code>initialProcess</code> on entrance. + */ + public void run() { + + synchronized (fDirtyRegionQueue) { + try { + fDirtyRegionQueue.wait(fDelay); + } catch (InterruptedException x) { + } + } + + initialProcess(); + + while (!fCanceled) { + + synchronized (fDirtyRegionQueue) { + try { + fDirtyRegionQueue.wait(fDelay); + } catch (InterruptedException x) { + } + } + + if (fCanceled) + break; + + if (!fIsDirty) + continue; + + if (fReset) { + synchronized (this) { + fReset= false; + } + continue; + } + + DirtyRegion r= null; + synchronized (fDirtyRegionQueue) { + r= fDirtyRegionQueue.removeNextDirtyRegion(); + } + + fIsActive= true; + + if (fProgressMonitor != null) + fProgressMonitor.setCanceled(false); + + process(r); + + synchronized (this) { + fIsDirty= false; + } + + fIsActive= false; + } + } + }; + + /** + * Internal document listener and text input listener. + */ + class Listener implements IDocumentListener, ITextInputListener { + + /* + * @see IDocumentListener#documentAboutToBeChanged + */ + public void documentAboutToBeChanged(DocumentEvent e) { + } + + /* + * @see IDocumentListener#documentChanged + */ + public void documentChanged(DocumentEvent e) { + + if (fProgressMonitor != null && fThread.isActive()) + fProgressMonitor.setCanceled(true); + + if (fIsIncrementalReconciler) + createDirtyRegion(e); + + fThread.reset(); + } + + /* + * @see ITextInputListener#inputDocumentAboutToBeChanged + */ + public void inputDocumentAboutToBeChanged(IDocument oldInput, IDocument newInput) { + + if (oldInput == fDocument) { + + if (fDocument != null) + fDocument.removeDocumentListener(this); + + if (fIsIncrementalReconciler) { + fDirtyRegionQueue.purgeQueue(); + if (fDocument != null && fDocument.getLength() > 0) { + DocumentEvent e= new DocumentEvent(fDocument, 0, fDocument.getLength(), null); + createDirtyRegion(e); + } + } + + fDocument= null; + } + } + + /* + * @see ITextInputListener#inputDocumentChanged + */ + public void inputDocumentChanged(IDocument oldInput, IDocument newInput) { + + if (newInput == null) + return; + + fDocument= newInput; + reconcilerDocumentChanged(fDocument); + + fDocument.addDocumentListener(this); + + forceReconciling(); + } + }; + + /** Queue to manage the changes applied to the text viewer */ + private DirtyRegionQueue fDirtyRegionQueue; + /** The background thread */ + private BackgroundThread fThread; + /** Internal document and text input listener */ + private Listener fListener; + /** The background thread delay */ + private int fDelay= 500; + /** Are there incremental reconciling strategies? */ + private boolean fIsIncrementalReconciler= true; + /** The progress monitor used by this reconciler */ + private IProgressMonitor fProgressMonitor; + + /** The text viewer's document */ + private IDocument fDocument; + /** The text viewer */ + private ITextViewer fViewer; + + + /** + * Processes a dirty region. If the dirty region is <code>null</code> the whole + * document is consider being dirty. The dirty region is partitioned by the + * document and each partition is handed over to a reconciling strategy registered + * for the partition's content type. + * + * @param dirtyRegion the dirty region to be processed + */ + abstract protected void process(DirtyRegion dirtyRegion); + + /** + * Hook called when the document whose contents should be reconciled + * has been changed, i.e., the input document of the text viewer this + * reconciler is installed on. Usually, subclasses use this hook to + * inform all their reconciling strategies about the change. + * + * @param newDocument the new reconciler document + */ + abstract protected void reconcilerDocumentChanged(IDocument newDocument); + + + /** + * Creates a new reconciler without configuring it. + */ + protected AbstractReconciler() { + super(); + } + + /** + * Tells the reconciler how long it should wait for further text changes before + * activating the appropriate reconciling strategies. + * + * @param delay the duration in milli seconds of a change collection period. + */ + public void setDelay(int delay) { + fDelay= delay; + } + + /** + * Tells the reconciler whether any of the available reconciling strategies + * is interested in getting detailed dirty region information or just in the + * fact the the document has been changed. In the second case, the reconciling + * can not incrementally be pursued. + * + * @param isIncremental indicates whether this reconciler will be configured with + * incremental reconciling strategies + * + * @see DirtyRegion + * @see IReconcilingStrategy + */ + public void setIsIncrementalReconciler(boolean isIncremental) { + fIsIncrementalReconciler= isIncremental; + } + + /** + * Sets the progress monitor of this reconciler. + * + * @param monitor the monitor to be used + */ + public void setProgressMonitor(IProgressMonitor monitor) { + fProgressMonitor= monitor; + } + + /** + * Returns whether any of the reconciling strategies is interested in + * detailed dirty region information. + * + * @return whether this reconciler is incremental + * + * @see IReconcilingStrategy + */ + protected boolean isIncrementalReconciler() { + return fIsIncrementalReconciler; + } + + /** + * Returns the input document of the text viewer this reconciler is installed on. + * + * @return the reconciler document + */ + protected IDocument getDocument() { + return fDocument; + } + + /** + * Returns the text viewer this reconciler is installed on. + * + * @return the text viewer this reconciler is installed on + */ + protected ITextViewer getTextViewer() { + return fViewer; + } + + /** + * Returns the progress monitor of this reconciler. + * + * @return the progress monitor of this reconciler + */ + protected IProgressMonitor getProgressMonitor() { + return fProgressMonitor; + } + + /* + * @see IReconciler#install + */ + public void install(ITextViewer textViewer) { + + Assert.isNotNull(textViewer); + + fViewer= textViewer; + + fListener= new Listener(); + fViewer.addTextInputListener(fListener); + + fDirtyRegionQueue= new DirtyRegionQueue(); + fThread= new BackgroundThread(getClass().getName()); + } + + /* + * @see IReconciler#uninstall + */ + public void uninstall() { + if (fListener != null) { + + fViewer.removeTextInputListener(fListener); + if (fDocument != null) fDocument.removeDocumentListener(fListener); + fListener= null; + + // http://dev.eclipse.org/bugs/show_bug.cgi?id=19135 + BackgroundThread bt= fThread; + fThread= null; + bt.cancel(); + } + } + + /** + * Creates a dirty region for a document event and adds it to the queue. + * + * @param e the document event for which to create a dirty region + */ + private void createDirtyRegion(DocumentEvent e) { + + if (e.getLength() == 0 && e.getText() != null) { + // Insert + fDirtyRegionQueue.addDirtyRegion(new DirtyRegion(e.getOffset(), e.getText().length(), DirtyRegion.INSERT, e.getText())); + + } else if (e.getText() == null || e.getText().length() == 0) { + // Remove + fDirtyRegionQueue.addDirtyRegion(new DirtyRegion(e.getOffset(), e.getLength(), DirtyRegion.REMOVE, null)); + + } else { + // Replace (Remove + Insert) + fDirtyRegionQueue.addDirtyRegion(new DirtyRegion(e.getOffset(), e.getLength(), DirtyRegion.REMOVE, null)); + fDirtyRegionQueue.addDirtyRegion(new DirtyRegion(e.getOffset(), e.getText().length(), DirtyRegion.INSERT, e.getText())); + } + } + + /** + * This method is called on startup of the background activity. It is called only + * once during the life time of the reconciler. Clients may reimplement this method. + */ + protected void initialProcess() { + } + + /** + * Forces the reconciler to reconcile the structure of the whole document. + * Clients may extend this method. + */ + protected void forceReconciling() { + + if (fIsIncrementalReconciler) { + DocumentEvent e= new DocumentEvent(fDocument, 0, 0, fDocument.get()); + createDirtyRegion(e); + } + + // http://dev.eclipse.org/bugs/show_bug.cgi?id=19135 + if (fThread == null) + return; + + if (!fThread.isAlive()) + fThread.start(); + else + fThread.reset(); + } + + /** + * Hook that is called after the reconciler thread has been reset. + */ + protected void reconcilerReset() { + } +} diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/reconciler/DirtyRegion.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/reconciler/DirtyRegion.java new file mode 100644 index 000000000..01c57a65a --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/reconciler/DirtyRegion.java @@ -0,0 +1,86 @@ +package org.eclipse.jface.text.reconciler; + +/* + * (c) Copyright IBM Corp. 2000, 2001. + * All Rights Reserved. + */ + +import org.eclipse.jface.text.ITypedRegion; + +/** + * A dirty region describes a document range which has been changed. + */ +public class DirtyRegion implements ITypedRegion { + + /** Definitions of edit types */ + final static public String INSERT= "__insert"; //$NON-NLS-1$ + final static public String REMOVE= "__remove"; //$NON-NLS-1$ + + /** The region's offset */ + private int fOffset; + /** The region's length */ + private int fLength; + /** Indicates the type of the applied change */ + private String fType; + /** The text which has been inserted */ + private String fText; + + /** + * Creates a new dirty region. + * + * + * @param offset the offset within the document where the change occurred + * @param length the length of the text within the document that changed + * @param type the type of change that this region represents: <code>INSERT</code> or <code>REMOVE</code> + * @param text the substitution text + */ + public DirtyRegion(int offset, int length, String type, String text) { + fOffset= offset; + fLength= length; + fType= type; + fText= text; + } + + /* + * @see ITypedRegion#getOffset() + */ + public int getOffset() { + return fOffset; + } + + /* + * @see ITypedRegion#getLength() + */ + public int getLength() { + return fLength; + } + + /* + * @see ITypedRegion#getType + */ + public String getType() { + return fType; + } + + /** + * Returns the text that changed as part of the region change. + * + * @return the changed text + */ + public String getText() { + return fText; + } + + /** + * Modify the receiver so that it encompasses the region specified by the dirty region. + * + * @param dr the dirty region with which to merge + */ + void mergeWith(DirtyRegion dr) { + int start= Math.min(fOffset, dr.fOffset); + int end= Math.max(fOffset + fLength, dr.fOffset + dr.fLength); + fOffset= start; + fLength= end - start; + fText= (dr.fText == null ? fText : (fText == null) ? dr.fText : fText + dr.fText); + } +} diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/reconciler/DirtyRegionQueue.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/reconciler/DirtyRegionQueue.java new file mode 100644 index 000000000..6e4e942ba --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/reconciler/DirtyRegionQueue.java @@ -0,0 +1,98 @@ +package org.eclipse.jface.text.reconciler; + +/* + * (c) Copyright IBM Corp. 2000, 2001. + * All Rights Reserved. + */ + +import java.util.List; +import java.util.ArrayList; + + +/** + * Queue used by <code>Reconciler</code> to manage dirty regions. When a dirty region + * is inserted into the queue, the queue tries to fold it into the neighboring dirty region. + * + * @see Reconciler + * @see DirtyRegion + */ +class DirtyRegionQueue { + + /** The list of dirty regions */ + private List fDirtyRegions= new ArrayList(); + + /** + * Creates a new empty dirty region. + */ + public DirtyRegionQueue() { + super(); + } + + /** + * Adds a dirty region to the end of the dirty-region queue. + * + * @param dr the dirty region to add + */ + public void addDirtyRegion(DirtyRegion dr) { + // If the dirty region being added is directly after the last dirty + // region on the queue then merge the two dirty regions together. + DirtyRegion lastDR= getLastDirtyRegion(); + boolean wasMerged= false; + if (lastDR != null) + if (lastDR.getType() == dr.getType()) + if (lastDR.getType() == DirtyRegion.INSERT) { + if (lastDR.getOffset() + lastDR.getLength() == dr.getOffset()) { + lastDR.mergeWith(dr); + wasMerged= true; + } + } else if (lastDR.getType() == DirtyRegion.REMOVE) { + if (dr.getOffset() + dr.getLength() == lastDR.getOffset()) { + lastDR.mergeWith(dr); + wasMerged= true; + } + } + + if (!wasMerged) + // Don't merge- just add the new one onto the queue. + fDirtyRegions.add(dr); + } + + /** + * Returns the last dirty region that was added to the queue. + * + * @return the last DirtyRegion on the queue + */ + private DirtyRegion getLastDirtyRegion() { + int size= fDirtyRegions.size(); + return (size == 0 ? null : (DirtyRegion) fDirtyRegions.get(size - 1)); + } + + /** + * Returns the number of regions in the queue. + * + * @return the dirty-region queue-size + */ + public int getSize() { + return fDirtyRegions.size(); + } + + /** + * Throws away all entries in the queue. + */ + public void purgeQueue() { + fDirtyRegions.clear(); + } + + /** + * Removes and returns the first dirty region in the queue + * + * @return the next dirty region on the queue + */ + public DirtyRegion removeNextDirtyRegion() { + if (fDirtyRegions.size() == 0) + return null; + DirtyRegion dr= (DirtyRegion) fDirtyRegions.get(0); + fDirtyRegions.remove(0); + return dr; + } +} diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/reconciler/IReconciler.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/reconciler/IReconciler.java new file mode 100644 index 000000000..fa3d8e530 --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/reconciler/IReconciler.java @@ -0,0 +1,56 @@ +package org.eclipse.jface.text.reconciler; + +/* + * (c) Copyright IBM Corp. 2000, 2001. + * All Rights Reserved. + */ + + +import org.eclipse.jface.text.ITextViewer; + + + +/** + * An <code>IReconciler</code> defines and maintains a model of the content + * of the text viewer's document in the presence of changes applied to this + * document. An <code>IReconciler</code> is a <code>ITextViewer</code> add-on.<p> + * Reconcilers are assumed to be asynchronous, i.e. they allow a certain + * temporal window of inconsistency between the document and the model of + * the content of this document. <p> + * Reconcilers have a list of <code>IReconcilingStrategy</code> objects + * each of which is registered for a particular document content type. + * The reconciler uses the strategy objects to react on the changes applied + * to the text viewer's document.<p> + * The interface can be implemented by clients. By default, clients use + * <code>Reconciler</code> as the standard implementer of this interface. + * + * @see ITextViewer + * @see IReconcilingStrategy + */ +public interface IReconciler { + + /** + * Installs the reconciler on the given text viewer. After this method has been + * finished, the reconciler is operational. I.e., it works without requesting + * further client actions until <code>uninstall</code> is called. + * + * @param textViewer the viewer on which the reconciler is installed + */ + void install(ITextViewer textViewer); + + /** + * Removes the reconciler from the text viewer it has previously been + * installed on. + */ + void uninstall(); + + /** + * Returns the reconciling strategy registered with the reconciler + * for the specified content type. + * + * @param contentType the content type for which to determine the reconciling strategy + * @return the reconciling strategy registered for the given content type, or + * <code>null</code> if there is no such strategy + */ + IReconcilingStrategy getReconcilingStrategy(String contentType); +} diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/reconciler/IReconcilingStrategy.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/reconciler/IReconcilingStrategy.java new file mode 100644 index 000000000..8292a23ae --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/reconciler/IReconcilingStrategy.java @@ -0,0 +1,54 @@ +package org.eclipse.jface.text.reconciler; + +/* + * (c) Copyright IBM Corp. 2000, 2001. + * All Rights Reserved. + */ + + +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IRegion; + + +/** + * A reconciling strategy is used by an reconciler to reconcile a model + * based on text of a particular content type. It provides methods for + * incremental as well as non-incremental reconciling.<p> + * This interface must be implemented by clients. Implementers should be + * registered with a reconciler in order get involved in the reconciling + * process. + */ +public interface IReconcilingStrategy { + + /** + * Tells this reconciling strategy on which document it will + * work. This method will be called before any other method + * and can be called multiple times. The regions passed to the + * other methods always refer to the most recent document + * passed into this method. + * + * @param document the document on which this strategy will work + */ + void setDocument(IDocument document); + + /** + * Activates incremental reconciling of the specified dirty region. + * As a dirty region might span multiple content types, the segment of the + * dirty region which should be investigated is also provided to this + * reconciling strategy. The given regions refer to the document passed into + * the most recent call of <code>setDocument</code>. + * + * @param dirtyRegion the document region which has been changed + * @param subRegion the sub region in the dirty region which should be reconciled + */ + void reconcile(DirtyRegion dirtyRegion, IRegion subRegion); + + /** + * Activates non-incremental reconciling. The reconciling strategy is just told + * that there are changes and that it should reconcile the given partition of the + * document most recently passed into <code>setDocument</code>. + * + * @param partition the document partition to be reconciled + */ + void reconcile(IRegion partition); +}
\ No newline at end of file diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/reconciler/IReconcilingStrategyExtension.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/reconciler/IReconcilingStrategyExtension.java new file mode 100644 index 000000000..971fb681e --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/reconciler/IReconcilingStrategyExtension.java @@ -0,0 +1,45 @@ +/********************************************************************** +Copyright (c) 2000, 2002 IBM Corp. and others. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Common Public License v1.0 +which accompanies this distribution, and is available at +http://www.eclipse.org/legal/cpl-v10.html + +Contributors: + IBM Corporation - Initial implementation +**********************************************************************/ + +package org.eclipse.jface.text.reconciler; + + +import org.eclipse.core.runtime.IProgressMonitor; + + +/** + * Extension interface for <code>IReconcilingStrategy</code>. + * The new functions are: + * <ul> + * <li> usage of a progress monitor + * <li> initial reconciling step: If a reconciler runs as periodic activity in the background, this + * methods offers the reconciler a chance for initializing its startegies and achieving a + * reconciled state before the periodic activity starts. + * </ul> + * + * @since 2.0 + */ +public interface IReconcilingStrategyExtension { + + /** + * Tells this reconciling strategy with which progress monitor + * it will work. This method will be called before any other + * method and can be called multiple times. + * + * @param monitor the progress monitor with which this strategy will work + */ + void setProgressMonitor(IProgressMonitor monitor); + + /** + * Called only once in the life time of this reconciling strategy. + */ + void initialReconcile(); +} diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/reconciler/MonoReconciler.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/reconciler/MonoReconciler.java new file mode 100644 index 000000000..2fd0a670b --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/reconciler/MonoReconciler.java @@ -0,0 +1,102 @@ +/********************************************************************** +Copyright (c) 2000, 2002 IBM Corp. and others. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Common Public License v1.0 +which accompanies this distribution, and is available at +http://www.eclipse.org/legal/cpl-v10.html + +Contributors: + IBM Corporation - Initial implementation +**********************************************************************/ + +package org.eclipse.jface.text.reconciler; + + +import org.eclipse.core.runtime.IProgressMonitor; + +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.Region; +import org.eclipse.jface.util.Assert; + +/** + * Standard implementation of <code>IReconciler</code>. The reconciler + * is configured with a single <code>IReconcilingStrategy</code> that is + * used independ from where a dirty region is located in the reconciler's + * document. <p> + * Usually, clients instantiate this class and configure it before using it. + * + * @see IReconciler + * @see org.eclipse.jface.text.IDocumentListener + * @see org.eclipse.jface.text.ITextInputListener + * @see DirtyRegion + * @since 2.0 + */ +public class MonoReconciler extends AbstractReconciler { +
+ /** The reconciling strategy */ + private IReconcilingStrategy fStrategy; + + + /** + * Creates a new reconciler that uses the same reconciling strategy to + * reconcile its document independent of the type of the document's contents. + * + * @param strategy the reconciling strategy to be used + * @param isIncremental the indication whether strategy is incremental or not + */ + public MonoReconciler(IReconcilingStrategy strategy, boolean isIncremental) { + super(); + + Assert.isNotNull(strategy); + + fStrategy= strategy; + setIsIncrementalReconciler(isIncremental); + } + + /* + * @see IReconciler#getReconcilingStrategy + */ + public IReconcilingStrategy getReconcilingStrategy(String contentType) { + Assert.isNotNull(contentType); + return fStrategy; + } + + /* + * @see AbstractReconciler#process(DirtyRegion) + */ + protected void process(DirtyRegion dirtyRegion) { + + if(dirtyRegion != null) + fStrategy.reconcile(dirtyRegion, dirtyRegion); + else + fStrategy.reconcile(new Region(0, getDocument().getLength())); + } + + /* + * @see AbstractReconciler#reconcilerDocumentChanged(IDocument) + */ + protected void reconcilerDocumentChanged(IDocument document) { + fStrategy.setDocument(document); + } + + /* + * @see AbstractReconciler#setProgressMonitor(IProgressMonitor) + */ + public void setProgressMonitor(IProgressMonitor monitor) { + super.setProgressMonitor(monitor); + if (fStrategy instanceof IReconcilingStrategyExtension) { + IReconcilingStrategyExtension extension= (IReconcilingStrategyExtension) fStrategy; + extension.setProgressMonitor(monitor); + } + } + + /* + * @see AbstractReconciler#initialProcess() + */ + protected void initialProcess() { + if (fStrategy instanceof IReconcilingStrategyExtension) { + IReconcilingStrategyExtension extension= (IReconcilingStrategyExtension) fStrategy; + extension.initialReconcile(); + } + } +} diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/reconciler/Reconciler.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/reconciler/Reconciler.java new file mode 100644 index 000000000..cd8a38580 --- /dev/null +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/reconciler/Reconciler.java @@ -0,0 +1,185 @@ +/********************************************************************** +Copyright (c) 2000, 2002 IBM Corp. and others. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Common Public License v1.0 +which accompanies this distribution, and is available at +http://www.eclipse.org/legal/cpl-v10.html + +Contributors: + IBM Corporation - Initial implementation +**********************************************************************/ + +package org.eclipse.jface.text.reconciler; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import org.eclipse.core.runtime.IProgressMonitor; + +import org.eclipse.jface.text.BadLocat |