diff dwtx/jface/internal/text/source/DiffPainter.d @ 129:eb30df5ca28b

Added JFace Text sources
author Frank Benoit <benoit@tionex.de>
date Sat, 23 Aug 2008 19:10:48 +0200
parents
children c4fb132a086c
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/internal/text/source/DiffPainter.d	Sat Aug 23 19:10:48 2008 +0200
@@ -0,0 +1,536 @@
+/*******************************************************************************
+ * Copyright (c) 2006 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
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwtx.jface.internal.text.source.DiffPainter;
+
+import dwt.dwthelper.utils;
+
+
+
+
+import dwt.DWT;
+import dwt.custom.StyledText;
+import dwt.events.DisposeEvent;
+import dwt.events.DisposeListener;
+import dwt.graphics.Color;
+import dwt.graphics.GC;
+import dwt.graphics.RGB;
+import dwt.widgets.Canvas;
+import dwt.widgets.Control;
+import dwt.widgets.Display;
+import dwtx.core.runtime.Assert;
+import dwtx.jface.text.ITextViewer;
+import dwtx.jface.text.JFaceTextUtil;
+import dwtx.jface.text.source.CompositeRuler;
+import dwtx.jface.text.source.IAnnotationHover;
+import dwtx.jface.text.source.IAnnotationModel;
+import dwtx.jface.text.source.IAnnotationModelExtension;
+import dwtx.jface.text.source.IAnnotationModelListener;
+import dwtx.jface.text.source.IChangeRulerColumn;
+import dwtx.jface.text.source.ILineDiffInfo;
+import dwtx.jface.text.source.ILineDiffer;
+import dwtx.jface.text.source.ILineDifferExtension2;
+import dwtx.jface.text.source.ILineRange;
+import dwtx.jface.text.source.ISharedTextColors;
+import dwtx.jface.text.source.IVerticalRulerColumn;
+
+
+/**
+ * A strategy for painting the quick diff colors onto the vertical ruler column. It also manages the
+ * quick diff hover.
+ * 
+ * @since 3.2
+ */
+public final class DiffPainter {
+    /**
+     * Internal listener class that will update the ruler when the underlying model changes.
+     */
+    private class AnnotationListener : IAnnotationModelListener {
+        /*
+         * @see dwtx.jface.text.source.IAnnotationModelListener#modelChanged(dwtx.jface.text.source.IAnnotationModel)
+         */
+        public void modelChanged(IAnnotationModel model) {
+            postRedraw();
+        }
+    }
+
+    /** The vertical ruler column that delegates painting to this painter. */
+    private final IVerticalRulerColumn fColumn;
+    /** The parent ruler. */
+    private CompositeRuler fParentRuler;
+    /** The column's control, typically a {@link Canvas}, possibly <code>null</code>. */
+    private Control fControl;
+    /** The text viewer that the column is attached to. */
+    private ITextViewer fViewer;
+    /** The viewer's text widget. */
+    private StyledText fWidget;
+    /** The line differ extracted from the annotation model. */
+    private ILineDiffer fLineDiffer= null;
+    /** Color for changed lines */
+    private Color fAddedColor;
+    /** Color for added lines */
+    private Color fChangedColor;
+    /** Color for the deleted line indicator */
+    private Color fDeletedColor;
+    /** The background color. */
+    private Color fBackground;
+    /** The ruler's hover */
+    private IAnnotationHover fHover;
+    /** The internal listener */
+    private final AnnotationListener fAnnotationListener= new AnnotationListener();
+    /** The shared color provider, possibly <code>null</code>. */
+    private final ISharedTextColors fSharedColors;
+
+    /**
+     * Creates a new diff painter for a vertical ruler column.
+     * 
+     * @param column the column that will delegate{@link #paint(GC, ILineRange) painting} to the
+     *        newly created painter.
+     * @param sharedColors a shared colors object to store shaded colors in, may be
+     *        <code>null</code>
+     */
+    public DiffPainter(IVerticalRulerColumn column, ISharedTextColors sharedColors) {
+        Assert.isLegal(column !is null);
+        fColumn= column;
+        fSharedColors= sharedColors;
+    }
+
+    /**
+     * Sets the parent ruler - the delegating column must call this method as soon as it creates its
+     * control.
+     * 
+     * @param parentRuler the parent ruler
+     */
+    public void setParentRuler(CompositeRuler parentRuler) {
+        fParentRuler= parentRuler;
+    }
+
+    /**
+     * Sets the quick diff hover later returned by {@link #getHover()}.
+     * 
+     * @param hover the hover
+     */
+    public void setHover(IAnnotationHover hover) {
+        fHover= hover;
+    }
+
+    /**
+     * Returns the quick diff hover set by {@link #setHover(IAnnotationHover)}.
+     * 
+     * @return the quick diff hover set by {@link #setHover(IAnnotationHover)}
+     */
+    public IAnnotationHover getHover() {
+        return fHover;
+    }
+
+    /**
+     * Sets the background color.
+     * 
+     * @param background the background color, <code>null</code> to use the platform's list background
+     */
+    public void setBackground(Color background) {
+        fBackground= background;
+    }
+
+    /**
+     * Delegates the painting of the quick diff colors to this painter. The painter will draw the
+     * color boxes onto the passed {@link GC} for all model (document) lines in
+     * <code>visibleModelLines</code>.
+     * 
+     * @param gc the {@link GC} to draw onto
+     * @param visibleModelLines the lines (in document offsets) that are currently (perhaps only
+     *        partially) visible
+     */
+    public void paint(GC gc, ILineRange visibleModelLines) {
+        connectIfNeeded();
+        if (!isConnected())
+            return;
+
+        // draw diff info
+        final int lastLine= end(visibleModelLines);
+        final int width= getWidth();
+        final Color deletionColor= getDeletionColor();
+        for (int line= visibleModelLines.getStartLine(); line < lastLine; line++) {
+            paintLine(line, gc, width, deletionColor);
+        }
+    }
+
+    /**
+     * Ensures that the column is fully instantiated, i.e. has a control, and that the viewer is
+     * visible.
+     */
+    private void connectIfNeeded() {
+        if (isConnected() || fParentRuler is null)
+            return;
+
+        fViewer= fParentRuler.getTextViewer();
+        if (fViewer is null)
+            return;
+
+        fWidget= fViewer.getTextWidget();
+        if (fWidget is null)
+            return;
+
+        fControl= fColumn.getControl();
+        if (fControl is null)
+            return;
+
+        fControl.addDisposeListener(new DisposeListener() {
+            /*
+             * @see dwt.events.DisposeListener#widgetDisposed(dwt.events.DisposeEvent)
+             */
+            public void widgetDisposed(DisposeEvent e) {
+                handleDispose();
+            }
+        });
+    }
+
+    /**
+     * Returns <code>true</code> if the column is fully connected.
+     * 
+     * @return <code>true</code> if the column is fully connected, false otherwise
+     */
+    private bool isConnected() {
+        return fControl !is null;
+    }
+
+    /**
+     * Disposes of this painter and releases any resources.
+     */
+    private void handleDispose() {
+        if (fLineDiffer !is null) {
+            ((IAnnotationModel) fLineDiffer).removeAnnotationModelListener(fAnnotationListener);
+            fLineDiffer= null;
+        }
+    }
+
+    /**
+     * Paints a single model line onto <code>gc</code>.
+     * 
+     * @param line the model line to paint
+     * @param gc the {@link GC} to paint onto
+     * @param width the width of the column
+     * @param deletionColor the background color used to indicate deletions
+     */
+    private void paintLine(int line, GC gc, int width, Color deletionColor) {
+        int widgetLine= JFaceTextUtil.modelLineToWidgetLine(fViewer, line);
+        if (widgetLine is -1)
+            return;
+
+        ILineDiffInfo info= getDiffInfo(line);
+
+        if (info !is null) {
+            int y= fWidget.getLinePixel(widgetLine);
+            int lineHeight= fWidget.getLineHeight(fWidget.getOffsetAtLine(widgetLine));
+
+            // draw background color if special
+            if (hasSpecialColor(info)) {
+                gc.setBackground(getColor(info));
+                gc.fillRectangle(0, y, width, lineHeight);
+            }
+
+            /* Deletion Indicator: Simply a horizontal line */
+            int delBefore= info.getRemovedLinesAbove();
+            int delBelow= info.getRemovedLinesBelow();
+            if (delBefore > 0 || delBelow > 0) {
+                gc.setForeground(deletionColor);
+                if (delBefore > 0)
+                    gc.drawLine(0, y, width, y);
+                if (delBelow > 0)
+                    gc.drawLine(0, y + lineHeight - 1, width, y + lineHeight - 1);
+            }
+        }
+    }
+
+    /**
+     * Returns whether the line background differs from the default.
+     * 
+     * @param info the info being queried
+     * @return <code>true</code> if <code>info</code> describes either a changed or an added
+     *         line.
+     */
+    private bool hasSpecialColor(ILineDiffInfo info) {
+        return info.getChangeType() is ILineDiffInfo.ADDED || info.getChangeType() is ILineDiffInfo.CHANGED;
+    }
+
+    /**
+     * Retrieves the <code>ILineDiffInfo</code> for <code>line</code> from the model. There are
+     * optimizations for direct access and sequential access patterns.
+     * 
+     * @param line the line we want the info for.
+     * @return the <code>ILineDiffInfo</code> for <code>line</code>, or <code>null</code>.
+     */
+    private ILineDiffInfo getDiffInfo(int line) {
+        if (fLineDiffer !is null)
+            return fLineDiffer.getLineInfo(line);
+
+        return null;
+    }
+
+    /**
+     * Returns the color for deleted lines.
+     * 
+     * @return the color to be used for the deletion indicator
+     */
+    private Color getDeletionColor() {
+        return fDeletedColor is null ? getBackground() : fDeletedColor;
+    }
+
+    /**
+     * Returns the color for the given line diff info.
+     * 
+     * @param info the <code>ILineDiffInfo</code> being queried
+     * @return the correct background color for the line type being described by <code>info</code>
+     */
+    private Color getColor(ILineDiffInfo info) {
+        Assert.isTrue(info !is null && info.getChangeType() !is ILineDiffInfo.UNCHANGED);
+        Color ret= null;
+        switch (info.getChangeType()) {
+            case ILineDiffInfo.CHANGED:
+                ret= getShadedColor(fChangedColor);
+                break;
+            case ILineDiffInfo.ADDED:
+                ret= getShadedColor(fAddedColor);
+                break;
+        }
+        return ret is null ? getBackground() : ret;
+    }
+
+    /**
+     * Sets the background color for changed lines.
+     * 
+     * @param color the new color to be used for the changed lines background
+     * @return the shaded color
+     */
+    private Color getShadedColor(Color color) {
+        if (color is null)
+            return null;
+
+        if (fSharedColors is null)
+            return color;
+
+        RGB baseRGB= color.getRGB();
+        RGB background= getBackground().getRGB();
+
+        bool darkBase= isDark(baseRGB);
+        bool darkBackground= isDark(background);
+        if (darkBase && darkBackground)
+            background= new RGB(255, 255, 255);
+        else if (!darkBase && !darkBackground)
+            background= new RGB(0, 0, 0);
+
+        return fSharedColors.getColor(interpolate(baseRGB, background, 0.6));
+    }
+
+    /**
+     * Sets the annotation model.
+     * 
+     * @param model the annotation model, possibly <code>null</code>
+     * @see IVerticalRulerColumn#setModel(IAnnotationModel)
+     */
+    public void setModel(IAnnotationModel model) {
+        IAnnotationModel newModel;
+        if (model instanceof IAnnotationModelExtension)
+            newModel= ((IAnnotationModelExtension) model).getAnnotationModel(IChangeRulerColumn.QUICK_DIFF_MODEL_ID);
+        else
+            newModel= model;
+
+        setDiffer(newModel);
+    }
+
+    /**
+     * Sets the line differ.
+     * 
+     * @param differ the line differ
+     */
+    private void setDiffer(IAnnotationModel differ) {
+        if (differ instanceof ILineDiffer) {
+            if (fLineDiffer !is differ) {
+                if (fLineDiffer !is null)
+                    ((IAnnotationModel) fLineDiffer).removeAnnotationModelListener(fAnnotationListener);
+                fLineDiffer= (ILineDiffer) differ;
+                if (fLineDiffer !is null)
+                    ((IAnnotationModel) fLineDiffer).addAnnotationModelListener(fAnnotationListener);
+            }
+        }
+    }
+
+    /**
+     * Triggers a redraw in the display thread.
+     */
+    private final void postRedraw() {
+        if (isConnected() && !fControl.isDisposed()) {
+            Display d= fControl.getDisplay();
+            if (d !is null) {
+                d.asyncExec(new Runnable() {
+                    public void run() {
+                        redraw();
+                    }
+                });
+            }
+        }
+    }
+
+    /**
+     * Triggers redrawing of the column.
+     */
+    private void redraw() {
+        fColumn.redraw();
+    }
+
+    /**
+     * Returns the width of the column.
+     * 
+     * @return the width of the column
+     */
+    private int getWidth() {
+        return fColumn.getWidth();
+    }
+
+    /**
+     * Computes the end index of a line range.
+     * 
+     * @param range a line range
+     * @return the last line (exclusive) of <code>range</code>
+     */
+    private static int end(ILineRange range) {
+        return range.getStartLine() + range.getNumberOfLines();
+    }
+
+    /**
+     * Returns the System background color for list widgets or the set background.
+     * 
+     * @return the System background color for list widgets
+     */
+    private Color getBackground() {
+        if (fBackground is null)
+            return fWidget.getDisplay().getSystemColor(DWT.COLOR_LIST_BACKGROUND);
+        return fBackground;
+    }
+
+    /**
+     * Sets the color for added lines.
+     * 
+     * @param addedColor the color for added lines
+     * @see dwtx.jface.text.source.IChangeRulerColumn#setAddedColor(dwt.graphics.Color)
+     */
+    public void setAddedColor(Color addedColor) {
+        fAddedColor= addedColor;
+    }
+
+    /**
+     * Sets the color for changed lines.
+     * 
+     * @param changedColor the color for changed lines
+     * @see dwtx.jface.text.source.IChangeRulerColumn#setChangedColor(dwt.graphics.Color)
+     */
+    public void setChangedColor(Color changedColor) {
+        fChangedColor= changedColor;
+    }
+
+    /**
+     * Sets the color for deleted lines.
+     * 
+     * @param deletedColor the color for deleted lines
+     * @see dwtx.jface.text.source.IChangeRulerColumn#setDeletedColor(dwt.graphics.Color)
+     */
+    public void setDeletedColor(Color deletedColor) {
+        fDeletedColor= deletedColor;
+    }
+
+    /**
+     * Returns <code>true</code> if the receiver can provide a hover for a certain document line.
+     * 
+     * @param activeLine the document line of interest
+     * @return <code>true</code> if the receiver can provide a hover
+     */
+    public bool hasHover(int activeLine) {
+        return true;
+    }
+
+    /**
+     * Returns the display character for the accessibility mode for a certain model line.
+     * 
+     * @param line the document line of interest
+     * @return the display character for <code>line</code>
+     */
+    public String getDisplayCharacter(int line) {
+        return getDisplayCharacter(getDiffInfo(line));
+    }
+
+    /**
+     * Returns the character to display in character display mode for the given
+     * <code>ILineDiffInfo</code>
+     * 
+     * @param info the <code>ILineDiffInfo</code> being queried
+     * @return the character indication for <code>info</code>
+     */
+    private String getDisplayCharacter(ILineDiffInfo info) {
+        if (info is null)
+            return " "; //$NON-NLS-1$
+        switch (info.getChangeType()) {
+            case ILineDiffInfo.CHANGED:
+                return "~"; //$NON-NLS-1$
+            case ILineDiffInfo.ADDED:
+                return "+"; //$NON-NLS-1$
+        }
+        return " "; //$NON-NLS-1$
+    }
+
+    /**
+     * Returns a specification of a color that lies between the given foreground and background
+     * color using the given scale factor.
+     * 
+     * @param fg the foreground color
+     * @param bg the background color
+     * @param scale the scale factor
+     * @return the interpolated color
+     */
+    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));
+    }
+
+    /**
+     * Returns the grey value in which the given color would be drawn in grey-scale.
+     * 
+     * @param rgb the color
+     * @return the grey-scale value
+     */
+    private static double greyLevel(RGB rgb) {
+        if (rgb.red is rgb.green && rgb.green is rgb.blue)
+            return rgb.red;
+        return (0.299 * rgb.red + 0.587 * rgb.green + 0.114 * rgb.blue + 0.5);
+    }
+
+    /**
+     * Returns whether the given color is dark or light depending on the colors grey-scale level.
+     * 
+     * @param rgb the color
+     * @return <code>true</code> if the color is dark, <code>false</code> if it is light
+     */
+    private static bool isDark(RGB rgb) {
+        return greyLevel(rgb) > 128;
+    }
+
+    /**
+     * Returns <code>true</code> if diff information is being displayed, <code>false</code> otherwise.
+     * 
+     * @return <code>true</code> if diff information is being displayed, <code>false</code> otherwise
+     * @since 3.3
+     */
+    public bool hasInformation() {
+        if (fLineDiffer instanceof ILineDifferExtension2)
+            return !((ILineDifferExtension2) fLineDiffer).isSuspended();
+        return fLineDiffer !is null;
+    }
+
+}