blob: eb3b1da835485d9a5e84f14771ca19cb597443c7 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2011 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.jdt.internal.ui.javaeditor;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.preference.PreferenceConverter;
import org.eclipse.jface.resource.StringConverter;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.TextAttribute;
import org.eclipse.jdt.ui.text.IColorManager;
import org.eclipse.jdt.ui.text.IColorManagerExtension;
import org.eclipse.jdt.ui.text.JavaSourceViewerConfiguration;
import org.eclipse.jdt.internal.ui.text.JavaPresentationReconciler;
/**
* Semantic highlighting manager
*
* @since 3.0
*/
public class SemanticHighlightingManager implements IPropertyChangeListener {
/**
* Highlighting.
*/
static class Highlighting { // TODO: rename to HighlightingStyle
/** Text attribute */
private TextAttribute fTextAttribute;
/** Enabled state */
private boolean fIsEnabled;
/**
* Initialize with the given text attribute.
* @param textAttribute The text attribute
* @param isEnabled the enabled state
*/
public Highlighting(TextAttribute textAttribute, boolean isEnabled) {
setTextAttribute(textAttribute);
setEnabled(isEnabled);
}
/**
* @return Returns the text attribute.
*/
public TextAttribute getTextAttribute() {
return fTextAttribute;
}
/**
* @param textAttribute The background to set.
*/
public void setTextAttribute(TextAttribute textAttribute) {
fTextAttribute= textAttribute;
}
/**
* @return the enabled state
*/
public boolean isEnabled() {
return fIsEnabled;
}
/**
* @param isEnabled the new enabled state
*/
public void setEnabled(boolean isEnabled) {
fIsEnabled= isEnabled;
}
}
/**
* Highlighted Positions.
*/
static class HighlightedPosition extends Position {
/** Highlighting of the position */
private Highlighting fStyle;
/** Lock object */
private Object fLock;
/**
* Initialize the styled positions with the given offset, length and foreground color.
*
* @param offset The position offset
* @param length The position length
* @param highlighting The position's highlighting
* @param lock The lock object
*/
public HighlightedPosition(int offset, int length, Highlighting highlighting, Object lock) {
super(offset, length);
fStyle= highlighting;
fLock= lock;
}
/**
* @return Returns a corresponding style range.
*/
public StyleRange createStyleRange() {
int len= 0;
if (fStyle.isEnabled())
len= getLength();
TextAttribute textAttribute= fStyle.getTextAttribute();
int style= textAttribute.getStyle();
int fontStyle= style & (SWT.ITALIC | SWT.BOLD | SWT.NORMAL);
StyleRange styleRange= new StyleRange(getOffset(), len, textAttribute.getForeground(), textAttribute.getBackground(), fontStyle);
styleRange.strikeout= (style & TextAttribute.STRIKETHROUGH) != 0;
styleRange.underline= (style & TextAttribute.UNDERLINE) != 0;
return styleRange;
}
/**
* Uses reference equality for the highlighting.
*
* @param off The offset
* @param len The length
* @param highlighting The highlighting
* @return <code>true</code> iff the given offset, length and highlighting are equal to the internal ones.
*/
public boolean isEqual(int off, int len, Highlighting highlighting) {
synchronized (fLock) {
return !isDeleted() && getOffset() == off && getLength() == len && fStyle == highlighting;
}
}
/**
* Is this position contained in the given range (inclusive)? Synchronizes on position updater.
*
* @param off The range offset
* @param len The range length
* @return <code>true</code> iff this position is not delete and contained in the given range.
*/
public boolean isContained(int off, int len) {
synchronized (fLock) {
return !isDeleted() && off <= getOffset() && off + len >= getOffset() + getLength();
}
}
public void update(int off, int len) {
synchronized (fLock) {
super.setOffset(off);
super.setLength(len);
}
}
/*
* @see org.eclipse.jface.text.Position#setLength(int)
*/
@Override
public void setLength(int length) {
synchronized (fLock) {
super.setLength(length);
}
}
/*
* @see org.eclipse.jface.text.Position#setOffset(int)
*/
@Override
public void setOffset(int offset) {
synchronized (fLock) {
super.setOffset(offset);
}
}
/*
* @see org.eclipse.jface.text.Position#delete()
*/
@Override
public void delete() {
synchronized (fLock) {
super.delete();
}
}
/*
* @see org.eclipse.jface.text.Position#undelete()
*/
@Override
public void undelete() {
synchronized (fLock) {
super.undelete();
}
}
/**
* @return Returns the highlighting.
*/
public Highlighting getHighlighting() {
return fStyle;
}
}
/**
* Highlighted ranges.
*/
public static class HighlightedRange extends Region {
/** The highlighting key as returned by {@link SemanticHighlighting#getPreferenceKey()}. */
private String fKey;
/**
* Initialize with the given offset, length and highlighting key.
*
* @param offset the offset
* @param length the length
* @param key the highlighting key as returned by
* {@link SemanticHighlighting#getPreferenceKey()}
*/
public HighlightedRange(int offset, int length, String key) {
super(offset, length);
fKey= key;
}
/**
* @return the highlighting key as returned by {@link SemanticHighlighting#getPreferenceKey()}
*/
public String getKey() {
return fKey;
}
/*
* @see org.eclipse.jface.text.Region#equals(java.lang.Object)
*/
@Override
public boolean equals(Object o) {
return super.equals(o) && o instanceof HighlightedRange && fKey.equals(((HighlightedRange)o).getKey());
}
/*
* @see org.eclipse.jface.text.Region#hashCode()
*/
@Override
public int hashCode() {
return super.hashCode() | fKey.hashCode();
}
}
/** Semantic highlighting presenter */
private SemanticHighlightingPresenter fPresenter;
/** Semantic highlighting reconciler */
private SemanticHighlightingReconciler fReconciler;
/** Semantic highlightings */
private SemanticHighlighting[] fSemanticHighlightings;
/** Highlightings */
private Highlighting[] fHighlightings;
/** The editor */
private JavaEditor fEditor;
/** The source viewer */
private JavaSourceViewer fSourceViewer;
/** The color manager */
private IColorManager fColorManager;
/** The preference store */
private IPreferenceStore fPreferenceStore;
/** The source viewer configuration */
private JavaSourceViewerConfiguration fConfiguration;
/** The presentation reconciler */
private JavaPresentationReconciler fPresentationReconciler;
/** The hard-coded ranges */
private HighlightedRange[][] fHardcodedRanges;
/**
* Install the semantic highlighting on the given editor infrastructure
*
* @param editor The Java editor
* @param sourceViewer The source viewer
* @param colorManager The color manager
* @param preferenceStore The preference store
*/
public void install(JavaEditor editor, JavaSourceViewer sourceViewer, IColorManager colorManager, IPreferenceStore preferenceStore) {
fEditor= editor;
fSourceViewer= sourceViewer;
fColorManager= colorManager;
fPreferenceStore= preferenceStore;
if (fEditor != null) {
fConfiguration= editor.createJavaSourceViewerConfiguration();
fPresentationReconciler= (JavaPresentationReconciler) fConfiguration.getPresentationReconciler(sourceViewer);
} else {
fConfiguration= null;
fPresentationReconciler= null;
}
fPreferenceStore.addPropertyChangeListener(this);
if (isEnabled())
enable();
}
/**
* Install the semantic highlighting on the given source viewer infrastructure. No reconciliation will be performed.
*
* @param sourceViewer the source viewer
* @param colorManager the color manager
* @param preferenceStore the preference store
* @param hardcodedRanges the hard-coded ranges to be highlighted
*/
public void install(JavaSourceViewer sourceViewer, IColorManager colorManager, IPreferenceStore preferenceStore, HighlightedRange[][] hardcodedRanges) {
fHardcodedRanges= hardcodedRanges;
install(null, sourceViewer, colorManager, preferenceStore);
}
/**
* Enable semantic highlighting.
*/
private void enable() {
initializeHighlightings();
fPresenter= new SemanticHighlightingPresenter();
fPresenter.install(fSourceViewer, fPresentationReconciler);
if (fEditor != null) {
fReconciler= new SemanticHighlightingReconciler();
fReconciler.install(fEditor, fSourceViewer, fPresenter, fSemanticHighlightings, fHighlightings);
} else {
fPresenter.updatePresentation(null, createHardcodedPositions(), new HighlightedPosition[0]);
}
}
/**
* Computes the hard-coded positions from the hard-coded ranges
*
* @return the hard-coded positions
*/
private HighlightedPosition[] createHardcodedPositions() {
List<HighlightedPosition> positions= new ArrayList<HighlightedPosition>();
for (int i= 0; i < fHardcodedRanges.length; i++) {
HighlightedRange range= null;
Highlighting hl= null;
for (int j= 0; j < fHardcodedRanges[i].length; j++ ) {
hl= getHighlighting(fHardcodedRanges[i][j].getKey());
if (hl.isEnabled()) {
range= fHardcodedRanges[i][j];
break;
}
}
if (range != null)
positions.add(fPresenter.createHighlightedPosition(range.getOffset(), range.getLength(), hl));
}
return positions.toArray(new HighlightedPosition[positions.size()]);
}
/**
* Returns the highlighting corresponding to the given key.
*
* @param key the highlighting key as returned by {@link SemanticHighlighting#getPreferenceKey()}
* @return the corresponding highlighting
*/
private Highlighting getHighlighting(String key) {
for (int i= 0; i < fSemanticHighlightings.length; i++) {
SemanticHighlighting semanticHighlighting= fSemanticHighlightings[i];
if (key.equals(semanticHighlighting.getPreferenceKey()))
return fHighlightings[i];
}
return null;
}
/**
* Uninstall the semantic highlighting
*/
public void uninstall() {
disable();
if (fPreferenceStore != null) {
fPreferenceStore.removePropertyChangeListener(this);
fPreferenceStore= null;
}
fEditor= null;
fSourceViewer= null;
fColorManager= null;
fConfiguration= null;
fPresentationReconciler= null;
fHardcodedRanges= null;
}
/**
* Disable semantic highlighting.
*/
private void disable() {
if (fReconciler != null) {
fReconciler.uninstall();
fReconciler= null;
}
if (fPresenter != null) {
fPresenter.uninstall();
fPresenter= null;
}
if (fSemanticHighlightings != null)
disposeHighlightings();
}
/**
* @return <code>true</code> iff semantic highlighting is enabled in the preferences
*/
private boolean isEnabled() {
return SemanticHighlightings.isEnabled(fPreferenceStore);
}
/**
* Initialize semantic highlightings.
*/
private void initializeHighlightings() {
fSemanticHighlightings= SemanticHighlightings.getSemanticHighlightings();
fHighlightings= new Highlighting[fSemanticHighlightings.length];
for (int i= 0, n= fSemanticHighlightings.length; i < n; i++) {
SemanticHighlighting semanticHighlighting= fSemanticHighlightings[i];
String colorKey= SemanticHighlightings.getColorPreferenceKey(semanticHighlighting);
addColor(colorKey);
String boldKey= SemanticHighlightings.getBoldPreferenceKey(semanticHighlighting);
int style= fPreferenceStore.getBoolean(boldKey) ? SWT.BOLD : SWT.NORMAL;
String italicKey= SemanticHighlightings.getItalicPreferenceKey(semanticHighlighting);
if (fPreferenceStore.getBoolean(italicKey))
style |= SWT.ITALIC;
String strikethroughKey= SemanticHighlightings.getStrikethroughPreferenceKey(semanticHighlighting);
if (fPreferenceStore.getBoolean(strikethroughKey))
style |= TextAttribute.STRIKETHROUGH;
String underlineKey= SemanticHighlightings.getUnderlinePreferenceKey(semanticHighlighting);
if (fPreferenceStore.getBoolean(underlineKey))
style |= TextAttribute.UNDERLINE;
boolean isEnabled= fPreferenceStore.getBoolean(SemanticHighlightings.getEnabledPreferenceKey(semanticHighlighting));
fHighlightings[i]= new Highlighting(new TextAttribute(fColorManager.getColor(PreferenceConverter.getColor(fPreferenceStore, colorKey)), null, style), isEnabled);
}
}
/**
* Dispose the semantic highlightings.
*/
private void disposeHighlightings() {
for (int i= 0, n= fSemanticHighlightings.length; i < n; i++)
removeColor(SemanticHighlightings.getColorPreferenceKey(fSemanticHighlightings[i]));
fSemanticHighlightings= null;
fHighlightings= null;
}
/*
* @see org.eclipse.jface.util.IPropertyChangeListener#propertyChange(org.eclipse.jface.util.PropertyChangeEvent)
*/
public void propertyChange(PropertyChangeEvent event) {
handlePropertyChangeEvent(event);
}
/**
* Handle the given property change event
*
* @param event The event
*/
private void handlePropertyChangeEvent(PropertyChangeEvent event) {
if (fPreferenceStore == null)
return; // Uninstalled during event notification
if (fConfiguration != null)
fConfiguration.handlePropertyChangeEvent(event);
if (SemanticHighlightings.affectsEnablement(fPreferenceStore, event)) {
if (isEnabled())
enable();
else
disable();
}
if (!isEnabled())
return;
boolean refreshNeeded= false;
for (int i= 0, n= fSemanticHighlightings.length; i < n; i++) {
SemanticHighlighting semanticHighlighting= fSemanticHighlightings[i];
String colorKey= SemanticHighlightings.getColorPreferenceKey(semanticHighlighting);
if (colorKey.equals(event.getProperty())) {
adaptToTextForegroundChange(fHighlightings[i], event);
fPresenter.highlightingStyleChanged(fHighlightings[i]);
refreshNeeded= true;
continue;
}
String boldKey= SemanticHighlightings.getBoldPreferenceKey(semanticHighlighting);
if (boldKey.equals(event.getProperty())) {
adaptToTextStyleChange(fHighlightings[i], event, SWT.BOLD);
fPresenter.highlightingStyleChanged(fHighlightings[i]);
refreshNeeded= true;
continue;
}
String italicKey= SemanticHighlightings.getItalicPreferenceKey(semanticHighlighting);
if (italicKey.equals(event.getProperty())) {
adaptToTextStyleChange(fHighlightings[i], event, SWT.ITALIC);
fPresenter.highlightingStyleChanged(fHighlightings[i]);
refreshNeeded= true;
continue;
}
String strikethroughKey= SemanticHighlightings.getStrikethroughPreferenceKey(semanticHighlighting);
if (strikethroughKey.equals(event.getProperty())) {
adaptToTextStyleChange(fHighlightings[i], event, TextAttribute.STRIKETHROUGH);
fPresenter.highlightingStyleChanged(fHighlightings[i]);
refreshNeeded= true;
continue;
}
String underlineKey= SemanticHighlightings.getUnderlinePreferenceKey(semanticHighlighting);
if (underlineKey.equals(event.getProperty())) {
adaptToTextStyleChange(fHighlightings[i], event, TextAttribute.UNDERLINE);
fPresenter.highlightingStyleChanged(fHighlightings[i]);
refreshNeeded= true;
continue;
}
String enabledKey= SemanticHighlightings.getEnabledPreferenceKey(semanticHighlighting);
if (enabledKey.equals(event.getProperty())) {
adaptToEnablementChange(fHighlightings[i], event);
fPresenter.highlightingStyleChanged(fHighlightings[i]);
refreshNeeded= true;
continue;
}
}
if (refreshNeeded && fReconciler != null)
fReconciler.refresh();
}
private void adaptToEnablementChange(Highlighting highlighting, PropertyChangeEvent event) {
Object value= event.getNewValue();
boolean eventValue;
if (value instanceof Boolean)
eventValue= ((Boolean) value).booleanValue();
else if (IPreferenceStore.TRUE.equals(value))
eventValue= true;
else
eventValue= false;
highlighting.setEnabled(eventValue);
}
private void adaptToTextForegroundChange(Highlighting highlighting, PropertyChangeEvent event) {
RGB rgb= null;
Object value= event.getNewValue();
if (value instanceof RGB)
rgb= (RGB) value;
else if (value instanceof String)
rgb= StringConverter.asRGB((String) value);
if (rgb != null) {
String property= event.getProperty();
Color color= fColorManager.getColor(property);
if ((color == null || !rgb.equals(color.getRGB())) && fColorManager instanceof IColorManagerExtension) {
IColorManagerExtension ext= (IColorManagerExtension) fColorManager;
ext.unbindColor(property);
ext.bindColor(property, rgb);
color= fColorManager.getColor(property);
}
TextAttribute oldAttr= highlighting.getTextAttribute();
highlighting.setTextAttribute(new TextAttribute(color, oldAttr.getBackground(), oldAttr.getStyle()));
}
}
private void adaptToTextStyleChange(Highlighting highlighting, PropertyChangeEvent event, int styleAttribute) {
boolean eventValue= false;
Object value= event.getNewValue();
if (value instanceof Boolean)
eventValue= ((Boolean) value).booleanValue();
else if (IPreferenceStore.TRUE.equals(value))
eventValue= true;
TextAttribute oldAttr= highlighting.getTextAttribute();
boolean activeValue= (oldAttr.getStyle() & styleAttribute) == styleAttribute;
if (activeValue != eventValue)
highlighting.setTextAttribute(new TextAttribute(oldAttr.getForeground(), oldAttr.getBackground(), eventValue ? oldAttr.getStyle() | styleAttribute : oldAttr.getStyle() & ~styleAttribute));
}
private void addColor(String colorKey) {
if (fColorManager != null && colorKey != null && fColorManager.getColor(colorKey) == null) {
RGB rgb= PreferenceConverter.getColor(fPreferenceStore, colorKey);
if (fColorManager instanceof IColorManagerExtension) {
IColorManagerExtension ext= (IColorManagerExtension) fColorManager;
ext.unbindColor(colorKey);
ext.bindColor(colorKey, rgb);
}
}
}
private void removeColor(String colorKey) {
if (fColorManager instanceof IColorManagerExtension)
((IColorManagerExtension) fColorManager).unbindColor(colorKey);
}
/**
* Returns this hightlighter's reconciler.
*
* @return the semantic highlighter reconciler or <code>null</code> if none
* @since 3.3
*/
public SemanticHighlightingReconciler getReconciler() {
return fReconciler;
}
}