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