Code style settings UI: "Set from" button moved to tabs
[idea/community.git] / platform / lang-impl / src / com / intellij / application / options / TabbedLanguageCodeStylePanel.java
1 /*
2  * Copyright 2000-2011 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 package com.intellij.application.options;
17
18 import com.intellij.application.options.codeStyle.*;
19 import com.intellij.lang.Language;
20 import com.intellij.openapi.application.ApplicationBundle;
21 import com.intellij.openapi.editor.colors.EditorColorsScheme;
22 import com.intellij.openapi.editor.highlighter.EditorHighlighter;
23 import com.intellij.openapi.editor.highlighter.EditorHighlighterFactory;
24 import com.intellij.openapi.fileTypes.FileType;
25 import com.intellij.openapi.fileTypes.FileTypes;
26 import com.intellij.openapi.options.Configurable;
27 import com.intellij.openapi.options.ConfigurationException;
28 import com.intellij.openapi.project.Project;
29 import com.intellij.openapi.project.ProjectUtil;
30 import com.intellij.openapi.util.Disposer;
31 import com.intellij.openapi.util.IconLoader;
32 import com.intellij.psi.codeStyle.*;
33 import com.intellij.ui.components.JBTabbedPane;
34 import org.jetbrains.annotations.NotNull;
35 import org.jetbrains.annotations.Nullable;
36
37 import javax.swing.*;
38 import java.awt.*;
39 import java.awt.event.*;
40 import java.util.ArrayList;
41 import java.util.Collections;
42 import java.util.Comparator;
43 import java.util.List;
44
45 /**
46  * @author Rustam Vishnyakov
47  */
48
49 public abstract class TabbedLanguageCodeStylePanel extends CodeStyleAbstractPanel {
50   
51   private final static Icon COPY_ICON = IconLoader.getIcon("/actions/import.png");
52
53   private CodeStyleAbstractPanel myActiveTab;
54   private List<CodeStyleAbstractPanel> myTabs;
55   private JPanel myPanel;
56   private JTabbedPane myTabbedPane;
57   private PredefinedCodeStyle[] myPredefinedCodeStyles;
58
59   protected TabbedLanguageCodeStylePanel(@Nullable Language language, CodeStyleSettings currentSettings, CodeStyleSettings settings) {
60     super(language, currentSettings, settings);
61     myPredefinedCodeStyles = getPredefinedStyles();
62   }
63
64   /**
65    * Initializes all standard tabs: "Tabs and Indents", "Spaces", "Blank Lines" and "Wrapping and Braces" if relevant.
66    * For "Tabs and Indents" LanguageCodeStyleSettingsProvider must instantiate its own indent options, for other standard tabs it
67    * must return false in usesSharedPreview() method. You can override this method to add your own tabs by calling super.initTabs() and
68    * then addTab() methods or selectively add needed tabs with your own implementation.
69    * @param settings  Code style settings to be used with initialized panels.
70    * @see LanguageCodeStyleSettingsProvider
71    * @see #addIndentOptionsTab(com.intellij.psi.codeStyle.CodeStyleSettings)
72    * @see #addSpacesTab(com.intellij.psi.codeStyle.CodeStyleSettings)
73    * @see #addBlankLinesTab(com.intellij.psi.codeStyle.CodeStyleSettings)
74    * @see #addWrappingAndBracesTab(com.intellij.psi.codeStyle.CodeStyleSettings)
75    */
76   protected void initTabs(CodeStyleSettings settings) {
77     LanguageCodeStyleSettingsProvider provider = LanguageCodeStyleSettingsProvider.forLanguage(getDefaultLanguage());
78     addIndentOptionsTab(settings);
79     if (provider != null && !provider.usesSharedPreview()) {
80       addSpacesTab(settings);
81       addWrappingAndBracesTab(settings);
82       addBlankLinesTab(settings);
83     }
84   }
85
86   /**
87    * Adds "Tabs and Indents" tab if the language has its own LanguageCodeStyleSettings provider and instantiates indent options in 
88    * getDefaultSettings() method.
89    * @param settings CodeStyleSettings to be used with "Tabs and Indents" panel.
90    */
91   protected void addIndentOptionsTab(CodeStyleSettings settings) {
92     LanguageCodeStyleSettingsProvider provider = LanguageCodeStyleSettingsProvider.forLanguage(getDefaultLanguage());
93     if (provider != null) {
94       IndentOptionsEditor indentOptionsEditor = provider.getIndentOptionsEditor();
95       if (indentOptionsEditor != null) {
96         MyIndentOptionsWrapper indentOptionsWrapper = new MyIndentOptionsWrapper(settings, provider, indentOptionsEditor);
97         addTab(indentOptionsWrapper);
98       }
99     }
100   }
101
102   protected void addSpacesTab(CodeStyleSettings settings) {
103     addTab(new MySpacesPanel(settings));
104   }
105
106   protected void addBlankLinesTab(CodeStyleSettings settings) {
107     addTab(new MyBlankLinesPanel(settings));
108   }
109
110   protected void addWrappingAndBracesTab(CodeStyleSettings settings) {
111     addTab(new MyWrappingAndBracesPanel(settings));
112   }
113
114   private void ensureTabs() {
115     if (myTabs == null) {
116       myPanel = new JPanel();
117       myPanel.setLayout(new BorderLayout());
118       myTabbedPane = new JBTabbedPane();
119       myTabs = new ArrayList<CodeStyleAbstractPanel>();
120       myPanel.add(myTabbedPane);
121       initTabs(getSettings());
122       addSetFrom();
123     }
124     assert !myTabs.isEmpty();
125   }
126
127   private void addSetFrom() {
128     myPredefinedCodeStyles = getPredefinedStyles();
129     JLabel dummyLabel = new JLabel("");
130     myTabbedPane.addTab("Set From...", dummyLabel);
131     int dummyIndex = myTabbedPane.indexOfComponent(dummyLabel);
132     myTabbedPane.setEnabledAt(dummyIndex, false);
133     JPanel setFromPanel = new JPanel();
134     setFromPanel.setBorder(BorderFactory.createEtchedBorder());
135     setFromPanel.setLayout(new FlowLayout(FlowLayout.LEFT, 10,0));
136     JLabel setFromLabel = new JLabel("Set from...");
137     setFromPanel.add(setFromLabel);
138     final PopupMenu copyMenu = new PopupMenu();
139     setFromLabel.add(copyMenu);
140     setFromLabel.setIcon(COPY_ICON);
141     setupCopyFromMenu(copyMenu);
142     setFromLabel.addMouseListener(new MouseAdapter() {
143       @Override
144       public void mousePressed(MouseEvent e) {
145           copyMenu.show(e.getComponent(), e.getX(), e.getY());
146       }
147     });
148     myTabbedPane.setTabComponentAt(dummyIndex, setFromPanel);
149   }
150
151   /**
152    * Adds a tab with the given CodeStyleAbstractPanel. Tab title is taken from getTabTitle() method.
153    * @param tab The panel to use in a tab.
154    */
155   protected final void addTab(CodeStyleAbstractPanel tab) {
156     myTabs.add(tab);
157     tab.setShouldUpdatePreview(true);
158     addPanelToWatch(tab.getPanel());
159     myTabbedPane.addTab(tab.getTabTitle(), tab.getPanel());
160     if (myActiveTab == null) {
161       myActiveTab = tab;
162     }
163   }
164
165   private void addTab(Configurable configurable) {
166     ConfigurableWrapper wrapper = new ConfigurableWrapper(configurable, getSettings());
167     addTab(wrapper);
168   }
169
170   /**
171    * Creates and adds a tab from CodeStyleSettingsProvider. The provider may return false in hasSettingsPage() method in order not to be
172    * shown at top level of code style settings.
173    * @param provider The provider used to create a settings page.
174    */
175   protected final void createTab(CodeStyleSettingsProvider provider) {
176     if (provider.hasSettingsPage()) return;
177     Configurable configurable = provider.createSettingsPage(getCurrentSettings(), getSettings());
178     addTab(configurable);
179   }
180
181   @Override
182   public final void setModel(CodeStyleSchemesModel model) {
183     super.setModel(model);
184     ensureTabs();
185     for (CodeStyleAbstractPanel tab : myTabs) {
186       tab.setModel(model);
187     }
188   }
189
190   @Override
191   protected int getRightMargin() {
192     ensureTabs();
193     return myActiveTab.getRightMargin();
194   }
195
196   @Override
197   protected EditorHighlighter createHighlighter(EditorColorsScheme scheme) {
198     ensureTabs();
199     return myActiveTab.createHighlighter(scheme);
200   }
201
202   @NotNull
203   @Override
204   protected FileType getFileType() {
205     ensureTabs();
206     return myActiveTab.getFileType();
207   }
208
209   @Override
210   protected String getPreviewText() {
211     ensureTabs();
212     return myActiveTab.getPreviewText();
213   }
214
215   @Override
216   protected void updatePreview(boolean useDefaultSample) {
217     ensureTabs();
218     for (CodeStyleAbstractPanel tab : myTabs) {
219       tab.updatePreview(useDefaultSample);
220     }
221   }
222
223   @Override
224   public void onSomethingChanged() {
225     ensureTabs();
226     for (CodeStyleAbstractPanel tab : myTabs) {
227       tab.setShouldUpdatePreview(true);
228       tab.onSomethingChanged();
229     }
230   }
231
232   @Override
233   protected void somethingChanged() {
234     super.somethingChanged();
235   }
236
237   @Override
238   public void apply(CodeStyleSettings settings) {
239     ensureTabs();
240     for (CodeStyleAbstractPanel tab : myTabs) {
241       tab.apply(settings);
242     }
243   }
244
245   @Override
246   public void dispose() {
247     super.dispose();
248     for (CodeStyleAbstractPanel tab : myTabs) {
249       Disposer.dispose(tab);
250     }
251   }
252
253   @Override
254   public boolean isModified(CodeStyleSettings settings) {
255     ensureTabs();
256     for (CodeStyleAbstractPanel tab : myTabs) {
257       if (tab.isModified(settings)) {
258         return true;
259       }
260     }
261     return false;
262   }
263
264   @Override
265   public JComponent getPanel() {
266     return myPanel;
267   }
268
269   @Override
270   protected void resetImpl(CodeStyleSettings settings) {
271     ensureTabs();
272     for (CodeStyleAbstractPanel tab : myTabs) {
273       tab.resetImpl(settings);
274     }
275   }
276
277
278   @Override
279   public void setupCopyFromMenu(Menu copyMenu) {
280     super.setupCopyFromMenu(copyMenu);
281     if (myPredefinedCodeStyles.length > 0) {
282       Menu langs = new Menu("Language"); //TODO<rv>: Move to resource bundle
283       copyMenu.add(langs);
284       fillLanguages(langs);
285       Menu predefined = new Menu("Predefined Style"); //TODO<rv>: Move to resource bundle
286       copyMenu.add(predefined);
287       fillPredefined(predefined);
288     }
289     else {
290       fillLanguages(copyMenu);
291     }
292   }
293
294
295   private void fillLanguages(Menu parentMenu) {
296       Language[] languages = LanguageCodeStyleSettingsProvider.getLanguagesWithCodeStyleSettings();
297       @SuppressWarnings("UnnecessaryFullyQualifiedName")
298       java.util.List<MenuItem> langItems = new ArrayList<MenuItem>();
299       for (final Language lang : languages) {
300         if (!lang.equals(getDefaultLanguage())) {
301           final String langName = LanguageCodeStyleSettingsProvider.getLanguageName(lang);
302           MenuItem langItem = new MenuItem(langName);
303           langItem.addActionListener(new ActionListener(){
304             @Override
305             public void actionPerformed(ActionEvent e) {
306               applyLanguageSettings(lang);
307             }
308           });
309           langItems.add(langItem);
310         }
311       }
312       Collections.sort(langItems, new Comparator<MenuItem>() {
313         @Override
314         public int compare(MenuItem item1, MenuItem item2) {
315           return item1.getLabel().compareToIgnoreCase(item2.getLabel());
316         }
317       });
318       for (MenuItem langItem : langItems) {
319         parentMenu.add(langItem);
320       }
321     }
322
323   private void fillPredefined(Menu parentMenu) {
324     for (final PredefinedCodeStyle predefinedCodeStyle : myPredefinedCodeStyles) {
325       MenuItem predefinedItem = new MenuItem(predefinedCodeStyle.getName());
326       parentMenu.add(predefinedItem);
327       predefinedItem.addActionListener(new ActionListener() {
328         @Override
329         public void actionPerformed(ActionEvent e) {
330           applyPredefinedStyle(predefinedCodeStyle.getName());
331         }
332       });
333     }
334   }
335
336   private PredefinedCodeStyle[] getPredefinedStyles() {
337     LanguageCodeStyleSettingsProvider provider = LanguageCodeStyleSettingsProvider.forLanguage(getDefaultLanguage());
338     if (provider == null) return new PredefinedCodeStyle[0];
339     return provider.getPredefinedCodeStyles();
340   }
341
342
343   private void applyLanguageSettings(Language lang) {
344     final Project currProject = ProjectUtil.guessCurrentProject(getPanel());
345     CodeStyleSettings rootSettings = CodeStyleSettingsManager.getSettings(currProject);
346     CommonCodeStyleSettings sourceSettings = rootSettings.getCommonSettings(lang);
347     CommonCodeStyleSettings targetSettings = getSettings().getCommonSettings(getDefaultLanguage());
348     if (sourceSettings == null || targetSettings == null) return;
349     CommonCodeStyleSettingsManager.copy(sourceSettings, targetSettings);
350     reset(getSettings());
351     onSomethingChanged();
352   }
353
354   private void applyPredefinedStyle(String styleName) {
355     for (PredefinedCodeStyle style : myPredefinedCodeStyles) {
356       if (style.getName().equals(styleName)) {
357         applyPredefinedSettings(style);
358       }
359     }
360   }
361
362 //========================================================================================================================================
363
364   private class MySpacesPanel extends CodeStyleSpacesPanel {
365
366     public MySpacesPanel(CodeStyleSettings settings) {
367       super(settings);
368       setPanelLanguage(TabbedLanguageCodeStylePanel.this.getDefaultLanguage());
369     }
370
371     @Override
372     protected void installPreviewPanel(JPanel previewPanel) {
373       previewPanel.setLayout(new BorderLayout());
374       previewPanel.add(getEditor().getComponent(), BorderLayout.CENTER);
375     }
376
377     @Override
378     protected void customizeSettings() {
379       customizePanel(this);
380     }
381
382     @Override
383     protected boolean shouldHideOptions() {
384       return true;
385     }
386   }
387
388   private class MyBlankLinesPanel extends CodeStyleBlankLinesPanel {
389
390     public MyBlankLinesPanel(CodeStyleSettings settings) {
391       super(settings);
392       setPanelLanguage(TabbedLanguageCodeStylePanel.this.getDefaultLanguage());
393     }
394
395     @Override
396     protected void customizeSettings() {
397       customizePanel(this);
398     }
399
400     @Override
401     protected void installPreviewPanel(JPanel previewPanel) {
402       previewPanel.setLayout(new BorderLayout());
403       previewPanel.add(getEditor().getComponent(), BorderLayout.CENTER);
404     }
405
406   }
407
408   private class MyWrappingAndBracesPanel extends WrappingAndBracesPanel {
409
410     public MyWrappingAndBracesPanel(CodeStyleSettings settings) {
411       super(settings);
412       setPanelLanguage(TabbedLanguageCodeStylePanel.this.getDefaultLanguage());
413     }
414
415     @Override
416     protected void customizeSettings() {
417       customizePanel(this);
418     }
419
420     @Override
421     protected void installPreviewPanel(JPanel previewPanel) {
422       previewPanel.setLayout(new BorderLayout());
423       previewPanel.add(getEditor().getComponent(), BorderLayout.CENTER);
424     }
425   }
426
427   private void customizePanel(MultilanguageCodeStyleAbstractPanel panel) {
428     LanguageCodeStyleSettingsProvider provider = LanguageCodeStyleSettingsProvider.forLanguage(getDefaultLanguage());
429     if (provider != null) {
430       provider.customizeSettings(panel, panel.getSettingsType());
431     }
432   }
433
434
435   //========================================================================================================================================
436
437   private class ConfigurableWrapper extends CodeStyleAbstractPanel {
438
439     private Configurable myConfigurable;
440
441     public ConfigurableWrapper(@NotNull Configurable configurable, CodeStyleSettings settings) {
442       super(settings);
443       myConfigurable = configurable;
444     }
445
446     @Override
447     protected int getRightMargin() {
448       return 0;
449     }
450
451     @Nullable
452     @Override
453     protected EditorHighlighter createHighlighter(EditorColorsScheme scheme) {
454       return null;
455     }
456
457     @SuppressWarnings("ConstantConditions")
458     @NotNull
459     @Override
460     protected FileType getFileType() {
461       Language language = getDefaultLanguage();
462       return language != null ? language.getAssociatedFileType() : FileTypes.PLAIN_TEXT;
463     }
464
465     @Override
466     public Language getDefaultLanguage() {
467       return TabbedLanguageCodeStylePanel.this.getDefaultLanguage();
468     }
469
470     @Override
471     protected String getTabTitle() {
472       return myConfigurable.getDisplayName();
473     }
474
475     @Override
476     protected String getPreviewText() {
477       return null;
478     }
479
480     @Override
481     public void apply(CodeStyleSettings settings) {
482       try {
483         myConfigurable.apply();
484       }
485       catch (ConfigurationException e) {
486         // Ignore
487       }
488     }
489
490     @Override
491     public boolean isModified(CodeStyleSettings settings) {
492       return myConfigurable.isModified();
493     }
494
495     @Nullable
496     @Override
497     public JComponent getPanel() {
498       return myConfigurable.createComponent();
499     }
500
501     @Override
502     protected void resetImpl(CodeStyleSettings settings) {
503       myConfigurable.reset();
504     }
505   }
506
507   @Override
508   public boolean isCopyFromMenuAvailable() {
509     return true;
510   }
511
512   //========================================================================================================================================
513   
514   private class MyIndentOptionsWrapper extends CodeStyleAbstractPanel {
515
516     private final IndentOptionsEditor myEditor;
517     private final LanguageCodeStyleSettingsProvider myProvider;
518     private JPanel myTopPanel;
519     private JPanel myLeftPanel;
520     private JPanel myRightPanel;
521
522     protected MyIndentOptionsWrapper(CodeStyleSettings settings, LanguageCodeStyleSettingsProvider provider, IndentOptionsEditor editor) {
523       super(settings);
524       myProvider = provider;
525       myTopPanel = new JPanel();
526       myTopPanel.setLayout(new BorderLayout());
527       myLeftPanel = new JPanel();
528       myTopPanel.add(myLeftPanel, BorderLayout.WEST);
529       myRightPanel = new JPanel();
530       installPreviewPanel(myRightPanel);
531       myEditor = editor;
532       if (myEditor != null) {
533         myLeftPanel.add(myEditor.createPanel());
534       }
535       myTopPanel.add(myRightPanel, BorderLayout.CENTER);
536     }
537
538     @Override
539     protected int getRightMargin() {
540       return getSettings().RIGHT_MARGIN;
541     }
542
543     @Override
544     protected EditorHighlighter createHighlighter(EditorColorsScheme scheme) {
545       //noinspection NullableProblems
546       return EditorHighlighterFactory.getInstance().createEditorHighlighter(getFileType(), scheme, null);
547     }
548
549     @SuppressWarnings("ConstantConditions")
550     @NotNull
551     @Override
552     protected FileType getFileType() {
553       Language language = TabbedLanguageCodeStylePanel.this.getDefaultLanguage();
554       return language != null ? language.getAssociatedFileType() : FileTypes.PLAIN_TEXT;
555     }
556
557     @Override
558     protected String getPreviewText() {
559       return myProvider != null ? myProvider.getCodeSample(LanguageCodeStyleSettingsProvider.SettingsType.INDENT_SETTINGS) : "Loading...";
560     }
561
562     @Override
563     public void apply(CodeStyleSettings settings) {
564       CommonCodeStyleSettings.IndentOptions indentOptions = getIndentOptions(settings);
565       if (indentOptions == null) return;
566       myEditor.apply(settings, indentOptions);
567     }
568
569     @Override
570     public boolean isModified(CodeStyleSettings settings) {
571       CommonCodeStyleSettings.IndentOptions indentOptions = getIndentOptions(settings);
572       if (indentOptions == null) return false;
573       return myEditor.isModified(settings, indentOptions);
574     }
575
576     @Override
577     public JComponent getPanel() {
578       return myTopPanel;
579     }
580
581     @Override
582     protected void resetImpl(CodeStyleSettings settings) {
583       CommonCodeStyleSettings.IndentOptions indentOptions = getIndentOptions(settings);
584       if (indentOptions == null) {
585         myEditor.setEnabled(false);
586         indentOptions = settings.getIndentOptions(myProvider.getLanguage().getAssociatedFileType());
587       }
588       myEditor.reset(settings, indentOptions);
589     }
590
591     @Nullable
592     private CommonCodeStyleSettings.IndentOptions getIndentOptions(CodeStyleSettings settings) {
593       return settings.getCommonSettings(getDefaultLanguage()).getIndentOptions();
594     }
595
596     @Override
597     public Language getDefaultLanguage() {
598       return TabbedLanguageCodeStylePanel.this.getDefaultLanguage();
599     }
600
601     @Override
602     protected String getTabTitle() {
603       return ApplicationBundle.message("title.tabs.and.indents");
604     }
605
606     @Override
607     public void onSomethingChanged() {
608       super.onSomethingChanged();
609       myEditor.setEnabled(true);
610     }
611
612   }
613 }