/*
* Copyright 2003-2013 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package jetbrains.mps.ide.editor;
import com.intellij.openapi.editor.DefaultLanguageHighlighterColors;
import com.intellij.openapi.editor.colors.ColorKey;
import com.intellij.openapi.editor.colors.EditorColorsListener;
import com.intellij.openapi.editor.colors.EditorColorsManager;
import com.intellij.openapi.editor.colors.EditorColorsScheme;
import com.intellij.openapi.editor.colors.TextAttributesKey;
import com.intellij.openapi.editor.markup.EffectType;
import com.intellij.openapi.editor.markup.TextAttributes;
import jetbrains.mps.editor.runtime.style.StyleAttributes;
import jetbrains.mps.editor.runtime.style.StyleImpl;
import jetbrains.mps.nodeEditor.MPSColors;
import jetbrains.mps.openapi.editor.style.Style;
import jetbrains.mps.openapi.editor.style.StyleRegistry;
import jetbrains.mps.util.Pair;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import java.awt.Color;
import java.util.HashMap;
import java.util.Map;
public class StyleRegistryIdeaImpl extends StyleRegistry implements EditorColorsListener {
private static final Logger LOG = LogManager.getLogger(StyleRegistryIdeaImpl.class);
private final static int brightnessTH = 125;
private final static int colorTH = 500;
private final static int colorIterationSteps = 5;
private final static int colorIterationDelta = 50;
private EditorColorsManager myColorsManager;
protected final Map<String, String> myIDEAStylesMapping = new HashMap<String, String>();
protected final Map<Pair<Color, Color>, Color> myColorsMapping = new HashMap<Pair<Color, Color>, Color>();
public StyleRegistryIdeaImpl(EditorColorsManager colorsManager) {
myColorsManager = colorsManager;
ourInstance = this;
fillIdeaMappings();
fillColorMappings();
}
@Override
public Color getEditorBackground() {
return getColorsScheme().getDefaultBackground();
}
@Override
public Color getEditorForeground() {
return getColorsScheme().getDefaultForeground();
}
@Override
public Color getColor(String key) {
if (myIDEAStylesMapping.containsKey(key)) {
key = myIDEAStylesMapping.get(key);
}
Color color = getColorsScheme().getColor(ColorKey.find(key));
if (color == null) {
color = getColorsScheme().getAttributes(TextAttributesKey.find(key)).getForegroundColor();
}
return color;
}
@Override
public Color getSimpleColor(Color color) {
return getSimpleColor(color, getEditorBackground());
}
@Override
public Color getSimpleColor(Color color, final Color bg) {
if (!isDarkTheme() || color == null || bg == null)
return color;
final Color original = color;
Pair<Color, Color> colorPair = new Pair<Color, Color>(original, bg);
if (myColorsMapping.containsKey(colorPair))
return myColorsMapping.get(colorPair);
if ((Math.abs(color.getRGB()) - Math.abs(Color.BLACK.getRGB()) / 2) * (Math.abs(bg.getRGB()) - Math.abs(Color.BLACK.getRGB()) / 2) < 0)
color = new Color(255 - color.getRed(), 255 - color.getGreen(), 255 - color.getBlue());
int counter = 0;
while (!isGoodContrastWithBG(color, bg) && counter < colorIterationSteps) {
int deltaR = Math.abs(bg.getRed() - color.getRed());
int deltaG = Math.abs(bg.getGreen() - color.getGreen());
int deltaB = Math.abs(bg.getBlue() - color.getBlue());
int deltaMin = Math.min((Math.min(deltaR, deltaG)), deltaB);
if (deltaMin == deltaR) {
color = new Color((color.getRed() + colorIterationDelta) % 256, color.getGreen(), color.getBlue());
} else if (deltaMin == deltaG) {
color = new Color(color.getRed(), (color.getGreen() + colorIterationDelta) % 256, color.getBlue());
} else if (deltaMin == deltaB) {
color = new Color(color.getRed(), color.getGreen(), (color.getBlue() + colorIterationDelta) % 256);
}
counter++;
}
myColorsMapping.put(colorPair, color);
return color;
}
private boolean isGoodContrastWithBG(Color color, final Color bg) {
int brightnessColor = (299 * color.getRed() + 587 * color.getGreen() + 114 * color.getBlue()) / 1000;
int brightnessBG = (299 * bg.getRed() + 587 * bg.getGreen() + 114 * bg.getBlue()) / 1000;
int brightnessDiff = brightnessBG - brightnessColor;
int colorDiff = Math.abs(color.getRed() - bg.getRed())
+ Math.abs(color.getGreen() - bg.getGreen())
+ Math.abs(color.getBlue() - bg.getBlue());
return Math.abs(brightnessDiff) >= brightnessTH || colorDiff >= colorTH;
}
@Override
public Style getStyle(String key) {
if (myIDEAStylesMapping.containsKey(key)) {
key = myIDEAStylesMapping.get(key);
}
Style style = super.getStyle(key);
if (style == null) {
// TODO: check if specified key is valid. We should return null for unknown keys...
style = new StyleImpl();
TextAttributes textAttributes = getColorsScheme().getAttributes(TextAttributesKey.find(key));
if (textAttributes == null)
textAttributes = new TextAttributes();
style.set(StyleAttributes.TEXT_COLOR, textAttributes.getForegroundColor());
style.set(StyleAttributes.TEXT_BACKGROUND_COLOR, textAttributes.getBackgroundColor());
style.set(StyleAttributes.FONT_STYLE, textAttributes.getFontType());
if (textAttributes.getEffectColor() != null) {
style.set(StyleAttributes.UNDERLINED, textAttributes.getEffectType().equals(EffectType.LINE_UNDERSCORE));
style.set(StyleAttributes.STRIKE_OUT, textAttributes.getEffectType().equals(EffectType.STRIKEOUT));
}
setStyle(key, style);
}
return style;
}
@Override
public boolean isDarkTheme() {
return getColorsScheme().getName().contains("Darcula");
}
private void fillIdeaMappings() {
try {
addIdeaMappingsExt("DEFAULT_NULL_TEXT_COLOR", "NOT_USED_ELEMENT_ATTRIBUTES");
addIdeaMappingsExt("FOLDED_TEXT", "FOLDED_TEXT_ATTRIBUTES");
addIdeaMappingsExt("URL", "HYPERLINK_ATTRIBUTES");
addIdeaMappingsExt("LOCAL_VARIABLE", DefaultLanguageHighlighterColors.LOCAL_VARIABLE.toString());
addIdeaMappingsExt("PARAMETER", DefaultLanguageHighlighterColors.PARAMETER.toString());
addIdeaMappingsExt("INSTANCE_FIELD", "INSTANCE_FIELD_ATTRIBUTES");
addIdeaMappingsExt("METHOD_DECLARATION", DefaultLanguageHighlighterColors.FUNCTION_DECLARATION.toString());
addIdeaMappingsExt("METHOD_CALL", DefaultLanguageHighlighterColors.FUNCTION_CALL.toString());
addIdeaMappingsExt("STATIC_FIELD", "STATIC_FIELD_ATTRIBUTES");
addIdeaMappingsExt("STATIC_FINAL_FIELD", "STATIC_FINAL_FIELD_ATTRIBUTES");
addIdeaMappingsExt("STATIC_METHOD", DefaultLanguageHighlighterColors.STATIC_METHOD.toString());
addIdeaMappingsExt("DEPRECATED", "DEPRECATED_ATTRIBUTES");
addIdeaMappingsExt("CLASS_NAME", DefaultLanguageHighlighterColors.CLASS_NAME.toString());
addIdeaMappingsExt("ANNOTATION", "ANNOTATION_NAME_ATTRIBUTES");
addIdeaMappingsExt("NOT_USED_ELEMENT", "NOT_USED_ELEMENT_ATTRIBUTES");
addIdeaMappingsExt("TODO", "TODO_DEFAULT_ATTRIBUTES");
addIdeaMappingsExt("DOC_TAG", DefaultLanguageHighlighterColors.DOC_COMMENT_TAG.toString());
addIdeaMappingsExt("DOC_COMMENT", DefaultLanguageHighlighterColors.DOC_COMMENT.toString());
addIdeaMappingsExt("KEYWORD", DefaultLanguageHighlighterColors.KEYWORD.toString());
addIdeaMappingsExt("LINE_COMMENT", DefaultLanguageHighlighterColors.LINE_COMMENT.toString());
addIdeaMappingsExt("BLOCK_COMMENT", DefaultLanguageHighlighterColors.BLOCK_COMMENT.toString());
addIdeaMappingsExt("NUMBER", DefaultLanguageHighlighterColors.NUMBER.toString());
addIdeaMappingsExt("STRING", DefaultLanguageHighlighterColors.STRING.toString());
addIdeaMappingsExt("OPERATION_SIGN", DefaultLanguageHighlighterColors.OPERATION_SIGN.toString());
addIdeaMappingsExt("PARENTH", DefaultLanguageHighlighterColors.PARENTHESES.toString());
addIdeaMappingsExt("BRACKETS", DefaultLanguageHighlighterColors.BRACKETS.toString());
addIdeaMappingsExt("BRACES", DefaultLanguageHighlighterColors.BRACES.toString());
addIdeaMappingsExt("SEMICOLON", DefaultLanguageHighlighterColors.SEMICOLON.toString());
addIdeaMappingsExt("DOT", DefaultLanguageHighlighterColors.DOT.toString());
addIdeaMappingsExt("BREAKPOINT", "BREAKPOINT_ATTRIBUTES");
addIdeaMappingsExt("EXECUTIONPOINT", "EXECUTIONPOINT_ATTRIBUTES");
//addIdeaMappingsExt("","");
} catch (StyleRegistryMappingKeyException e) {
LOG.error("Exception on registering IDEA style mappings", e);
}
}
private void fillColorMappings() {
final Color bg = getEditorBackground();
myColorsMapping.put(new Pair<Color, Color>(MPSColors.LIGHT_BLUE, bg), new Color(104, 151, 186));
myColorsMapping.put(new Pair<Color, Color>(MPSColors.DARK_BLUE, bg), new Color(204, 120, 50));
myColorsMapping.put(new Pair<Color, Color>(MPSColors.DARK_GREEN, bg), new Color(98, 151, 85));
myColorsMapping.put(new Pair<Color, Color>(MPSColors.DARK_MAGENTA, bg), new Color(152, 118, 170));
myColorsMapping.put(new Pair<Color, Color>(MPSColors.RED, bg), new Color(255, 107, 104));
myColorsMapping.put(new Pair<Color, Color>(MPSColors.PINK, bg), new Color(90, 100, 126));
myColorsMapping.put(new Pair<Color, Color>(MPSColors.ORANGE, bg), new Color(255, 198, 109));
myColorsMapping.put(new Pair<Color, Color>(MPSColors.YELLOW, bg), new Color(0, 99, 0));
myColorsMapping.put(new Pair<Color, Color>(MPSColors.GREEN, bg), new Color(0, 128, 0));
myColorsMapping.put(new Pair<Color, Color>(MPSColors.MAGENTA, bg), new Color(174, 138, 190));
myColorsMapping.put(new Pair<Color, Color>(MPSColors.CYAN, bg), new Color(32, 153, 157));
myColorsMapping.put(new Pair<Color, Color>(MPSColors.BLUE, bg), new Color(40, 123, 222));
myColorsMapping.put(new Pair<Color, Color>(MPSColors.LIGHT_GRAY, bg), new Color(96, 96, 96));
myColorsMapping.put(new Pair<Color, Color>(MPSColors.GRAY, bg), MPSColors.GRAY);
myColorsMapping.put(new Pair<Color, Color>(MPSColors.DARK_GRAY, bg), MPSColors.LIGHT_GRAY);
myColorsMapping.put(new Pair<Color, Color>(MPSColors.WHITE, bg), getEditorBackground());
myColorsMapping.put(new Pair<Color, Color>(MPSColors.BLACK, bg), getEditorForeground());
}
private void addIdeaMappingsExt(String mpsKey, String ideaKey) throws StyleRegistryMappingKeyException {
if (myIDEAStylesMapping.containsKey(mpsKey))
throw new StyleRegistryMappingKeyException(mpsKey, myIDEAStylesMapping.get(mpsKey), ideaKey);
myIDEAStylesMapping.put(mpsKey, ideaKey);
}
@Override
public void globalSchemeChange(EditorColorsScheme scheme) {
clearCache();
}
@NotNull
private EditorColorsScheme getColorsScheme() {
return myColorsManager.getGlobalScheme();
}
private class StyleRegistryMappingKeyException extends Exception {
public StyleRegistryMappingKeyException(String mpsKey, String oldIdeaKey, String newIdeaKey) {
super("Can't set value '" + newIdeaKey + "' for key '" + mpsKey
+ "', because it has been already defined with value '" + oldIdeaKey + "'");
}
}
}