/* * Copyright 2000-2015 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.openapi.editor.colors.impl; import com.intellij.ide.ui.LafManager; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.components.*; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.EditorFactory; 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.TextAttributes; import com.intellij.openapi.options.BaseSchemeProcessor; import com.intellij.openapi.options.SchemesManager; import com.intellij.openapi.options.SchemesManagerFactory; import com.intellij.openapi.util.JDOMUtil; import com.intellij.openapi.util.WriteExternalException; import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.ComponentTreeEventDispatcher; import com.intellij.util.ThrowableConvertor; import com.intellij.util.io.URLUtil; import com.intellij.util.ui.UIUtil; import com.intellij.util.xmlb.annotations.OptionTag; import consulo.annotations.Immutable; import org.jdom.Element; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.TestOnly; import java.net.URL; import java.util.*; @State( name = "EditorColorsManagerImpl", storages = @Storage(file = StoragePathMacros.APP_CONFIG + "/colors.scheme.xml"), additionalExportFile = EditorColorsManagerImpl.FILE_SPEC ) public class EditorColorsManagerImpl extends EditorColorsManager implements PersistentStateComponent<EditorColorsManagerImpl.State> { private static final Logger LOG = Logger.getInstance(EditorColorsManagerImpl.class); @NonNls private static final String SCHEME_NODE_NAME = "scheme"; private static final String DEFAULT_NAME = "Default"; static final String FILE_SPEC = StoragePathMacros.ROOT_CONFIG + "/colors"; private final ComponentTreeEventDispatcher<EditorColorsListener> myTreeDispatcher = ComponentTreeEventDispatcher.create(EditorColorsListener.class); private final SchemesManager<EditorColorsScheme, EditorColorsSchemeImpl> mySchemesManager; private State myState = new State(); private final Map<String, EditorColorsScheme> myDefaultColorsSchemes = new LinkedHashMap<String, EditorColorsScheme>(); public EditorColorsManagerImpl(SchemesManagerFactory schemesManagerFactory) { mySchemesManager = schemesManagerFactory.createSchemesManager(FILE_SPEC, new BaseSchemeProcessor<EditorColorsSchemeImpl>() { @NotNull @Override public EditorColorsSchemeImpl readScheme(@NotNull Element element) { EditorColorsSchemeImpl scheme = new EditorColorsSchemeImpl(null, EditorColorsManagerImpl.this); scheme.readExternal(element); return scheme; } @Override public Element writeScheme(@NotNull final EditorColorsSchemeImpl scheme) { Element root = new Element(SCHEME_NODE_NAME); try { scheme.writeExternal(root); } catch (WriteExternalException e) { LOG.error(e); return null; } return root; } @NotNull @Override public State getState(@NotNull EditorColorsSchemeImpl scheme) { return scheme instanceof ReadOnlyColorsScheme ? State.NON_PERSISTENT : State.POSSIBLY_CHANGED; } @Override public void onCurrentSchemeChanged(final EditorColorsSchemeImpl newCurrentScheme) { fireChanges(mySchemesManager.getCurrentScheme()); } @NotNull @NonNls @Override public String getSchemeExtension() { return ".icls"; } @Override public boolean isUpgradeNeeded() { return true; } }, RoamingType.PER_USER); addDefaultSchemes(); // Load default schemes from providers for (BundledColorSchemeEP ep : BundledColorSchemeEP.EP_NAME.getExtensions()) { mySchemesManager.loadBundledScheme(ep.path, ep, new ThrowableConvertor<Element, EditorColorsScheme, Throwable>() { @Override public EditorColorsScheme convert(Element element) throws Throwable { DefaultColorsScheme defaultColorsScheme = new DefaultColorsScheme(EditorColorsManagerImpl.this); defaultColorsScheme.readExternal(element); myDefaultColorsSchemes.put(defaultColorsScheme.getName(), defaultColorsScheme); return defaultColorsScheme; } }); } mySchemesManager.loadSchemes(); loadAdditionalTextAttributes(); setGlobalSchemeInner(getDefaultScheme()); } static class State { public boolean USE_ONLY_MONOSPACED_FONTS = true; @OptionTag(tag = "global_color_scheme", nameAttribute = "", valueAttribute = "name") public String colorScheme; } private static boolean isUnitTestOrHeadlessMode() { return ApplicationManager.getApplication().isUnitTestMode() || ApplicationManager.getApplication().isHeadlessEnvironment(); } public TextAttributes getDefaultAttributes(TextAttributesKey key) { final boolean dark = UIUtil.isUnderDarkBuildInLaf() && getScheme("Darcula") != null; // It is reasonable to fetch attributes from Default color scheme. Otherwise if we launch IDE and then // try switch from custom colors scheme (e.g. with dark background) to default one. Editor will show // incorrect highlighting with "traces" of color scheme which was active during IDE startup. return getScheme(dark ? "Darcula" : EditorColorsScheme.DEFAULT_SCHEME_NAME).getAttributes(key); } private void loadAdditionalTextAttributes() { for (AdditionalTextAttributesEP attributesEP : AdditionalTextAttributesEP.EP_NAME.getExtensions()) { EditorColorsScheme editorColorsScheme = mySchemesManager.findSchemeByName(attributesEP.scheme); if (editorColorsScheme == null) { if (!isUnitTestOrHeadlessMode()) { LOG.warn("Cannot find scheme: " + attributesEP.scheme + " from plugin: " + attributesEP.getPluginDescriptor().getPluginId()); } continue; } try { URL resource = attributesEP.getLoaderForClass().getResource(attributesEP.file); assert resource != null; ((AbstractColorsScheme)editorColorsScheme).readAttributes(JDOMUtil.load(URLUtil.openStream(resource))); } catch (Exception e) { LOG.error(e); } } } @Override public void addColorsScheme(@NotNull EditorColorsScheme scheme) { if (!isDefaultScheme(scheme) && !StringUtil.isEmpty(scheme.getName())) { mySchemesManager.addNewScheme(scheme, true); } } @Override public void removeAllSchemes() { mySchemesManager.clearAllSchemes(); addDefaultSchemes(); } @Override @NotNull @Immutable public Map<String, EditorColorsScheme> getBundledSchemes() { return myDefaultColorsSchemes; } private void addDefaultSchemes() { for (EditorColorsScheme defaultScheme : myDefaultColorsSchemes.values()) { mySchemesManager.addNewScheme(defaultScheme, true); } } @NotNull @Override public EditorColorsScheme[] getAllSchemes() { List<EditorColorsScheme> schemes = mySchemesManager.getAllSchemes(); EditorColorsScheme[] result = schemes.toArray(new EditorColorsScheme[schemes.size()]); Arrays.sort(result, new Comparator<EditorColorsScheme>() { @Override public int compare(@NotNull EditorColorsScheme s1, @NotNull EditorColorsScheme s2) { if (isDefaultScheme(s1) && !isDefaultScheme(s2)) return -1; if (!isDefaultScheme(s1) && isDefaultScheme(s2)) return 1; if (s1.getName().equals(DEFAULT_NAME)) return -1; if (s2.getName().equals(DEFAULT_NAME)) return 1; return s1.getName().compareToIgnoreCase(s2.getName()); } }); return result; } @Override public void setGlobalScheme(@Nullable EditorColorsScheme scheme) { setGlobalSchemeInner(scheme); LafManager.getInstance().updateUI(); EditorFactory.getInstance().refreshAllEditors(); fireChanges(scheme); } private void setGlobalSchemeInner(@Nullable EditorColorsScheme scheme) { mySchemesManager.setCurrentSchemeName(scheme == null ? getDefaultScheme().getName() : scheme.getName()); } @NotNull private EditorColorsScheme getDefaultScheme() { return myDefaultColorsSchemes.get(DEFAULT_SCHEME_NAME); } @NotNull @Override public EditorColorsScheme getGlobalScheme() { EditorColorsScheme scheme = mySchemesManager.getCurrentScheme(); return scheme == null ? getDefaultScheme() : scheme; } @Override public EditorColorsScheme getScheme(@NotNull String schemeName) { return mySchemesManager.findSchemeByName(schemeName); } private void fireChanges(EditorColorsScheme scheme) { // we need to push events to components that use editor font, e.g. HTML editor panes ApplicationManager.getApplication().getMessageBus().syncPublisher(TOPIC).globalSchemeChange(scheme); myTreeDispatcher.getMulticaster().globalSchemeChange(scheme); } @Override public void setUseOnlyMonospacedFonts(boolean value) { myState.USE_ONLY_MONOSPACED_FONTS = value; } @Override public boolean isUseOnlyMonospacedFonts() { return myState.USE_ONLY_MONOSPACED_FONTS; } @Nullable @Override public State getState() { if (mySchemesManager.getCurrentScheme() != null) { String name = mySchemesManager.getCurrentScheme().getName(); myState.colorScheme = "Default".equals(name) ? null : name; } return myState; } @Override public void loadState(State state) { myState = state; setGlobalSchemeInner(myState.colorScheme == null ? getDefaultScheme() : mySchemesManager.findSchemeByName(myState.colorScheme)); } @Override public boolean isDefaultScheme(EditorColorsScheme scheme) { return scheme instanceof DefaultColorsScheme; } @TestOnly public SchemesManager<EditorColorsScheme, EditorColorsSchemeImpl> getSchemesManager() { return mySchemesManager; } }