09c152e0c16d77cc4fce6e68b7d5057c11142ddd
[idea/community.git] / platform / lang-impl / src / com / intellij / application / options / colors / ColorAndFontOptions.java
1 /*
2  * Copyright 2000-2016 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package com.intellij.application.options.colors;
18
19 import com.intellij.application.options.OptionsContainingConfigurable;
20 import com.intellij.application.options.editor.EditorOptionsProvider;
21 import com.intellij.execution.impl.ConsoleViewUtil;
22 import com.intellij.ide.ui.LafManager;
23 import com.intellij.ide.ui.laf.LafManagerImpl;
24 import com.intellij.ide.ui.laf.darcula.DarculaInstaller;
25 import com.intellij.ide.ui.laf.darcula.DarculaLookAndFeelInfo;
26 import com.intellij.ide.util.PropertiesComponent;
27 import com.intellij.openapi.Disposable;
28 import com.intellij.openapi.application.ApplicationBundle;
29 import com.intellij.openapi.application.ApplicationNamesInfo;
30 import com.intellij.openapi.diagnostic.Logger;
31 import com.intellij.openapi.editor.colors.ColorKey;
32 import com.intellij.openapi.editor.colors.EditorColorsManager;
33 import com.intellij.openapi.editor.colors.EditorColorsScheme;
34 import com.intellij.openapi.editor.colors.TextAttributesKey;
35 import com.intellij.openapi.editor.colors.impl.*;
36 import com.intellij.openapi.editor.markup.EffectType;
37 import com.intellij.openapi.editor.markup.TextAttributes;
38 import com.intellij.openapi.extensions.Extensions;
39 import com.intellij.openapi.options.Configurable;
40 import com.intellij.openapi.options.ConfigurationException;
41 import com.intellij.openapi.options.SchemeManager;
42 import com.intellij.openapi.options.SearchableConfigurable;
43 import com.intellij.openapi.options.colors.*;
44 import com.intellij.openapi.project.Project;
45 import com.intellij.openapi.project.ProjectManager;
46 import com.intellij.openapi.ui.DialogWrapper;
47 import com.intellij.openapi.ui.Messages;
48 import com.intellij.openapi.util.Comparing;
49 import com.intellij.openapi.util.Disposer;
50 import com.intellij.openapi.util.Pair;
51 import com.intellij.openapi.vcs.FileStatus;
52 import com.intellij.openapi.vcs.FileStatusFactory;
53 import com.intellij.packageDependencies.DependencyValidationManager;
54 import com.intellij.packageDependencies.DependencyValidationManagerImpl;
55 import com.intellij.psi.codeStyle.DisplayPriority;
56 import com.intellij.psi.codeStyle.DisplayPrioritySortable;
57 import com.intellij.psi.search.scope.packageSet.NamedScope;
58 import com.intellij.psi.search.scope.packageSet.NamedScopesHolder;
59 import com.intellij.psi.search.scope.packageSet.PackageSet;
60 import com.intellij.ui.ColorUtil;
61 import com.intellij.util.ArrayUtil;
62 import com.intellij.util.ui.UIUtil;
63 import gnu.trove.THashMap;
64 import gnu.trove.THashSet;
65 import gnu.trove.TObjectHashingStrategy;
66 import org.jetbrains.annotations.Nls;
67 import org.jetbrains.annotations.NonNls;
68 import org.jetbrains.annotations.NotNull;
69 import org.jetbrains.annotations.Nullable;
70
71 import javax.swing.*;
72 import java.awt.*;
73 import java.util.*;
74 import java.util.List;
75
76 public class ColorAndFontOptions extends SearchableConfigurable.Parent.Abstract implements EditorOptionsProvider {
77   public static final String ID = "reference.settingsdialog.IDE.editor.colors";
78
79   private Map<String, MyColorScheme> mySchemes;
80   private MyColorScheme mySelectedScheme;
81   public static final String FILE_STATUS_GROUP = ApplicationBundle.message("title.file.status");
82   public static final String SCOPES_GROUP = ApplicationBundle.message("title.scope.based");
83
84   private boolean mySomeSchemesDeleted = false;
85   private Map<ColorAndFontPanelFactory, InnerSearchableConfigurable> mySubPanelFactories;
86
87   private SchemesPanel myRootSchemesPanel;
88
89   private boolean myInitResetCompleted = false;
90   private boolean myInitResetInvoked = false;
91
92   private boolean myRevertChangesCompleted = false;
93
94   private boolean myApplyCompleted = false;
95   private boolean myDisposeCompleted = false;
96   private final Disposable myDisposable = Disposer.newDisposable();
97
98   @Override
99   public boolean isModified() {
100     boolean listModified = isSchemeListModified();
101     boolean schemeModified = isSomeSchemeModified();
102
103     if (listModified || schemeModified) {
104       myApplyCompleted = false;
105     }
106
107     return listModified;
108   }
109
110   private boolean isSchemeListModified(){
111     if (mySomeSchemesDeleted) return true;
112
113     if (!mySelectedScheme.getName().equals(EditorColorsManager.getInstance().getGlobalScheme().getName())) return true;
114
115     for (MyColorScheme scheme : mySchemes.values()) {
116       if (scheme.isNew()) return true;
117     }
118
119     return false;
120   }
121
122   private boolean isSomeSchemeModified() {
123     for (MyColorScheme scheme : mySchemes.values()) {
124       if (scheme.isModified()) return true;
125     }
126     return false;
127   }
128
129   public EditorColorsScheme selectScheme(@NotNull String name) {
130     mySelectedScheme = getScheme(name);
131     return mySelectedScheme;
132   }
133
134   private MyColorScheme getScheme(String name) {
135     return mySchemes.get(name);
136   }
137
138   @NotNull
139   public String getUniqueName(@NotNull String preferredName) {
140     String name;
141     if (mySchemes.containsKey(preferredName)) {
142       for (int i = 1; ; i++) {
143         name = preferredName + " (" + i + ")";
144         if (!mySchemes.containsKey(name)) {
145           break;
146         }
147       }
148     }
149     else {
150       name = preferredName;
151     }
152     return name;
153   }
154
155   public EditorColorsScheme getSelectedScheme() {
156     return mySelectedScheme;
157   }
158
159   public EditorSchemeAttributeDescriptor[] getCurrentDescriptions() {
160     return mySelectedScheme.getDescriptors();
161   }
162
163   public static boolean isReadOnly(@NotNull final EditorColorsScheme scheme) {
164     return ((MyColorScheme)scheme).isReadOnly();
165   }
166
167   @NotNull
168   public String[] getSchemeNames() {
169     List<MyColorScheme> schemes = new ArrayList<MyColorScheme>(mySchemes.values());
170     Collections.sort(schemes, (o1, o2) -> {
171       if (isReadOnly(o1) && !isReadOnly(o2)) return -1;
172       if (!isReadOnly(o1) && isReadOnly(o2)) return 1;
173
174       return o1.getName().compareToIgnoreCase(o2.getName());
175     });
176
177     List<String> names = new ArrayList<String>(schemes.size());
178     for (MyColorScheme scheme : schemes) {
179       names.add(scheme.getName());
180     }
181
182     return ArrayUtil.toStringArray(names);
183   }
184
185   @NotNull
186   public Collection<EditorColorsScheme> getSchemes() {
187     return new ArrayList<EditorColorsScheme>(mySchemes.values());
188   }
189
190   public void saveSchemeAs(String name) {
191     MyColorScheme scheme = mySelectedScheme;
192     if (scheme == null) return;
193
194     EditorColorsScheme clone = (EditorColorsScheme)scheme.getOriginalScheme().clone();
195
196     scheme.apply(clone);
197
198     clone.setName(name);
199     MyColorScheme newScheme = new MyColorScheme(clone);
200     initScheme(newScheme);
201
202     newScheme.setIsNew();
203
204     mySchemes.put(name, newScheme);
205     selectScheme(newScheme.getName());
206     resetSchemesCombo(null);
207   }
208
209   public void addImportedScheme(@NotNull EditorColorsScheme imported) {
210     MyColorScheme newScheme = new MyColorScheme(imported);
211     initScheme(newScheme);
212
213     mySchemes.put(imported.getName(), newScheme);
214     selectScheme(newScheme.getName());
215     resetSchemesCombo(null);
216   }
217
218   public void removeScheme(String name) {
219     if (mySelectedScheme.getName().equals(name)) {
220       //noinspection HardCodedStringLiteral
221       selectScheme("Default");
222     }
223
224     boolean deletedNewlyCreated = false;
225
226     MyColorScheme toDelete = mySchemes.get(name);
227
228     if (toDelete != null) {
229       deletedNewlyCreated = toDelete.isNew();
230     }
231
232     mySchemes.remove(name);
233     resetSchemesCombo(null);
234     mySomeSchemesDeleted = mySomeSchemesDeleted || !deletedNewlyCreated;
235   }
236
237   @Override
238   public void apply() throws ConfigurationException {
239     if (myApplyCompleted) {
240       return;
241     }
242
243     try {
244       EditorColorsManager myColorsManager = EditorColorsManager.getInstance();
245       SchemeManager<EditorColorsScheme> schemeManager = ((EditorColorsManagerImpl)myColorsManager).getSchemeManager();
246
247       List<EditorColorsScheme> result = new ArrayList<EditorColorsScheme>(mySchemes.values().size());
248       boolean activeSchemeModified = false;
249       EditorColorsScheme activeOriginalScheme = mySelectedScheme.getOriginalScheme();
250       for (MyColorScheme scheme : mySchemes.values()) {
251         if (!activeSchemeModified && activeOriginalScheme == scheme.getOriginalScheme()) {
252           activeSchemeModified = scheme.isModified();
253         }
254
255         if (!scheme.isDefault()) {
256           scheme.apply();
257         }
258         result.add(scheme.getOriginalScheme());
259       }
260
261       // refresh only if scheme is not switched
262       boolean refreshEditors = activeSchemeModified && schemeManager.getCurrentScheme() == activeOriginalScheme;
263       schemeManager.setSchemes(result, activeOriginalScheme);
264       if (refreshEditors) {
265         EditorColorsManagerImpl.schemeChangedOrSwitched();
266       }
267
268       final boolean isEditorThemeDark = ColorUtil.isDark(activeOriginalScheme.getDefaultBackground());
269       changeLafIfNecessary(isEditorThemeDark);
270
271       reset();
272     }
273     finally {
274       myApplyCompleted = true;
275     }
276   }
277
278   private static void changeLafIfNecessary(boolean isDarkEditorTheme) {
279     String propKey = "change.laf.on.editor.theme.change";
280     String value = PropertiesComponent.getInstance().getValue(propKey);
281     if ("false".equals(value)) return;
282     boolean applyAlways = "true".equals(value);
283     DialogWrapper.DoNotAskOption doNotAskOption = new DialogWrapper.DoNotAskOption.Adapter() {
284       @Override
285       public void rememberChoice(boolean isSelected, int exitCode) {
286         if (isSelected) {
287           PropertiesComponent.getInstance().setValue(propKey, Boolean.toString(exitCode == Messages.YES));
288         }
289       }
290
291       @Override
292       public boolean shouldSaveOptionsOnCancel() {
293         return true;
294       }
295     };
296
297     final String productName = ApplicationNamesInfo.getInstance().getFullProductName();
298     final LafManager lafManager = LafManager.getInstance();
299     if (isDarkEditorTheme && !UIUtil.isUnderDarcula()) {
300       if (applyAlways || Messages.showYesNoDialog(
301         "Looks like you have set a dark editor theme. Would you like to set dark theme for entire " + productName,
302         "Change " + productName + " theme", Messages.YES_BUTTON, Messages.NO_BUTTON,
303         Messages.getQuestionIcon(), doNotAskOption) == Messages.YES) {
304         lafManager.setCurrentLookAndFeel(new DarculaLookAndFeelInfo());
305         lafManager.updateUI();
306         //noinspection SSBasedInspection
307         SwingUtilities.invokeLater(DarculaInstaller::install);
308       }
309     } else if (!isDarkEditorTheme && UIUtil.isUnderDarcula()) {
310
311       if (lafManager instanceof LafManagerImpl
312           &&
313           (applyAlways || Messages.showYesNoDialog(
314             "Looks like you have set a bright editor theme. Would you like to set bright theme for entire " + productName,
315             "Change " + productName + " theme", Messages.YES_BUTTON, Messages.NO_BUTTON,
316             Messages.getQuestionIcon(), doNotAskOption) == Messages.YES)) {
317         lafManager.setCurrentLookAndFeel(((LafManagerImpl)lafManager).getDefaultLaf());
318         lafManager.updateUI();
319         //noinspection SSBasedInspection
320         SwingUtilities.invokeLater(DarculaInstaller::uninstall);
321       }
322     }
323   }
324
325   private boolean myIsReset = false;
326
327   private void resetSchemesCombo(Object source) {
328     myIsReset = true;
329     try {
330       myRootSchemesPanel.resetSchemesCombo(source);
331       if (mySubPanelFactories != null) {
332         for (NewColorAndFontPanel subPartialConfigurable : getPanels()) {
333           subPartialConfigurable.reset(source);
334         }
335       }
336     }
337     finally {
338       myIsReset = false;
339     }
340   }
341
342   @Override
343   public JComponent createComponent() {
344     if (myRootSchemesPanel == null) {
345       ensureSchemesPanel();
346     }
347     return myRootSchemesPanel;
348   }
349
350   @Override
351   public boolean hasOwnContent() {
352     return true;
353   }
354
355   @NotNull
356   @Override
357   public Configurable[] buildConfigurables() {
358     myDisposeCompleted = false;
359     initAll();
360
361     List<ColorAndFontPanelFactory> panelFactories = createPanelFactories();
362
363     List<Configurable> result = new ArrayList<Configurable>();
364     mySubPanelFactories = new LinkedHashMap<ColorAndFontPanelFactory, InnerSearchableConfigurable>(panelFactories.size());
365     for (ColorAndFontPanelFactory panelFactory : panelFactories) {
366       mySubPanelFactories.put(panelFactory, new InnerSearchableConfigurable(panelFactory));
367     }
368
369     result.addAll(new ArrayList<SearchableConfigurable>(mySubPanelFactories.values()));
370     return result.toArray(new Configurable[result.size()]);
371   }
372
373   @NotNull
374   private Set<NewColorAndFontPanel> getPanels() {
375     Set<NewColorAndFontPanel> result = new HashSet<NewColorAndFontPanel>();
376     for (InnerSearchableConfigurable configurable : mySubPanelFactories.values()) {
377       NewColorAndFontPanel panel = configurable.getSubPanelIfInitialized();
378       if (panel != null) {
379         result.add(panel);
380       }
381     }
382     return result;
383   }
384
385   protected List<ColorAndFontPanelFactory> createPanelFactories() {
386     List<ColorAndFontPanelFactory> result = new ArrayList<ColorAndFontPanelFactory>();
387     result.add(new FontConfigurableFactory());
388
389     List<ColorAndFontPanelFactory> extensions = new ArrayList<ColorAndFontPanelFactory>();
390     extensions.add(new ConsoleFontConfigurableFactory());
391     ColorSettingsPage[] pages = ColorSettingsPages.getInstance().getRegisteredPages();
392     for (final ColorSettingsPage page : pages) {
393       extensions.add(new ColorAndFontPanelFactoryEx() {
394         @Override
395         @NotNull
396         public NewColorAndFontPanel createPanel(@NotNull ColorAndFontOptions options) {
397           final SimpleEditorPreview preview = new SimpleEditorPreview(options, page);
398           return NewColorAndFontPanel.create(preview, page.getDisplayName(), options, null, page);
399         }
400
401         @Override
402         @NotNull
403         public String getPanelDisplayName() {
404           return page.getDisplayName();
405         }
406
407         @Override
408         public DisplayPriority getPriority() {
409           if (page instanceof DisplayPrioritySortable) {
410             return ((DisplayPrioritySortable)page).getPriority();
411           }
412           return DisplayPriority.LANGUAGE_SETTINGS;
413         }
414       });
415     }
416     Collections.addAll(extensions, Extensions.getExtensions(ColorAndFontPanelFactory.EP_NAME));
417     Collections.sort(extensions, (f1, f2) -> {
418       if (f1 instanceof DisplayPrioritySortable) {
419         if (f2 instanceof DisplayPrioritySortable) {
420           int result1 = ((DisplayPrioritySortable)f1).getPriority().compareTo(((DisplayPrioritySortable)f2).getPriority());
421           if (result1 != 0) return result1;
422         }
423         else {
424           return 1;
425         }
426       }
427       else if (f2 instanceof DisplayPrioritySortable) {
428         return -1;
429       }
430       return f1.getPanelDisplayName().compareToIgnoreCase(f2.getPanelDisplayName());
431     });
432     result.addAll(extensions);
433
434     result.add(new FileStatusColorsPageFactory());
435     result.add(new ScopeColorsPageFactory());
436
437     return result;
438   }
439
440   private static class FontConfigurableFactory implements ColorAndFontPanelFactory {
441     @Override
442     @NotNull
443     public NewColorAndFontPanel createPanel(@NotNull ColorAndFontOptions options) {
444       FontEditorPreview previewPanel = new FontEditorPreview(options, true);
445       return new NewColorAndFontPanel(new SchemesPanel(options), new FontOptions(options), previewPanel, "Font", null, null){
446         @Override
447         public boolean containsFontOptions() {
448           return true;
449         }
450       };
451     }
452
453     @Override
454     @NotNull
455     public String getPanelDisplayName() {
456       return "Font";
457     }
458   }
459
460    private static class ConsoleFontConfigurableFactory implements ColorAndFontPanelFactoryEx {
461     @Override
462     @NotNull
463     public NewColorAndFontPanel createPanel(@NotNull ColorAndFontOptions options) {
464       FontEditorPreview previewPanel = new FontEditorPreview(options, false) {
465         @Override
466         protected EditorColorsScheme updateOptionsScheme(EditorColorsScheme selectedScheme) {
467           return ConsoleViewUtil.updateConsoleColorScheme(selectedScheme);
468         }
469       };
470       return new NewColorAndFontPanel(new SchemesPanel(options), new ConsoleFontOptions(options), previewPanel, "Font", null, null){
471         @Override
472         public boolean containsFontOptions() {
473           return true;
474         }
475       };
476     }
477
478     @Override
479     @NotNull
480     public String getPanelDisplayName() {
481       return "Console Font";
482     }
483
484      @NotNull
485      @Override
486      public DisplayPriority getPriority() {
487        return DisplayPriority.COMMON_SETTINGS;
488      }
489    }
490
491   private void initAll() {
492     mySchemes = new THashMap<String, MyColorScheme>();
493     for (EditorColorsScheme allScheme : EditorColorsManager.getInstance().getAllSchemes()) {
494       MyColorScheme schemeDelegate = new MyColorScheme(allScheme);
495       initScheme(schemeDelegate);
496       mySchemes.put(schemeDelegate.getName(), schemeDelegate);
497     }
498
499     mySelectedScheme = mySchemes.get(EditorColorsManager.getInstance().getGlobalScheme().getName());
500     assert mySelectedScheme != null : EditorColorsManager.getInstance().getGlobalScheme().getName() + "; myschemes=" + mySchemes;
501   }
502
503   private static void initScheme(@NotNull MyColorScheme scheme) {
504     List<EditorSchemeAttributeDescriptor> descriptions = new ArrayList<EditorSchemeAttributeDescriptor>();
505     initPluggedDescriptions(descriptions, scheme);
506     initFileStatusDescriptors(descriptions, scheme);
507     initScopesDescriptors(descriptions, scheme);
508
509     scheme.setDescriptors(descriptions.toArray(new EditorSchemeAttributeDescriptor[descriptions.size()]));
510   }
511
512   private static void initPluggedDescriptions(@NotNull List<EditorSchemeAttributeDescriptor> descriptions, @NotNull MyColorScheme scheme) {
513     ColorSettingsPage[] pages = ColorSettingsPages.getInstance().getRegisteredPages();
514     for (ColorSettingsPage page : pages) {
515       initDescriptions(page, descriptions, scheme);
516     }
517     for (ColorAndFontDescriptorsProvider provider : Extensions.getExtensions(ColorAndFontDescriptorsProvider.EP_NAME)) {
518       initDescriptions(provider, descriptions, scheme);
519     }
520   }
521
522   private static void initDescriptions(@NotNull ColorAndFontDescriptorsProvider provider,
523                                        @NotNull List<EditorSchemeAttributeDescriptor> descriptions,
524                                        @NotNull MyColorScheme scheme) {
525     String group = provider.getDisplayName();
526     List<AttributesDescriptor> attributeDescriptors = ColorSettingsUtil.getAllAttributeDescriptors(provider);
527     for (AttributesDescriptor descriptor : attributeDescriptors) {
528       addSchemedDescription(descriptions, descriptor.getDisplayName(), group, descriptor.getKey(), scheme, null, null);
529     }
530
531     ColorDescriptor[] colorDescriptors = provider.getColorDescriptors();
532     for (ColorDescriptor descriptor : colorDescriptors) {
533       ColorKey back = descriptor.getKind() == ColorDescriptor.Kind.BACKGROUND ? descriptor.getKey() : null;
534       ColorKey fore = descriptor.getKind() == ColorDescriptor.Kind.FOREGROUND ? descriptor.getKey() : null;
535       addEditorSettingDescription(descriptions, descriptor.getDisplayName(), group, back, fore, scheme);
536     }
537   }
538
539   private static void initFileStatusDescriptors(@NotNull List<EditorSchemeAttributeDescriptor> descriptions, MyColorScheme scheme) {
540
541     FileStatus[] statuses = FileStatusFactory.getInstance().getAllFileStatuses();
542
543     for (FileStatus fileStatus : statuses) {
544       addEditorSettingDescription(descriptions,
545                                   fileStatus.getText(),
546                                   FILE_STATUS_GROUP,
547                                   null,
548                                   fileStatus.getColorKey(),
549                                   scheme);
550
551     }
552   }
553   private static void initScopesDescriptors(@NotNull List<EditorSchemeAttributeDescriptor> descriptions, @NotNull MyColorScheme scheme) {
554     Set<Pair<NamedScope,NamedScopesHolder>> namedScopes = new THashSet<Pair<NamedScope,NamedScopesHolder>>(new TObjectHashingStrategy<Pair<NamedScope,NamedScopesHolder>>() {
555       @Override
556       public int computeHashCode(@NotNull final Pair<NamedScope, NamedScopesHolder> object) {
557         return object.getFirst().getName().hashCode();
558       }
559
560       @Override
561       public boolean equals(@NotNull final Pair<NamedScope, NamedScopesHolder> o1, @NotNull final Pair<NamedScope, NamedScopesHolder> o2) {
562         return o1.getFirst().getName().equals(o2.getFirst().getName());
563       }
564     });
565     Project[] projects = ProjectManager.getInstance().getOpenProjects();
566     for (Project project : projects) {
567       DependencyValidationManagerImpl validationManager = (DependencyValidationManagerImpl)DependencyValidationManager.getInstance(project);
568       List<Pair<NamedScope,NamedScopesHolder>> cachedScopes = validationManager.getScopeBasedHighlightingCachedScopes();
569       namedScopes.addAll(cachedScopes);
570     }
571
572     List<Pair<NamedScope, NamedScopesHolder>> list = new ArrayList<Pair<NamedScope, NamedScopesHolder>>(namedScopes);
573
574     Collections.sort(list, (o1, o2) -> o1.getFirst().getName().compareToIgnoreCase(o2.getFirst().getName()));
575     for (Pair<NamedScope,NamedScopesHolder> pair : list) {
576       NamedScope namedScope = pair.getFirst();
577       String name = namedScope.getName();
578       TextAttributesKey textAttributesKey = ScopeAttributesUtil.getScopeTextAttributeKey(name);
579       if (scheme.getAttributes(textAttributesKey) == null) {
580         scheme.setAttributes(textAttributesKey, new TextAttributes());
581       }
582       NamedScopesHolder holder = pair.getSecond();
583
584       PackageSet value = namedScope.getValue();
585       String toolTip = holder.getDisplayName() + (value==null ? "" : ": "+ value.getText());
586       addSchemedDescription(descriptions,
587                             name,
588                             SCOPES_GROUP,
589                             textAttributesKey,
590                             scheme, holder.getIcon(), toolTip);
591     }
592   }
593
594   @Nullable
595   private static String calcType(@Nullable ColorKey backgroundKey, @Nullable ColorKey foregroundKey) {
596     if (foregroundKey != null) {
597       return foregroundKey.getExternalName();
598     }
599     else if (backgroundKey != null) {
600       return backgroundKey.getExternalName();
601     }
602     return null;
603   }
604
605   private static void addEditorSettingDescription(@NotNull List<EditorSchemeAttributeDescriptor> list,
606                                                   String name,
607                                                   String group,
608                                                   @Nullable ColorKey backgroundKey,
609                                                   @Nullable ColorKey foregroundKey,
610                                                   @NotNull EditorColorsScheme scheme) {
611     list.add(new EditorSettingColorDescription(name, group, backgroundKey, foregroundKey, calcType(backgroundKey, foregroundKey), scheme));
612   }
613
614   private static void addSchemedDescription(@NotNull List<EditorSchemeAttributeDescriptor> list,
615                                             String name,
616                                             String group,
617                                             @NotNull TextAttributesKey key,
618                                             @NotNull MyColorScheme scheme,
619                                             Icon icon,
620                                             String toolTip) {
621     list.add(new SchemeTextAttributesDescription(name, group, key, scheme, icon, toolTip));
622   }
623
624   @Override
625   public String getDisplayName() {
626     return ApplicationBundle.message("title.colors.and.fonts");
627   }
628
629   private void revertChanges(){
630     if (isSchemeListModified() || isSomeSchemeModified()) {
631       myRevertChangesCompleted = false;
632     }
633
634     if (!myRevertChangesCompleted) {
635       ensureSchemesPanel();
636
637
638       try {
639         resetImpl();
640       }
641       finally {
642         myRevertChangesCompleted = true;
643       }
644     }
645
646   }
647
648   private void resetImpl() {
649     mySomeSchemesDeleted = false;
650     initAll();
651     resetSchemesCombo(null);
652   }
653
654   @Override
655   public synchronized void reset() {
656     if (!myInitResetInvoked) {
657       try {
658         if (!myInitResetCompleted) {
659           ensureSchemesPanel();
660
661           try {
662             resetImpl();
663           }
664           finally {
665             myInitResetCompleted = true;
666           }
667         }
668       }
669       finally {
670         myInitResetInvoked = true;
671       }
672     }
673     else {
674       revertChanges();
675     }
676   }
677
678   public synchronized void resetFromChild() {
679     if (!myInitResetCompleted) {
680       ensureSchemesPanel();
681
682
683       try {
684         resetImpl();
685       }
686       finally {
687         myInitResetCompleted = true;
688       }
689     }
690
691   }
692
693   private void ensureSchemesPanel() {
694     if (myRootSchemesPanel == null) {
695       myRootSchemesPanel = new SchemesPanel(this);
696
697       myRootSchemesPanel.addListener(new ColorAndFontSettingsListener.Abstract(){
698         @Override
699         public void schemeChanged(final Object source) {
700           if (!myIsReset) {
701             resetSchemesCombo(source);
702           }
703         }
704       });
705
706     }
707   }
708
709   @Override
710   public void disposeUIResources() {
711     try {
712       if (!myDisposeCompleted) {
713         try {
714           super.disposeUIResources();
715           Disposer.dispose(myDisposable);
716         }
717         finally {
718           myDisposeCompleted = true;
719         }
720       }
721     }
722     finally {
723       mySubPanelFactories = null;
724
725       myInitResetCompleted = false;
726       myInitResetInvoked = false;
727       myRevertChangesCompleted = false;
728
729       myApplyCompleted = false;
730       myRootSchemesPanel = null;
731     }
732   }
733
734   private static class SchemeTextAttributesDescription extends TextAttributesDescription {
735     @NotNull private final TextAttributes myInitialAttributes;
736     @NotNull private final TextAttributesKey key;
737     private TextAttributes myFallbackAttributes;
738     private Pair<ColorSettingsPage,AttributesDescriptor> myBaseAttributeDescriptor;
739     private boolean myIsInheritedInitial = false;
740
741     private SchemeTextAttributesDescription(String name, String group, @NotNull TextAttributesKey key, @NotNull MyColorScheme  scheme, Icon icon,
742                                            String toolTip) {
743       super(name, group,
744             getInitialAttributes(scheme, key).clone(),
745             key, scheme, icon, toolTip);
746       this.key = key;
747       myInitialAttributes = getInitialAttributes(scheme, key);
748       TextAttributesKey fallbackKey = key.getFallbackAttributeKey();
749       if (fallbackKey != null) {
750         myFallbackAttributes = scheme.getAttributes(fallbackKey);
751         myBaseAttributeDescriptor = ColorSettingsPages.getInstance().getAttributeDescriptor(fallbackKey);
752         if (myBaseAttributeDescriptor == null) {
753           myBaseAttributeDescriptor =
754             new Pair<ColorSettingsPage, AttributesDescriptor>(null, new AttributesDescriptor(fallbackKey.getExternalName(), fallbackKey));
755         }
756       }
757       myIsInheritedInitial = scheme.isInherited(key);
758       setInherited(myIsInheritedInitial);
759       if (myIsInheritedInitial) {
760         setInheritedAttributes(getTextAttributes());
761       }
762       initCheckedStatus();
763     }
764
765
766     private void setInheritedAttributes(@NotNull TextAttributes attributes) {
767       attributes.setFontType(myFallbackAttributes.getFontType());
768       attributes.setForegroundColor(myFallbackAttributes.getForegroundColor());
769       attributes.setBackgroundColor(myFallbackAttributes.getBackgroundColor());
770       attributes.setErrorStripeColor(myFallbackAttributes.getErrorStripeColor());
771       attributes.setEffectColor(myFallbackAttributes.getEffectColor());
772       attributes.setEffectType(myFallbackAttributes.getEffectType());
773     }
774
775
776     @NotNull
777     private static TextAttributes getInitialAttributes(@NotNull MyColorScheme scheme, @NotNull TextAttributesKey key) {
778       TextAttributes attributes = scheme.getAttributes(key);
779       return attributes != null ? attributes : new TextAttributes();
780     }
781
782     @Override
783     public void apply(EditorColorsScheme scheme) {
784       if (scheme == null) scheme = getScheme();
785       scheme.setAttributes(key, isInherited() ? new TextAttributes() : getTextAttributes());
786     }
787
788     @Override
789     public boolean isModified() {
790       if (isInherited()) {
791         return !myIsInheritedInitial;
792       }
793       return !Comparing.equal(myInitialAttributes, getTextAttributes()) || myIsInheritedInitial;
794     }
795
796     @Override
797     public boolean isErrorStripeEnabled() {
798       return true;
799     }
800
801     @Nullable
802     @Override
803     public TextAttributes getBaseAttributes() {
804       return myFallbackAttributes;
805     }
806
807     @Nullable
808     @Override
809     public Pair<ColorSettingsPage,AttributesDescriptor> getBaseAttributeDescriptor() {
810       return myBaseAttributeDescriptor;
811     }
812
813     @Override
814     public void setInherited(boolean isInherited) {
815       super.setInherited(isInherited);
816     }
817   }
818
819   private static class GetSetColor {
820     private final ColorKey myKey;
821     private final EditorColorsScheme myScheme;
822     private boolean isModified = false;
823     private Color myColor;
824
825     private GetSetColor(ColorKey key, EditorColorsScheme scheme) {
826       myKey = key;
827       myScheme = scheme;
828       myColor = myScheme.getColor(myKey);
829     }
830
831     public Color getColor() {
832       return myColor;
833     }
834
835     public void setColor(Color col) {
836       if (getColor() == null || !getColor().equals(col)) {
837         isModified = true;
838         myColor = col;
839       }
840     }
841
842     public void apply(EditorColorsScheme scheme) {
843       if (scheme == null) scheme = myScheme;
844       scheme.setColor(myKey, myColor);
845     }
846
847     public boolean isModified() {
848       return isModified;
849     }
850   }
851
852   private static class EditorSettingColorDescription extends ColorAndFontDescription {
853     private GetSetColor myGetSetForeground;
854     private GetSetColor myGetSetBackground;
855
856     private EditorSettingColorDescription(String name,
857                                          String group,
858                                          ColorKey backgroundKey,
859                                          ColorKey foregroundKey,
860                                          String type,
861                                          EditorColorsScheme scheme) {
862       super(name, group, type, scheme, null, null);
863       if (backgroundKey != null) {
864         myGetSetBackground = new GetSetColor(backgroundKey, scheme);
865       }
866       if (foregroundKey != null) {
867         myGetSetForeground = new GetSetColor(foregroundKey, scheme);
868       }
869       initCheckedStatus();
870     }
871
872     @Override
873     public int getFontType() {
874       return Font.PLAIN;
875     }
876
877     @Override
878     public void setFontType(int type) {
879     }
880
881     @Override
882     public Color getExternalEffectColor() {
883       return null;
884     }
885
886     @Override
887     public void setExternalEffectColor(Color color) {
888     }
889
890     @Override
891     public void setExternalEffectType(EffectType type) {
892     }
893
894     @NotNull
895     @Override
896     public EffectType getExternalEffectType() {
897       return EffectType.LINE_UNDERSCORE;
898     }
899
900     @Override
901     public Color getExternalForeground() {
902       if (myGetSetForeground == null) {
903         return null;
904       }
905       return myGetSetForeground.getColor();
906     }
907
908     @Override
909     public void setExternalForeground(Color col) {
910       if (myGetSetForeground == null) {
911         return;
912       }
913       myGetSetForeground.setColor(col);
914     }
915
916     @Override
917     public Color getExternalBackground() {
918       if (myGetSetBackground == null) {
919         return null;
920       }
921       return myGetSetBackground.getColor();
922     }
923
924     @Override
925     public void setExternalBackground(Color col) {
926       if (myGetSetBackground == null) {
927         return;
928       }
929       myGetSetBackground.setColor(col);
930     }
931
932     @Override
933     public Color getExternalErrorStripe() {
934       return null;
935     }
936
937     @Override
938     public void setExternalErrorStripe(Color col) {
939     }
940
941     @Override
942     public boolean isFontEnabled() {
943       return false;
944     }
945
946     @Override
947     public boolean isForegroundEnabled() {
948       return myGetSetForeground != null;
949     }
950
951     @Override
952     public boolean isBackgroundEnabled() {
953       return myGetSetBackground != null;
954     }
955
956     @Override
957     public boolean isEffectsColorEnabled() {
958       return false;
959     }
960
961     @Override
962     public boolean isModified() {
963       return myGetSetBackground != null && myGetSetBackground.isModified()
964              || myGetSetForeground != null && myGetSetForeground.isModified();
965     }
966
967     @Override
968     public void apply(EditorColorsScheme scheme) {
969       if (myGetSetBackground != null) {
970         myGetSetBackground.apply(scheme);
971       }
972       if (myGetSetForeground != null) {
973         myGetSetForeground.apply(scheme);
974       }
975     }
976   }
977
978   @Override
979   @NotNull
980   public String getHelpTopic() {
981     return ID;
982   }
983
984   private static class MyColorScheme extends EditorColorsSchemeImpl {
985     private EditorSchemeAttributeDescriptor[] myDescriptors;
986     private String                            myName;
987     private boolean myIsNew = false;
988
989     private MyColorScheme(@NotNull EditorColorsScheme parentScheme) {
990       super(parentScheme);
991
992       parentScheme.getFontPreferences().copyTo(getFontPreferences());
993       setLineSpacing(parentScheme.getLineSpacing());
994
995       parentScheme.getConsoleFontPreferences().copyTo(getConsoleFontPreferences());
996       setConsoleLineSpacing(parentScheme.getConsoleLineSpacing());
997
998       setQuickDocFontSize(parentScheme.getQuickDocFontSize());
999       myName = parentScheme.getName();
1000       initFonts();
1001     }
1002
1003     @NotNull
1004     @Override
1005     public String getName() {
1006       return myName;
1007     }
1008
1009     @Override
1010     public void setName(@NotNull String name) {
1011       myName = name;
1012     }
1013
1014     public void setDescriptors(EditorSchemeAttributeDescriptor[] descriptors) {
1015       myDescriptors = descriptors;
1016     }
1017
1018     public EditorSchemeAttributeDescriptor[] getDescriptors() {
1019       return myDescriptors;
1020     }
1021
1022     public boolean isDefault() {
1023       return myParentScheme instanceof DefaultColorsScheme;
1024     }
1025
1026     @Override
1027     public boolean isReadOnly() {
1028       return myParentScheme instanceof ReadOnlyColorsScheme;
1029     }
1030
1031     public boolean isModified() {
1032       if (isFontModified() || isConsoleFontModified()) return true;
1033
1034       for (EditorSchemeAttributeDescriptor descriptor : myDescriptors) {
1035         if (descriptor.isModified()) {
1036           return true;
1037         }
1038       }
1039
1040       return false;
1041     }
1042
1043     private boolean isFontModified() {
1044       if (!getFontPreferences().equals(myParentScheme.getFontPreferences())) return true;
1045       if (getLineSpacing() != myParentScheme.getLineSpacing()) return true;
1046       return getQuickDocFontSize() != myParentScheme.getQuickDocFontSize();
1047     }
1048
1049     private boolean isConsoleFontModified() {
1050       if (!getConsoleFontPreferences().equals(myParentScheme.getConsoleFontPreferences())) return true;
1051       return getConsoleLineSpacing() != myParentScheme.getConsoleLineSpacing();
1052     }
1053
1054     public void apply() {
1055       if (!(myParentScheme instanceof ReadOnlyColorsScheme)) {
1056         apply(myParentScheme);
1057       }
1058     }
1059
1060     public void apply(@NotNull EditorColorsScheme scheme) {
1061       scheme.setFontPreferences(getFontPreferences());
1062       scheme.setLineSpacing(myLineSpacing);
1063       scheme.setQuickDocFontSize(getQuickDocFontSize());
1064       scheme.setConsoleFontPreferences(getConsoleFontPreferences());
1065       scheme.setConsoleLineSpacing(getConsoleLineSpacing());
1066
1067       for (EditorSchemeAttributeDescriptor descriptor : myDescriptors) {
1068         descriptor.apply(scheme);
1069       }
1070
1071       if (scheme instanceof AbstractColorsScheme) {
1072         ((AbstractColorsScheme)scheme).setSaveNeeded(true);
1073       }
1074     }
1075
1076     @Override
1077     public Object clone() {
1078       return null;
1079     }
1080
1081     @NotNull
1082     public EditorColorsScheme getOriginalScheme() {
1083       return myParentScheme;
1084     }
1085
1086     public void setIsNew() {
1087       myIsNew = true;
1088     }
1089
1090     public boolean isNew() {
1091       return myIsNew;
1092     }
1093
1094     @NotNull
1095     @Override
1096     public String toString() {
1097       return "temporary scheme for " + myName;
1098     }
1099
1100     public boolean isInherited(TextAttributesKey key) {
1101       TextAttributesKey fallbackKey = key.getFallbackAttributeKey();
1102       if (fallbackKey != null) {
1103         if (myParentScheme instanceof AbstractColorsScheme) {
1104           TextAttributes ownAttrs = ((AbstractColorsScheme)myParentScheme).getDirectlyDefinedAttributes(key);
1105           if (ownAttrs != null) {
1106             return ownAttrs.isFallbackEnabled();
1107           }
1108         }
1109         TextAttributes attributes = getAttributes(key);
1110         if (attributes != null) {
1111           TextAttributes fallbackAttributes = getAttributes(fallbackKey);
1112           return attributes == fallbackAttributes;
1113         }
1114       }
1115       return false;
1116     }
1117   }
1118
1119   @Override
1120   @NotNull
1121   public String getId() {
1122     return getHelpTopic();
1123   }
1124
1125   @Override
1126   @Nullable
1127   public Runnable enableSearch(final String option) {
1128     return null;
1129   }
1130
1131   @Nullable
1132   public SearchableConfigurable findSubConfigurable(@NotNull Class pageClass) {
1133     if (mySubPanelFactories == null) {
1134       buildConfigurables();
1135     }
1136     for (Map.Entry<ColorAndFontPanelFactory, InnerSearchableConfigurable> entry : mySubPanelFactories.entrySet()) {
1137       if (pageClass.isInstance(entry.getValue().createPanel().getSettingsPage())) {
1138         return entry.getValue();
1139       }
1140     }
1141     return null;
1142   }
1143
1144   @Nullable
1145   public SearchableConfigurable findSubConfigurable(String pageName) {
1146     if (mySubPanelFactories == null) {
1147       buildConfigurables();
1148     }
1149     for (InnerSearchableConfigurable configurable : mySubPanelFactories.values()) {
1150       if (configurable.getDisplayName().equals(pageName)) {
1151         return configurable;
1152       }
1153     }
1154     return null;
1155   }
1156
1157   @Nullable
1158   public NewColorAndFontPanel findPage(String pageName) {
1159     InnerSearchableConfigurable child = (InnerSearchableConfigurable)findSubConfigurable(pageName);
1160     return child == null ? null : child.createPanel();
1161   }
1162
1163   private class InnerSearchableConfigurable implements SearchableConfigurable, OptionsContainingConfigurable, NoScroll {
1164     private NewColorAndFontPanel mySubPanel;
1165     private boolean mySubInitInvoked = false;
1166     @NotNull private final ColorAndFontPanelFactory myFactory;
1167
1168     private InnerSearchableConfigurable(@NotNull ColorAndFontPanelFactory factory) {
1169       myFactory = factory;
1170     }
1171
1172     @NotNull
1173     @Override
1174     @Nls
1175     public String getDisplayName() {
1176       return myFactory.getPanelDisplayName();
1177     }
1178
1179     public NewColorAndFontPanel getSubPanelIfInitialized() {
1180       return mySubPanel;
1181     }
1182
1183     private NewColorAndFontPanel createPanel() {
1184       if (mySubPanel == null) {
1185         mySubPanel = myFactory.createPanel(ColorAndFontOptions.this);
1186         mySubPanel.reset(this);
1187         mySubPanel.addSchemesListener(new ColorAndFontSettingsListener.Abstract(){
1188           @Override
1189           public void schemeChanged(final Object source) {
1190             if (!myIsReset) {
1191               resetSchemesCombo(source);
1192             }
1193           }
1194         });
1195
1196         mySubPanel.addDescriptionListener(new ColorAndFontSettingsListener.Abstract(){
1197           @Override
1198           public void fontChanged() {
1199             for (NewColorAndFontPanel panel : getPanels()) {
1200               panel.updatePreview();
1201             }
1202           }
1203         });
1204       }
1205       return mySubPanel;
1206     }
1207
1208     @Override
1209     public String getHelpTopic() {
1210       return null;
1211     }
1212
1213     @Override
1214     public JComponent createComponent() {
1215       return createPanel().getPanel();
1216     }
1217
1218     @Override
1219     public boolean isModified() {
1220       createPanel();
1221       for (MyColorScheme scheme : mySchemes.values()) {
1222         if (mySubPanel.containsFontOptions()) {
1223           if (scheme.isFontModified() || scheme.isConsoleFontModified()) {
1224             myRevertChangesCompleted = false;
1225             return true;
1226           }
1227         }
1228         else {
1229           for (EditorSchemeAttributeDescriptor descriptor : scheme.getDescriptors()) {
1230             if (mySubPanel.contains(descriptor) && descriptor.isModified()) {
1231               myRevertChangesCompleted = false;
1232               return true;
1233             }
1234           }
1235         }
1236
1237       }
1238
1239       return false;
1240
1241     }
1242
1243     @Override
1244     public void apply() throws ConfigurationException {
1245       ColorAndFontOptions.this.apply();
1246     }
1247
1248     @Override
1249     public void reset() {
1250       if (!mySubInitInvoked) {
1251         if (!myInitResetCompleted) {
1252           resetFromChild();
1253         }
1254         mySubInitInvoked = true;
1255       }
1256       else {
1257         revertChanges();
1258       }
1259     }
1260
1261     @Override
1262     public void disposeUIResources() {
1263       if (mySubPanel != null) {
1264         mySubPanel.disposeUIResources();
1265         mySubPanel = null;
1266       }
1267     }
1268
1269     @Override
1270     @NotNull
1271     public String getId() {
1272       return ColorAndFontOptions.this.getId() + "." + getDisplayName();
1273     }
1274
1275     @Override
1276     public Runnable enableSearch(final String option) {
1277       return createPanel().showOption(option);
1278     }
1279
1280     @NotNull
1281     @Override
1282     public Set<String> processListOptions() {
1283       return createPanel().processListOptions();
1284     }
1285
1286     @NotNull
1287     @NonNls
1288     @Override
1289     public String toString() {
1290       return "Color And Fonts for "+getDisplayName();
1291     }
1292   }
1293 }