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