blob: 3b96efa606ab9528be6c30ef1974b5a295a82146 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2015 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jface.text.source;
import java.util.Iterator;
import org.eclipse.swt.SWT;
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.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.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.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.core.runtime.Assert;
import org.eclipse.jface.internal.text.InformationControlReplacer;
import org.eclipse.jface.internal.text.InternalAccessor;
import org.eclipse.jface.text.AbstractHoverInformationControlManager;
import org.eclipse.jface.text.AbstractInformationControlManager;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IInformationControl;
import org.eclipse.jface.text.IInformationControlCreator;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextViewerExtension5;
import org.eclipse.jface.text.ITextViewerExtension8.EnrichMode;
import org.eclipse.jface.text.JFaceTextUtil;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.TextUtilities;
/**
* This manager controls the layout, content, and visibility of an information
* control in reaction to mouse hover events issued by the vertical ruler of a
* source viewer.
* @since 2.0
*/
public class AnnotationBarHoverManager extends AbstractHoverInformationControlManager {
/**
* The information control closer for the hover information. Closes the information control as soon as the mouse pointer leaves the subject area, a mouse button is pressed, the user presses a key, or the subject control is resized or moved.
*
* @since 3.0
* @deprecated As of 3.4, no longer used as closer from super class is used
*/
@Deprecated
protected class Closer extends MouseTrackAdapter implements IInformationControlCloser, MouseListener, MouseMoveListener, ControlListener, KeyListener, DisposeListener, ShellListener, Listener {
/** The closer's subject control */
private Control fSubjectControl;
/** The subject area */
private Rectangle fSubjectArea;
/** Indicates whether this closer is active */
private boolean fIsActive= false;
/** The information control. */
private IInformationControl fInformationControlToClose;
/**
* <code>true</code> if a wheel handler is installed.
* @since 3.2
*/
private boolean fHasWheelFilter= false;
/**
* The cached display.
* @since 3.2
*/
private Display fDisplay;
/**
* Creates a new information control closer.
*/
public Closer() {
}
@Override
public void setSubjectControl(Control control) {
fSubjectControl= control;
}
/*
* @see IInformationControlCloser#setHoverControl(IHoverControl)
*/
@Override
public void setInformationControl(IInformationControl control) {
fInformationControlToClose= control;
}
@Override
public void start(Rectangle subjectArea) {
if (fIsActive) return;
fIsActive= true;
fSubjectArea= subjectArea;
fInformationControlToClose.addDisposeListener(this);
if (fSubjectControl != null && !fSubjectControl.isDisposed()) {
fSubjectControl.addMouseListener(this);
fSubjectControl.addMouseMoveListener(this);
fSubjectControl.addMouseTrackListener(this);
fSubjectControl.getShell().addShellListener(this);
fSubjectControl.addControlListener(this);
fSubjectControl.addKeyListener(this);
fDisplay= fSubjectControl.getDisplay();
if (!fDisplay.isDisposed() && fHideOnMouseWheel) {
fHasWheelFilter= true;
fDisplay.addFilter(SWT.MouseHorizontalWheel, this);
fDisplay.addFilter(SWT.MouseVerticalWheel, this);
}
}
}
@Override
public void stop() {
if (!fIsActive)
return;
fIsActive= false;
if (fSubjectControl != null && !fSubjectControl.isDisposed()) {
fSubjectControl.removeMouseListener(this);
fSubjectControl.removeMouseMoveListener(this);
fSubjectControl.removeMouseTrackListener(this);
fSubjectControl.getShell().removeShellListener(this);
fSubjectControl.removeControlListener(this);
fSubjectControl.removeKeyListener(this);
}
if (fDisplay != null && !fDisplay.isDisposed() && fHasWheelFilter) {
fDisplay.removeFilter(SWT.MouseHorizontalWheel, this);
fDisplay.removeFilter(SWT.MouseVerticalWheel, this);
}
fHasWheelFilter= false;
fDisplay= null;
}
/**
* 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
* @deprecated As of 3.4, replaced by {@link #stop()}. Note that <code>delayRestart</code> was never honored.
*/
@Deprecated
protected void stop(boolean delayRestart) {
stop();
}
@Override
public void mouseMove(MouseEvent event) {
if (!fSubjectArea.contains(event.x, event.y))
hideInformationControl();
}
@Override
public void mouseUp(MouseEvent event) {
}
@Override
public void mouseDown(MouseEvent event) {
hideInformationControl();
}
@Override
public void mouseDoubleClick(MouseEvent event) {
hideInformationControl();
}
@Override
public void handleEvent(Event event) {
if (event.type == SWT.MouseHorizontalWheel || event.type == SWT.MouseVerticalWheel)
hideInformationControl();
}
@Override
public void mouseExit(MouseEvent event) {
if (!fAllowMouseExit)
hideInformationControl();
}
@Override
public void controlResized(ControlEvent event) {
hideInformationControl();
}
@Override
public void controlMoved(ControlEvent event) {
hideInformationControl();
}
@Override
public void keyReleased(KeyEvent event) {
}
@Override
public void keyPressed(KeyEvent event) {
hideInformationControl();
}
@Override
public void shellActivated(ShellEvent e) {
}
@Override
public void shellClosed(ShellEvent e) {
}
@Override
public void shellDeactivated(ShellEvent e) {
hideInformationControl();
}
@Override
public void shellDeiconified(ShellEvent e) {
}
@Override
public void shellIconified(ShellEvent e) {
}
@Override
public void widgetDisposed(DisposeEvent e) {
hideInformationControl();
}
}
/** The source viewer the manager is connected to */
private ISourceViewer fSourceViewer;
/** The vertical ruler the manager is registered with */
private IVerticalRulerInfo fVerticalRulerInfo;
/** The annotation hover the manager uses to retrieve the information to display. Can be <code>null</code>. */
private IAnnotationHover fAnnotationHover;
/**
* Indicates whether the mouse cursor is allowed to leave the subject area without closing the hover.
* @since 3.0
*/
protected boolean fAllowMouseExit= false;
/**
* Whether we should hide the over on mouse wheel action.
*
* @since 3.2
*/
private boolean fHideOnMouseWheel= true;
/**
* The current annotation hover.
* @since 3.2
*/
private IAnnotationHover fCurrentHover;
/**
* Creates an annotation hover manager with the given parameters. In addition,
* the hovers anchor is RIGHT and the margin is 5 points to the right.
*
* @param sourceViewer the source viewer this manager connects to
* @param ruler the vertical ruler this manager connects to
* @param annotationHover the annotation hover providing the information to be displayed
* @param creator the information control creator
* @deprecated As of 2.1, replaced by {@link AnnotationBarHoverManager#AnnotationBarHoverManager(IVerticalRulerInfo, ISourceViewer, IAnnotationHover, IInformationControlCreator)}
*/
@Deprecated
public AnnotationBarHoverManager(ISourceViewer sourceViewer, IVerticalRuler ruler, IAnnotationHover annotationHover, IInformationControlCreator creator) {
this(ruler, sourceViewer, annotationHover, creator);
}
/**
* Creates an annotation hover manager with the given parameters. In addition,
* the hovers anchor is RIGHT and the margin is 5 points to the right.
*
* @param rulerInfo the vertical ruler this manager connects to
* @param sourceViewer the source viewer this manager connects to
* @param annotationHover the annotation hover providing the information to be displayed or <code>null</code> if none
* @param creator the information control creator
* @since 2.1
*/
public AnnotationBarHoverManager(IVerticalRulerInfo rulerInfo, ISourceViewer sourceViewer, IAnnotationHover annotationHover, IInformationControlCreator creator) {
super(creator);
Assert.isNotNull(sourceViewer);
fSourceViewer= sourceViewer;
fVerticalRulerInfo= rulerInfo;
fAnnotationHover= annotationHover;
setAnchor(ANCHOR_RIGHT);
setMargins(5, 0);
// use closer from super class
}
@Override
protected void computeInformation() {
fAllowMouseExit= false;
MouseEvent event= getHoverEvent();
if ((event.stateMask & SWT.BUTTON_MASK) != 0) {
setInformation(null, null);
return;
}
IAnnotationHover hover= getHover(event);
if (hover == null) {
setInformation(null, null);
return;
}
int line= getHoverLine(event);
if (hover instanceof IAnnotationHoverExtension) {
IAnnotationHoverExtension extension= (IAnnotationHoverExtension) hover;
ILineRange range= extension.getHoverLineRange(fSourceViewer, line);
setCustomInformationControlCreator(extension.getHoverControlCreator());
range= adaptLineRange(range, line);
if (range != null)
setInformation(extension.getHoverInfo(fSourceViewer, range, computeNumberOfVisibleLines()), computeArea(range));
else
setInformation(null, null);
} else {
setCustomInformationControlCreator(null);
setInformation(hover.getHoverInfo(fSourceViewer, line), computeArea(line));
}
}
@Override
protected void showInformationControl(Rectangle subjectArea) {
super.showInformationControl(subjectArea);
fCurrentHover= getHover(getHoverEvent());
}
@Override
protected void hideInformationControl() {
fCurrentHover= null;
super.hideInformationControl();
}
/**
* Adapts a given line range so that the result is a line range that does
* not overlap with any collapsed region and fits into the view port of the
* attached viewer.
*
* @param lineRange the original line range
* @param line the anchor line
* @return the adapted line range
* @since 3.0
*/
private ILineRange adaptLineRange(ILineRange lineRange, int line) {
if (lineRange != null) {
lineRange= adaptLineRangeToFolding(lineRange, line);
if (lineRange != null)
return adaptLineRangeToViewport(lineRange);
}
return null;
}
/**
* Adapts a given line range so that the result is a line range that does
* not overlap with any collapsed region of the attached viewer.
*
* @param lineRange the original line range
* @param line the anchor line
* @return the adapted line range
* @since 3.0
*/
private ILineRange adaptLineRangeToFolding(ILineRange lineRange, int line) {
if (fSourceViewer instanceof ITextViewerExtension5) {
ITextViewerExtension5 extension= (ITextViewerExtension5) fSourceViewer;
try {
IRegion region= convertToRegion(lineRange);
IRegion[] coverage= extension.getCoveredModelRanges(region);
if (coverage != null && coverage.length > 0) {
IRegion container= findRegionContainingLine(coverage, line);
if (container != null)
return convertToLineRange(container);
}
} catch (BadLocationException x) {
}
return null;
}
return lineRange;
}
/**
* Adapts a given line range so that the result is a line range that fits
* into the view port of the attached viewer.
*
* @param lineRange the original line range
* @return the adapted line range
* @since 3.0
*/
private ILineRange adaptLineRangeToViewport(ILineRange lineRange) {
try {
StyledText text= fSourceViewer.getTextWidget();
int topLine= text.getTopIndex();
int rangeTopLine= getWidgetLineNumber(lineRange.getStartLine());
int topDelta= Math.max(topLine - rangeTopLine, 0);
Rectangle size= text.getClientArea();
Rectangle trim= text.computeTrim(0, 0, 0, 0);
int height= size.height - trim.height;
int lines= JFaceTextUtil.getLineIndex(text, height) - text.getTopIndex();
int bottomLine= topLine + lines;
int rangeBottomLine= getWidgetLineNumber(lineRange.getStartLine() + lineRange.getNumberOfLines() - 1);
int bottomDelta= Math.max(rangeBottomLine - bottomLine, 0);
return new LineRange(lineRange.getStartLine() + topDelta, lineRange.getNumberOfLines() - bottomDelta - topDelta);
} catch (BadLocationException ex) {
}
return null;
}
/**
* Converts a line range into a character range.
*
* @param lineRange the line range
* @return the corresponding character range
* @throws BadLocationException in case the given line range is invalid
*/
private IRegion convertToRegion(ILineRange lineRange) throws BadLocationException {
IDocument document= fSourceViewer.getDocument();
int startOffset= document.getLineOffset(lineRange.getStartLine());
int endLine= lineRange.getStartLine() + Math.max(0, lineRange.getNumberOfLines() - 1);
IRegion lineInfo= document.getLineInformation(endLine);
int endOffset= lineInfo.getOffset() + lineInfo.getLength();
return new Region(startOffset, endOffset - startOffset);
}
/**
* Returns the region out of the given set that contains the given line or
* <code>null</code>.
*
* @param regions the set of regions
* @param line the line
* @return the region of the set that contains the line
* @throws BadLocationException in case line is invalid
*/
private IRegion findRegionContainingLine(IRegion[] regions, int line) throws BadLocationException {
IDocument document= fSourceViewer.getDocument();
IRegion lineInfo= document.getLineInformation(line);
for (int i= 0; i < regions.length; i++) {
if (TextUtilities.overlaps(regions[i], lineInfo))
return regions[i];
}
return null;
}
/**
* Converts a given character region into a line range.
*
* @param region the character region
* @return the corresponding line range
* @throws BadLocationException in case the given region in invalid
*/
private ILineRange convertToLineRange(IRegion region) throws BadLocationException {
IDocument document= fSourceViewer.getDocument();
int startLine= document.getLineOfOffset(region.getOffset());
int endLine= document.getLineOfOffset(region.getOffset() + region.getLength());
return new LineRange(startLine, endLine - startLine + 1);
}
/**
* Returns the visible area of the vertical ruler covered by the given line
* range.
*
* @param lineRange the line range
* @return the visible area
*/
private Rectangle computeArea(ILineRange lineRange) {
try {
StyledText text= fSourceViewer.getTextWidget();
final int startLine= getWidgetLineNumber(lineRange.getStartLine());
int y= JFaceTextUtil.computeLineHeight(text, 0, startLine, startLine) - text.getTopPixel();
int height= JFaceTextUtil.computeLineHeight(text, startLine, startLine + lineRange.getNumberOfLines(), lineRange.getNumberOfLines());
Point size= fVerticalRulerInfo.getControl().getSize();
return new Rectangle(0, y, size.x, height);
} catch (BadLocationException x) {
}
return null;
}
/**
* Returns the number of the currently visible lines.
*
* @return the number of the currently visible lines
*/
private int computeNumberOfVisibleLines() {
StyledText textWidget= fSourceViewer.getTextWidget();
int lineHeight= textWidget.getLineHeight();
int clientAreaHeight= textWidget.getClientArea().height;
return clientAreaHeight / lineHeight;
}
/**
* Determines the hover to be used to display information based on the source of the
* mouse hover event. If <code>fVerticalRulerInfo</code> is not a composite ruler, the
* standard hover is returned.
*
* @param event the source of the mouse hover event
* @return the hover depending on <code>source</code>, or <code>fAnnotationHover</code> if none can be found.
* @since 3.0
*/
private IAnnotationHover getHover(MouseEvent event) {
if (event == null || event.getSource() == null)
return fAnnotationHover;
if (fVerticalRulerInfo instanceof CompositeRuler) {
CompositeRuler comp= (CompositeRuler) fVerticalRulerInfo;
for (Iterator<IVerticalRulerColumn> it= comp.getDecoratorIterator(); it.hasNext();) {
Object o= it.next();
if (o instanceof IVerticalRulerInfoExtension && o instanceof IVerticalRulerInfo) {
if (((IVerticalRulerInfo) o).getControl() == event.getSource()) {
IAnnotationHover hover= ((IVerticalRulerInfoExtension) o).getHover();
if (hover != null)
return hover;
}
}
}
}
return fAnnotationHover;
}
/**
* Returns the line of interest deduced from the mouse hover event.
*
* @param event a mouse hover event that triggered hovering
* @return the document model line number on which the hover event occurred or <code>-1</code> if there is no event
* @since 3.0
*/
private int getHoverLine(MouseEvent event) {
return event == null ? -1 : fVerticalRulerInfo.toDocumentLineNumber(event.y);
}
/**
* Returns for the widget line number for the given document line number.
*
* @param line the absolute line number
* @return the line number relative to the viewer's visible region
* @throws BadLocationException if <code>line</code> is not valid in the viewer's document
*/
private int getWidgetLineNumber(int line) throws BadLocationException {
if (fSourceViewer instanceof ITextViewerExtension5) {
ITextViewerExtension5 extension= (ITextViewerExtension5) fSourceViewer;
return extension.modelLine2WidgetLine(line);
}
IRegion region= fSourceViewer.getVisibleRegion();
int firstLine= fSourceViewer.getDocument().getLineOfOffset(region.getOffset());
return line - firstLine;
}
/**
* Determines graphical area covered by the given line.
*
* @param line the number of the line in the viewer whose graphical extend in the vertical ruler must be computed
* @return the graphical extend of the given line
*/
private Rectangle computeArea(int line) {
try {
StyledText text= fSourceViewer.getTextWidget();
int widgetLine= getWidgetLineNumber(line);
int y= JFaceTextUtil.computeLineHeight(text, 0, widgetLine, widgetLine) - text.getTopPixel();
Point size= fVerticalRulerInfo.getControl().getSize();
return new Rectangle(0, y, size.x, text.getLineHeight(text.getOffsetAtLine(widgetLine)));
} catch (IllegalArgumentException ex) {
} catch (BadLocationException ex) {
}
return null;
}
/**
* Returns the annotation hover for this hover manager.
*
* @return the annotation hover for this hover manager or <code>null</code> if none
* @since 2.1
*/
protected IAnnotationHover getAnnotationHover() {
return fAnnotationHover;
}
/**
* Returns the source viewer for this hover manager.
*
* @return the source viewer for this hover manager
* @since 2.1
*/
protected ISourceViewer getSourceViewer() {
return fSourceViewer;
}
/**
* Returns the vertical ruler info for this hover manager
*
* @return the vertical ruler info for this hover manager
* @since 2.1
*/
protected IVerticalRulerInfo getVerticalRulerInfo() {
return fVerticalRulerInfo;
}
@Override
protected Point computeSizeConstraints(Control subjectControl, Rectangle subjectArea, IInformationControl informationControl) {
Point constraints= super.computeSizeConstraints(subjectControl, subjectArea, informationControl);
// make as big as text area, if possible
StyledText styledText= fSourceViewer.getTextWidget();
if (styledText != null) {
Rectangle r= styledText.getClientArea();
if (r != null) {
constraints.x= r.width;
constraints.y= r.height;
}
}
return constraints;
}
@Override
protected Point computeInformationControlLocation(Rectangle subjectArea, Point controlSize) {
MouseEvent event= getHoverEvent();
IAnnotationHover hover= getHover(event);
if (hover instanceof IAnnotationHoverExtension) {
IAnnotationHoverExtension extension= (IAnnotationHoverExtension) hover;
boolean allowMouseExit= extension.canHandleMouseCursor();
if (allowMouseExit) {
return computeLocation(subjectArea, controlSize, ANCHOR_RIGHT);
}
}
return super.computeInformationControlLocation(subjectArea, controlSize);
}
@Override
protected Point computeLocation(Rectangle subjectArea, Point controlSize, Anchor anchor) {
MouseEvent event= getHoverEvent();
IAnnotationHover hover= getHover(event);
boolean allowMouseExit= false;
if (hover instanceof IAnnotationHoverExtension) {
IAnnotationHoverExtension extension= (IAnnotationHoverExtension) hover;
allowMouseExit= extension.canHandleMouseCursor();
}
boolean hideOnMouseWheel= true;
if (hover instanceof IAnnotationHoverExtension2) {
IAnnotationHoverExtension2 extension= (IAnnotationHoverExtension2) hover;
hideOnMouseWheel= !extension.canHandleMouseWheel();
}
fHideOnMouseWheel= hideOnMouseWheel;
if (allowMouseExit) {
fAllowMouseExit= true;
Control subjectControl= getSubjectControl();
// return a location that just overlaps the annotation on the bar
if (anchor == AbstractInformationControlManager.ANCHOR_RIGHT)
return subjectControl.toDisplay(subjectArea.x - 4, subjectArea.y - 2);
else if (anchor == AbstractInformationControlManager.ANCHOR_LEFT)
return subjectControl.toDisplay(subjectArea.x + subjectArea.width - controlSize.x + 4, subjectArea.y - 2);
}
fAllowMouseExit= false;
return super.computeLocation(subjectArea, controlSize, anchor);
}
/**
* Returns the currently shown annotation hover or <code>null</code> if none
* hover is shown.
*
* @return the currently shown annotation hover or <code>null</code>
* @since 3.2
*/
public IAnnotationHover getCurrentAnnotationHover() {
return fCurrentHover;
}
/**
* Returns an adapter that gives access to internal methods.
* <p>
* <strong>Note:</strong> This method is not intended to be referenced or overridden by clients.
* </p>
*
* @return the replaceable information control accessor
* @since 3.4
* @noreference This method is not intended to be referenced by clients.
* @nooverride This method is not intended to be re-implemented or extended by clients.
*/
@Override
public InternalAccessor getInternalAccessor() {
return new InternalAccessor() {
@Override
public IInformationControl getCurrentInformationControl() {
return AnnotationBarHoverManager.super.getInternalAccessor().getCurrentInformationControl();
}
@Override
public void setInformationControlReplacer(InformationControlReplacer replacer) {
AnnotationBarHoverManager.super.getInternalAccessor().setInformationControlReplacer(replacer);
}
@Override
public InformationControlReplacer getInformationControlReplacer() {
return AnnotationBarHoverManager.super.getInternalAccessor().getInformationControlReplacer();
}
@Override
public boolean canReplace(IInformationControl control) {
return AnnotationBarHoverManager.super.getInternalAccessor().canReplace(control);
}
@Override
public boolean isReplaceInProgress() {
return AnnotationBarHoverManager.super.getInternalAccessor().isReplaceInProgress();
}
@Override
public void replaceInformationControl(boolean takeFocus) {
AnnotationBarHoverManager.super.getInternalAccessor().replaceInformationControl(takeFocus);
}
@Override
public void cropToClosestMonitor(Rectangle bounds) {
AnnotationBarHoverManager.super.getInternalAccessor().cropToClosestMonitor(bounds);
}
@Override
public void setHoverEnrichMode(EnrichMode mode) {
AnnotationBarHoverManager.super.getInternalAccessor().setHoverEnrichMode(mode);
}
@Override
public boolean getAllowMouseExit() {
return fAllowMouseExit;
}
};
}
}