Assertion for memleak
[idea/community.git] / platform / platform-impl / src / com / intellij / openapi / options / newEditor / ConfigurableEditor.java
1 /*
2  * Copyright 2000-2015 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.openapi.options.newEditor;
17
18 import com.intellij.CommonBundle;
19 import com.intellij.internal.statistic.UsageTrigger;
20 import com.intellij.internal.statistic.beans.ConvertUsagesUtil;
21 import com.intellij.openapi.Disposable;
22 import com.intellij.openapi.actionSystem.ActionManager;
23 import com.intellij.openapi.actionSystem.AnAction;
24 import com.intellij.openapi.actionSystem.AnActionEvent;
25 import com.intellij.openapi.actionSystem.DataContext;
26 import com.intellij.openapi.actionSystem.ex.AnActionListener;
27 import com.intellij.openapi.options.*;
28 import com.intellij.openapi.options.ex.ConfigurableCardPanel;
29 import com.intellij.openapi.options.ex.ConfigurableVisitor;
30 import com.intellij.openapi.options.ex.ConfigurableWrapper;
31 import com.intellij.openapi.ui.Messages;
32 import com.intellij.openapi.util.ActionCallback;
33 import com.intellij.ui.JBColor;
34 import com.intellij.ui.RelativeFont;
35 import com.intellij.ui.components.labels.LinkLabel;
36 import com.intellij.util.ui.JBUI;
37 import com.intellij.util.ui.UIUtil;
38 import com.intellij.util.ui.update.MergingUpdateQueue;
39 import com.intellij.util.ui.update.Update;
40
41 import javax.swing.*;
42 import java.awt.*;
43 import java.awt.event.AWTEventListener;
44 import java.awt.event.ActionEvent;
45 import java.awt.event.KeyEvent;
46 import java.awt.event.MouseEvent;
47 import java.util.ResourceBundle;
48
49 import static java.awt.Toolkit.getDefaultToolkit;
50 import static javax.swing.SwingUtilities.isDescendingFrom;
51
52 /**
53  * @author Sergey.Malenkov
54  */
55 class ConfigurableEditor extends AbstractEditor implements AnActionListener, AWTEventListener {
56   private static final JBColor ERROR_BACKGROUND = new JBColor(0xffbfbf, 0x591f1f);
57   private static final String RESET_NAME = "Reset";
58   private static final String RESET_DESCRIPTION = "Rollback changes for this configuration element";
59   private final MergingUpdateQueue myQueue = new MergingUpdateQueue("SettingsModification", 1000, false, this, this, this);
60   private final ConfigurableCardPanel myCardPanel = new ConfigurableCardPanel() {
61     @Override
62     protected JComponent create(Configurable configurable) {
63       JComponent content = super.create(configurable);
64       return content != null ? content : createDefaultContent(configurable);
65     }
66   };
67   private final JLabel myErrorLabel = new JLabel();
68   private final AbstractAction myApplyAction = new AbstractAction(CommonBundle.getApplyButtonText()) {
69     @Override
70     public void actionPerformed(ActionEvent event) {
71       apply();
72     }
73   };
74   private final AbstractAction myResetAction = new AbstractAction(RESET_NAME) {
75     @Override
76     public void actionPerformed(ActionEvent event) {
77       if (myConfigurable != null) {
78         ConfigurableCardPanel.reset(myConfigurable);
79         updateCurrent(myConfigurable, true);
80       }
81     }
82   };
83   private Configurable myConfigurable;
84
85   ConfigurableEditor(Disposable parent, Configurable configurable) {
86     super(parent);
87     myApplyAction.setEnabled(false);
88     myResetAction.putValue(Action.SHORT_DESCRIPTION, RESET_DESCRIPTION);
89     myResetAction.setEnabled(false);
90     myErrorLabel.setOpaque(true);
91     myErrorLabel.setEnabled(parent instanceof SettingsEditor);
92     myErrorLabel.setVisible(false);
93     myErrorLabel.setVerticalTextPosition(SwingConstants.TOP);
94     myErrorLabel.setBorder(BorderFactory.createEmptyBorder(10, 15, 15, 15));
95     myErrorLabel.setBackground(ERROR_BACKGROUND);
96     add(BorderLayout.SOUTH, RelativeFont.HUGE.install(myErrorLabel));
97     add(BorderLayout.CENTER, myCardPanel);
98     ActionManager.getInstance().addAnActionListener(this, this);
99     getDefaultToolkit().addAWTEventListener(this, AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK | AWTEvent.KEY_EVENT_MASK);
100     myConfigurable = configurable;
101     myCardPanel.select(configurable, true);
102     updateCurrent(configurable, false);
103   }
104
105   @Override
106   void disposeOnce() {
107     getDefaultToolkit().removeAWTEventListener(this);
108     myCardPanel.removeAll();
109   }
110
111   @Override
112   String getHelpTopic() {
113     return myConfigurable == null ? null : myConfigurable.getHelpTopic();
114   }
115
116   @Override
117   Action getApplyAction() {
118     return myApplyAction;
119   }
120
121   @Override
122   Action getResetAction() {
123     return myResetAction;
124   }
125
126   @Override
127   boolean apply() {
128     // do not apply changes of a single configurable if it is not modified
129     return setError(apply(myApplyAction.isEnabled() ? myConfigurable : null));
130   }
131
132   void openLink(Configurable configurable) {
133     ShowSettingsUtil.getInstance().editConfigurable(this, configurable);
134   }
135
136   @Override
137   public final void beforeEditorTyping(char ch, DataContext context) {
138   }
139
140   @Override
141   public final void beforeActionPerformed(AnAction action, DataContext context, AnActionEvent event) {
142   }
143
144   @Override
145   public final void afterActionPerformed(AnAction action, DataContext context, AnActionEvent event) {
146     requestUpdate();
147   }
148
149   @Override
150   public JComponent getPreferredFocusedComponent() {
151     if (myConfigurable instanceof BaseConfigurable) {
152       JComponent preferred = ((BaseConfigurable)myConfigurable).getPreferredFocusedComponent();
153       if (preferred != null) return preferred;
154     }
155     return super.getPreferredFocusedComponent();
156   }
157
158   @Override
159   public final void eventDispatched(AWTEvent event) {
160     switch (event.getID()) {
161       case MouseEvent.MOUSE_PRESSED:
162       case MouseEvent.MOUSE_RELEASED:
163       case MouseEvent.MOUSE_DRAGGED:
164         MouseEvent me = (MouseEvent)event;
165         if (isDescendingFrom(me.getComponent(), this) || isPopupOverEditor(me.getComponent())) {
166           requestUpdate();
167         }
168         break;
169       case KeyEvent.KEY_PRESSED:
170       case KeyEvent.KEY_RELEASED:
171         KeyEvent ke = (KeyEvent)event;
172         if (isDescendingFrom(ke.getComponent(), this)) {
173           requestUpdate();
174         }
175         break;
176     }
177   }
178
179   private void requestUpdate() {
180     final Configurable configurable = myConfigurable;
181     myQueue.queue(new Update(this) {
182       @Override
183       public void run() {
184         updateIfCurrent(configurable);
185       }
186
187       @Override
188       public boolean isExpired() {
189         return myConfigurable != configurable;
190       }
191     });
192   }
193
194   private boolean isPopupOverEditor(Component component) {
195     Window editor = UIUtil.getWindow(this);
196     if (editor != null) {
197       Window popup = UIUtil.getWindow(component);
198       // light-weight popup is located on the layered pane of the same window
199       // heavy-weight popup opens new window with the corresponding parent
200       if (popup == editor || popup != null && editor == popup.getParent()) {
201         if (popup instanceof JDialog) {
202           JDialog dialog = (JDialog)popup;
203           return Dialog.ModalityType.MODELESS == dialog.getModalityType();
204         }
205         return popup instanceof JWindow;
206       }
207     }
208     return false;
209   }
210
211   void updateCurrent(Configurable configurable, boolean reset) {
212     boolean modified = configurable != null && configurable.isModified();
213     myApplyAction.setEnabled(modified);
214     myResetAction.setEnabled(modified);
215     if (!modified && reset) {
216       setError(null);
217     }
218   }
219
220   final boolean updateIfCurrent(Configurable configurable) {
221     if (myConfigurable != configurable) {
222       return false;
223     }
224     updateCurrent(configurable, false);
225     return true;
226   }
227
228   final ActionCallback select(final Configurable configurable) {
229     assert !myDisposed : "Already disposed";
230     ActionCallback callback = myCardPanel.select(configurable, false);
231     callback.doWhenDone(new Runnable() {
232       @Override
233       public void run() {
234         myConfigurable = configurable;
235         updateCurrent(configurable, false);
236       }
237     });
238     return callback;
239   }
240
241   final boolean setError(ConfigurationException exception) {
242     if (exception == null) {
243       myErrorLabel.setVisible(false);
244       return true;
245     }
246     if (myErrorLabel.isEnabled()) {
247       myErrorLabel.setText("<html><body><strong>" + exception.getTitle() + "</strong>:<br>" + exception.getMessage());
248       myErrorLabel.setVisible(true);
249     }
250     else {
251       Messages.showMessageDialog(this, exception.getMessage(), exception.getTitle(), Messages.getErrorIcon());
252     }
253     return false;
254   }
255
256   final JComponent getContent(Configurable configurable) {
257     return myCardPanel.getValue(configurable, false);
258   }
259
260   final JComponent readContent(Configurable configurable) {
261     return myCardPanel.getValue(configurable, true);
262   }
263
264   private JComponent createDefaultContent(Configurable configurable) {
265     JComponent content = new JPanel(new BorderLayout());
266     content.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
267     String key = configurable == null ? null : ConfigurableVisitor.ByID.getID(configurable) + ".settings.description";
268     String description = key == null ? null : getString(configurable, key);
269     if (description == null) {
270       description = "Select configuration element in the tree to edit its settings";
271       content.add(BorderLayout.CENTER, new JLabel(description, SwingConstants.CENTER));
272       content.setPreferredSize(JBUI.size(800, 600));
273     }
274     else {
275       content.add(BorderLayout.NORTH, new JLabel(description));
276       if (configurable instanceof Configurable.Composite) {
277         Configurable.Composite composite = (Configurable.Composite)configurable;
278
279         JPanel panel = new JPanel();
280         panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
281         content.add(BorderLayout.CENTER, panel);
282         panel.add(Box.createVerticalStrut(10));
283         for (final Configurable current : composite.getConfigurables()) {
284           LinkLabel label = new LinkLabel(current.getDisplayName(), null) {
285             @Override
286             public void doClick() {
287               openLink(current);
288             }
289           };
290           label.setBorder(BorderFactory.createEmptyBorder(1, 17, 3, 1));
291           panel.add(label);
292         }
293       }
294     }
295     return content;
296   }
297
298   private static String getString(Configurable configurable, String key) {
299     try {
300       if (configurable instanceof ConfigurableWrapper) {
301         ConfigurableWrapper wrapper = (ConfigurableWrapper)configurable;
302         ResourceBundle bundle = wrapper.getExtensionPoint().findBundle();
303         if (bundle != null) {
304           return CommonBundle.message(bundle, key);
305         }
306       }
307       return OptionsBundle.message(key);
308     }
309     catch (AssertionError error) {
310       return null;
311     }
312   }
313
314   static ConfigurationException apply(Configurable configurable) {
315     if (configurable != null) {
316       try {
317         configurable.apply();
318         UsageTrigger.trigger("ide.settings." + ConvertUsagesUtil.escapeDescriptorName(configurable.getDisplayName()));
319       }
320       catch (ConfigurationException exception) {
321         return exception;
322       }
323     }
324     return null;
325   }
326 }