Commit 82132a46 authored by Andrey Filippov's avatar Andrey Filippov

added LineBackgroundPainter.java from CDT project as it has the same EPL

parent 5e846e99
/*******************************************************************************
* Copyright (c) 2007, 2011 Wind River Systems, Inc. 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
*******************************************************************************/
package com.elphel.vdt.veditor.cdt;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
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.jface.text.Position;
import org.eclipse.jface.text.TypedPosition;
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;
import org.eclipse.swt.graphics.Rectangle;
/**
* A painter for configurable background painting a range of text lines.
* Replicates also the functionality of the
* {@link org.eclipse.jface.text.CursorLinePainter}
* because only one {@link LineBackgroundListener} is allowed
* per {@link StyledText} widget.
*
* @author anton.leherbauer@windriver.com
*
* @since 4.0
*/
public class LineBackgroundPainter implements IPainter, LineBackgroundListener {
/** The default position type for untyped positions */
private static final String DEFAULT_TYPE= "__default__"; //$NON-NLS-1$
/** The position type for the cursor line position */
private static final String CURSOR_LINE_TYPE= "__cursor_line__"; //$NON-NLS-1$
/** Manager for position changes */
private IPaintPositionManager fPositionManager;
/** Indicates whether this painter is active */
private boolean fIsActive= false;
/** The text viewer this painter is associated with */
private ITextViewer fTextViewer;
/** The viewer's widget */
private StyledText fTextWidget;
/** Text positions (cursor line position is always at index 0 */
private List<Position> fPositions= new ArrayList<Position>();
/** Cached text positions */
private List<Position> fLastPositions= new ArrayList<Position>();
/** Temporary changed positions */
private List<Position> fChangedPositions= new ArrayList<Position>();
/** Cursor line position */
private Position fCursorLine= new TypedPosition(0, 0, CURSOR_LINE_TYPE);
/** Saved cursor line position */
private Position fLastCursorLine= new Position(0, 0);
/** Enablement of the cursor line highlighting */
private boolean fCursorLineEnabled;
/** Whether cursor line highlighting is active */
private boolean fCursorLineActive;
/** Map of position type to color */
private Map<String, Color> fColorMap= new HashMap<String, Color>();
/**
* Creates a new painter for the given text viewer.
* @param textViewer
*/
public LineBackgroundPainter(ITextViewer textViewer) {
super();
fTextViewer= textViewer;
fTextWidget= textViewer.getTextWidget();
fPositions.add(fCursorLine);
fLastPositions.add(fLastCursorLine);
}
/**
* Sets the color in which to draw the background of the given position type.
*
* @param positionType the position type for which to specify the background color
* @param color the color in which to draw the background of the given position type
*/
public void setBackgroundColor(String positionType, Color color) {
fColorMap.put(positionType, color);
}
/**
* Sets the color in which to draw the background of the cursor line.
*
* @param cursorLineColor the color in which to draw the background of the cursor line
*/
public void setCursorLineColor(Color cursorLineColor) {
fColorMap.put(CURSOR_LINE_TYPE, cursorLineColor);
}
/**
* Sets the color in which to draw the background of untyped positions.
*
* @param color the color in which to draw the background of untyped positions
*/
public void setDefaultColor(Color color) {
fColorMap.put(DEFAULT_TYPE, color);
}
/**
* Enable/disable cursor line highlighting.
*
* @param enable
*/
public void enableCursorLine(boolean enable) {
fCursorLineEnabled= enable;
fCursorLineActive= enable;
if (fCursorLineActive) {
updateCursorLine();
}
}
/**
* Set highlight positions. It is assumed that all positions
* are up-to-date with respect to the text viewer document.
*
* @param positions a list of <code>Position</code>s
*/
public void setHighlightPositions(List<Position> positions) {
boolean isActive= fIsActive;
deactivate(isActive);
fPositions.clear();
fPositions.add(fCursorLine);
fPositions.addAll(positions);
if (isActive) {
activate(true);
}
}
/**
* Add highlight positions. It is assumed that all positions
* are up-to-date with respect to the text viewer document.
*
* @param positions a list of <code>Position</code>s
*/
public void addHighlightPositions(List<Position> positions) {
boolean isActive= fIsActive;
deactivate(isActive);
fPositions.addAll(positions);
if (isActive) {
activate(true);
}
}
/**
* Remove highlight positions by identity.
*
* @param positions a list of <code>Position</code>s
*/
public void removeHighlightPositions(List<Position> positions) {
boolean isActive= fIsActive;
deactivate(isActive);
fPositions.removeAll(positions);
if (isActive) {
activate(true);
}
}
/**
* Replace given highlight positions in one step.
*
* @param removePositions a list of <code>Position</code>s to remove
* @param addPositions a list of <code>Position</code>s to add
*/
public void replaceHighlightPositions(List<Position> removePositions, List<Position> addPositions) {
boolean isActive= fIsActive;
deactivate(isActive);
fPositions.removeAll(removePositions);
fPositions.addAll(addPositions);
if (isActive) {
activate(true);
}
}
/**
* Trigger redraw of managed positions.
*/
public void redraw() {
if(fIsActive) {
fTextWidget.redraw();
}
}
/**
* Manage all positions.
* @param positions
*/
private void managePositions(List<Position> positions) {
if (fPositionManager == null) {
return;
}
int sz= fPositions.size();
for (int i= 0; i < sz; ++i) {
Position position= positions.get(i);
fPositionManager.managePosition(position);
}
}
/**
* Unmanage all positions.
* @param positions
*/
private void unmanagePositions(List<Position> positions) {
if (fPositionManager == null) {
return;
}
int sz= fPositions.size();
for (int i= 0; i < sz; ++i) {
Position position= positions.get(i);
fPositionManager.unmanagePosition(position);
}
}
/*
* @see org.eclipse.jface.text.IPainter#dispose()
*/
@Override
public void dispose() {
// no deactivate!
fIsActive= false;
fTextViewer= null;
fTextWidget= null;
fCursorLine= null;
fLastCursorLine= null;
fPositions= null;
fLastPositions= null;
fChangedPositions= null;
fColorMap= null;
}
/**
* Query whether this painter is already disposed.
* @return <code>true</code> if the painter is disposed
*/
public boolean isDisposed() {
return fTextViewer == null;
}
/*
* @see org.eclipse.jface.text.IPainter#paint(int)
*/
@Override
public void paint(int reason) {
IDocument document= fTextViewer.getDocument();
if (document == null) {
deactivate(false);
return;
}
activate(false);
if (fCursorLineEnabled) {
// check selection
StyledText textWidget= fTextViewer.getTextWidget();
Point selection= textWidget.getSelection();
int startLine= textWidget.getLineAtOffset(selection.x);
int endLine= textWidget.getLineAtOffset(selection.y);
if (startLine != endLine) {
redrawPositions(Collections.singletonList(fLastCursorLine));
fCursorLineActive= false;
} else {
fCursorLineActive= true;
}
if (fCursorLineActive) {
// redraw in case of text changes prior to update of current cursor line
if (!fLastCursorLine.equals(fCursorLine)) {
redrawPositions(Collections.singletonList(fLastCursorLine));
fLastCursorLine.offset= fCursorLine.offset;
fLastCursorLine.length= fCursorLine.length;
}
updateCursorLine();
}
}
List<Position> changedPositions= getChangedPositions();
if (changedPositions != null) {
redrawPositions(changedPositions);
updatePositions();
redrawPositions(changedPositions);
}
}
/**
* Activate the painter.
* @param redraw
*/
private void activate(boolean redraw) {
if (!fIsActive) {
fIsActive= true;
fTextWidget.addLineBackgroundListener(this);
if (redraw) {
if (fCursorLineActive) {
updateCursorLine();
}
updatePositions();
redrawPositions(fPositions);
}
managePositions(fPositions);
}
}
/**
* Copy positions from the current position list to the last position list.
*/
private void updatePositions() {
int sz= fPositions.size();
for (int i= 0; i < sz; ++i) {
Position position= fPositions.get(i);
Position copy;
if (i == fLastPositions.size()) {
copy= new Position(position.offset, position.length);
copy.isDeleted= position.isDeleted;
fLastPositions.add(copy);
} else {
copy= fLastPositions.get(i);
copy.offset= position.offset;
copy.length= position.length;
copy.isDeleted= position.isDeleted;
}
position.isDeleted= false;
}
int diff= fLastPositions.size() - sz;
while (diff > 0) {
--diff;
fLastPositions.remove(sz + diff);
}
}
/**
* Check which positions have changed since last redraw.
* @return a list of changed positions or <code>null</code> if none changed.
*/
private List<Position> getChangedPositions() {
if (fLastPositions.size() != fPositions.size()) {
return fLastPositions;
}
List<Position> changedPositions= null;
for (int i= 0, sz= fPositions.size(); i < sz; ++i) {
Position previous= fLastPositions.get(i);
Position current= fPositions.get(i);
if (!previous.equals(current)) {
if (changedPositions == null) {
changedPositions= fChangedPositions;
changedPositions.clear();
}
changedPositions.add(previous);
}
}
return changedPositions;
}
/**
* Trigger redraw of given text positions.
*
* @param positions
*/
private void redrawPositions(List<Position> positions) {
// TextViewer.getTopIndexStartOffset is buggy
// see https://bugs.eclipse.org/bugs/show_bug.cgi?id=174419
// final int minOffset= fTextViewer.getTopIndexStartOffset();
final int minOffset= getTopIndexStartOffset();
final int maxOffset= fTextViewer.getBottomIndexEndOffset()+3;
Rectangle clientArea= fTextWidget.getClientArea();
int width= clientArea.width + fTextWidget.getHorizontalPixel();
int lineHeight= fTextWidget.getLineHeight();
for (int i= 0, sz= positions.size(); i < sz; ++i) {
Position position= positions.get(i);
// if the position that is about to be drawn was deleted then we can't
if (position.isDeleted()) {
continue;
}
// check if position overlaps with visible area
if (!position.overlapsWith(minOffset, maxOffset - minOffset + 1)) {
continue;
}
int widgetOffset= getWidgetOffset(position.offset);
if (widgetOffset < 0 || widgetOffset > fTextWidget.getCharCount()) {
continue;
}
// TLETODO [performance] SyledText.getLocationAtOffset() is very expensive
Point upperLeft= fTextWidget.getLocationAtOffset(widgetOffset);
int upperY= Math.max(Math.min(upperLeft.y, clientArea.height), 0);
int height;
if (position.length == 0) {
height= lineHeight;
} else {
int widgetEndOffset= Math.min(widgetOffset + position.length, fTextWidget.getCharCount());
Point lowerRight= fTextWidget.getLocationAtOffset(widgetEndOffset);
int lowerY= Math.min(lowerRight.y + lineHeight, clientArea.height);
height= lowerY - upperY;
}
if (height > 0) {
fTextWidget.redraw(0, upperY, width, height, false);
}
}
}
/**
* Workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=174419
* @return the offset of the topmost visible line
* @see ITextViewer#getTopIndexStartOffset()
*/
private int getTopIndexStartOffset() {
if (fTextWidget != null) {
int top= fTextWidget.getTopIndex();
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=183653
top= fTextWidget.getOffsetAtLine(Math.min(fTextWidget.getLineCount() - 1, top));
if (top >= 0) {
return getDocumentOffset(top);
}
}
return -1;
}
/*
* @see org.eclipse.jface.text.IPainter#deactivate(boolean)
*/
@Override
public void deactivate(boolean redraw) {
if (fIsActive) {
fIsActive= false;
fTextWidget.removeLineBackgroundListener(this);
if (redraw) {
redrawPositions(fLastPositions);
}
unmanagePositions(fPositions);
}
}
/*
* @see org.eclipse.jface.text.IPainter#setPositionManager(org.eclipse.jface.text.IPaintPositionManager)
*/
@Override
public void setPositionManager(IPaintPositionManager manager) {
fPositionManager= manager;
}
/**
* Convert a document offset to the corresponding widget offset.
* @param documentOffset
* @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
* @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();
}
/*
* @see org.eclipse.swt.custom.LineBackgroundListener#lineGetBackground(org.eclipse.swt.custom.LineBackgroundEvent)
*/
@Override
public void lineGetBackground(LineBackgroundEvent event) {
if (fTextWidget != null) {
Position match= findIncludingPosition(getDocumentOffset(event.lineOffset));
if (match != null) {
Color color= getColorForPosition(match);
if (color != null) {
event.lineBackground= color;
}
}
}
}
/**
* Get the color associated with given position.
* @param position
* @return the color associated with the position type
*/
private Color getColorForPosition(Position position) {
if (position == fCursorLine) {
if (fCursorLine.length == 0) {
return fColorMap.get(CURSOR_LINE_TYPE);
}
} else {
if (position instanceof TypedPosition) {
String type= ((TypedPosition)position).getType();
return fColorMap.get(type);
}
return fColorMap.get(DEFAULT_TYPE);
}
return null;
}
/**
* Find position which includes the (document-)offset.
* @param offset
* @return the first position including the offset or <code>null</code>.
*/
private Position findIncludingPosition(int offset) {
// TLETODO [performance] Use binary search?
for (int i= fCursorLineActive ? 0 : 1, sz= fPositions.size(); i < sz; ++i) {
Position position= fPositions.get(i);
if (position.offset == offset || position.includes(offset)) {
return position;
}
}
return null;
}
/**
* Updates the position of the cursor line.
*/
private void updateCursorLine() {
try {
IDocument document= fTextViewer.getDocument();
if (document != null) {
int lineNumber= document.getLineOfOffset(getDocumentOffset(fTextWidget.getCaretOffset()));
fCursorLine.isDeleted= false;
fCursorLine.offset= document.getLineOffset(lineNumber);
fCursorLine.length= 0;
}
} catch (BadLocationException e) {
// gracefully ignored
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment