X-Git-Url: http://git.phpeclipse.com diff --git a/net.sourceforge.phpeclipse/src/net/sourceforge/phpeclipse/phpeditor/OverviewRuler.java b/net.sourceforge.phpeclipse/src/net/sourceforge/phpeclipse/phpeditor/OverviewRuler.java new file mode 100644 index 0000000..dfd4408 --- /dev/null +++ b/net.sourceforge.phpeclipse/src/net/sourceforge/phpeclipse/phpeditor/OverviewRuler.java @@ -0,0 +1,775 @@ +/********************************************************************** +Copyright (c) 2000, 2002 IBM Corp. and others. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Common Public License v1.0 +which accompanies this distribution, and is available at +http://www.eclipse.org/legal/cpl-v10.html + +Contributors: + IBM Corporation - Initial implementation +**********************************************************************/ + +package net.sourceforge.phpeclipse.phpeditor; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +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.ITextViewerExtension3; +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.IAnnotationModel; +import org.eclipse.jface.text.source.IAnnotationModelListener; +import org.eclipse.jface.text.source.IVerticalRulerInfo; +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.MouseAdapter; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.MouseMoveListener; +import org.eclipse.swt.events.PaintEvent; +import org.eclipse.swt.events.PaintListener; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Cursor; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.RGB; +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; + +/** + * + */ +public class OverviewRuler implements IVerticalRulerInfo { + + /** + * Internal listener class. + */ + class InternalListener implements ITextListener, IAnnotationModelListener { + + /* + * @see ITextListener#textChanged + */ + public void textChanged(TextEvent e) { + if (fTextViewer != null + && e.getDocumentEvent() == null + && e.getViewerRedrawState()) { + // handle only changes of visible document + redraw(); + } + } + + /* + * @see IAnnotationModelListener#modelChanged(IAnnotationModel) + */ + public void modelChanged(IAnnotationModel model) { + update(); + } + } + + /** + * Filters problems based on their types. + */ + class FilterIterator implements Iterator { + + private final static int IGNORE = 0; + private final static int TEMPORARY = 1; + private final static int PERSISTENT = 2; + + private Iterator fIterator; + private AnnotationType fType; + private IProblemAnnotation fNext; + private int fTemporary; + + public FilterIterator(AnnotationType type) { + this(type, IGNORE); + } + + public FilterIterator(AnnotationType type, boolean temporary) { + this(type, temporary ? TEMPORARY : PERSISTENT); + } + + private FilterIterator(AnnotationType type, int temporary) { + fType = type; + fTemporary = temporary; + if (fModel != null) { + fIterator = fModel.getAnnotationIterator(); + skip(); + } + } + + private void skip() { + while (fIterator.hasNext()) { + Object next = fIterator.next(); + if (next instanceof IProblemAnnotation) { + fNext = (IProblemAnnotation) next; + AnnotationType type = fNext.getAnnotationType(); + if (fType == AnnotationType.ALL || fType == type) { + if (fTemporary == IGNORE) + return; + if (fTemporary == TEMPORARY && fNext.isTemporary()) + return; + if (fTemporary == PERSISTENT && !fNext.isTemporary()) + return; + } + } + } + fNext = null; + } + + /* + * @see Iterator#hasNext() + */ + public boolean hasNext() { + return fNext != null; + } + /* + * @see Iterator#next() + */ + public Object next() { + try { + return fNext; + } finally { + if (fModel != null) + skip(); + } + } + /* + * @see Iterator#remove() + */ + public void remove() { + throw new UnsupportedOperationException(); + } + }; + + private static final int INSET = 2; + private static final int PROBLEM_HEIGHT_MIN = 4; + private static boolean PROBLEM_HEIGHT_SCALABLE = false; + + /** The model of the overview ruler */ + private IAnnotationModel fModel; + /** The view to which this ruler is connected */ + private ITextViewer fTextViewer; + /** The ruler's canvas */ + private Canvas fCanvas; + /** The drawable for double buffering */ + private Image fBuffer; + /** The internal listener */ + private InternalListener fInternalListener = new InternalListener(); + /** The width of this vertical ruler */ + private int fWidth; + /** The hit detection cursor */ + private Cursor fHitDetectionCursor; + /** The last cursor */ + private Cursor fLastCursor; + /** Cache for the actual scroll position in pixels */ + private int fScrollPos; + /** The line of the last mouse button activity */ + private int fLastMouseButtonActivityLine = -1; + /** The actual problem height */ + private int fProblemHeight = -1; + + private Set fAnnotationSet = new HashSet(); + private Map fLayers = new HashMap(); + private Map fColorTable = new HashMap(); + + /** + * Constructs a vertical ruler with the given width. + * + * @param width the width of the vertical ruler + */ + public OverviewRuler(int width) { + fWidth = width; + } + + public Control getControl() { + return fCanvas; + } + + public int getWidth() { + return fWidth; + } + + public void setModel(IAnnotationModel model) { + if (model != fModel || model != null) { + + if (fModel != null) + fModel.removeAnnotationModelListener(fInternalListener); + + fModel = model; + + if (fModel != null) + fModel.addAnnotationModelListener(fInternalListener); + + update(); + } + } + + public Control createControl(Composite parent, ITextViewer textViewer) { + + fTextViewer = textViewer; + + fHitDetectionCursor = new Cursor(parent.getDisplay(), SWT.CURSOR_HAND); + fCanvas = new Canvas(parent, SWT.NO_BACKGROUND); + + fCanvas.addPaintListener(new PaintListener() { + public void paintControl(PaintEvent event) { + if (fTextViewer != null) + doubleBufferPaint(event.gc); + } + }); + + fCanvas.addDisposeListener(new DisposeListener() { + public void widgetDisposed(DisposeEvent event) { + handleDispose(); + fTextViewer = null; + } + }); + + fCanvas.addMouseListener(new MouseAdapter() { + public void mouseDown(MouseEvent event) { + handleMouseDown(event); + } + }); + + fCanvas.addMouseMoveListener(new MouseMoveListener() { + public void mouseMove(MouseEvent event) { + handleMouseMove(event); + } + }); + + if (fTextViewer != null) + fTextViewer.addTextListener(fInternalListener); + + return fCanvas; + } + + /** + * Disposes the ruler's resources. + */ + private void handleDispose() { + + if (fTextViewer != null) { + fTextViewer.removeTextListener(fInternalListener); + fTextViewer = null; + } + + if (fModel != null) + fModel.removeAnnotationModelListener(fInternalListener); + + if (fBuffer != null) { + fBuffer.dispose(); + fBuffer = null; + } + + if (fHitDetectionCursor != null) { + fHitDetectionCursor.dispose(); + fHitDetectionCursor = null; + } + + fAnnotationSet.clear(); + fLayers.clear(); + fColorTable.clear(); + } + + /** + * Double buffer drawing. + */ + 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); + try { + gc.setBackground(fCanvas.getBackground()); + gc.fillRectangle(0, 0, size.x, size.y); + + if (fTextViewer instanceof ITextViewerExtension3) + doPaint1(gc); + else + doPaint(gc); + + } finally { + gc.dispose(); + } + + dest.drawImage(fBuffer, 0, 0); + } + + private void doPaint(GC gc) { + + if (fTextViewer == null) + return; + + Rectangle r = new Rectangle(0, 0, 0, 0); + int yy, hh = PROBLEM_HEIGHT_MIN; + + IDocument document = fTextViewer.getDocument(); + IRegion visible = fTextViewer.getVisibleRegion(); + + StyledText textWidget = fTextViewer.getTextWidget(); + int maxLines = textWidget.getLineCount(); + fScrollPos = textWidget.getTopPixel(); + + Point size = fCanvas.getSize(); + int writable = maxLines * textWidget.getLineHeight(); + if (size.y > writable) + size.y = writable; + + List indices = new ArrayList(fLayers.keySet()); + Collections.sort(indices); + + for (Iterator iterator = indices.iterator(); iterator.hasNext();) { + Object layer = iterator.next(); + AnnotationType annotationType = (AnnotationType) fLayers.get(layer); + + if (skip(annotationType)) + continue; + + boolean[] temporary = new boolean[] { false, true }; + for (int t = 0; t < temporary.length; t++) { + + Iterator e = new FilterIterator(annotationType, temporary[t]); + Color fill = getFillColor(annotationType, temporary[t]); + Color stroke = getStrokeColor(annotationType, temporary[t]); + + for (int i = 0; e.hasNext(); i++) { + + Annotation a = (Annotation) e.next(); + Position p = fModel.getPosition(a); + + if (p == null + || !p.overlapsWith(visible.getOffset(), visible.getLength())) + continue; + + int problemOffset = Math.max(p.getOffset(), visible.getOffset()); + int problemEnd = + Math.min( + p.getOffset() + p.getLength(), + visible.getOffset() + visible.getLength()); + int problemLength = problemEnd - problemOffset; + + try { + if (PROBLEM_HEIGHT_SCALABLE) { + int numbersOfLines = + document.getNumberOfLines(problemOffset, problemLength); + hh = (numbersOfLines * size.y) / maxLines; + if (hh < PROBLEM_HEIGHT_MIN) + hh = PROBLEM_HEIGHT_MIN; + } + fProblemHeight = hh; + + int startLine = + textWidget.getLineAtOffset(problemOffset - visible.getOffset()); + yy = Math.min((startLine * size.y) / maxLines, size.y - hh); + + if (fill != null) { + gc.setBackground(fill); + gc.fillRectangle(INSET, yy, size.x - (2 * INSET), hh); + } + + if (stroke != null) { + gc.setForeground(stroke); + r.x = INSET; + r.y = yy; + r.width = size.x - (2 * INSET) - 1; + r.height = hh; + gc.setLineWidth(1); + gc.drawRectangle(r); + } + } catch (BadLocationException x) { + } + } + } + } + } + + private void doPaint1(GC gc) { + + if (fTextViewer == null) + return; + + Rectangle r = new Rectangle(0, 0, 0, 0); + int yy, hh = PROBLEM_HEIGHT_MIN; + + ITextViewerExtension3 extension = (ITextViewerExtension3) fTextViewer; + IDocument document = fTextViewer.getDocument(); + StyledText textWidget = fTextViewer.getTextWidget(); + fScrollPos = textWidget.getTopPixel(); + + int maxLines = textWidget.getLineCount(); + Point size = fCanvas.getSize(); + int writable = maxLines * textWidget.getLineHeight(); + if (size.y > writable) + size.y = writable; + + List indices = new ArrayList(fLayers.keySet()); + Collections.sort(indices); + + for (Iterator iterator = indices.iterator(); iterator.hasNext();) { + Object layer = iterator.next(); + AnnotationType annotationType = (AnnotationType) fLayers.get(layer); + + if (skip(annotationType)) + continue; + + boolean[] temporary = new boolean[] { false, true }; + for (int t = 0; t < temporary.length; t++) { + + Iterator e = new FilterIterator(annotationType, temporary[t]); + Color fill = getFillColor(annotationType, temporary[t]); + Color stroke = getStrokeColor(annotationType, temporary[t]); + + for (int i = 0; e.hasNext(); i++) { + + Annotation a = (Annotation) e.next(); + Position p = fModel.getPosition(a); + + if (p == null) + continue; + + IRegion widgetRegion = + extension.modelRange2WidgetRange( + new Region(p.getOffset(), p.getLength())); + if (widgetRegion == null) + continue; + + try { + if (PROBLEM_HEIGHT_SCALABLE) { + int numbersOfLines = + document.getNumberOfLines(p.getOffset(), p.getLength()); + hh = (numbersOfLines * size.y) / maxLines; + if (hh < PROBLEM_HEIGHT_MIN) + hh = PROBLEM_HEIGHT_MIN; + } + fProblemHeight = hh; + + int startLine = + textWidget.getLineAtOffset(widgetRegion.getOffset()); + yy = Math.min((startLine * size.y) / maxLines, size.y - hh); + + if (fill != null) { + gc.setBackground(fill); + gc.fillRectangle(INSET, yy, size.x - (2 * INSET), hh); + } + + if (stroke != null) { + gc.setForeground(stroke); + r.x = INSET; + r.y = yy; + r.width = size.x - (2 * INSET) - 1; + r.height = hh; + gc.setLineWidth(1); + gc.drawRectangle(r); + } + } catch (BadLocationException x) { + } + } + } + } + } + + /** + * Thread-safe implementation. + * Can be called from any thread. + */ + 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 overview ruler. + */ + private void redraw() { + if (fCanvas != null && !fCanvas.isDisposed()) { + GC gc = new GC(fCanvas); + doubleBufferPaint(gc); + gc.dispose(); + } + } + + private int[] toLineNumbers(int y_coordinate) { + + StyledText textWidget = fTextViewer.getTextWidget(); + int maxLines = textWidget.getContent().getLineCount(); + + int rulerLength = fCanvas.getSize().y; + int writable = maxLines * textWidget.getLineHeight(); + + if (rulerLength > writable) + rulerLength = writable; + + if (y_coordinate >= writable) + return new int[] { -1, -1 }; + + int[] lines = new int[2]; + + int pixel = Math.max(y_coordinate - 1, 0); + lines[0] = (pixel * maxLines) / rulerLength; + + pixel = Math.min(rulerLength, y_coordinate + 1); + lines[1] = (pixel * maxLines) / rulerLength; + + if (fTextViewer instanceof ITextViewerExtension3) { + ITextViewerExtension3 extension = (ITextViewerExtension3) fTextViewer; + lines[0] = extension.widgetlLine2ModelLine(lines[0]); + lines[1] = extension.widgetlLine2ModelLine(lines[1]); + } else { + try { + IRegion visible = fTextViewer.getVisibleRegion(); + int lineNumber = + fTextViewer.getDocument().getLineOfOffset(visible.getOffset()); + lines[0] += lineNumber; + lines[1] += lineNumber; + } catch (BadLocationException x) { + } + } + + return lines; + } + + boolean hasAnnotationAt(int y_coordinate) { + return findBestMatchingLineNumber(toLineNumbers(y_coordinate)) != -1; + } + + private Position getProblemPositionAt(int[] lineNumbers) { + if (lineNumbers[0] == -1) + return null; + + Position found = null; + + try { + IDocument d = fTextViewer.getDocument(); + IRegion line = d.getLineInformation(lineNumbers[0]); + + int start = line.getOffset(); + + line = d.getLineInformation(lineNumbers[lineNumbers.length - 1]); + int end = line.getOffset() + line.getLength(); + + Iterator e = new FilterIterator(AnnotationType.ALL); + while (e.hasNext()) { + Annotation a = (Annotation) e.next(); + Position p = fModel.getPosition(a); + if (start <= p.getOffset() && p.getOffset() < end) { + if (found == null || p.getOffset() < found.getOffset()) + found = p; + } + } + + } catch (BadLocationException x) { + } + + return found; + } + + /** + * Returns the line which best corresponds to one of + * the underlying problem annotations at the given + * y ruler coordinate. + * + * @return the best matching line or -1 if no such line can be found + * @since 2.1 + */ + private int findBestMatchingLineNumber(int[] lineNumbers) { + if (lineNumbers == null || lineNumbers.length < 1) + return -1; + + try { + Position pos = getProblemPositionAt(lineNumbers); + if (pos == null) + return -1; + return fTextViewer.getDocument().getLineOfOffset(pos.getOffset()); + } catch (BadLocationException ex) { + return -1; + } + } + + private void handleMouseDown(MouseEvent event) { + if (fTextViewer != null) { + int[] lines = toLineNumbers(event.y); + Position p = getProblemPositionAt(lines); + if (p != null) { + fTextViewer.revealRange(p.getOffset(), p.getLength()); + fTextViewer.setSelectedRange(p.getOffset(), p.getLength()); + } + fTextViewer.getTextWidget().setFocus(); + } + fLastMouseButtonActivityLine = toDocumentLineNumber(event.y); + } + + private void handleMouseMove(MouseEvent event) { + if (fTextViewer != null) { + int[] lines = toLineNumbers(event.y); + Position p = getProblemPositionAt(lines); + Cursor cursor = (p != null ? fHitDetectionCursor : null); + if (cursor != fLastCursor) { + fCanvas.setCursor(cursor); + fLastCursor = cursor; + } + } + } + + private void handleMouseDoubleClick(MouseEvent event) { + fLastMouseButtonActivityLine = toDocumentLineNumber(event.y); + } + + public void showAnnotation(AnnotationType annotationType, boolean show) { + if (show) + fAnnotationSet.add(annotationType); + else + fAnnotationSet.remove(annotationType); + } + + public void setLayer(AnnotationType annotationType, int layer) { + if (layer >= 0) + fLayers.put(new Integer(layer), annotationType); + else { + Iterator e = fLayers.keySet().iterator(); + while (e.hasNext()) { + Object key = e.next(); + if (annotationType.equals(fLayers.get(key))) { + fLayers.remove(key); + return; + } + } + } + } + + public void setColor(AnnotationType annotationType, Color color) { + if (color != null) + fColorTable.put(annotationType, color); + else + fColorTable.remove(annotationType); + } + + private boolean skip(AnnotationType annotationType) { + return !fAnnotationSet.contains(annotationType); + } + + private static RGB interpolate(RGB fg, RGB bg, double scale) { + return new RGB( + (int) ((1.0 - scale) * fg.red + scale * bg.red), + (int) ((1.0 - scale) * fg.green + scale * bg.green), + (int) ((1.0 - scale) * fg.blue + scale * bg.blue)); + } + + private static double greyLevel(RGB rgb) { + if (rgb.red == rgb.green && rgb.green == rgb.blue) + return rgb.red; + return (0.299 * rgb.red + 0.587 * rgb.green + 0.114 * rgb.blue + 0.5); + } + + private static boolean isDark(RGB rgb) { + return greyLevel(rgb) > 128; + } + + private static Color getColor(RGB rgb) { + // PHPTextTools textTools= PHPeclipsePlugin.getDefault().getPHPTextTools(); + // return textTools.getColorManager().getColor(rgb); + return PHPEditorEnvironment.getPHPColorProvider().getColor(rgb); + } + + private Color getColor(AnnotationType annotationType, double scale) { + Color base = (Color) fColorTable.get(annotationType); + if (base == null) + return null; + + RGB baseRGB = base.getRGB(); + RGB background = fCanvas.getBackground().getRGB(); + + boolean darkBase = isDark(baseRGB); + boolean darkBackground = isDark(background); + if (darkBase && darkBackground) + background = new RGB(255, 255, 255); + else if (!darkBase && !darkBackground) + background = new RGB(0, 0, 0); + + return getColor(interpolate(baseRGB, background, scale)); + } + + private Color getStrokeColor( + AnnotationType annotationType, + boolean temporary) { + return getColor(annotationType, temporary ? 0.5 : 0.2); + } + + private Color getFillColor( + AnnotationType annotationType, + boolean temporary) { + return getColor(annotationType, temporary ? 0.9 : 0.6); + } + + /** + * @see IVerticalRulerInfo#getLineOfLastMouseButtonActivity() + * @since 2.1 + */ + public int getLineOfLastMouseButtonActivity() { + return fLastMouseButtonActivityLine; + } + + /** + * @see IVerticalRulerInfo#toDocumentLineNumber(int) + * @since 2.1 + */ + public int toDocumentLineNumber(int y_coordinate) { + + if (fTextViewer == null || y_coordinate == -1) + return -1; + + int[] lineNumbers = toLineNumbers(y_coordinate); + int bestLine = findBestMatchingLineNumber(lineNumbers); + if (bestLine == -1 && lineNumbers.length > 0) + return lineNumbers[0]; + return bestLine; + } + + /** + * Returns the height of the problem rectangle. + * + * @return the height of the problem rectangle + * @since 2.1 + */ + int getAnnotationHeight() { + return fProblemHeight; + } +}