jp.sourceforge.pdt_tools.indentguide.IndentGuidePainter.java Source code

Java tutorial

Introduction

Here is the source code for jp.sourceforge.pdt_tools.indentguide.IndentGuidePainter.java

Source

/*******************************************************************************
 * Copyright (c) 2006, 2009 Wind River Systems, Inc., 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:
 *     Anton Leherbauer (Wind River Systems) - initial API and implementation - https://bugs.eclipse.org/bugs/show_bug.cgi?id=22712
 *     Anton Leherbauer (Wind River Systems) - [painting] Long lines take too long to display when "Show Whitespace Characters" is enabled - https://bugs.eclipse.org/bugs/show_bug.cgi?id=196116
 *     Anton Leherbauer (Wind River Systems) - [painting] Whitespace characters not drawn when scrolling to right slowly - https://bugs.eclipse.org/bugs/show_bug.cgi?id=206633
 *     Tom Eicher (Avaloq Evolution AG) - block selection mode
 *******************************************************************************/
package jp.sourceforge.pdt_tools.indentguide;

import jp.sourceforge.pdt_tools.indentguide.preferences.PreferenceConstants;

import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IPaintPositionManager;
import org.eclipse.jface.text.IPainter;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.ITextViewerExtension5;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.custom.StyledTextContent;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.LineAttributes;
import org.eclipse.swt.graphics.Point;

/**
 * A painter for drawing visible characters for (invisible) whitespace
 * characters.
 * 
 * @since 3.3
 * @see org.eclipse.jface.text.WhitespaceCharacterPainter
 */
public class IndentGuidePainter implements IPainter, PaintListener {

    /** Indicates whether this painter is active. */
    private boolean fIsActive = false;
    /** The source viewer this painter is attached to. */
    private ITextViewer fTextViewer;
    /** The viewer's widget. */
    private StyledText fTextWidget;
    /** Tells whether the advanced graphics sub system is available. */
    private final boolean fIsAdvancedGraphicsPresent;

    private int lineAlpha;
    private int lineStyle;
    private int lineWidth;
    private int lineShift;
    private int spaceWidth;
    private boolean drawLeftEnd;
    private boolean drawBlankLine;
    private boolean skipCommentBlock;

    /**
     * Creates a new painter for the given text viewer.
     * 
     * @param textViewer
     *            the text viewer the painter should be attached to
     */
    public IndentGuidePainter(ITextViewer textViewer) {
        super();
        fTextViewer = textViewer;
        fTextWidget = textViewer.getTextWidget();
        GC gc = new GC(fTextWidget);
        gc.setAdvanced(true);
        fIsAdvancedGraphicsPresent = gc.getAdvanced();
        gc.dispose();

        IPreferenceStore store = Activator.getDefault().getPreferenceStore();
        lineAlpha = store.getInt(PreferenceConstants.LINE_ALPHA);
        lineStyle = store.getInt(PreferenceConstants.LINE_STYLE);
        lineWidth = store.getInt(PreferenceConstants.LINE_WIDTH);
        lineShift = store.getInt(PreferenceConstants.LINE_SHIFT);
        drawLeftEnd = store.getBoolean(PreferenceConstants.DRAW_LEFT_END);
        drawBlankLine = store.getBoolean(PreferenceConstants.DRAW_BLANK_LINE);
        skipCommentBlock = store.getBoolean(PreferenceConstants.SKIP_COMMENT_BLOCK);
    }

    /*
     * @see org.eclipse.jface.text.IPainter#dispose()
     */
    public void dispose() {
        fTextViewer = null;
        fTextWidget = null;
    }

