| /******************************************************************************* |
| * Copyright (c) 2000, 2015 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| * Tom Eicher (Avaloq Evolution AG) - block selection mode |
| * Holger Voormann - Word Wrap - https://bugs.eclipse.org/bugs/show_bug.cgi?id=35779 |
| * Florian Weßling <flo@cdhq.de> - Word Wrap - https://bugs.eclipse.org/bugs/show_bug.cgi?id=35779 |
| *******************************************************************************/ |
| |
| package org.eclipse.jface.text; |
| |
| |
| import org.eclipse.swt.custom.LineBackgroundEvent; |
| import org.eclipse.swt.custom.LineBackgroundListener; |
| import org.eclipse.swt.custom.StyledText; |
| import org.eclipse.swt.graphics.Color; |
| import org.eclipse.swt.graphics.Point; |
| |
| |
| /** |
| * A painter the draws the background of the caret line in a configured color. |
| * <p> |
| * Clients usually instantiate and configure object of this class.</p> |
| * <p> |
| * This class is not intended to be subclassed.</p> |
| * |
| * @since 2.1 |
| * @noextend This class is not intended to be subclassed by clients. |
| */ |
| public class CursorLinePainter implements IPainter, LineBackgroundListener { |
| |
| /** The viewer the painter works on */ |
| private final ITextViewer fViewer; |
| /** The cursor line back ground color */ |
| private Color fHighlightColor; |
| /** The paint position manager for managing the line coordinates */ |
| private IPaintPositionManager fPositionManager; |
| |
| /** Keeps track of the line to be painted */ |
| private Position fCurrentLine= new Position(0, 0); |
| /** Keeps track of the line to be cleared */ |
| private Position fLastLine= new Position(0, 0); |
| /** Keeps track of the line number of the last painted line */ |
| private int fLastLineNumber= -1; |
| /** Indicates whether this painter is active */ |
| private boolean fIsActive; |
| |
| /** |
| * Creates a new painter for the given source viewer. |
| * |
| * @param textViewer the source viewer for which to create a painter |
| */ |
| public CursorLinePainter(ITextViewer textViewer) { |
| fViewer= textViewer; |
| } |
| |
| /** |
| * Sets the color in which to draw the background of the cursor line. |
| * |
| * @param highlightColor the color in which to draw the background of the cursor line |
| */ |
| public void setHighlightColor(Color highlightColor) { |
| fHighlightColor= highlightColor; |
| } |
| |
| @Override |
| public void lineGetBackground(LineBackgroundEvent event) { |
| // don't use cached line information because of asynchronous painting |
| |
| StyledText textWidget= fViewer.getTextWidget(); |
| if (textWidget != null) { |
| |
| int caret= textWidget.getCaretOffset(); |
| int length= event.lineText.length(); |
| |
| if (event.lineOffset <= caret && caret <= event.lineOffset + length && !hasMultiLineSelection(textWidget)) |
| event.lineBackground= fHighlightColor; |
| } |
| } |
| |
| /** |
| * Updates all the cached information about the lines to be painted and to be cleared. Returns <code>true</code> |
| * if the line number of the cursor line has changed. |
| * |
| * @return <code>true</code> if cursor line changed |
| */ |
| private boolean updateHighlightLine() { |
| try { |
| |
| IDocument document= fViewer.getDocument(); |
| int modelCaret= getModelCaret(); |
| int lineNumber= document.getLineOfOffset(modelCaret); |
| |
| // redraw if the current line number is different from the last line number we painted |
| // initially fLastLineNumber is -1 |
| if (lineNumber != fLastLineNumber || !fCurrentLine.overlapsWith(modelCaret, 0)) { |
| |
| fLastLine.offset= fCurrentLine.offset; |
| fLastLine.length= fCurrentLine.length; |
| fLastLine.isDeleted= fCurrentLine.isDeleted; |
| |
| if (fCurrentLine.isDeleted) { |
| fCurrentLine.isDeleted= false; |
| fPositionManager.managePosition(fCurrentLine); |
| } |
| |
| fCurrentLine.offset= document.getLineOffset(lineNumber); |
| if (lineNumber == document.getNumberOfLines() - 1) |
| fCurrentLine.length= document.getLength() - fCurrentLine.offset; |
| else |
| fCurrentLine.length= document.getLineOffset(lineNumber + 1) - fCurrentLine.offset; |
| |
| fLastLineNumber= lineNumber; |
| return true; |
| |
| } |
| |
| } catch (BadLocationException e) { |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Returns the location of the caret as offset in the source viewer's |
| * input document. |
| * |
| * @return the caret location |
| */ |
| private int getModelCaret() { |
| int widgetCaret= fViewer.getTextWidget().getCaretOffset(); |
| if (fViewer instanceof ITextViewerExtension5) { |
| ITextViewerExtension5 extension= (ITextViewerExtension5) fViewer; |
| return extension.widgetOffset2ModelOffset(widgetCaret); |
| } |
| IRegion visible= fViewer.getVisibleRegion(); |
| return widgetCaret + visible.getOffset(); |
| } |
| |
| /** |
| * Assumes the given position to specify offset and length of a line to be painted. |
| * |
| * @param position the specification of the line to be painted |
| */ |
| private void drawHighlightLine(Position position) { |
| |
| // if the position that is about to be drawn was deleted then we can't |
| if (position.isDeleted()) |
| return; |
| |
| int widgetOffset= 0; |
| if (fViewer instanceof ITextViewerExtension5) { |
| |
| ITextViewerExtension5 extension= (ITextViewerExtension5) fViewer; |
| widgetOffset= extension.modelOffset2WidgetOffset(position.getOffset()); |
| if (widgetOffset == -1) |
| return; |
| |
| } else { |
| |
| IRegion visible= fViewer.getVisibleRegion(); |
| widgetOffset= position.getOffset() - visible.getOffset(); |
| if (widgetOffset < 0 || visible.getLength() < widgetOffset ) |
| return; |
| } |
| |
| StyledText textWidget= fViewer.getTextWidget(); |
| // check for https://bugs.eclipse.org/bugs/show_bug.cgi?id=64898 |
| // this is a guard against the symptoms but not the actual solution |
| int charCount= textWidget.getCharCount(); |
| if (widgetOffset > charCount) { |
| return; |
| } |
| Point upperLeft= textWidget.getLocationAtOffset(widgetOffset); |
| int width= textWidget.getClientArea().width + textWidget.getHorizontalPixel(); |
| |
| // different height if word wrap is activated and line is not empty |
| int height; |
| if (position.length == 0 || !textWidget.getWordWrap()) { |
| height= textWidget.getLineHeight(widgetOffset); |
| } else { |
| int offsetEnd= widgetOffset + position.length - 1; |
| if (offsetEnd >= charCount) { |
| offsetEnd= charCount - 1; |
| } |
| height= textWidget.getTextBounds(widgetOffset, offsetEnd).height; |
| } |
| textWidget.redraw(0, upperLeft.y, width, height, false); |
| } |
| |
| @Override |
| public void deactivate(boolean redraw) { |
| if (fIsActive) { |
| fIsActive= false; |
| |
| /* on turning off the feature one has to paint the currently |
| * highlighted line with the standard background color |
| */ |
| if (redraw) |
| drawHighlightLine(fCurrentLine); |
| |
| fViewer.getTextWidget().removeLineBackgroundListener(this); |
| |
| if (fPositionManager != null) |
| fPositionManager.unmanagePosition(fCurrentLine); |
| |
| fLastLineNumber= -1; |
| fCurrentLine.offset= 0; |
| fCurrentLine.length= 0; |
| } |
| } |
| |
| @Override |
| public void dispose() { |
| } |
| |
| @Override |
| public void paint(int reason) { |
| if (fViewer.getDocument() == null) { |
| deactivate(false); |
| return; |
| } |
| |
| StyledText textWidget= fViewer.getTextWidget(); |
| |
| // check selection |
| if (hasMultiLineSelection(textWidget)) { |
| deactivate(true); |
| return; |
| } |
| |
| // initialization |
| if (!fIsActive) { |
| textWidget.addLineBackgroundListener(this); |
| fPositionManager.managePosition(fCurrentLine); |
| fIsActive= true; |
| } |
| |
| //redraw line highlight only if it hasn't been drawn yet on the respective line |
| if (updateHighlightLine()) { |
| // clear last line |
| drawHighlightLine(fLastLine); |
| // draw new line |
| drawHighlightLine(fCurrentLine); |
| } |
| } |
| |
| /** |
| * Returns <code>true</code> if the widget has a selection spanning multiple lines, |
| * <code>false</code> otherwise. |
| * |
| * @param textWidget the text widget to check |
| * @return <code>true</code> if <code>textWidget</code> has a multiline selection, |
| * <code>false</code> otherwise |
| * @since 3.5 |
| */ |
| private boolean hasMultiLineSelection(StyledText textWidget) { |
| Point selection= textWidget.getSelection(); |
| try { |
| int startLine= textWidget.getLineAtOffset(selection.x); |
| int endLine= textWidget.getLineAtOffset(selection.y); |
| return startLine != endLine; |
| } catch (IllegalArgumentException e) { |
| // ignore - apparently, the widget has a stale selection |
| // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=273721 |
| return false; |
| } |
| } |
| |
| @Override |
| public void setPositionManager(IPaintPositionManager manager) { |
| fPositionManager = manager; |
| } |
| } |