+/**
+* Copyright (C) 2007 EDIT
+* European Distributed Institute of Taxonomy
+* http://www.e-taxonomy.eu
+*
+* The contents of this file are subject to the Mozilla Public License Version 1.1
+* See LICENSE.TXT at the top of this package for the full license terms.
+*/
+
+package eu.etaxonomy.taxeditor.editor.name.container;
+
+import java.util.Iterator;
+
+import org.apache.log4j.Logger;
+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;
+import org.eclipse.jface.text.source.Annotation;
+import org.eclipse.jface.text.source.IAnnotationAccess;
+import org.eclipse.jface.text.source.IAnnotationAccessExtension;
+import org.eclipse.jface.text.source.IAnnotationModel;
+import org.eclipse.jface.text.source.IAnnotationModelListener;
+import org.eclipse.jface.text.source.IAnnotationPresentation;
+import org.eclipse.jface.text.source.IVerticalRuler;
+import org.eclipse.jface.text.source.IVerticalRulerExtension;
+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.ui.forms.widgets.TableWrapData;
+
+/**
+ * 99% of the code in this class was copied from org.eclipse.jface.text.source.VerticalRuler,
+ * which allows neither access to its paint methods nor subclassing to do same.
+ * <p>
+ * Changes made in method doubleBufferPaint(doubleBufferPaint(GC dest)).
+ *
+ * @see org.eclipse.jface.text.source.VerticalRuler
+ * @author p.ciardelli
+ * @created 27.01.2009
+ * @version 1.0
+ */
+public class RulerWithIcon implements IVerticalRuler, IVerticalRulerExtension {
+ private static final Logger logger = Logger.getLogger(RulerWithIcon.class);
+
+ /**
+ * Internal listener class.
+ */
+ class InternalListener implements IViewportListener, IAnnotationModelListener, ITextListener {
+
+ /*
+ * @see IViewportListener#viewportChanged(int)
+ */
+ public void viewportChanged(int verticalPosition) {
+ if (verticalPosition != fScrollPos)
+ redraw();
+ }
+
+ /*
+ * @see IAnnotationModelListener#modelChanged(IAnnotationModel)
+ */
+ public void modelChanged(IAnnotationModel model) {
+ update();
+ }
+
+ /*
+ * @see ITextListener#textChanged(TextEvent)
+ */
+ public void textChanged(TextEvent e) {
+ if (fTextViewer != null && e.getViewerRedrawState())
+ redraw();
+ }
+ }
+
+ /** 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;
+ private Image icon;
+
+ /**
+ * Constructs a vertical ruler with the given width.
+ *
+ * @param width the width of the vertical ruler
+ */
+ public RulerWithIcon(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 RulerWithIcon(int width, IAnnotationAccess annotationAcccess) {
+ fWidth= width;
+ fAnnotationAccess= annotationAcccess;
+ }
+
+ /*
+ * @see IVerticalRuler#getControl()
+ */
+ /**
+ * <p>getControl</p>
+ *
+ * @return a {@link org.eclipse.swt.widgets.Control} object.
+ */
+ public Control getControl() {
+ return fCanvas;
+ }
+
+ /*
+ * @see IVerticalRuler#createControl(Composite, ITextViewer)
+ */
+ /** {@inheritDoc} */
+ public Control createControl(Composite parent, ITextViewer textViewer) {
+
+ fTextViewer= textViewer;
+
+ fCanvas= new Canvas(parent, SWT.NO_BACKGROUND);
+
+ TableWrapData layout = new TableWrapData(TableWrapData.LEFT);
+ layout.heightHint = 20;
+ layout.maxWidth = fWidth;
+ fCanvas.setLayoutData(layout);
+
+ fCanvas.addPaintListener(new PaintListener() {
+ public void paintControl(PaintEvent event) {
+ if (fTextViewer != null)
+ doubleBufferPaint(event.gc);
+ }
+ });
+
+ fCanvas.addDisposeListener(new DisposeListener() {
+ public void widgetDisposed(DisposeEvent e) {
+ handleDispose();
+ fTextViewer= null;
+ }
+ });
+
+ fCanvas.addMouseListener(new MouseListener() {
+ public void mouseUp(MouseEvent event) {
+ }
+
+ public void mouseDown(MouseEvent event) {
+ fLastMouseButtonActivityLine= toDocumentLineNumber(event.y);
+ }
+
+ 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);
+
+ // Code added to VerticalRuler starts here
+ if (icon != null) {
+
+ Rectangle r = icon.getBounds();
+
+ if (r.width > size.x || r.height > size.y) {
+ logger.warn(r.width + "x" + r.height + " icon too big for " + size.x + "x" + size.y + " Canvas.");
+ } else {
+
+ // Set destination coordinates to center icon
+ int x = (size.x - r.width) / 2;
+ int y = (size.y - r.height) / 2;
+
+ gc.drawImage(icon, 0, 0, r.width, r.height, x, y, r.width, r.height);
+ }
+ }
+ // Code added to VerticalRuler ends here
+
+ 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<?> iter= fModel.getAnnotationIterator();
+ while (iter.hasNext()) {
+ IAnnotationPresentation annotationPresentation= null;
+ Annotation 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<?> iter= fModel.getAnnotationIterator();
+ while (iter.hasNext()) {
+ IAnnotationPresentation annotationPresentation= null;
+ Annotation 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.
+ */
+ /*
+ * @see IVerticalRuler#update()
+ */
+ public void update() {
+ if (fCanvas != null && !fCanvas.isDisposed()) {
+ Display d= fCanvas.getDisplay();
+ if (d != null) {
+ d.asyncExec(new Runnable() {
+ public void run() {
+ redraw();
+ }
+ });
+ }
+ }
+ }
+
+ /**
+ * Redraws the vertical ruler.
+ */
+ private void redraw() {
+ if (fCanvas != null && !fCanvas.isDisposed()) {
+ GC gc= new GC(fCanvas);
+ doubleBufferPaint(gc);
+ gc.dispose();
+ }
+ }
+
+ /*
+ * @see IVerticalRuler#setModel(IAnnotationModel)
+ */
+ /** {@inheritDoc} */
+ public void setModel(IAnnotationModel model) {
+ if (model != fModel) {
+
+ if (fModel != null)
+ fModel.removeAnnotationModelListener(fInternalListener);
+
+ fModel= model;
+
+ if (fModel != null)
+ fModel.addAnnotationModelListener(fInternalListener);
+
+ update();
+ }
+ }
+
+ /*
+ * @see IVerticalRuler#getModel()
+ */
+ /**
+ * <p>getModel</p>
+ *
+ * @return a {@link org.eclipse.jface.text.source.IAnnotationModel} object.
+ */
+ public IAnnotationModel getModel() {
+ return fModel;
+ }
+
+ /*
+ * @see IVerticalRulerInfo#getWidth()
+ */
+ /**
+ * <p>getWidth</p>
+ *
+ * @return a int.
+ */
+ public int getWidth() {
+ return fWidth;
+ }
+
+ /*
+ * @see IVerticalRulerInfo#getLineOfLastMouseButtonActivity()
+ */
+ /**
+ * <p>getLineOfLastMouseButtonActivity</p>
+ *
+ * @return a int.
+ */
+ public int getLineOfLastMouseButtonActivity() {
+ return fLastMouseButtonActivityLine;
+ }
+
+ /*
+ * @see IVerticalRulerInfo#toDocumentLineNumber(int)
+ */
+ /** {@inheritDoc} */
+ 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;
+ }
+
+ /*
+ * @see IVerticalRulerExtension#setFont(Font)
+ * @since 2.0
+ */
+ /** {@inheritDoc} */
+ public void setFont(Font font) {
+ }
+
+ /*
+ * @see IVerticalRulerExtension#setLocationOfLastMouseButtonActivity(int, int)
+ * @since 2.0
+ */
+ /** {@inheritDoc} */
+ 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
+ */
+ 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
+ */
+ public void removeMouseListener(MouseListener listener) {
+ if (fCanvas != null && !fCanvas.isDisposed())
+ fCanvas.removeMouseListener(listener);
+ }
+
+ /**
+ * <p>Setter for the field <code>icon</code>.</p>
+ *
+ * @param icon a {@link org.eclipse.swt.graphics.Image} object.
+ */
+ public void setIcon(Image icon) {
+ this.icon = icon;
+ redraw();
+ }
+}
+