    /*
     * @see org.eclipse.jface.text.IPainter#paint(int)
     */
    public void paint(int reason) {
        IDocument document = fTextViewer.getDocument();
        if (document == null) {
            deactivate(false);
            return;
        }
        if (!fIsActive) {
            fIsActive = true;
            fTextWidget.addPaintListener(this);
            redrawAll();
        } else if (reason == CONFIGURATION || reason == INTERNAL) {
            redrawAll();
        } else if (reason == TEXT_CHANGE) {
            // redraw current line only
            try {
                IRegion lineRegion = document
                        .getLineInformationOfOffset(getDocumentOffset(fTextWidget.getCaretOffset()));
                int widgetOffset = getWidgetOffset(lineRegion.getOffset());
                int charCount = fTextWidget.getCharCount();
                int redrawLength = Math.min(lineRegion.getLength(), charCount - widgetOffset);
                if (widgetOffset >= 0 && redrawLength > 0) {
                    fTextWidget.redrawRange(widgetOffset, redrawLength, true);
                }
            } catch (BadLocationException e) {
                // ignore
            }
        }
    }

    /*
     * @see org.eclipse.jface.text.IPainter#deactivate(boolean)
     */
    public void deactivate(boolean redraw) {
        if (fIsActive) {
            fIsActive = false;
            fTextWidget.removePaintListener(this);
            if (redraw) {
                redrawAll();
            }
        }
    }

    /*
     * @see
     * org.eclipse.jface.text.IPainter#setPositionManager(org.eclipse.jface.
     * text.IPaintPositionManager)
     */
    public void setPositionManager(IPaintPositionManager manager) {
        // no need for a position manager
    }

    /*
     * @see
     * org.eclipse.swt.events.PaintListener#paintControl(org.eclipse.swt.events
     * .PaintEvent)
     */
    public void paintControl(PaintEvent event) {
        if (fTextWidget != null) {
            handleDrawRequest(event.gc, event.x, event.y, event.width, event.height);
        }
    }

    /*
     * Draw characters in view range.
     */
    private void handleDrawRequest(GC gc, int x, int y, int w, int h) {
        int startLine = fTextWidget.getLineIndex(y);
        int endLine = fTextWidget.getLineIndex(y + h - 1);
        if (startLine <= endLine && startLine < fTextWidget.getLineCount()) {
            Color fgColor = gc.getForeground();
            LineAttributes lineAttributes = gc.getLineAttributes();
            gc.setForeground(Activator.getDefault().getColor());
            gc.setLineStyle(lineStyle);
            gc.setLineWidth(lineWidth);
            spaceWidth = gc.getAdvanceWidth(' ');
            if (fIsAdvancedGraphicsPresent) {
                int alpha = gc.getAlpha();
                gc.setAlpha(this.lineAlpha);
                drawLineRange(gc, startLine, endLine, x, w);
                gc.setAlpha(alpha);
            } else {
                drawLineRange(gc, startLine, endLine, x, w);
            }
            gc.setForeground(fgColor);
            gc.setLineAttributes(lineAttributes);
        }
    }

    /**
     * Draw the given line range.
     * 
     * @param gc
     *            the GC
     * @param startLine
     *            first line number
     * @param endLine
     *            last line number (inclusive)
     * @param x
     *            the X-coordinate of the drawing range
     * @param w
     *            the width of the drawing range
     */
    private void drawLineRange(GC gc, int startLine, int endLine, int x, int w) {
        int tabs = fTextWidget.getTabs();

        StyledTextContent content = fTextWidget.getContent();
        for (int line = startLine; line <= endLine; line++) {
            int widgetOffset = fTextWidget.getOffsetAtLine(line);
            if (!isFoldedLine(content.getLineAtOffset(widgetOffset))) {
                String text = fTextWidget.getLine(line);
                int extend = 0;
                if (skipCommentBlock && assumeCommentBlock(text, tabs)) {
                    extend -= tabs;
                }
                if (drawBlankLine && text.trim().length() == 0) {
                    int prevLine = line;
                    while (--prevLine >= 0) {
                        text = fTextWidget.getLine(prevLine);
                        if (text.trim().length() > 0) {
                            int postLine = line;
                            int lineCount = fTextWidget.getLineCount();
                            while (++postLine < lineCount) {
                                String tmp = fTextWidget.getLine(postLine);
                                if (tmp.trim().length() > 0) {
                                    if (countSpaces(text, tabs) < countSpaces(tmp, tabs)) {
                                        extend += tabs;
                                    }
                                    break;
                                }
                            }
                            break;
                        }
                    }
                }
                int count = countSpaces(text, tabs) + extend;
                for (int i = drawLeftEnd ? 0 : tabs; i < count; i += tabs) {
                    draw(gc, widgetOffset, i);
                }
            }
        }
    }

