blob: 7baf254107822d2656f0b842bd3b8612a8cfa59a [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2009 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
*******************************************************************************/
package org.eclipse.wst.sse.ui.internal.style;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.BadPositionCategoryException;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentListener;
import org.eclipse.jface.text.IPositionUpdater;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ISynchronizable;
import org.eclipse.jface.text.ITextInputListener;
import org.eclipse.jface.text.ITextPresentationListener;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.TextPresentation;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
import org.eclipse.wst.sse.ui.internal.Logger;
import org.eclipse.wst.sse.ui.internal.SSEUIPlugin;
import org.eclipse.wst.sse.ui.internal.StructuredTextViewer;
import org.eclipse.wst.sse.ui.internal.preferences.EditorPreferenceNames;
import org.eclipse.wst.sse.ui.internal.provisional.style.StructuredPresentationReconciler;
import org.eclipse.wst.sse.ui.internal.style.SemanticHighlightingManager.HighlightedPosition;
import org.eclipse.wst.sse.ui.internal.style.SemanticHighlightingManager.HighlightingStyle;
import org.eclipse.wst.sse.ui.internal.util.EditorUtility;
/**
* Semantic highlighting presenter - UI thread implementation. Based on
* org.eclipse.jdt.internal.ui.javaeditor.SemanticHighlightingPresenter
*
*/
public class SemanticHighlightingPresenter implements ITextPresentationListener, ITextInputListener, IDocumentListener {
/**
* Semantic highlighting position updater.
*/
private class HighlightingPositionUpdater implements IPositionUpdater {
/** The position category. */
private final String fCategory;
/**
* Creates a new updater for the given <code>category</code>.
*
* @param category the new category.
*/
public HighlightingPositionUpdater(String category) {
fCategory= category;
}
/*
* @see org.eclipse.jface.text.IPositionUpdater#update(org.eclipse.jface.text.DocumentEvent)
*/
public void update(DocumentEvent event) {
int eventOffset= event.getOffset();
int eventOldLength= event.getLength();
int eventEnd= eventOffset + eventOldLength;
try {
Position[] positions= event.getDocument().getPositions(fCategory);
for (int i= 0; i != positions.length; i++) {
HighlightedPosition position= (HighlightedPosition) positions[i];
// Also update deleted positions because they get deleted by the background thread and removed/invalidated only in the UI runnable
// if (position.isDeleted())
// continue;
int offset= position.getOffset();
int length= position.getLength();
int end= offset + length;
if (offset > eventEnd)
updateWithPrecedingEvent(position, event);
else if (end < eventOffset) {
// do nothing
// updateWithSucceedingEvent(position, event);
}
else if (offset <= eventOffset && end >= eventEnd)
updateWithIncludedEvent(position, event);
else if (offset <= eventOffset)
updateWithOverEndEvent(position, event);
else if (end >= eventEnd)
updateWithOverStartEvent(position, event);
else
updateWithIncludingEvent(position, event);
}
} catch (BadPositionCategoryException e) {
// ignore and return
}
}
/**
* Update the given position with the given event. The event precedes the position.
*
* @param position The position
* @param event The event
*/
private void updateWithPrecedingEvent(HighlightedPosition position, DocumentEvent event) {
String newText= event.getText();
int eventNewLength= newText != null ? newText.length() : 0;
int deltaLength= eventNewLength - event.getLength();
position.setOffset(position.getOffset() + deltaLength);
}
/**
* Update the given position with the given event. The event succeeds the position.
*
* @param position The position
* @param event The event
*/
// private void updateWithSucceedingEvent(HighlightedPosition position, DocumentEvent event) {
// }
/**
* Update the given position with the given event. The event is included by the position.
*
* @param position The position
* @param event The event
*/
private void updateWithIncludedEvent(HighlightedPosition position, DocumentEvent event) {
int eventOffset= event.getOffset();
String newText= event.getText();
if (newText == null)
newText= ""; //$NON-NLS-1$
int eventNewLength= newText.length();
int deltaLength= eventNewLength - event.getLength();
int offset= position.getOffset();
int length= position.getLength();
int end= offset + length;
int includedLength= 0;
while (includedLength < eventNewLength && !Character.isWhitespace(newText.charAt(includedLength)))
includedLength++;
if (includedLength == eventNewLength)
position.setLength(length + deltaLength);
else {
int newLeftLength= eventOffset - offset + includedLength;
int excludedLength= eventNewLength;
while (excludedLength > 0 && !Character.isWhitespace(newText.charAt(excludedLength - 1)))
excludedLength--;
int newRightOffset= eventOffset + excludedLength;
int newRightLength= end + deltaLength - newRightOffset;
if (newRightLength == 0) {
position.setLength(newLeftLength);
} else {
if (newLeftLength == 0) {
position.update(newRightOffset, newRightLength);
} else {
position.setLength(newLeftLength);
addPositionFromUI(position);
}
}
}
}
/**
* Update the given position with the given event. The event overlaps with the end of the position.
*
* @param position The position
* @param event The event
*/
private void updateWithOverEndEvent(HighlightedPosition position, DocumentEvent event) {
String newText= event.getText();
if (newText == null)
newText= ""; //$NON-NLS-1$
int eventNewLength= newText.length();
int includedLength= 0;
while (includedLength < eventNewLength && !Character.isWhitespace(newText.charAt(includedLength)))
includedLength++;
position.setLength(event.getOffset() - position.getOffset() + includedLength);
}
/**
* Update the given position with the given event. The event overlaps with the start of the position.
*
* @param position The position
* @param event The event
*/
private void updateWithOverStartEvent(HighlightedPosition position, DocumentEvent event) {
int eventOffset= event.getOffset();
int eventEnd= eventOffset + event.getLength();
String newText= event.getText();
if (newText == null)
newText= ""; //$NON-NLS-1$
int eventNewLength= newText.length();
int excludedLength= eventNewLength;
while (excludedLength > 0 && !Character.isWhitespace(newText.charAt(excludedLength - 1)))
excludedLength--;
int deleted= eventEnd - position.getOffset();
int inserted= eventNewLength - excludedLength;
position.update(eventOffset + excludedLength, position.getLength() - deleted + inserted);
}
/**
* Update the given position with the given event. The event includes the position.
*
* @param position The position
* @param event The event
*/
private void updateWithIncludingEvent(HighlightedPosition position, DocumentEvent event) {
position.delete();
position.update(event.getOffset(), 0);
}
}
/** Position updater */
private IPositionUpdater fPositionUpdater= new HighlightingPositionUpdater(getPositionCategory());
/** The source viewer this semantic highlighting reconciler is installed on */
private StructuredTextViewer fSourceViewer;
/** The background presentation reconciler */
private StructuredPresentationReconciler fPresentationReconciler;
/** UI's current highlighted positions - can contain <code>null</code> elements */
private List fPositions= new ArrayList();
/** UI position lock */
private Object fPositionLock= new Object();
/** <code>true</code> iff the current reconcile is canceled. */
private boolean fIsCanceled= false;
/** Read-only color support */
private YUV_RGBConverter rgbConverter;
private Map readOnlyColorTable;
double readOnlyForegroundScaleFactor = 30;
public SemanticHighlightingPresenter() {
// no listener for now since there's no UI to change the value
IPreferenceStore editorStore = SSEUIPlugin.getDefault().getPreferenceStore();
readOnlyForegroundScaleFactor = editorStore.getInt(EditorPreferenceNames.READ_ONLY_FOREGROUND_SCALE);
}
/**
* Creates and returns a new highlighted position with the given offset, length and highlighting.
* <p>
* NOTE: Also called from background thread.
* </p>
*
* @param offset The offset
* @param length The length
* @param highlighting The highlighting
* @return The new highlighted position
*/
public HighlightedPosition createHighlightedPosition(int offset, int length, HighlightingStyle highlighting) {
// TODO: reuse deleted positions
return new HighlightedPosition(offset, length, highlighting, fPositionUpdater);
}
/**
* Creates and returns a new highlighted position from the given position and highlighting.
* <p>
* NOTE: Also called from background thread.
* </p>
*
* @param position The position
* @param highlighting The highlighting
* @return The new highlighted position
*/
public HighlightedPosition createHighlightedPosition(Position position, HighlightingStyle highlighting) {
// TODO: reuse deleted positions
return new HighlightedPosition(position, highlighting, fPositionUpdater);
}
/**
* Creates and returns a new highlighted position from the given position and highlighting.
* <p>
* NOTE: Also called from background thread.
* </p>
*
* @param position The position
* @param highlighting The highlighting
* @param isReadOnly Is this a read-only position
* @return The new highlighted position
*/
public HighlightedPosition createHighlightedPosition(Position position, HighlightingStyle highlighting, boolean isReadOnly) {
// TODO: reuse deleted positions
return new HighlightedPosition(position, highlighting, fPositionUpdater, isReadOnly);
}
/**
* Adds all current positions to the given list.
* <p>
* NOTE: Called from background thread.
* </p>
*
* @param list The list
*/
public void addAllPositions(List list) {
synchronized (fPositionLock) {
list.addAll(fPositions);
}
}
/**
* Create a text presentation in the background.
* <p>
* NOTE: Called from background thread.
* </p>
*
* @param addedPositions the added positions
* @param removedPositions the removed positions
* @return the text presentation or <code>null</code>, if reconciliation should be canceled
*/
public TextPresentation createPresentation(List addedPositions, List removedPositions) {
ISourceViewer sourceViewer= fSourceViewer;
StructuredPresentationReconciler presentationReconciler= fPresentationReconciler;
if (sourceViewer == null || presentationReconciler == null)
return null;
if (isCanceled())
return null;
IDocument document= sourceViewer.getDocument();
if (document == null)
return null;
int minStart= Integer.MAX_VALUE;
int maxEnd= Integer.MIN_VALUE;
for (int i= 0, n= removedPositions.size(); i < n; i++) {
Position position= (Position) removedPositions.get(i);
int offset= position.getOffset();
minStart= Math.min(minStart, offset);
maxEnd= Math.max(maxEnd, offset + position.getLength());
}
for (int i= 0, n= addedPositions.size(); i < n; i++) {
Position position= (Position) addedPositions.get(i);
int offset= position.getOffset();
minStart= Math.min(minStart, offset);
maxEnd= Math.max(maxEnd, offset + position.getLength());
}
if (minStart < maxEnd)
try {
return presentationReconciler.createRepairDescription(new Region(minStart, maxEnd - minStart), document);
} catch (RuntimeException e) {
// Assume concurrent modification from UI thread
}
return null;
}
/**
* Create a runnable for updating the presentation.
* <p>
* NOTE: Called from background thread.
* </p>
* @param textPresentation the text presentation
* @param addedPositions the added positions
* @param removedPositions the removed positions
* @return the runnable or <code>null</code>, if reconciliation should be canceled
*/
public Runnable createUpdateRunnable(final TextPresentation textPresentation, List addedPositions, List removedPositions) {
if (fSourceViewer == null || textPresentation == null)
return null;
// TODO: do clustering of positions and post multiple fast runnables
final HighlightedPosition[] added= new SemanticHighlightingManager.HighlightedPosition[addedPositions.size()];
addedPositions.toArray(added);
final SemanticHighlightingManager.HighlightedPosition[] removed= new SemanticHighlightingManager.HighlightedPosition[removedPositions.size()];
removedPositions.toArray(removed);
if (isCanceled())
return null;
Runnable runnable= new Runnable() {
public void run() {
updatePresentation(textPresentation, added, removed);
}
};
return runnable;
}
/**
* Invalidate the presentation of the positions based on the given added positions and the existing deleted positions.
* Also unregisters the deleted positions from the document and patches the positions of this presenter.
* <p>
* NOTE: Indirectly called from background thread by UI runnable.
* </p>
* @param textPresentation the text presentation or <code>null</code>, if the presentation should computed in the UI thread
* @param addedPositions the added positions
* @param removedPositions the removed positions
*/
public void updatePresentation(TextPresentation textPresentation, HighlightedPosition[] addedPositions, HighlightedPosition[] removedPositions) {
if (fSourceViewer == null)
return;
// checkOrdering("added positions: ", Arrays.asList(addedPositions)); //$NON-NLS-1$
// checkOrdering("removed positions: ", Arrays.asList(removedPositions)); //$NON-NLS-1$
// checkOrdering("old positions: ", fPositions); //$NON-NLS-1$
// TODO: double-check consistency with document.getPositions(...)
// TODO: reuse removed positions
if (isCanceled())
return;
IDocument document= fSourceViewer.getDocument();
if (document == null)
return;
String positionCategory= getPositionCategory();
List removedPositionsList= Arrays.asList(removedPositions);
try {
synchronized (fPositionLock) {
List oldPositions= fPositions;
int newSize= Math.max(fPositions.size() + addedPositions.length - removedPositions.length, 10);
/*
* The following loop is a kind of merge sort: it merges two List<Position>, each
* sorted by position.offset, into one new list. The first of the two is the
* previous list of positions (oldPositions), from which any deleted positions get
* removed on the fly. The second of two is the list of added positions. The result
* is stored in newPositions.
*/
List newPositions= new ArrayList(newSize);
Position position= null;
Position addedPosition= null;
for (int i= 0, j= 0, n= oldPositions.size(), m= addedPositions.length; i < n || position != null || j < m || addedPosition != null;) {
// loop variant: i + j < old(i + j)
// a) find the next non-deleted Position from the old list
while (position == null && i < n) {
position= (Position) oldPositions.get(i++);
if (position.isDeleted() || contain(removedPositionsList, position)) {
document.removePosition(positionCategory, position);
position= null;
}
}
// b) find the next Position from the added list
if (addedPosition == null && j < m) {
addedPosition= addedPositions[j++];
document.addPosition(positionCategory, addedPosition);
}
// c) merge: add the next of position/addedPosition with the lower offset
if (position != null) {
if (addedPosition != null)
if (position.getOffset() <= addedPosition.getOffset()) {
newPositions.add(position);
position= null;
} else {
newPositions.add(addedPosition);
addedPosition= null;
}
else {
newPositions.add(position);
position= null;
}
} else if (addedPosition != null) {
newPositions.add(addedPosition);
addedPosition= null;
}
}
fPositions= newPositions;
}
} catch (BadPositionCategoryException e) {
// Should not happen
Logger.logException(e);
} catch (BadLocationException e) {
// Should not happen
Logger.logException(e);
}
// checkOrdering("new positions: ", fPositions); //$NON-NLS-1$
if (textPresentation != null)
fSourceViewer.changeTextPresentation(textPresentation, false);
else
fSourceViewer.invalidateTextPresentation();
}
// private void checkOrdering(String s, List positions) {
// Position previous= null;
// for (int i= 0, n= positions.size(); i < n; i++) {
// Position current= (Position) positions.get(i);
// if (previous != null && previous.getOffset() + previous.getLength() > current.getOffset())
// return;
// }
// }
/**
* Returns <code>true</code> iff the positions contain the position.
* @param positions the positions, must be ordered by offset but may overlap
* @param position the position
* @return <code>true</code> iff the positions contain the position
*/
private boolean contain(List positions, Position position) {
return indexOf(positions, position) != -1;
}
/**
* Returns index of the position in the positions, <code>-1</code> if not found.
* @param positions the positions, must be ordered by offset but may overlap
* @param position the position
* @return the index
*/
private int indexOf(List positions, Position position) {
int index= computeIndexAtOffset(positions, position.getOffset());
int size= positions.size();
while (index < size) {
if (positions.get(index) == position)
return index;
index++;
}
return -1;
}
/**
* Insert the given position in <code>fPositions</code>, s.t. the offsets remain in linear order.
*
* @param position The position for insertion
*/
private void insertPosition(Position position) {
int i= computeIndexAfterOffset(fPositions, position.getOffset());
fPositions.add(i, position);
}
/**
* Returns the index of the first position with an offset greater than the given offset.
*
* @param positions the positions, must be ordered by offset and must not overlap
* @param offset the offset
* @return the index of the last position with an offset greater than the given offset
*/
private int computeIndexAfterOffset(List positions, int offset) {
int i= -1;
int j= positions.size();
while (j - i > 1) {
int k= (i + j) >> 1;
Position position= (Position) positions.get(k);
if (position.getOffset() > offset)
j= k;
else
i= k;
}
return j;
}
/**
* Returns the index of the first position with an offset equal or greater than the given offset.
*
* @param positions the positions, must be ordered by offset and must not overlap
* @param offset the offset
* @return the index of the last position with an offset equal or greater than the given offset
*/
private int computeIndexAtOffset(List positions, int offset) {
int i= -1;
int j= positions.size();
while (j - i > 1) {
int k= (i + j) >> 1;
Position position= (Position) positions.get(k);
if (position.getOffset() >= offset)
j= k;
else
i= k;
}
return j;
}
/*
* @see org.eclipse.jface.text.ITextPresentationListener#applyTextPresentation(org.eclipse.jface.text.TextPresentation)
*/
public void applyTextPresentation(TextPresentation textPresentation) {
IRegion region= textPresentation.getExtent();
int minStart= Integer.MAX_VALUE;
int maxEnd= Integer.MIN_VALUE;
int i= computeIndexAtOffset(fPositions, region.getOffset()), n= computeIndexAtOffset(fPositions, region.getOffset() + region.getLength());
if (n - i > 2) {
List ranges= new ArrayList(n - i);
for (; i < n; i++) {
HighlightedPosition position= (HighlightedPosition) fPositions.get(i);
if (!position.isDeleted()) {
if (!position.isReadOnly())
ranges.add(position.createStyleRange());
else {
int offset= position.getOffset();
minStart= Math.min(minStart, offset);
maxEnd= Math.max(maxEnd, offset + position.getLength());
}
}
}
StyleRange[] array= new StyleRange[ranges.size()];
array= (StyleRange[]) ranges.toArray(array);
textPresentation.replaceStyleRanges(array);
} else {
for (; i < n; i++) {
HighlightedPosition position= (HighlightedPosition) fPositions.get(i);
if (!position.isDeleted()) {
if (!position.isReadOnly())
textPresentation.replaceStyleRange(position.createStyleRange());
else {
int offset= position.getOffset();
minStart= Math.min(minStart, offset);
maxEnd= Math.max(maxEnd, offset + position.getLength());
}
}
}
}
if (minStart < maxEnd) {
IStructuredDocument document = (IStructuredDocument) fSourceViewer.getDocument();
if (document.containsReadOnly(minStart, maxEnd)) {
Iterator nonDefaultStyleRangeIterator = textPresentation.getNonDefaultStyleRangeIterator();
while (nonDefaultStyleRangeIterator.hasNext()) {
StyleRange styleRange = (StyleRange) nonDefaultStyleRangeIterator.next();
if (document.containsReadOnly(styleRange.start, styleRange.length)) {
adjustForeground(styleRange);
}
}
}
}
}
/*
* @see org.eclipse.jface.text.ITextInputListener#inputDocumentAboutToBeChanged(org.eclipse.jface.text.IDocument, org.eclipse.jface.text.IDocument)
*/
public void inputDocumentAboutToBeChanged(IDocument oldInput, IDocument newInput) {
setCanceled(true);
releaseDocument(oldInput);
resetState();
}
/*
* @see org.eclipse.jface.text.ITextInputListener#inputDocumentChanged(org.eclipse.jface.text.IDocument, org.eclipse.jface.text.IDocument)
*/
public void inputDocumentChanged(IDocument oldInput, IDocument newInput) {
manageDocument(newInput);
}
/*
* @see org.eclipse.jface.text.IDocumentListener#documentAboutToBeChanged(org.eclipse.jface.text.DocumentEvent)
*/
public void documentAboutToBeChanged(DocumentEvent event) {
setCanceled(true);
}
/*
* @see org.eclipse.jface.text.IDocumentListener#documentChanged(org.eclipse.jface.text.DocumentEvent)
*/
public void documentChanged(DocumentEvent event) {
}
/**
* @return Returns <code>true</code> iff the current reconcile is canceled.
* <p>
* NOTE: Also called from background thread.
* </p>
*/
public boolean isCanceled() {
IDocument document= fSourceViewer != null ? fSourceViewer.getDocument() : null;
if (document == null)
return fIsCanceled;
synchronized (getLockObject(document)) {
return fIsCanceled;
}
}
/**
* Set whether or not the current reconcile is canceled.
*
* @param isCanceled <code>true</code> iff the current reconcile is canceled
*/
public void setCanceled(boolean isCanceled) {
IDocument document= fSourceViewer != null ? fSourceViewer.getDocument() : null;
if (document == null) {
fIsCanceled= isCanceled;
return;
}
synchronized (getLockObject(document)) {
fIsCanceled= isCanceled;
}
}
/**
* @param document the document
* @return the document's lock object
*/
private Object getLockObject(IDocument document) {
if (document instanceof ISynchronizable) {
Object lock= ((ISynchronizable)document).getLockObject();
if (lock != null)
return lock;
}
return document;
}
/**
* Install this presenter on the given source viewer and background presentation
* reconciler.
*
* @param sourceViewer the source viewer
* @param backgroundPresentationReconciler the background presentation reconciler,
* can be <code>null</code>, in that case {@link SemanticHighlightingPresenter#createPresentation(List, List)}
* should not be called
*/
public void install(StructuredTextViewer sourceViewer, StructuredPresentationReconciler backgroundPresentationReconciler) {
fSourceViewer= sourceViewer;
fPresentationReconciler= backgroundPresentationReconciler;
fSourceViewer.prependTextPresentationListener(this);
fSourceViewer.addTextInputListener(this);
manageDocument(fSourceViewer.getDocument());
}
/**
* Uninstall this presenter.
*/
public void uninstall() {
setCanceled(true);
if (fSourceViewer != null) {
fSourceViewer.removeTextPresentationListener(this);
releaseDocument(fSourceViewer.getDocument());
invalidateTextPresentation();
resetState();
fSourceViewer.removeTextInputListener(this);
fSourceViewer= null;
}
}
/**
* Invalidate text presentation of positions with the given highlighting.
*
* @param highlighting The highlighting
*/
public void highlightingStyleChanged(HighlightingStyle highlighting) {
for (int i= 0, n= fPositions.size(); i < n; i++) {
HighlightedPosition position= (HighlightedPosition) fPositions.get(i);
if (position.getHighlighting() == highlighting)
fSourceViewer.invalidateTextPresentation(position.getOffset(), position.getLength());
}
}
/**
* Invalidate text presentation of all positions.
*/
private void invalidateTextPresentation() {
for (int i= 0, n= fPositions.size(); i < n; i++) {
Position position= (Position) fPositions.get(i);
fSourceViewer.invalidateTextPresentation(position.getOffset(), position.getLength());
}
}
/**
* Add a position with the given range and highlighting unconditionally, only from UI thread.
* The position will also be registered on the document. The text presentation is not invalidated.
*
* @param uiPosition the highlighted position to add from the UI
*/
private void addPositionFromUI(HighlightedPosition uiPosition) {
Position position= createHighlightedPosition(uiPosition, uiPosition.getHighlighting(), uiPosition.isReadOnly());
synchronized (fPositionLock) {
insertPosition(position);
}
IDocument document= fSourceViewer.getDocument();
if (document == null)
return;
String positionCategory= getPositionCategory();
try {
document.addPosition(positionCategory, position);
} catch (BadLocationException e) {
// Should not happen
Logger.logException(e);
} catch (BadPositionCategoryException e) {
// Should not happen
Logger.logException(e);
}
}
/**
* Reset to initial state.
*/
private void resetState() {
synchronized (fPositionLock) {
fPositions.clear();
}
}
/**
* Start managing the given document.
*
* @param document The document
*/
private void manageDocument(IDocument document) {
if (document != null) {
document.addPositionCategory(getPositionCategory());
document.addPositionUpdater(fPositionUpdater);
document.addDocumentListener(this);
}
}
/**
* Stop managing the given document.
*
* @param document The document
*/
private void releaseDocument(IDocument document) {
if (document != null) {
document.removeDocumentListener(this);
document.removePositionUpdater(fPositionUpdater);
try {
document.removePositionCategory(getPositionCategory());
} catch (BadPositionCategoryException e) {
// Should not happen
Logger.logException(e);
}
}
}
/**
* @return The semantic reconciler position's category.
*/
private String getPositionCategory() {
return toString();
}
private void adjustForeground(StyleRange styleRange) {
RGB oldRGB = null;
// Color oldColor = styleRange.foreground;
Color oldColor = styleRange.background;
if (oldColor == null) {
// oldRGB = getTextWidget().getForeground().getRGB();
oldColor = fSourceViewer.getTextWidget().getBackground();
oldRGB = oldColor.getRGB();
}
else {
oldRGB = oldColor.getRGB();
}
Color newColor = getCachedColorFor(oldRGB);
if (newColor == null) {
// make text "closer to" background lumanence
double target = getRGBConverter().calculateYComponent(oldColor);
RGB newRGB = getRGBConverter().transformRGBToGrey(oldRGB, readOnlyForegroundScaleFactor / 100.0, target);
// save conversion, so calculations only need to be done once
cacheColor(oldRGB, newRGB);
newColor = getCachedColorFor(oldRGB);
}
styleRange.foreground = newColor;
}
private YUV_RGBConverter getRGBConverter() {
if (rgbConverter == null) {
rgbConverter = new YUV_RGBConverter();
}
return rgbConverter;
}
/**
* Cache read-only color.
*
* @param oldRGB
* @param newColor
*/
private void cacheColor(RGB oldRGB, RGB newColor) {
if (readOnlyColorTable == null) {
readOnlyColorTable = new HashMap();
}
readOnlyColorTable.put(oldRGB, newColor);
}
/**
* This method is just to get existing read-only colors.
*/
private Color getCachedColorFor(RGB oldRGB) {
Color result = null;
if (readOnlyColorTable != null) {
RGB readOnlyRGB = (RGB) readOnlyColorTable.get(oldRGB);
result = EditorUtility.getColor(readOnlyRGB);
}
return result;
}
/**
* A utility class to do various color manipulations
*/
private class YUV_RGBConverter {
/**
* This class "holds" the YUV values corresponding to RGB color
*/
private class YUV {
class NormalizedRGB {
double blue;
double green;
private final double maxRGB = 256.0;
double red;
public NormalizedRGB(RGB rgb) {
// first normalize to between 0 - 1
red = rgb.red / maxRGB;
green = rgb.green / maxRGB;
blue = rgb.blue / maxRGB;
red = gammaNormalized(red);
green = gammaNormalized(green);
blue = gammaNormalized(blue);
}
}
private NormalizedRGB normalizedRGB;
private double u = -1;
private double v = -1;
private double y = -1;
private YUV() {
super();
}
public YUV(RGB rgb) {
this();
normalizedRGB = new NormalizedRGB(rgb);
// force calculations
getY();
getV();
getU();
}
/**
* normalize to "average" gamma 2.2222 or 1/0.45
*/
double gammaNormalized(double colorComponent) {
if (colorComponent < 0.018) {
return colorComponent * 0.45;
}
else {
return 1.099 * Math.pow(colorComponent, 0.45) - 0.099;
}
}
/**
* @return RGB based on original RGB and current YUV values;
*/
public double getU() {
if (u == -1) {
u = 0.4949 * (normalizedRGB.blue - getY());
}
return u;
}
public double getV() {
if (v == -1) {
v = 0.877 * (normalizedRGB.red - getY());
}
return v;
}
public double getY() {
if (y == -1) {
y = 0.299 * normalizedRGB.red + 0.587 * normalizedRGB.green + 0.114 * normalizedRGB.blue;
}
return y;
}
}
public YUV_RGBConverter() {
super();
}
public double calculateYComponent(Color targetColor) {
return new YUV(targetColor.getRGB()).getY();
}
public RGB transformRGBToGrey(RGB originalRGB, double scaleFactor, double target) {
RGB transformedRGB = null;
// we left the "full" API method signature, but this
// version does not take into account originalRGB, though
// it might someday.
// for now, we'll simply make the new RGB grey, either a little
// lighter, or a little darker than background.
double y = 0;
double mid = 0.5;
// zero is black, one is white
if (target < mid) {
// is "dark" make lighter
y = target + scaleFactor;
}
else {
// is "light" make darker
y = target - scaleFactor;
}
int c = (int) Math.round(y * 255);
// just to gaurd against mis-use, or scale's values greater
// than mid point (and possibly rounding error)
if (c > 255)
c = 255;
if (c < 0)
c = 0;
transformedRGB = new RGB(c, c, c);
return transformedRGB;
}
}
}