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 org.eclipse.jface.text.BadLocationException;
24 import org.eclipse.jface.text.IDocument;
25 import org.eclipse.jface.text.IRegion;
26 import org.eclipse.jface.text.ITextListener;
27 import org.eclipse.jface.text.ITextViewer;
28 import org.eclipse.jface.text.ITextViewerExtension3;
29 import org.eclipse.jface.text.Position;
30 import org.eclipse.jface.text.Region;
31 import org.eclipse.jface.text.TextEvent;
32 import org.eclipse.jface.text.source.Annotation;
33 import org.eclipse.jface.text.source.IAnnotationModel;
34 import org.eclipse.jface.text.source.IAnnotationModelListener;
35 import org.eclipse.jface.text.source.IVerticalRulerInfo;
36 import org.eclipse.swt.SWT;
37 import org.eclipse.swt.custom.StyledText;
38 import org.eclipse.swt.events.DisposeEvent;
39 import org.eclipse.swt.events.DisposeListener;
40 import org.eclipse.swt.events.MouseAdapter;
41 import org.eclipse.swt.events.MouseEvent;
42 import org.eclipse.swt.events.MouseMoveListener;
43 import org.eclipse.swt.events.PaintEvent;
44 import org.eclipse.swt.events.PaintListener;
45 import org.eclipse.swt.graphics.Color;
46 import org.eclipse.swt.graphics.Cursor;
47 import org.eclipse.swt.graphics.GC;
48 import org.eclipse.swt.graphics.Image;
49 import org.eclipse.swt.graphics.Point;
50 import org.eclipse.swt.graphics.RGB;
51 import org.eclipse.swt.graphics.Rectangle;
52 import org.eclipse.swt.widgets.Canvas;
53 import org.eclipse.swt.widgets.Composite;
54 import org.eclipse.swt.widgets.Control;
55 import org.eclipse.swt.widgets.Display;
60 public class OverviewRuler implements IVerticalRulerInfo {
63 * Internal listener class.
65 class InternalListener implements ITextListener, IAnnotationModelListener {
68 * @see ITextListener#textChanged
70 public void textChanged(TextEvent e) {
71 if (fTextViewer != null
72 && e.getDocumentEvent() == null
73 && e.getViewerRedrawState()) {
74 // handle only changes of visible document
80 * @see IAnnotationModelListener#modelChanged(IAnnotationModel)
82 public void modelChanged(IAnnotationModel model) {
88 * Filters problems based on their types.
90 class FilterIterator implements Iterator {
92 private final static int IGNORE = 0;
93 private final static int TEMPORARY = 1;
94 private final static int PERSISTENT = 2;
96 private Iterator fIterator;
97 private AnnotationType fType;
98 private IProblemAnnotation fNext;
99 private int fTemporary;
101 public FilterIterator(AnnotationType type) {
105 public FilterIterator(AnnotationType type, boolean temporary) {
106 this(type, temporary ? TEMPORARY : PERSISTENT);
109 private FilterIterator(AnnotationType type, int temporary) {
111 fTemporary = temporary;
112 if (fModel != null) {
113 fIterator = fModel.getAnnotationIterator();
118 private void skip() {
119 while (fIterator.hasNext()) {
120 Object next = fIterator.next();
121 if (next instanceof IProblemAnnotation) {
122 fNext = (IProblemAnnotation) next;
123 AnnotationType type = fNext.getAnnotationType();
124 if (fType == AnnotationType.ALL || fType == type) {
125 if (fTemporary == IGNORE)
127 if (fTemporary == TEMPORARY && fNext.isTemporary())
129 if (fTemporary == PERSISTENT && !fNext.isTemporary())
138 * @see Iterator#hasNext()
140 public boolean hasNext() {
141 return fNext != null;
144 * @see Iterator#next()
146 public Object next() {
155 * @see Iterator#remove()
157 public void remove() {
158 throw new UnsupportedOperationException();
162 private static final int INSET = 2;
163 private static final int PROBLEM_HEIGHT_MIN = 4;
164 private static boolean PROBLEM_HEIGHT_SCALABLE = false;
166 /** The model of the overview ruler */
167 private IAnnotationModel fModel;
168 /** The view to which this ruler is connected */
169 private ITextViewer fTextViewer;
170 /** The ruler's canvas */
171 private Canvas fCanvas;
172 /** The drawable for double buffering */
173 private Image fBuffer;
174 /** The internal listener */
175 private InternalListener fInternalListener = new InternalListener();
176 /** The width of this vertical ruler */
178 /** The hit detection cursor */
179 private Cursor fHitDetectionCursor;
180 /** The last cursor */
181 private Cursor fLastCursor;
182 /** Cache for the actual scroll position in pixels */
183 private int fScrollPos;
184 /** The line of the last mouse button activity */
185 private int fLastMouseButtonActivityLine = -1;
186 /** The actual problem height */
187 private int fProblemHeight = -1;
189 private Set fAnnotationSet = new HashSet();
190 private Map fLayers = new HashMap();
191 private Map fColorTable = new HashMap();
194 * Constructs a vertical ruler with the given width.
196 * @param width the width of the vertical ruler
198 public OverviewRuler(int width) {
202 public Control getControl() {
206 public int getWidth() {
210 public void setModel(IAnnotationModel model) {
211 if (model != fModel || model != null) {
214 fModel.removeAnnotationModelListener(fInternalListener);
219 fModel.addAnnotationModelListener(fInternalListener);
225 public Control createControl(Composite parent, ITextViewer textViewer) {
227 fTextViewer = textViewer;
229 fHitDetectionCursor = new Cursor(parent.getDisplay(), SWT.CURSOR_HAND);
230 fCanvas = new Canvas(parent, SWT.NO_BACKGROUND);
232 fCanvas.addPaintListener(new PaintListener() {
233 public void paintControl(PaintEvent event) {
234 if (fTextViewer != null)
235 doubleBufferPaint(event.gc);
239 fCanvas.addDisposeListener(new DisposeListener() {
240 public void widgetDisposed(DisposeEvent event) {
246 fCanvas.addMouseListener(new MouseAdapter() {
247 public void mouseDown(MouseEvent event) {
248 handleMouseDown(event);
252 fCanvas.addMouseMoveListener(new MouseMoveListener() {
253 public void mouseMove(MouseEvent event) {
254 handleMouseMove(event);
258 if (fTextViewer != null)
259 fTextViewer.addTextListener(fInternalListener);
265 * Disposes the ruler's resources.
267 private void handleDispose() {
269 if (fTextViewer != null) {
270 fTextViewer.removeTextListener(fInternalListener);
275 fModel.removeAnnotationModelListener(fInternalListener);
277 if (fBuffer != null) {
282 if (fHitDetectionCursor != null) {
283 fHitDetectionCursor.dispose();
284 fHitDetectionCursor = null;
287 fAnnotationSet.clear();
293 * Double buffer drawing.
295 private void doubleBufferPaint(GC dest) {
297 Point size = fCanvas.getSize();
299 if (size.x <= 0 || size.y <= 0)
302 if (fBuffer != null) {
303 Rectangle r = fBuffer.getBounds();
304 if (r.width != size.x || r.height != size.y) {
310 fBuffer = new Image(fCanvas.getDisplay(), size.x, size.y);
312 GC gc = new GC(fBuffer);
314 gc.setBackground(fCanvas.getBackground());
315 gc.fillRectangle(0, 0, size.x, size.y);
317 if (fTextViewer instanceof ITextViewerExtension3)
326 dest.drawImage(fBuffer, 0, 0);
329 private void doPaint(GC gc) {
331 if (fTextViewer == null)
334 Rectangle r = new Rectangle(0, 0, 0, 0);
335 int yy, hh = PROBLEM_HEIGHT_MIN;
337 IDocument document = fTextViewer.getDocument();
338 IRegion visible = fTextViewer.getVisibleRegion();
340 StyledText textWidget = fTextViewer.getTextWidget();
341 int maxLines = textWidget.getLineCount();
342 fScrollPos = textWidget.getTopPixel();
344 Point size = fCanvas.getSize();
345 int writable = maxLines * textWidget.getLineHeight();
346 if (size.y > writable)
349 List indices = new ArrayList(fLayers.keySet());
350 Collections.sort(indices);
352 for (Iterator iterator = indices.iterator(); iterator.hasNext();) {
353 Object layer = iterator.next();
354 AnnotationType annotationType = (AnnotationType) fLayers.get(layer);
356 if (skip(annotationType))
359 boolean[] temporary = new boolean[] { false, true };
360 for (int t = 0; t < temporary.length; t++) {
362 Iterator e = new FilterIterator(annotationType, temporary[t]);
363 Color fill = getFillColor(annotationType, temporary[t]);
364 Color stroke = getStrokeColor(annotationType, temporary[t]);
366 for (int i = 0; e.hasNext(); i++) {
368 Annotation a = (Annotation) e.next();
369 Position p = fModel.getPosition(a);
372 || !p.overlapsWith(visible.getOffset(), visible.getLength()))
375 int problemOffset = Math.max(p.getOffset(), visible.getOffset());
378 p.getOffset() + p.getLength(),
379 visible.getOffset() + visible.getLength());
380 int problemLength = problemEnd - problemOffset;
383 if (PROBLEM_HEIGHT_SCALABLE) {
385 document.getNumberOfLines(problemOffset, problemLength);
386 hh = (numbersOfLines * size.y) / maxLines;
387 if (hh < PROBLEM_HEIGHT_MIN)
388 hh = PROBLEM_HEIGHT_MIN;
393 textWidget.getLineAtOffset(problemOffset - visible.getOffset());
394 yy = Math.min((startLine * size.y) / maxLines, size.y - hh);
397 gc.setBackground(fill);
398 gc.fillRectangle(INSET, yy, size.x - (2 * INSET), hh);
401 if (stroke != null) {
402 gc.setForeground(stroke);
405 r.width = size.x - (2 * INSET) - 1;
410 } catch (BadLocationException x) {
417 private void doPaint1(GC gc) {
419 if (fTextViewer == null)
422 Rectangle r = new Rectangle(0, 0, 0, 0);
423 int yy, hh = PROBLEM_HEIGHT_MIN;
425 ITextViewerExtension3 extension = (ITextViewerExtension3) fTextViewer;
426 IDocument document = fTextViewer.getDocument();
427 StyledText textWidget = fTextViewer.getTextWidget();
428 fScrollPos = textWidget.getTopPixel();
430 int maxLines = textWidget.getLineCount();
431 Point size = fCanvas.getSize();
432 int writable = maxLines * textWidget.getLineHeight();
433 if (size.y > writable)
436 List indices = new ArrayList(fLayers.keySet());
437 Collections.sort(indices);
439 for (Iterator iterator = indices.iterator(); iterator.hasNext();) {
440 Object layer = iterator.next();
441 AnnotationType annotationType = (AnnotationType) fLayers.get(layer);
443 if (skip(annotationType))
446 boolean[] temporary = new boolean[] { false, true };
447 for (int t = 0; t < temporary.length; t++) {
449 Iterator e = new FilterIterator(annotationType, temporary[t]);
450 Color fill = getFillColor(annotationType, temporary[t]);
451 Color stroke = getStrokeColor(annotationType, temporary[t]);
453 for (int i = 0; e.hasNext(); i++) {
455 Annotation a = (Annotation) e.next();
456 Position p = fModel.getPosition(a);
461 IRegion widgetRegion =
462 extension.modelRange2WidgetRange(
463 new Region(p.getOffset(), p.getLength()));
464 if (widgetRegion == null)
468 if (PROBLEM_HEIGHT_SCALABLE) {
470 document.getNumberOfLines(p.getOffset(), p.getLength());
471 hh = (numbersOfLines * size.y) / maxLines;
472 if (hh < PROBLEM_HEIGHT_MIN)
473 hh = PROBLEM_HEIGHT_MIN;
478 textWidget.getLineAtOffset(widgetRegion.getOffset());
479 yy = Math.min((startLine * size.y) / maxLines, size.y - hh);
482 gc.setBackground(fill);
483 gc.fillRectangle(INSET, yy, size.x - (2 * INSET), hh);
486 if (stroke != null) {
487 gc.setForeground(stroke);
490 r.width = size.x - (2 * INSET) - 1;
495 } catch (BadLocationException x) {
503 * Thread-safe implementation.
504 * Can be called from any thread.
506 public void update() {
507 if (fCanvas != null && !fCanvas.isDisposed()) {
508 Display d = fCanvas.getDisplay();
510 d.asyncExec(new Runnable() {
520 * Redraws the overview ruler.
522 private void redraw() {
523 if (fCanvas != null && !fCanvas.isDisposed()) {
524 GC gc = new GC(fCanvas);
525 doubleBufferPaint(gc);
530 private int[] toLineNumbers(int y_coordinate) {
532 StyledText textWidget = fTextViewer.getTextWidget();
533 int maxLines = textWidget.getContent().getLineCount();
535 int rulerLength = fCanvas.getSize().y;
536 int writable = maxLines * textWidget.getLineHeight();
538 if (rulerLength > writable)
539 rulerLength = writable;
541 if (y_coordinate >= writable)
542 return new int[] { -1, -1 };
544 int[] lines = new int[2];
546 int pixel = Math.max(y_coordinate - 1, 0);
547 lines[0] = (pixel * maxLines) / rulerLength;
549 pixel = Math.min(rulerLength, y_coordinate + 1);
550 lines[1] = (pixel * maxLines) / rulerLength;
552 if (fTextViewer instanceof ITextViewerExtension3) {
553 ITextViewerExtension3 extension = (ITextViewerExtension3) fTextViewer;
554 lines[0] = extension.widgetlLine2ModelLine(lines[0]);
555 lines[1] = extension.widgetlLine2ModelLine(lines[1]);
558 IRegion visible = fTextViewer.getVisibleRegion();
560 fTextViewer.getDocument().getLineOfOffset(visible.getOffset());
561 lines[0] += lineNumber;
562 lines[1] += lineNumber;
563 } catch (BadLocationException x) {
570 boolean hasAnnotationAt(int y_coordinate) {
571 return findBestMatchingLineNumber(toLineNumbers(y_coordinate)) != -1;
574 private Position getProblemPositionAt(int[] lineNumbers) {
575 if (lineNumbers[0] == -1)
578 Position found = null;
581 IDocument d = fTextViewer.getDocument();
582 IRegion line = d.getLineInformation(lineNumbers[0]);
584 int start = line.getOffset();
586 line = d.getLineInformation(lineNumbers[lineNumbers.length - 1]);
587 int end = line.getOffset() + line.getLength();
589 Iterator e = new FilterIterator(AnnotationType.ALL);
590 while (e.hasNext()) {
591 Annotation a = (Annotation) e.next();
592 Position p = fModel.getPosition(a);
593 if (start <= p.getOffset() && p.getOffset() < end) {
594 if (found == null || p.getOffset() < found.getOffset())
599 } catch (BadLocationException x) {
606 * Returns the line which best corresponds to one of
607 * the underlying problem annotations at the given
608 * y ruler coordinate.
610 * @return the best matching line or <code>-1</code> if no such line can be found
613 private int findBestMatchingLineNumber(int[] lineNumbers) {
614 if (lineNumbers == null || lineNumbers.length < 1)
618 Position pos = getProblemPositionAt(lineNumbers);
621 return fTextViewer.getDocument().getLineOfOffset(pos.getOffset());
622 } catch (BadLocationException ex) {
627 private void handleMouseDown(MouseEvent event) {
628 if (fTextViewer != null) {
629 int[] lines = toLineNumbers(event.y);
630 Position p = getProblemPositionAt(lines);
632 fTextViewer.revealRange(p.getOffset(), p.getLength());
633 fTextViewer.setSelectedRange(p.getOffset(), p.getLength());
635 fTextViewer.getTextWidget().setFocus();
637 fLastMouseButtonActivityLine = toDocumentLineNumber(event.y);
640 private void handleMouseMove(MouseEvent event) {
641 if (fTextViewer != null) {
642 int[] lines = toLineNumbers(event.y);
643 Position p = getProblemPositionAt(lines);
644 Cursor cursor = (p != null ? fHitDetectionCursor : null);
645 if (cursor != fLastCursor) {
646 fCanvas.setCursor(cursor);
647 fLastCursor = cursor;
652 private void handleMouseDoubleClick(MouseEvent event) {
653 fLastMouseButtonActivityLine = toDocumentLineNumber(event.y);
656 public void showAnnotation(AnnotationType annotationType, boolean show) {
658 fAnnotationSet.add(annotationType);
660 fAnnotationSet.remove(annotationType);
663 public void setLayer(AnnotationType annotationType, int layer) {
665 fLayers.put(new Integer(layer), annotationType);
667 Iterator e = fLayers.keySet().iterator();
668 while (e.hasNext()) {
669 Object key = e.next();
670 if (annotationType.equals(fLayers.get(key))) {
678 public void setColor(AnnotationType annotationType, Color color) {
680 fColorTable.put(annotationType, color);
682 fColorTable.remove(annotationType);
685 private boolean skip(AnnotationType annotationType) {
686 return !fAnnotationSet.contains(annotationType);
689 private static RGB interpolate(RGB fg, RGB bg, double scale) {
691 (int) ((1.0 - scale) * fg.red + scale * bg.red),
692 (int) ((1.0 - scale) * fg.green + scale * bg.green),
693 (int) ((1.0 - scale) * fg.blue + scale * bg.blue));
696 private static double greyLevel(RGB rgb) {
697 if (rgb.red == rgb.green && rgb.green == rgb.blue)
699 return (0.299 * rgb.red + 0.587 * rgb.green + 0.114 * rgb.blue + 0.5);
702 private static boolean isDark(RGB rgb) {
703 return greyLevel(rgb) > 128;
706 private static Color getColor(RGB rgb) {
707 // PHPTextTools textTools= PHPeclipsePlugin.getDefault().getPHPTextTools();
708 // return textTools.getColorManager().getColor(rgb);
709 return PHPEditorEnvironment.getPHPColorProvider().getColor(rgb);
712 private Color getColor(AnnotationType annotationType, double scale) {
713 Color base = (Color) fColorTable.get(annotationType);
717 RGB baseRGB = base.getRGB();
718 RGB background = fCanvas.getBackground().getRGB();
720 boolean darkBase = isDark(baseRGB);
721 boolean darkBackground = isDark(background);
722 if (darkBase && darkBackground)
723 background = new RGB(255, 255, 255);
724 else if (!darkBase && !darkBackground)
725 background = new RGB(0, 0, 0);
727 return getColor(interpolate(baseRGB, background, scale));
730 private Color getStrokeColor(
731 AnnotationType annotationType,
733 return getColor(annotationType, temporary ? 0.5 : 0.2);
736 private Color getFillColor(
737 AnnotationType annotationType,
739 return getColor(annotationType, temporary ? 0.9 : 0.6);
743 * @see IVerticalRulerInfo#getLineOfLastMouseButtonActivity()
746 public int getLineOfLastMouseButtonActivity() {
747 return fLastMouseButtonActivityLine;
751 * @see IVerticalRulerInfo#toDocumentLineNumber(int)
754 public int toDocumentLineNumber(int y_coordinate) {
756 if (fTextViewer == null || y_coordinate == -1)
759 int[] lineNumbers = toLineNumbers(y_coordinate);
760 int bestLine = findBestMatchingLineNumber(lineNumbers);
761 if (bestLine == -1 && lineNumbers.length > 0)
762 return lineNumbers[0];
767 * Returns the height of the problem rectangle.
769 * @return the height of the problem rectangle
772 int getAnnotationHeight() {
773 return fProblemHeight;