blob: b06cd21daa609c3ca615d82dfaea0a565ac8d232 [file] [log] [blame]
/*
* Copyright 2000-2014 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 com.intellij.ide.ui.laf.darcula;
import com.intellij.ide.ui.laf.DarculaMetalTheme;
import com.intellij.ide.ui.laf.IdeaLaf;
import com.intellij.ide.ui.laf.LafManagerImpl;
import com.intellij.openapi.util.IconLoader;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.ui.ColorUtil;
import com.intellij.util.containers.hash.HashMap;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import sun.awt.AppContext;
import javax.swing.*;
import javax.swing.plaf.*;
import javax.swing.plaf.basic.BasicLookAndFeel;
import javax.swing.plaf.metal.DefaultMetalTheme;
import javax.swing.plaf.metal.MetalLookAndFeel;
import javax.swing.text.html.HTMLEditorKit;
import javax.swing.text.html.StyleSheet;
import java.awt.*;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.List;
import java.util.Properties;
/**
* @author Konstantin Bulenkov
*/
public class DarculaLaf extends BasicLookAndFeel {
public static final String NAME = "Darcula";
BasicLookAndFeel base;
public DarculaLaf() {
try {
if (SystemInfo.isWindows || SystemInfo.isLinux) {
base = new IdeaLaf();
} else {
final String name = UIManager.getSystemLookAndFeelClassName();
base = (BasicLookAndFeel)Class.forName(name).newInstance();
}
}
catch (Exception e) {
log(e);
}
}
private void callInit(String method, UIDefaults defaults) {
try {
final Method superMethod = BasicLookAndFeel.class.getDeclaredMethod(method, UIDefaults.class);
superMethod.setAccessible(true);
superMethod.invoke(base, defaults);
}
catch (Exception e) {
log(e);
}
}
@SuppressWarnings("UnusedParameters")
private static void log(Exception e) {
// everything is gonna be alright
// e.printStackTrace();
}
@Override
public UIDefaults getDefaults() {
try {
final Method superMethod = BasicLookAndFeel.class.getDeclaredMethod("getDefaults");
superMethod.setAccessible(true);
final UIDefaults metalDefaults = (UIDefaults)superMethod.invoke(new MetalLookAndFeel());
final UIDefaults defaults = (UIDefaults)superMethod.invoke(base);
if (SystemInfo.isLinux && !Registry.is("darcula.use.native.fonts.on.linux")) {
Font font = findFont("DejaVu Sans");
if (font != null) {
for (Object key : defaults.keySet()) {
if (key instanceof String && ((String)key).endsWith(".font")) {
defaults.put(key, new FontUIResource(font.deriveFont(13f)));
}
}
}
}
LafManagerImpl.initInputMapDefaults(defaults);
initIdeaDefaults(defaults);
patchStyledEditorKit(defaults);
patchComboBox(metalDefaults, defaults);
defaults.remove("Spinner.arrowButtonBorder");
defaults.put("Spinner.arrowButtonSize", new Dimension(16, 5));
MetalLookAndFeel.setCurrentTheme(createMetalTheme());
if (SystemInfo.isWindows) {
//JFrame.setDefaultLookAndFeelDecorated(true);
}
defaults.put("EditorPane.font", defaults.getFont("TextField.font"));
return defaults;
}
catch (Exception e) {
log(e);
}
return super.getDefaults();
}
protected DefaultMetalTheme createMetalTheme() {
return new DarculaMetalTheme();
}
private static Font findFont(String name) {
for (Font font : GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts()) {
if (font.getName().equals(name)) {
return font;
}
}
return null;
}
private static void patchComboBox(UIDefaults metalDefaults, UIDefaults defaults) {
defaults.remove("ComboBox.ancestorInputMap");
defaults.remove("ComboBox.actionMap");
defaults.put("ComboBox.ancestorInputMap", metalDefaults.get("ComboBox.ancestorInputMap"));
defaults.put("ComboBox.actionMap", metalDefaults.get("ComboBox.actionMap"));
}
@SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
private void patchStyledEditorKit(UIDefaults defaults) {
URL url = getClass().getResource(getPrefix() + ".css");
StyleSheet styleSheet = UIUtil.loadStyleSheet(url);
defaults.put("StyledEditorKit.JBDefaultStyle", styleSheet);
try {
Field keyField = HTMLEditorKit.class.getDeclaredField("DEFAULT_STYLES_KEY");
keyField.setAccessible(true);
AppContext.getAppContext().put(keyField.get(null), UIUtil.loadStyleSheet(url));
}
catch (Exception e) {
log(e);
}
}
protected String getPrefix() {
return "darcula";
}
private void call(String method) {
try {
final Method superMethod = BasicLookAndFeel.class.getDeclaredMethod(method);
superMethod.setAccessible(true);
superMethod.invoke(base);
}
catch (Exception ignore) {
log(ignore);
}
}
public void initComponentDefaults(UIDefaults defaults) {
callInit("initComponentDefaults", defaults);
}
@SuppressWarnings({"HardCodedStringLiteral"})
protected void initIdeaDefaults(UIDefaults defaults) {
loadDefaults(defaults);
defaults.put("Table.ancestorInputMap", new UIDefaults.LazyInputMap(new Object[] {
"ctrl C", "copy",
"ctrl V", "paste",
"ctrl X", "cut",
"COPY", "copy",
"PASTE", "paste",
"CUT", "cut",
"control INSERT", "copy",
"shift INSERT", "paste",
"shift DELETE", "cut",
"RIGHT", "selectNextColumn",
"KP_RIGHT", "selectNextColumn",
"LEFT", "selectPreviousColumn",
"KP_LEFT", "selectPreviousColumn",
"DOWN", "selectNextRow",
"KP_DOWN", "selectNextRow",
"UP", "selectPreviousRow",
"KP_UP", "selectPreviousRow",
"shift RIGHT", "selectNextColumnExtendSelection",
"shift KP_RIGHT", "selectNextColumnExtendSelection",
"shift LEFT", "selectPreviousColumnExtendSelection",
"shift KP_LEFT", "selectPreviousColumnExtendSelection",
"shift DOWN", "selectNextRowExtendSelection",
"shift KP_DOWN", "selectNextRowExtendSelection",
"shift UP", "selectPreviousRowExtendSelection",
"shift KP_UP", "selectPreviousRowExtendSelection",
"PAGE_UP", "scrollUpChangeSelection",
"PAGE_DOWN", "scrollDownChangeSelection",
"HOME", "selectFirstColumn",
"END", "selectLastColumn",
"shift PAGE_UP", "scrollUpExtendSelection",
"shift PAGE_DOWN", "scrollDownExtendSelection",
"shift HOME", "selectFirstColumnExtendSelection",
"shift END", "selectLastColumnExtendSelection",
"ctrl PAGE_UP", "scrollLeftChangeSelection",
"ctrl PAGE_DOWN", "scrollRightChangeSelection",
"ctrl HOME", "selectFirstRow",
"ctrl END", "selectLastRow",
"ctrl shift PAGE_UP", "scrollRightExtendSelection",
"ctrl shift PAGE_DOWN", "scrollLeftExtendSelection",
"ctrl shift HOME", "selectFirstRowExtendSelection",
"ctrl shift END", "selectLastRowExtendSelection",
"TAB", "selectNextColumnCell",
"shift TAB", "selectPreviousColumnCell",
//"ENTER", "selectNextRowCell",
"shift ENTER", "selectPreviousRowCell",
"ctrl A", "selectAll",
"meta A", "selectAll",
//"ESCAPE", "cancel",
"F2", "startEditing"
}));
}
@SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
protected void loadDefaults(UIDefaults defaults) {
final Properties properties = new Properties();
final String osSuffix = SystemInfo.isMac ? "mac" : SystemInfo.isWindows ? "windows" : "linux";
try {
InputStream stream = getClass().getResourceAsStream(getPrefix() + ".properties");
properties.load(stream);
stream.close();
stream = getClass().getResourceAsStream(getPrefix() + "_" + osSuffix + ".properties");
properties.load(stream);
stream.close();
HashMap<String, Object> darculaGlobalSettings = new HashMap<String, Object>();
final String prefix = getPrefix() + ".";
for (String key : properties.stringPropertyNames()) {
if (key.startsWith(prefix)) {
darculaGlobalSettings.put(key.substring(prefix.length()), parseValue(key, properties.getProperty(key)));
}
}
for (Object key : defaults.keySet()) {
if (key instanceof String && ((String)key).contains(".")) {
final String s = (String)key;
final String darculaKey = s.substring(s.lastIndexOf('.') + 1);
if (darculaGlobalSettings.containsKey(darculaKey)) {
defaults.put(key, darculaGlobalSettings.get(darculaKey));
}
}
}
for (String key : properties.stringPropertyNames()) {
final String value = properties.getProperty(key);
defaults.put(key, parseValue(key, value));
}
}
catch (IOException e) {log(e);}
}
protected Object parseValue(String key, @NotNull String value) {
if ("null".equals(value)) {
return null;
}
if (key.endsWith("Insets")) {
return parseInsets(value);
} else if (key.endsWith("Border") || key.endsWith("border")) {
try {
if (StringUtil.split(value, ",").size() == 4) {
return new BorderUIResource.EmptyBorderUIResource(parseInsets(value));
} else {
return Class.forName(value).newInstance();
}
} catch (Exception e) {
log(e);
}
} else {
final Color color = parseColor(value);
final Integer invVal = getInteger(value);
final Boolean boolVal = "true".equals(value) ? Boolean.TRUE : "false".equals(value) ? Boolean.FALSE : null;
Icon icon = value.startsWith("AllIcons.") ? IconLoader.getIcon(value) : null;
if (icon == null && value.endsWith(".png")) {
icon = IconLoader.findIcon(value, getClass(), true);
}
if (color != null) {
return new ColorUIResource(color);
} else if (invVal != null) {
return invVal;
} else if (icon != null) {
return new IconUIResource(icon);
} else if (boolVal != null) {
return boolVal;
}
}
return value;
}
private static Insets parseInsets(String value) {
final List<String> numbers = StringUtil.split(value, ",");
return new InsetsUIResource(Integer.parseInt(numbers.get(0)),
Integer.parseInt(numbers.get(1)),
Integer.parseInt(numbers.get(2)),
Integer.parseInt(numbers.get(3)));
}
@SuppressWarnings("UseJBColor")
private static Color parseColor(String value) {
if (value != null && value.length() == 8) {
final Color color = ColorUtil.fromHex(value.substring(0, 6));
if (color != null) {
try {
int alpha = Integer.parseInt(value.substring(6, 8), 16);
return new ColorUIResource(new Color(color.getRed(), color.getGreen(), color.getBlue(), alpha));
} catch (Exception ignore){}
}
return null;
}
return ColorUtil.fromHex(value, null);
}
private static Integer getInteger(String value) {
try {
return Integer.parseInt(value);
}
catch (NumberFormatException e) {
return null;
}
}
@Override
public String getName() {
return NAME;
}
@Override
public String getID() {
return getName();
}
@Override
public String getDescription() {
return "IntelliJ Dark Look and Feel";
}
@Override
public boolean isNativeLookAndFeel() {
return true;
}
@Override
public boolean isSupportedLookAndFeel() {
return true;
}
@Override
protected void initSystemColorDefaults(UIDefaults defaults) {
callInit("initSystemColorDefaults", defaults);
}
@Override
protected void initClassDefaults(UIDefaults defaults) {
callInit("initClassDefaults", defaults);
}
@Override
public void initialize() {
call("initialize");
}
@Override
public void uninitialize() {
call("uninitialize");
}
@Override
protected void loadSystemColors(UIDefaults defaults, String[] systemColors, boolean useNative) {
try {
final Method superMethod = BasicLookAndFeel.class.getDeclaredMethod("loadSystemColors",
UIDefaults.class,
String[].class,
boolean.class);
superMethod.setAccessible(true);
superMethod.invoke(base, defaults, systemColors, useNative);
}
catch (Exception ignore) {
log(ignore);
}
}
@Override
public boolean getSupportsWindowDecorations() {
return true;
}
}