2 * Copyright 2000-2015 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com.intellij.openapi.options.newEditor;
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;
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;
49 import static java.awt.Toolkit.getDefaultToolkit;
50 import static javax.swing.SwingUtilities.isDescendingFrom;
53 * @author Sergey.Malenkov
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() {
62 protected JComponent create(Configurable configurable) {
63 JComponent content = super.create(configurable);
64 return content != null ? content : createDefaultContent(configurable);
67 private final JLabel myErrorLabel = new JLabel();
68 private final AbstractAction myApplyAction = new AbstractAction(CommonBundle.getApplyButtonText()) {
70 public void actionPerformed(ActionEvent event) {
74 private final AbstractAction myResetAction = new AbstractAction(RESET_NAME) {
76 public void actionPerformed(ActionEvent event) {
77 if (myConfigurable != null) {
78 ConfigurableCardPanel.reset(myConfigurable);
79 updateCurrent(myConfigurable, true);
83 private Configurable myConfigurable;
85 ConfigurableEditor(Disposable parent, Configurable configurable) {
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);
107 getDefaultToolkit().removeAWTEventListener(this);
108 myCardPanel.removeAll();
112 String getHelpTopic() {
113 return myConfigurable == null ? null : myConfigurable.getHelpTopic();
117 Action getApplyAction() {
118 return myApplyAction;
122 Action getResetAction() {
123 return myResetAction;
128 // do not apply changes of a single configurable if it is not modified
129 return setError(apply(myApplyAction.isEnabled() ? myConfigurable : null));
132 void openLink(Configurable configurable) {
133 ShowSettingsUtil.getInstance().editConfigurable(this, configurable);
137 public final void beforeEditorTyping(char ch, DataContext context) {
141 public final void beforeActionPerformed(AnAction action, DataContext context, AnActionEvent event) {
145 public final void afterActionPerformed(AnAction action, DataContext context, AnActionEvent event) {
150 public JComponent getPreferredFocusedComponent() {
151 if (myConfigurable instanceof BaseConfigurable) {
152 JComponent preferred = ((BaseConfigurable)myConfigurable).getPreferredFocusedComponent();
153 if (preferred != null) return preferred;
155 return super.getPreferredFocusedComponent();
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())) {
169 case KeyEvent.KEY_PRESSED:
170 case KeyEvent.KEY_RELEASED:
171 KeyEvent ke = (KeyEvent)event;
172 if (isDescendingFrom(ke.getComponent(), this)) {
179 private void requestUpdate() {
180 final Configurable configurable = myConfigurable;
181 myQueue.queue(new Update(this) {
184 updateIfCurrent(configurable);
188 public boolean isExpired() {
189 return myConfigurable != configurable;
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();
205 return popup instanceof JWindow;
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) {
220 final boolean updateIfCurrent(Configurable configurable) {
221 if (myConfigurable != configurable) {
224 updateCurrent(configurable, false);
228 final ActionCallback select(final Configurable configurable) {
229 assert !myDisposed : "Already disposed";
230 ActionCallback callback = myCardPanel.select(configurable, false);
231 callback.doWhenDone(new Runnable() {
234 myConfigurable = configurable;
235 updateCurrent(configurable, false);
241 final boolean setError(ConfigurationException exception) {
242 if (exception == null) {
243 myErrorLabel.setVisible(false);
246 if (myErrorLabel.isEnabled()) {
247 myErrorLabel.setText("<html><body><strong>" + exception.getTitle() + "</strong>:<br>" + exception.getMessage());
248 myErrorLabel.setVisible(true);
251 Messages.showMessageDialog(this, exception.getMessage(), exception.getTitle(), Messages.getErrorIcon());
256 final JComponent getContent(Configurable configurable) {
257 return myCardPanel.getValue(configurable, false);
260 final JComponent readContent(Configurable configurable) {
261 return myCardPanel.getValue(configurable, true);
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));
275 content.add(BorderLayout.NORTH, new JLabel(description));
276 if (configurable instanceof Configurable.Composite) {
277 Configurable.Composite composite = (Configurable.Composite)configurable;
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) {
286 public void doClick() {
290 label.setBorder(BorderFactory.createEmptyBorder(1, 17, 3, 1));
298 private static String getString(Configurable configurable, String key) {
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);
307 return OptionsBundle.message(key);
309 catch (AssertionError error) {
314 static ConfigurationException apply(Configurable configurable) {
315 if (configurable != null) {
317 configurable.apply();
318 UsageTrigger.trigger("ide.settings." + ConvertUsagesUtil.escapeDescriptorName(configurable.getDisplayName()));
320 catch (ConfigurationException exception) {