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