blob: b710fafa605138a213cb5237f7378aaeee8378e3 [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.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.jface.util.Util;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextListener;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.ITextViewerExtension5;
import org.eclipse.jface.text.IViewportListener;
import org.eclipse.jface.text.JFaceTextUtil;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.TextEvent;
/**
* A vertical ruler which is connected to a text viewer. Single column standard
* implementation of {@link org.eclipse.jface.text.source.IVerticalRuler}.
* <p>
* The same can be achieved by using <code>CompositeRuler</code> configured
* with an <code>AnnotationRulerColumn</code>. Clients may use this class as
* is.
*
* @see org.eclipse.jface.text.ITextViewer
*/
public final class VerticalRuler implements IVerticalRuler, IVerticalRulerExtension {
/**
* Internal listener class.
*/
class InternalListener implements IViewportListener, IAnnotationModelListener, ITextListener {
@Override
public void viewportChanged(int verticalPosition) {
if (verticalPosition != fScrollPos)
redraw();
}
@Override
public void modelChanged(IAnnotationModel model) {
update();
}
@Override
public void textChanged(TextEvent e) {
if (fTextViewer != null && e.getViewerRedrawState())
redraw();
}
}
/**
* <code>true</code> if we're on a Mac, where "new GC(canvas)" is expensive.
* @see <a href="https://bugs.eclipse.org/298936">bug 298936</a>
* @since 3.6
*/
static final boolean IS_MAC_BUG_298936= Util.isMac();
/** The vertical ruler's text viewer */
private ITextViewer fTextViewer;
/** The ruler's canvas */
private Canvas fCanvas;
/** The vertical ruler's model */
private IAnnotationModel fModel;
/** Cache for the actual scroll position in pixels */
private int fScrollPos;
/** The buffer for double buffering */
private Image fBuffer;
/** The line of the last mouse button activity */
private int fLastMouseButtonActivityLine= -1;
/** The internal listener */
private InternalListener fInternalListener= new InternalListener();
/** The width of this vertical ruler */
private int fWidth;
/**
* The annotation access of this vertical ruler
* @since 3.0
*/
private IAnnotationAccess fAnnotationAccess;
/**
* Constructs a vertical ruler with the given width.
*
* @param width the width of the vertical ruler
*/
public VerticalRuler(int width) {
this(width, null);
}
/**
* Constructs a vertical ruler with the given width and the given annotation
* access.
*
* @param width the width of the vertical ruler
* @param annotationAcccess the annotation access
* @since 3.0
*/
public VerticalRuler(int width, IAnnotationAccess annotationAcccess) {
fWidth= width;
fAnnotationAccess= annotationAcccess;
}
@Override
public Control getControl() {
return fCanvas;
}
@Override
public Control createControl(Composite parent, ITextViewer textViewer) {
fTextViewer= textViewer;
fCanvas= new Canvas(parent, SWT.NO_BACKGROUND);
fCanvas.addPaintListener(new PaintListener() {
@Override
public void paintControl(PaintEvent event) {
if (fTextViewer != null)
doubleBufferPaint(event.gc);
}
});
fCanvas.addDisposeListener(new DisposeListener() {
@Override
public void widgetDisposed(DisposeEvent e) {
handleDispose();
fTextViewer= null;
}
});
fCanvas.addMouseListener(new MouseListener() {
@Override
public void mouseUp(MouseEvent event) {
}
@Override
public void mouseDown(MouseEvent event) {
fLastMouseButtonActivityLine= toDocumentLineNumber(event.y);
}
@Override
public void mouseDoubleClick(MouseEvent event) {
fLastMouseButtonActivityLine= toDocumentLineNumber(event.y);
}
});
if (fTextViewer != null) {
fTextViewer.addViewportListener(fInternalListener);
fTextViewer.addTextListener(fInternalListener);
}
return fCanvas;
}
/**
* Disposes the ruler's resources.
*/
private void handleDispose() {
if (fTextViewer != null) {
fTextViewer.removeViewportListener(fInternalListener);
fTextViewer.removeTextListener(fInternalListener);
fTextViewer= null;
}
if (fModel != null)
fModel.removeAnnotationModelListener(fInternalListener);
if (fBuffer != null) {
fBuffer.dispose();
fBuffer= null;
}
}
/**
* Double buffer drawing.
*
* @param dest the GC to draw into
*/
private void doubleBufferPaint(GC dest) {
Point size= fCanvas.getSize();
if (size.x <= 0 || size.y <= 0)
return;
if (fBuffer != null) {
Rectangle r= fBuffer.getBounds();
if (r.width != size.x || r.height != size.y) {
fBuffer.dispose();
fBuffer= null;
}
}
if (fBuffer == null)
fBuffer= new Image(fCanvas.getDisplay(), size.x, size.y);
GC gc= new GC(fBuffer);
gc.setFont(fTextViewer.getTextWidget().getFont());
try {
gc.setBackground(fCanvas.getBackground());
gc.fillRectangle(0, 0, size.x, size.y);
if (fTextViewer instanceof ITextViewerExtension5)
doPaint1(gc);
else
doPaint(gc);
} finally {
gc.dispose();
}
dest.drawImage(fBuffer, 0, 0);
}
/**
* Returns the document offset of the upper left corner of the
* widgets view port, possibly including partially visible lines.
*
* @return the document offset of the upper left corner including partially visible lines
* @since 2.0
*/
private int getInclusiveTopIndexStartOffset() {
StyledText textWidget= fTextViewer.getTextWidget();
if (textWidget != null && !textWidget.isDisposed()) {
int top= JFaceTextUtil.getPartialTopIndex(fTextViewer);
try {
IDocument document= fTextViewer.getDocument();
return document.getLineOffset(top);
} catch (BadLocationException x) {
}
}
return -1;
}
/**
* Draws the vertical ruler w/o drawing the Canvas background.
*
* @param gc the GC to draw into
*/
protected void doPaint(GC gc) {
if (fModel == null || fTextViewer == null)
return;
IAnnotationAccessExtension annotationAccessExtension= null;
if (fAnnotationAccess instanceof IAnnotationAccessExtension)
annotationAccessExtension= (IAnnotationAccessExtension) fAnnotationAccess;
StyledText styledText= fTextViewer.getTextWidget();
IDocument doc= fTextViewer.getDocument();
int topLeft= getInclusiveTopIndexStartOffset();
int bottomRight= fTextViewer.getBottomIndexEndOffset();
int viewPort= bottomRight - topLeft;
Point d= fCanvas.getSize();
fScrollPos= styledText.getTopPixel();
int topLine= -1, bottomLine= -1;
try {
IRegion region= fTextViewer.getVisibleRegion();
topLine= doc.getLineOfOffset(region.getOffset());
bottomLine= doc.getLineOfOffset(region.getOffset() + region.getLength());
} catch (BadLocationException x) {
return;
}
// draw Annotations
Rectangle r= new Rectangle(0, 0, 0, 0);
int maxLayer= 1; // loop at least once though layers.
for (int layer= 0; layer < maxLayer; layer++) {
Iterator<Annotation> iter= fModel.getAnnotationIterator();
while (iter.hasNext()) {
IAnnotationPresentation annotationPresentation= null;
Annotation annotation= iter.next();
int lay= IAnnotationAccessExtension.DEFAULT_LAYER;
if (annotationAccessExtension != null)
lay= annotationAccessExtension.getLayer(annotation);
else if (annotation instanceof IAnnotationPresentation) {
annotationPresentation= (IAnnotationPresentation)annotation;
lay= annotationPresentation.getLayer();
}
maxLayer= Math.max(maxLayer, lay+1); // dynamically update layer maximum
if (lay != layer) // wrong layer: skip annotation
continue;
Position position= fModel.getPosition(annotation);
if (position == null)
continue;
if (!position.overlapsWith(topLeft, viewPort))
continue;
try {
int offset= position.getOffset();
int length= position.getLength();
int startLine= doc.getLineOfOffset(offset);
if (startLine < topLine)
startLine= topLine;
int endLine= startLine;
if (length > 0)
endLine= doc.getLineOfOffset(offset + length - 1);
if (endLine > bottomLine)
endLine= bottomLine;
startLine -= topLine;
endLine -= topLine;
r.x= 0;
r.y= JFaceTextUtil.computeLineHeight(styledText, 0, startLine, startLine) - fScrollPos;
r.width= d.x;
int lines= endLine - startLine;
r.height= JFaceTextUtil.computeLineHeight(styledText, startLine, endLine + 1, (lines+1));
if (r.y < d.y && annotationAccessExtension != null) // annotation within visible area
annotationAccessExtension.paint(annotation, gc, fCanvas, r);
else if (annotationPresentation != null)
annotationPresentation.paint(gc, fCanvas, r);
} catch (BadLocationException e) {
}
}
}
}
/**
* Draws the vertical ruler w/o drawing the Canvas background. Uses
* <code>ITextViewerExtension5</code> for its implementation. Will replace
* <code>doPaint(GC)</code>.
*
* @param gc the GC to draw into
*/
protected void doPaint1(GC gc) {
if (fModel == null || fTextViewer == null)
return;
IAnnotationAccessExtension annotationAccessExtension= null;
if (fAnnotationAccess instanceof IAnnotationAccessExtension)
annotationAccessExtension= (IAnnotationAccessExtension) fAnnotationAccess;
ITextViewerExtension5 extension= (ITextViewerExtension5) fTextViewer;
StyledText textWidget= fTextViewer.getTextWidget();
fScrollPos= textWidget.getTopPixel();
Point dimension= fCanvas.getSize();
// draw Annotations
Rectangle r= new Rectangle(0, 0, 0, 0);
int maxLayer= 1; // loop at least once through layers.
for (int layer= 0; layer < maxLayer; layer++) {
Iterator<Annotation> iter= fModel.getAnnotationIterator();
while (iter.hasNext()) {
IAnnotationPresentation annotationPresentation= null;
Annotation annotation= iter.next();
int lay= IAnnotationAccessExtension.DEFAULT_LAYER;
if (annotationAccessExtension != null)
lay= annotationAccessExtension.getLayer(annotation);
else if (annotation instanceof IAnnotationPresentation) {
annotationPresentation= (IAnnotationPresentation)annotation;
lay= annotationPresentation.getLayer();
}
maxLayer= Math.max(maxLayer, lay+1); // dynamically update layer maximum
if (lay != layer) // wrong layer: skip annotation
continue;
Position position= fModel.getPosition(annotation);
if (position == null)
continue;
IRegion widgetRegion= extension.modelRange2WidgetRange(new Region(position.getOffset(), position.getLength()));
if (widgetRegion == null)
continue;
int startLine= extension.widgetLineOfWidgetOffset(widgetRegion.getOffset());
if (startLine == -1)
continue;
int endLine= extension.widgetLineOfWidgetOffset(widgetRegion.getOffset() + Math.max(widgetRegion.getLength() -1, 0));
if (endLine == -1)
continue;
r.x= 0;
r.y= JFaceTextUtil.computeLineHeight(textWidget, 0, startLine, startLine) - fScrollPos;
r.width= dimension.x;
int lines= endLine - startLine;
r.height= JFaceTextUtil.computeLineHeight(textWidget, startLine, endLine + 1, lines+1);
if (r.y < dimension.y && annotationAccessExtension != null) // annotation within visible area
annotationAccessExtension.paint(annotation, gc, fCanvas, r);
else if (annotationPresentation != null)
annotationPresentation.paint(gc, fCanvas, r);
}
}
}
/**
* Thread-safe implementation.
* Can be called from any thread.
*/
@Override
public void update() {
if (fCanvas != null && !fCanvas.isDisposed()) {
Display d= fCanvas.getDisplay();
if (d != null) {
d.asyncExec(new Runnable() {
@Override
public void run() {
redraw();
}
});
}
}
}
/**
* Redraws the vertical ruler.
*/
private void redraw() {
if (fCanvas != null && !fCanvas.isDisposed()) {
if (IS_MAC_BUG_298936) {
fCanvas.redraw();
fCanvas.update();
} else {
GC gc= new GC(fCanvas);
doubleBufferPaint(gc);
gc.dispose();
}
}
}
@Override
public void setModel(IAnnotationModel model) {
if (model != fModel) {
if (fModel != null)
fModel.removeAnnotationModelListener(fInternalListener);
fModel= model;
if (fModel != null)
fModel.addAnnotationModelListener(fInternalListener);
update();
}
}
@Override
public IAnnotationModel getModel() {
return fModel;
}
@Override
public int getWidth() {
return fWidth;
}
@Override
public int getLineOfLastMouseButtonActivity() {
IDocument doc= fTextViewer.getDocument();
if (doc == null || fLastMouseButtonActivityLine >= fTextViewer.getDocument().getNumberOfLines())
fLastMouseButtonActivityLine= -1;
return fLastMouseButtonActivityLine;
}
@Override
public int toDocumentLineNumber(int y_coordinate) {
if (fTextViewer == null || y_coordinate == -1)
return -1;
StyledText text= fTextViewer.getTextWidget();
int line= text.getLineIndex(y_coordinate);
if (line == text.getLineCount() - 1) {
// check whether y_coordinate exceeds last line
if (y_coordinate > text.getLinePixel(line + 1))
return -1;
}
return widgetLine2ModelLine(fTextViewer, line);
}
/**
* Returns the line of the viewer's document that corresponds to the given widget line.
*
* @param viewer the viewer
* @param widgetLine the widget line
* @return the corresponding line of the viewer's document
* @since 2.1
*/
protected final static int widgetLine2ModelLine(ITextViewer viewer, int widgetLine) {
if (viewer instanceof ITextViewerExtension5) {
ITextViewerExtension5 extension= (ITextViewerExtension5) viewer;
return extension.widgetLine2ModelLine(widgetLine);
}
try {
IRegion r= viewer.getVisibleRegion();
IDocument d= viewer.getDocument();
return widgetLine += d.getLineOfOffset(r.getOffset());
} catch (BadLocationException x) {
}
return widgetLine;
}
@Override
public void setFont(Font font) {
}
@Override
public void setLocationOfLastMouseButtonActivity(int x, int y) {
fLastMouseButtonActivityLine= toDocumentLineNumber(y);
}
/**
* Adds the given mouse listener.
*
* @param listener the listener to be added
* @deprecated will be removed
* @since 2.0
*/
@Deprecated
public void addMouseListener(MouseListener listener) {
if (fCanvas != null && !fCanvas.isDisposed())
fCanvas.addMouseListener(listener);
}
/**
* Removes the given mouse listener.
*
* @param listener the listener to be removed
* @deprecated will be removed
* @since 2.0
*/
@Deprecated
public void removeMouseListener(MouseListener listener) {
if (fCanvas != null && !fCanvas.isDisposed())
fCanvas.removeMouseListener(listener);
}
}