/*******************************************************************************
* Copyright (c) 2014, 2015 Cisco 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
*
*******************************************************************************/
package com.cisco.yangide.editor.editors;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jdt.ui.text.IColorManager;
import org.eclipse.jdt.ui.text.IColorManagerExtension;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.preference.PreferenceConverter;
import org.eclipse.jface.resource.StringConverter;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.TextAttribute;
import org.eclipse.jface.text.source.SourceViewer;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.RGB;
import com.cisco.yangide.editor.YangEditorPlugin;
/**
* Semantic highlighting manager
*
* @author Alexey Kholupko
*/
public class SemanticHighlightingManager implements IPropertyChangeListener {
static class Highlighting { // TODO: rename to HighlightingStyle
/** Text attribute */
private TextAttribute fTextAttribute;
/** Enabled state */
private boolean fIsEnabled;
/**
* Initialize with the given text attribute.
*/
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();
}
}
private SemanticHighlightingPresenter fPresenter;
private SemanticHighlightingReconciler fReconciler;
private SemanticHighlighting[] fSemanticHighlightings;
private Highlighting[] fHighlightings;
private YangEditor fEditor;
private SourceViewer fSourceViewer;
private IColorManager fColorManager;
private IPreferenceStore fPreferenceStore;
private YangSourceViewerConfiguration fConfiguration;
private YangPresentationReconciler fPresentationReconciler;
/** The hard-coded ranges */
private HighlightedRange[][] fHardcodedRanges;
/**
* Install the semantic highlighting on the given editor infrastructure
*/
public void install(YangEditor editor, SourceViewer sourceViewer, IColorManager colorManager,
IPreferenceStore preferenceStore) {
fEditor = editor;
fSourceViewer = sourceViewer;
fColorManager = colorManager;
fPreferenceStore = preferenceStore;
if (fEditor != null) {
fConfiguration = new YangSourceViewerConfiguration(YangEditorPlugin.getDefault()
.getCombinedPreferenceStore(), fColorManager, null);
fPresentationReconciler = (YangPresentationReconciler) 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.
*/
public void install(SourceViewer 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.
*/
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)
*/
@Override
public void propertyChange(PropertyChangeEvent event) {
handlePropertyChangeEvent(event);
fSourceViewer.invalidateTextPresentation();
}
/**
* 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.
*/
public SemanticHighlightingReconciler getReconciler() {
return fReconciler;
}
}