    private int countSpaces(String str, int tabs) {
        int count = 0;
        for (int i = 0; i < str.length(); i++) {
            switch (str.charAt(i)) {
            case ' ':
                count++;
                break;
            case '\t':
                int z = tabs - count % tabs;
                count += z;
                break;
            default:
                return count;
            }
        }
        return count;
    }

    private boolean assumeCommentBlock(String text, int tabs) {
        int count = countSpaces(text, tabs);
        count = (count / tabs) * tabs;
        int index = 0;
        for (int i = 0; i < count; i++) {
            switch (text.charAt(index)) {
            case ' ':
                index++;
                break;
            case '\t':
                index++;
                int z = tabs - i % tabs;
                i += z;
                break;
            default:
                i = count;
            }
        }
        text = text.substring(index);
        if (text.matches("^ \\*([ \\t].*|/.*|)$")) {
            return true;
        }
        return false;
    }

    /**
     * Check if the given widget line is a folded line.
     * 
     * @param widgetLine
     *            the widget line number
     * @return <code>true</code> if the line is folded
     */
    private boolean isFoldedLine(int widgetLine) {
        if (fTextViewer instanceof ITextViewerExtension5) {
            ITextViewerExtension5 extension = (ITextViewerExtension5) fTextViewer;
            int modelLine = extension.widgetLine2ModelLine(widgetLine);
            int widgetLine2 = extension.modelLine2WidgetLine(modelLine + 1);
            return widgetLine2 == -1;
        }
        return false;
    }

    /**
     * Redraw all of the text widgets visible content.
     */
    private void redrawAll() {
        fTextWidget.redraw();
    }

    /**
     * 
     * @param gc
     * @param offset
     * @param column
     */
    private void draw(GC gc, int offset, int column) {
        Point pos = fTextWidget.getLocationAtOffset(offset);
        pos.x += column * spaceWidth + lineShift;
        gc.drawLine(pos.x, pos.y, pos.x, pos.y + fTextWidget.getLineHeight(offset));
    }

    /**
     * Convert a document offset to the corresponding widget offset.
     * 
     * @param documentOffset
     *            the document offset
     * @return widget offset
     */
    private int getWidgetOffset(int documentOffset) {
        if (fTextViewer instanceof ITextViewerExtension5) {
            ITextViewerExtension5 extension = (ITextViewerExtension5) fTextViewer;
            return extension.modelOffset2WidgetOffset(documentOffset);
        }
        IRegion visible = fTextViewer.getVisibleRegion();
        int widgetOffset = documentOffset - visible.getOffset();
        if (widgetOffset > visible.getLength()) {
            return -1;
        }
        return widgetOffset;
    }

    /**
     * Convert a widget offset to the corresponding document offset.
     * 
     * @param widgetOffset
     *            the widget offset
     * @return document offset
     */
    private int getDocumentOffset(int widgetOffset) {
        if (fTextViewer instanceof ITextViewerExtension5) {
            ITextViewerExtension5 extension = (ITextViewerExtension5) fTextViewer;
            return extension.widgetOffset2ModelOffset(widgetOffset);
        }
        IRegion visible = fTextViewer.getVisibleRegion();
        if (widgetOffset > visible.getLength()) {
            return -1;
        }
        return widgetOffset + visible.getOffset();
    }

}