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