1 /**********************************************************************
2 Copyright (c) 2000, 2002 IBM Corp. and others.
3 All rights reserved. This program and the accompanying materials
4 are made available under the terms of the Common Public License v1.0
5 which accompanies this distribution, and is available at
6 http://www.eclipse.org/legal/cpl-v10.html
9 IBM Corporation - Initial implementation
10 **********************************************************************/
12 package net.sourceforge.phpeclipse.phpeditor;
14 import java.util.ArrayList;
15 import java.util.Collections;
16 import java.util.HashMap;
17 import java.util.HashSet;
18 import java.util.Iterator;
19 import java.util.List;
23 import net.sourceforge.phpdt.ui.text.JavaTextTools;
24 import net.sourceforge.phpeclipse.PHPeclipsePlugin;
26 import org.eclipse.jface.text.BadLocationException;
27 import org.eclipse.jface.text.IDocument;
28 import org.eclipse.jface.text.IRegion;
29 import org.eclipse.jface.text.ITextListener;
30 import org.eclipse.jface.text.ITextViewer;
31 import org.eclipse.jface.text.ITextViewerExtension3;
32 import org.eclipse.jface.text.Position;
33 import org.eclipse.jface.text.Region;
34 import org.eclipse.jface.text.TextEvent;
35 import org.eclipse.jface.text.source.Annotation;
36 import org.eclipse.jface.text.source.IAnnotationModel;
37 import org.eclipse.jface.text.source.IAnnotationModelListener;
38 import org.eclipse.jface.text.source.IVerticalRulerInfo;
39 import org.eclipse.swt.SWT;
40 import org.eclipse.swt.custom.StyledText;
41 import org.eclipse.swt.events.DisposeEvent;
42 import org.eclipse.swt.events.DisposeListener;
43 import org.eclipse.swt.events.MouseAdapter;
44 import org.eclipse.swt.events.MouseEvent;
45 import org.eclipse.swt.events.MouseMoveListener;
46 import org.eclipse.swt.events.PaintEvent;
47 import org.eclipse.swt.events.PaintListener;
48 import org.eclipse.swt.graphics.Color;
49 import org.eclipse.swt.graphics.Cursor;
50 import org.eclipse.swt.graphics.GC;
51 import org.eclipse.swt.graphics.Image;
52 import org.eclipse.swt.graphics.Point;
53 import org.eclipse.swt.graphics.RGB;
54 import org.eclipse.swt.graphics.Rectangle;
55 import org.eclipse.swt.widgets.Canvas;
56 import org.eclipse.swt.widgets.Composite;
57 import org.eclipse.swt.widgets.Control;
58 import org.eclipse.swt.widgets.Display;
63 public class OverviewRuler implements IVerticalRulerInfo {
66 * Internal listener class.
68 class InternalListener implements ITextListener, IAnnotationModelListener {
71 * @see ITextListener#textChanged
73 public void textChanged(TextEvent e) {
74 if (fTextViewer != null && e.getDocumentEvent() == null && e.getViewerRedrawState()) {
75 // handle only changes of visible document
81 * @see IAnnotationModelListener#modelChanged(IAnnotationModel)
83 public void modelChanged(IAnnotationModel model) {
89 * Filters problems based on their types.
91 class FilterIterator implements Iterator {
93 private final static int IGNORE = 0;
94 private final static int TEMPORARY = 1;
95 private final static int PERSISTENT = 2;
97 private Iterator fIterator;
98 private AnnotationType fType;
99 private IProblemAnnotation fNext;
100 private int fTemporary;
102 public FilterIterator(AnnotationType type) {
106 public FilterIterator(AnnotationType type, boolean temporary) {
107 this(type, temporary ? TEMPORARY : PERSISTENT);
110 private FilterIterator(AnnotationType type, int temporary) {
112 fTemporary = temporary;
113 if (fModel != null) {
114 fIterator = fModel.getAnnotationIterator();
119 private void skip() {
120 while (fIterator.hasNext()) {
121 Object next = fIterator.next();
122 if (next instanceof IProblemAnnotation) {
123 fNext = (IProblemAnnotation) next;
124 AnnotationType type = fNext.getAnnotationType();
125 if (fType == AnnotationType.ALL || fType == type) {
126 if (fTemporary == IGNORE)
128 if (fTemporary == TEMPORARY && fNext.isTemporary())
130 if (fTemporary == PERSISTENT && !fNext.isTemporary())
139 * @see Iterator#hasNext()
141 public boolean hasNext() {
142 return fNext != null;
145 * @see Iterator#next()
147 public Object next() {
156 * @see Iterator#remove()
158 public void remove() {
159 throw new UnsupportedOperationException();
163 private static final int INSET = 2;
164 private static final int PROBLEM_HEIGHT_MIN = 4;
165 private static boolean PROBLEM_HEIGHT_SCALABLE = false;
167 /** The model of the overview ruler */
168 private IAnnotationModel fModel;
169 /** The view to which this ruler is connected */
170 private ITextViewer fTextViewer;
171 /** The ruler's canvas */
172 private Canvas fCanvas;
173 /** The drawable for double buffering */
174 private Image fBuffer;
175 /** The internal listener */
176 private InternalListener fInternalListener = new InternalListener();
177 /** The width of this vertical ruler */
179 /** The hit detection cursor */
180 private Cursor fHitDetectionCursor;
181 /** The last cursor */
182 private Cursor fLastCursor;
183 /** Cache for the actual scroll position in pixels */
184 private int fScrollPos;
185 /** The line of the last mouse button activity */
186 private int fLastMouseButtonActivityLine = -1;
187 /** The actual problem height */
188 private int fProblemHeight = -1;
190 private Set fAnnotationSet = new HashSet();
191 private Map fLayers = new HashMap();
192 private Map fColorTable = new HashMap();
195 * Constructs a vertical ruler with the given width.
197 * @param width the width of the vertical ruler
199 public OverviewRuler(int width) {
203 public Control getControl() {
207 public int getWidth() {
211 public void setModel(IAnnotationModel model) {
212 if (model != fModel || model != null) {
215 fModel.removeAnnotationModelListener(fInternalListener);
220 fModel.addAnnotationModelListener(fInternalListener);
226 public Control createControl(Composite parent, ITextViewer textViewer) {
228 fTextViewer = textViewer;
230 fHitDetectionCursor = new Cursor(parent.getDisplay(), SWT.CURSOR_HAND);
231 fCanvas = new Canvas(parent, SWT.NO_BACKGROUND);
233 fCanvas.addPaintListener(new PaintListener() {
234 public void paintControl(PaintEvent event) {
235 if (fTextViewer != null)
236 doubleBufferPaint(event.gc);
240 fCanvas.addDisposeListener(new DisposeListener() {
241 public void widgetDisposed(DisposeEvent event) {
247 fCanvas.addMouseListener(new MouseAdapter() {
248 public void mouseDown(MouseEvent event) {
249 handleMouseDown(event);
253 fCanvas.addMouseMoveListener(new MouseMoveListener() {
254 public void mouseMove(MouseEvent event) {
255 handleMouseMove(event);
259 if (fTextViewer != null)
260 fTextViewer.addTextListener(fInternalListener);
266 * Disposes the ruler's resources.
268 private void handleDispose() {
270 if (fTextViewer != null) {
271 fTextViewer.removeTextListener(fInternalListener);
276 fModel.removeAnnotationModelListener(fInternalListener);
278 if (fBuffer != null) {
283 if (fHitDetectionCursor != null) {
284 fHitDetectionCursor.dispose();
285 fHitDetectionCursor = null;
288 fAnnotationSet.clear();
294 * Double buffer drawing.
296 private void doubleBufferPaint(GC dest) {
298 Point size = fCanvas.getSize();
300 if (size.x <= 0 || size.y <= 0)
303 if (fBuffer != null) {
304 Rectangle r = fBuffer.getBounds();
305 if (r.width != size.x || r.height != size.y) {
311 fBuffer = new Image(fCanvas.getDisplay(), size.x, size.y);
313 GC gc = new GC(fBuffer);
315 gc.setBackground(fCanvas.getBackground());
316 gc.fillRectangle(0, 0, size.x, size.y);
318 if (fTextViewer instanceof ITextViewerExtension3)
327 dest.drawImage(fBuffer, 0, 0);
330 private void doPaint(GC gc) {
332 if (fTextViewer == null)
335 Rectangle r = new Rectangle(0, 0, 0, 0);
336 int yy, hh = PROBLEM_HEIGHT_MIN;
338 IDocument document = fTextViewer.getDocument();
339 IRegion visible = fTextViewer.getVisibleRegion();
341 StyledText textWidget = fTextViewer.getTextWidget();
342 int maxLines = textWidget.getLineCount();
343 fScrollPos = textWidget.getTopPixel();
345 Point size = fCanvas.getSize();
346 int writable = maxLines * textWidget.getLineHeight();
347 if (size.y > writable)
350 List indices = new ArrayList(fLayers.keySet());
351 Collections.sort(indices);
353 for (Iterator iterator = indices.iterator(); iterator.hasNext();) {
354 Object layer = iterator.next();
355 AnnotationType annotationType = (AnnotationType) fLayers.get(layer);
357 if (skip(annotationType))
360 boolean[] temporary = new boolean[] { false, true };
361 for (int t = 0; t < temporary.length; t++) {
363 Iterator e = new FilterIterator(annotationType, temporary[t]);
364 Color fill = getFillColor(annotationType, temporary[t]);
365 Color stroke = getStrokeColor(annotationType, temporary[t]);
367 for (int i = 0; e.hasNext(); i++) {
369 Annotation a = (Annotation) e.next();
370 Position p = fModel.getPosition(a);
372 if (p == null || !p.overlapsWith(visible.getOffset(), visible.getLength()))
375 int problemOffset = Math.max(p.getOffset(), visible.getOffset());
376 int problemEnd = Math.min(p.getOffset() + p.getLength(), visible.getOffset() + visible.getLength());
377 int problemLength = problemEnd - problemOffset;
380 if (PROBLEM_HEIGHT_SCALABLE) {
381 int numbersOfLines = document.getNumberOfLines(problemOffset, problemLength);
382 hh = (numbersOfLines * size.y) / maxLines;
383 if (hh < PROBLEM_HEIGHT_MIN)
384 hh = PROBLEM_HEIGHT_MIN;
388 int startLine = textWidget.getLineAtOffset(problemOffset - visible.getOffset());
389 yy = Math.min((startLine * size.y) / maxLines, size.y - hh);
392 gc.setBackground(fill);
393 gc.fillRectangle(INSET, yy, size.x - (2 * INSET), hh);
396 if (stroke != null) {
397 gc.setForeground(stroke);
400 r.width = size.x - (2 * INSET) - 1;
405 } catch (BadLocationException x) {
412 private void doPaint1(GC gc) {
414 if (fTextViewer == null)
417 Rectangle r = new Rectangle(0, 0, 0, 0);
418 int yy, hh = PROBLEM_HEIGHT_MIN;
420 ITextViewerExtension3 extension = (ITextViewerExtension3) fTextViewer;
421 IDocument document = fTextViewer.getDocument();
422 StyledText textWidget = fTextViewer.getTextWidget();
423 fScrollPos = textWidget.getTopPixel();
425 int maxLines = textWidget.getLineCount();
426 Point size = fCanvas.getSize();
427 int writable = maxLines * textWidget.getLineHeight();
428 if (size.y > writable)
431 List indices = new ArrayList(fLayers.keySet());
432 Collections.sort(indices);
434 for (Iterator iterator = indices.iterator(); iterator.hasNext();) {
435 Object layer = iterator.next();
436 AnnotationType annotationType = (AnnotationType) fLayers.get(layer);
438 if (skip(annotationType))
441 boolean[] temporary = new boolean[] { false, true };
442 for (int t = 0; t < temporary.length; t++) {
444 Iterator e = new FilterIterator(annotationType, temporary[t]);
445 Color fill = getFillColor(annotationType, temporary[t]);
446 Color stroke = getStrokeColor(annotationType, temporary[t]);
448 for (int i = 0; e.hasNext(); i++) {
450 Annotation a = (Annotation) e.next();
451 Position p = fModel.getPosition(a);
456 IRegion widgetRegion = extension.modelRange2WidgetRange(new Region(p.getOffset(), p.getLength()));
457 if (widgetRegion == null)
461 if (PROBLEM_HEIGHT_SCALABLE) {
462 int numbersOfLines = document.getNumberOfLines(p.getOffset(), p.getLength());
463 hh = (numbersOfLines * size.y) / maxLines;
464 if (hh < PROBLEM_HEIGHT_MIN)
465 hh = PROBLEM_HEIGHT_MIN;
469 int startLine = textWidget.getLineAtOffset(widgetRegion.getOffset());
470 yy = Math.min((startLine * size.y) / maxLines, size.y - hh);
473 gc.setBackground(fill);
474 gc.fillRectangle(INSET, yy, size.x - (2 * INSET), hh);
477 if (stroke != null) {
478 gc.setForeground(stroke);
481 r.width = size.x - (2 * INSET) - 1;
486 } catch (BadLocationException x) {
494 * Thread-safe implementation.
495 * Can be called from any thread.
497 public void update() {
498 if (fCanvas != null && !fCanvas.isDisposed()) {
499 Display d = fCanvas.getDisplay();
501 d.asyncExec(new Runnable() {
511 * Redraws the overview ruler.
513 private void redraw() {
514 if (fCanvas != null && !fCanvas.isDisposed()) {
515 GC gc = new GC(fCanvas);
516 doubleBufferPaint(gc);
521 private int[] toLineNumbers(int y_coordinate) {
523 StyledText textWidget = fTextViewer.getTextWidget();
524 int maxLines = textWidget.getContent().getLineCount();
526 int rulerLength = fCanvas.getSize().y;
527 int writable = maxLines * textWidget.getLineHeight();
529 if (rulerLength > writable)
530 rulerLength = writable;
532 if (y_coordinate >= writable)
533 return new int[] { -1, -1 };
535 int[] lines = new int[2];
537 int pixel = Math.max(y_coordinate - 1, 0);
538 lines[0] = (pixel * maxLines) / rulerLength;
540 pixel = Math.min(rulerLength, y_coordinate + 1);
541 lines[1] = (pixel * maxLines) / rulerLength;
543 if (fTextViewer instanceof ITextViewerExtension3) {
544 ITextViewerExtension3 extension = (ITextViewerExtension3) fTextViewer;
545 lines[0] = extension.widgetlLine2ModelLine(lines[0]);
546 lines[1] = extension.widgetlLine2ModelLine(lines[1]);
549 IRegion visible = fTextViewer.getVisibleRegion();
550 int lineNumber = fTextViewer.getDocument().getLineOfOffset(visible.getOffset());
551 lines[0] += lineNumber;
552 lines[1] += lineNumber;
553 } catch (BadLocationException x) {
560 boolean hasAnnotationAt(int y_coordinate) {
561 return findBestMatchingLineNumber(toLineNumbers(y_coordinate)) != -1;
564 private Position getProblemPositionAt(int[] lineNumbers) {
565 if (lineNumbers[0] == -1)
568 Position found = null;
571 IDocument d = fTextViewer.getDocument();
572 IRegion line = d.getLineInformation(lineNumbers[0]);
574 int start = line.getOffset();
576 line = d.getLineInformation(lineNumbers[lineNumbers.length - 1]);
577 int end = line.getOffset() + line.getLength();
579 Iterator e = new FilterIterator(AnnotationType.ALL);
580 while (e.hasNext()) {
581 Annotation a = (Annotation) e.next();
582 Position p = fModel.getPosition(a);
583 if (start <= p.getOffset() && p.getOffset() < end) {
584 if (found == null || p.getOffset() < found.getOffset())
589 } catch (BadLocationException x) {
596 * Returns the line which best corresponds to one of
597 * the underlying problem annotations at the given
598 * y ruler coordinate.
600 * @return the best matching line or <code>-1</code> if no such line can be found
603 private int findBestMatchingLineNumber(int[] lineNumbers) {
604 if (lineNumbers == null || lineNumbers.length < 1)
608 Position pos = getProblemPositionAt(lineNumbers);
611 return fTextViewer.getDocument().getLineOfOffset(pos.getOffset());
612 } catch (BadLocationException ex) {
617 private void handleMouseDown(MouseEvent event) {
618 if (fTextViewer != null) {
619 int[] lines = toLineNumbers(event.y);
620 Position p = getProblemPositionAt(lines);
622 fTextViewer.revealRange(p.getOffset(), p.getLength());
623 fTextViewer.setSelectedRange(p.getOffset(), p.getLength());
625 fTextViewer.getTextWidget().setFocus();
627 fLastMouseButtonActivityLine = toDocumentLineNumber(event.y);
630 private void handleMouseMove(MouseEvent event) {
631 if (fTextViewer != null) {
632 int[] lines = toLineNumbers(event.y);
633 Position p = getProblemPositionAt(lines);
634 Cursor cursor = (p != null ? fHitDetectionCursor : null);
635 if (cursor != fLastCursor) {
636 fCanvas.setCursor(cursor);
637 fLastCursor = cursor;
642 private void handleMouseDoubleClick(MouseEvent event) {
643 fLastMouseButtonActivityLine = toDocumentLineNumber(event.y);
646 public void showAnnotation(AnnotationType annotationType, boolean show) {
648 fAnnotationSet.add(annotationType);
650 fAnnotationSet.remove(annotationType);
653 public void setLayer(AnnotationType annotationType, int layer) {
655 fLayers.put(new Integer(layer), annotationType);
657 Iterator e = fLayers.keySet().iterator();
658 while (e.hasNext()) {
659 Object key = e.next();
660 if (annotationType.equals(fLayers.get(key))) {
668 public void setColor(AnnotationType annotationType, Color color) {
670 fColorTable.put(annotationType, color);
672 fColorTable.remove(annotationType);
675 private boolean skip(AnnotationType annotationType) {
676 return !fAnnotationSet.contains(annotationType);
679 private static RGB interpolate(RGB fg, RGB bg, double scale) {
681 (int) ((1.0 - scale) * fg.red + scale * bg.red),
682 (int) ((1.0 - scale) * fg.green + scale * bg.green),
683 (int) ((1.0 - scale) * fg.blue + scale * bg.blue));
686 private static double greyLevel(RGB rgb) {
687 if (rgb.red == rgb.green && rgb.green == rgb.blue)
689 return (0.299 * rgb.red + 0.587 * rgb.green + 0.114 * rgb.blue + 0.5);
692 private static boolean isDark(RGB rgb) {
693 return greyLevel(rgb) > 128;
696 private static Color getColor(RGB rgb) {
697 JavaTextTools textTools = PHPeclipsePlugin.getDefault().getJavaTextTools();
698 return textTools.getColorManager().getColor(rgb);
701 private Color getColor(AnnotationType annotationType, double scale) {
702 Color base = (Color) fColorTable.get(annotationType);
706 RGB baseRGB = base.getRGB();
707 RGB background = fCanvas.getBackground().getRGB();
709 boolean darkBase = isDark(baseRGB);
710 boolean darkBackground = isDark(background);
711 if (darkBase && darkBackground)
712 background = new RGB(255, 255, 255);
713 else if (!darkBase && !darkBackground)
714 background = new RGB(0, 0, 0);
716 return getColor(interpolate(baseRGB, background, scale));
719 private Color getStrokeColor(AnnotationType annotationType, boolean temporary) {
720 return getColor(annotationType, temporary ? 0.5 : 0.2);
723 private Color getFillColor(AnnotationType annotationType, boolean temporary) {
724 return getColor(annotationType, temporary ? 0.9 : 0.6);
728 * @see IVerticalRulerInfo#getLineOfLastMouseButtonActivity()
731 public int getLineOfLastMouseButtonActivity() {
732 return fLastMouseButtonActivityLine;
736 * @see IVerticalRulerInfo#toDocumentLineNumber(int)
739 public int toDocumentLineNumber(int y_coordinate) {
741 if (fTextViewer == null || y_coordinate == -1)
744 int[] lineNumbers = toLineNumbers(y_coordinate);
745 int bestLine = findBestMatchingLineNumber(lineNumbers);
746 if (bestLine == -1 && lineNumbers.length > 0)
747 return lineNumbers[0];
752 * Returns the height of the problem rectangle.
754 * @return the height of the problem rectangle
757 int getAnnotationHeight() {
758 return fProblemHeight;