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