IDEADEV-38853 - fix + some extra diagnostics
[fedora-idea.git] / platform-impl / src / com / intellij / openapi / options / newEditor / OptionsEditor.java
blobc21bf88d8aa049e9a01eae1f1038bca2290452ef
1 package com.intellij.openapi.options.newEditor;
3 import com.intellij.ide.ui.search.ConfigurableHit;
4 import com.intellij.ide.ui.search.SearchUtil;
5 import com.intellij.ide.ui.search.SearchableOptionsRegistrar;
6 import com.intellij.ide.util.PropertiesComponent;
7 import com.intellij.openapi.Disposable;
8 import com.intellij.openapi.actionSystem.DataKey;
9 import com.intellij.openapi.actionSystem.DataProvider;
10 import com.intellij.openapi.application.ApplicationManager;
11 import com.intellij.openapi.diagnostic.Logger;
12 import com.intellij.openapi.options.*;
13 import com.intellij.openapi.options.ex.GlassPanel;
14 import com.intellij.openapi.project.Project;
15 import com.intellij.openapi.ui.*;
16 import com.intellij.openapi.util.ActionCallback;
17 import com.intellij.openapi.util.Disposer;
18 import com.intellij.openapi.util.EdtRunnable;
19 import com.intellij.openapi.util.Ref;
20 import com.intellij.openapi.wm.IdeGlassPaneUtil;
21 import com.intellij.ui.LightColors;
22 import com.intellij.ui.SearchTextField;
23 import com.intellij.ui.components.panels.NonOpaquePanel;
24 import com.intellij.ui.components.panels.Wrapper;
25 import com.intellij.ui.navigation.History;
26 import com.intellij.ui.navigation.Place;
27 import com.intellij.ui.speedSearch.ElementFilter;
28 import com.intellij.ui.treeStructure.SimpleNode;
29 import com.intellij.util.ui.UIUtil;
30 import com.intellij.util.ui.update.Activatable;
31 import com.intellij.util.ui.update.MergingUpdateQueue;
32 import com.intellij.util.ui.update.UiNotifyConnector;
33 import com.intellij.util.ui.update.Update;
34 import org.jetbrains.annotations.Nls;
35 import org.jetbrains.annotations.NonNls;
36 import org.jetbrains.annotations.NotNull;
37 import org.jetbrains.annotations.Nullable;
39 import javax.swing.*;
40 import javax.swing.event.DocumentEvent;
41 import javax.swing.event.DocumentListener;
42 import java.awt.*;
43 import java.awt.event.*;
44 import java.beans.PropertyChangeEvent;
45 import java.beans.PropertyChangeListener;
46 import java.util.*;
47 import java.util.List;
49 public class OptionsEditor extends JPanel implements DataProvider, Place.Navigator, Disposable, AWTEventListener {
50 public static DataKey<OptionsEditor> KEY = DataKey.create("options.editor");
52 private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.options.newEditor.OptionsEditor");
54 @NonNls private static final String MAIN_SPLITTER_PROPORTION = "options.splitter.main.proportions";
55 @NonNls private static final String DETAILS_SPLITTER_PROPORTION = "options.splitter.details.proportions";
57 @NonNls private static final String SEARCH_VISIBLE = "options.searchVisible";
59 private final Project myProject;
61 private final OptionsEditorContext myContext;
63 private final History myHistory = new History(this);
65 private final OptionsTree myTree;
66 private final MySearchField mySearch;
67 private final Splitter myMainSplitter;
68 //[back/forward] JComponent myToolbarComponent;
70 private final DetailsComponent myOwnDetails = new DetailsComponent().setEmptyContentText("Select configuration element in the tree to edit its settings");
71 private final ContentWrapper myContentWrapper = new ContentWrapper();
74 private final Map<Configurable, ConfigurableContent> myConfigurable2Content = new HashMap<Configurable, ConfigurableContent>();
75 private final Map<Configurable, ActionCallback> myConfigurable2LoadCallback = new HashMap<Configurable, ActionCallback>();
77 private final MergingUpdateQueue myModificationChecker;
78 private final ConfigurableGroup[] myGroups;
80 private final SpotlightPainter mySpotlightPainter = new SpotlightPainter();
81 private final MergingUpdateQueue mySpotlightUpdate;
82 private final LoadingDecorator myLoadingDecorator;
83 private final Filter myFilter;
85 private final Wrapper mySearchWrapper = new Wrapper();
86 private final JPanel myLeftSide;
88 private boolean myFilterFocumentWasChanged;
89 //[back/forward] private ActionToolbar myToolbar;
90 private Window myWindow;
92 public OptionsEditor(Project project, ConfigurableGroup[] groups, Configurable preselectedConfigurable) {
93 myProject = project;
94 myGroups = groups;
96 myFilter = new Filter();
97 myContext = new OptionsEditorContext(myFilter);
99 mySearch = new MySearchField() {
100 @Override
101 protected void onTextKeyEvent(final KeyEvent e) {
102 myTree.processTextEvent(e);
105 myTree = new OptionsTree(myProject, groups, getContext()) {
106 @Override
107 protected void onTreeKeyEvent(final KeyEvent e) {
108 myFilterFocumentWasChanged = false;
109 try {
110 mySearch.keyEventToTextField(e);
112 finally {
113 if (myFilterFocumentWasChanged && !isFilterFieldVisible()) {
114 setFilterFieldVisible(true, false, false);
120 getContext().addColleague(myTree);
121 Disposer.register(this, myTree);
122 mySearch.addDocumentListener(new DocumentListener() {
123 public void insertUpdate(final DocumentEvent e) {
124 myFilter.update(e.getType(), true, false);
127 public void removeUpdate(final DocumentEvent e) {
128 myFilter.update(e.getType(), true, false);
131 public void changedUpdate(final DocumentEvent e) {
132 myFilter.update(e.getType(), true, false);
137 /* [back/forward]
138 final DefaultActionGroup toolbarActions = new DefaultActionGroup();
139 toolbarActions.add(new BackAction(myTree));
140 toolbarActions.add(new ForwardAction(myTree));
141 toolbarActions.addSeparator();
142 toolbarActions.add(new ShowSearchFieldAction(this));
143 myToolbar = ActionManager.getInstance().createActionToolbar(ActionPlaces.UNKNOWN, toolbarActions, true);
144 myToolbar.setTargetComponent(this);
145 myToolbarComponent = myToolbar.getComponent();
147 myHistory.addListener(new HistoryListener.Adapter() {
148 @Override
149 public void navigationFinished(final Place from, final Place to) {
150 UIUtil.invokeLaterIfNeeded(new Runnable() {
151 public void run() {
152 if (myToolbarComponent.isShowing()) {
153 myToolbar.updateActionsImmediately();
158 }, this);
162 myLeftSide = new JPanel(new BorderLayout());
164 /* [back/forward]
166 final NonOpaquePanel toolbarPanel = new NonOpaquePanel(new BorderLayout());
167 toolbarPanel.add(myToolbarComponent, BorderLayout.WEST);
168 toolbarPanel.add(mySearchWrapper, BorderLayout.CENTER);
171 myLeftSide.add(mySearchWrapper, BorderLayout.NORTH);
173 myLeftSide.add(myTree, BorderLayout.CENTER);
175 setLayout(new BorderLayout());
177 myMainSplitter = new Splitter(false);
178 myMainSplitter.setFirstComponent(myLeftSide);
179 myMainSplitter.setHonorComponentsMinimumSize(false);
181 myLoadingDecorator = new LoadingDecorator(myOwnDetails.getComponent(), this, 150);
182 myMainSplitter.setSecondComponent(myLoadingDecorator.getComponent());
185 myMainSplitter.setProportion(readPropertion(0.3f, MAIN_SPLITTER_PROPORTION));
186 myContentWrapper.mySplitter.setProportion(readPropertion(0.2f, DETAILS_SPLITTER_PROPORTION));
188 add(myMainSplitter, BorderLayout.CENTER);
190 MyColleague colleague = new MyColleague();
191 getContext().addColleague(colleague);
193 if (preselectedConfigurable != null) {
194 myTree.select(preselectedConfigurable);
195 } else {
196 myTree.selectFirst();
199 Toolkit.getDefaultToolkit().addAWTEventListener(this, AWTEvent.MOUSE_EVENT_MASK | AWTEvent.KEY_EVENT_MASK);
201 myModificationChecker = new MergingUpdateQueue("OptionsModificationChecker", 1000, false, this, this, this);
202 mySpotlightUpdate = new MergingUpdateQueue("OptionsSplotlight", 500, false, this, this, this);
204 IdeGlassPaneUtil.installPainter(myOwnDetails.getContentGutter(), mySpotlightPainter, this);
207 String visible = PropertiesComponent.getInstance(myProject).getValue(SEARCH_VISIBLE);
208 if (visible == null) {
209 visible = "true";
213 setFilterFieldVisible(true, false, false);
215 new UiNotifyConnector.Once(this, new Activatable() {
216 public void showNotify() {
217 myWindow = SwingUtilities.getWindowAncestor(OptionsEditor.this);
220 public void hideNotify() {
225 public ActionCallback select(Configurable configurable) {
226 return myTree.select(configurable);
229 private float readPropertion(final float defaultValue, final String propertyName) {
230 float proportion = defaultValue;
231 try {
232 final String p = PropertiesComponent.getInstance(myProject).getValue(propertyName);
233 if (p != null) {
234 proportion = Float.valueOf(p);
237 catch (NumberFormatException e) {
238 LOG.debug(e);
240 return proportion;
243 private ActionCallback processSelected(final Configurable configurable, final Configurable oldConfigurable) {
244 if (isShowing(configurable)) return new ActionCallback.Done();
246 final ActionCallback result = new ActionCallback();
248 if (configurable == null) {
249 myOwnDetails.setContent(null);
251 updateSpotlight(true);
252 checkModified(oldConfigurable);
254 result.setDone();
256 } else {
257 getUiFor(configurable).doWhenDone(new EdtRunnable() {
258 public void runEdt() {
259 final Configurable current = getContext().getCurrentConfigurable();
260 if (current != configurable) {
261 result.setRejected();
262 return;
265 myHistory.pushQueryPlace();
267 updateDetails();
269 myOwnDetails.setContent(myContentWrapper);
270 myOwnDetails.setBannerMinHeight(mySearchWrapper.getHeight());
271 myOwnDetails.setText(getBannerText(configurable));
273 final ConfigurableContent content = myConfigurable2Content.get(current);
275 content.setText(getBannerText(configurable));
276 content.setBannerActions(new Action[] {new ResetAction(configurable)});
278 content.updateBannerActions();
280 myLoadingDecorator.stopLoading();
282 updateSpotlight(true);
284 checkModified(oldConfigurable);
285 checkModified(configurable);
287 result.setDone();
292 return result;
295 private ActionCallback getUiFor(final Configurable configurable) {
296 final ActionCallback result = new ActionCallback();
298 if (!myConfigurable2Content.containsKey(configurable)) {
300 final ActionCallback readyCallback = myConfigurable2LoadCallback.get(configurable);
301 if (readyCallback != null) {
302 return readyCallback;
305 myConfigurable2LoadCallback.put(configurable, result);
306 myLoadingDecorator.startLoading(false);
307 ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
308 public void run() {
309 initConfigurable(configurable).notifyWhenDone(result);
313 } else {
314 result.setDone();
317 return result;
320 private ActionCallback initConfigurable(@NotNull final Configurable configurable) {
321 final ActionCallback result = new ActionCallback();
323 final Ref<ConfigurableContent> content = new Ref<ConfigurableContent>();
325 if (configurable instanceof MasterDetails) {
326 content.set(new Details((MasterDetails)configurable));
327 } else {
328 content.set(new Simple(configurable));
331 if (!myConfigurable2Content.containsKey(configurable)) {
332 if (configurable instanceof Place.Navigator) {
333 ((Place.Navigator)configurable).setHistory(myHistory);
336 configurable.reset();
339 UIUtil.invokeLaterIfNeeded(new Runnable() {
340 public void run() {
341 myConfigurable2Content.put(configurable, content.get());
342 result.setDone();
346 return result;
350 private void updateSpotlight(boolean now) {
351 if (now) {
352 final boolean success = mySpotlightPainter.updateForCurrentConfigurable();
353 if (!success) {
354 updateSpotlight(false);
356 } else {
357 mySpotlightUpdate.queue(new Update(this) {
358 public void run() {
359 final boolean success = mySpotlightPainter.updateForCurrentConfigurable();
360 if (!success) {
361 updateSpotlight(false);
368 private String[] getBannerText(Configurable configurable) {
369 final List<Configurable> list = myTree.getPathToRoot(configurable);
370 final String[] result = new String[list.size()];
371 int add = 0;
372 for (int i = list.size() - 1; i >=0; i--) {
373 result[add++] = list.get(i).getDisplayName().replace('\n', ' ');
375 return result;
378 private void checkModified(final Configurable configurable) {
379 fireModification(configurable);
382 private void fireModification(final Configurable actual) {
384 Collection<Configurable> toCheck = colectAllParentsAndSiblings(actual);
386 for (Configurable configurable : toCheck) {
387 fireModificationForItem(configurable);
392 private Collection<Configurable> colectAllParentsAndSiblings(final Configurable actual) {
393 ArrayList<Configurable> result = new ArrayList<Configurable>();
394 Configurable nearestParent = getContext().getParentConfigurable(actual);
396 if (nearestParent != null) {
397 Configurable parent = nearestParent;
398 while (parent != null) {
399 result.add(parent);
400 parent = getContext().getParentConfigurable(parent);
403 result.addAll(getContext().getChildren(nearestParent));
404 } else {
405 result.add(actual);
409 return result;
412 private void fireModificationForItem(final Configurable configurable) {
413 if (configurable != null) {
414 if (!myConfigurable2Content.containsKey(configurable) && isParentWithContent(configurable)) {
416 ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
417 public void run() {
418 initConfigurable(configurable).doWhenDone(new Runnable() {
419 public void run() {
420 fireModifiationInt(configurable);
426 else if (myConfigurable2Content.containsKey(configurable)) {
427 fireModifiationInt(configurable);
432 private static boolean isParentWithContent(final Configurable configurable) {
433 return configurable instanceof SearchableConfigurable.Parent &&
434 ((SearchableConfigurable.Parent)configurable).hasOwnContent();
437 private void fireModifiationInt(final Configurable configurable) {
438 if (configurable.isModified()) {
439 getContext().fireModifiedAdded(configurable, null);
440 } else if (!configurable.isModified() && !getContext().getErrors().containsKey(configurable)) {
441 getContext().fireModifiedRemoved(configurable, null);
445 private void updateDetails() {
446 final Configurable current = getContext().getCurrentConfigurable();
448 assert current != null;
450 final ConfigurableContent content = myConfigurable2Content.get(current);
451 content.set(myContentWrapper);
454 private boolean isShowing(Configurable configurable) {
455 final ConfigurableContent content = myConfigurable2Content.get(configurable);
456 return content != null && content.isShowing();
459 @Nullable
460 public String getHelpTopic() {
461 Configurable current = getContext().getCurrentConfigurable();
462 while (current != null) {
463 String topic = current.getHelpTopic();
464 if (topic != null) return topic;
465 current = getContext().getParentConfigurable(current);
467 return null;
470 public boolean isFilterFieldVisible() {
471 return mySearch.getParent() == mySearchWrapper;
474 public void setFilterFieldVisible(final boolean visible, boolean requestFocus, boolean checkFocus) {
475 if (isFilterFieldVisible() && checkFocus && requestFocus && !isSearchFieldFocused()) {
476 UIUtil.requestFocus(mySearch);
477 return;
480 mySearchWrapper.setContent(visible ? mySearch : null);
482 myLeftSide.revalidate();
483 myLeftSide.repaint();
485 if (visible && requestFocus) {
486 UIUtil.requestFocus(mySearch);
490 public boolean isSearchFieldFocused() {
491 return mySearch.getTextEditor().isFocusOwner();
494 private class ResetAction extends AbstractAction {
495 Configurable myConfigurable;
497 ResetAction(final Configurable configurable) {
498 myConfigurable = configurable;
499 putValue(NAME, "Reset");
500 putValue(SHORT_DESCRIPTION, "Rollback changes for this configuration element");
503 public void actionPerformed(final ActionEvent e) {
504 reset(myConfigurable, true);
505 checkModified(myConfigurable);
508 @Override
509 public boolean isEnabled() {
510 return myContext.isModified(myConfigurable) || getContext().getErrors().containsKey(myConfigurable);
514 private static class ContentWrapper extends NonOpaquePanel {
516 private final JLabel myErrorLabel;
518 private JComponent mySimpleContent;
519 private ConfigurationException myException;
522 private JComponent myMaster;
523 private JComponent myToolbar;
524 private DetailsComponent myDetails;
526 private final Splitter mySplitter = new Splitter(false);
527 private JPanel myLeft = new JPanel(new BorderLayout());
528 public float myLastSplitterProproprtion;
530 private ContentWrapper() {
531 setLayout(new BorderLayout());
532 myErrorLabel = new JLabel();
533 myErrorLabel.setOpaque(true);
534 myErrorLabel.setBackground(LightColors.RED);
536 myLeft = new JPanel(new BorderLayout());
538 mySplitter.addPropertyChangeListener(Splitter.PROP_PROPORTION, new PropertyChangeListener() {
539 public void propertyChange(final PropertyChangeEvent evt) {
540 myLastSplitterProproprtion = ((Float)evt.getNewValue()).floatValue();
545 void setContent(JComponent c, ConfigurationException e, boolean scrollable) {
546 if (c != null && mySimpleContent == c && myException == e) return;
548 removeAll();
550 if (c != null) {
551 if (scrollable) {
552 JScrollPane scroll = new JScrollPane(c);
553 scroll.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
554 scroll.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
555 scroll.setBorder(null);
556 add(scroll, BorderLayout.CENTER);
558 else {
559 add(c, BorderLayout.CENTER);
563 if (e != null) {
564 myErrorLabel.setText(UIUtil.toHtml(e.getMessage()));
565 add(myErrorLabel, BorderLayout.NORTH);
568 mySimpleContent = c;
569 myException = e;
571 myMaster = null;
572 myToolbar = null;
573 myDetails = null;
574 mySplitter.setFirstComponent(null);
575 mySplitter.setSecondComponent(null);
578 void setContent(JComponent master, JComponent toolbar, DetailsComponent details, ConfigurationException e) {
579 if (myMaster == master && myToolbar == toolbar && myDetails == details && myException == e) return;
581 myMaster = master;
582 myToolbar = toolbar;
583 myDetails = details;
584 myException = e;
587 removeAll();
588 myLeft.removeAll();
590 myLeft.add(myToolbar, BorderLayout.NORTH);
591 myLeft.add(myMaster, BorderLayout.CENTER);
593 myDetails.setBannerMinHeight(myToolbar.getPreferredSize().height);
595 mySplitter.setFirstComponent(myLeft);
596 mySplitter.setSecondComponent(myDetails.getComponent());
597 mySplitter.setProportion(myLastSplitterProproprtion);
599 add(mySplitter, BorderLayout.CENTER);
601 mySimpleContent = null;
605 @Override
606 public boolean isNull() {
607 final boolean superNull = super.isNull();
608 if (superNull) return superNull;
610 if (myMaster == null) {
611 return NullableComponent.Check.isNull(mySimpleContent);
612 } else {
613 return NullableComponent.Check.isNull(myMaster);
618 public void reset(Configurable configurable, boolean notify) {
619 configurable.reset();
620 if (notify) {
621 getContext().fireReset(configurable);
625 public void apply() {
626 Map<Configurable, ConfigurationException> errors = new LinkedHashMap<Configurable, ConfigurationException>();
627 final Set<Configurable> modified = getContext().getModified();
628 for (Configurable each : modified) {
629 try {
630 each.apply();
631 if (!each.isModified()) {
632 getContext().fireModifiedRemoved(each, null);
635 catch (ConfigurationException e) {
636 errors.put(each, e);
637 LOG.debug(e);
641 getContext().fireErrorsChanged(errors, null);
643 if (!errors.isEmpty()) {
644 myTree.select(errors.keySet().iterator().next());
649 public Object getData(@NonNls final String dataId) {
650 if (dataId.equals(KEY.getName())) {
651 return this;
653 return History.KEY.getName().equals(dataId) ? myHistory : null;
656 public JTree getPreferredFocusedComponent() {
657 return myTree.getTree();
661 private class Filter extends ElementFilter.Active.Impl<SimpleNode> {
663 SearchableOptionsRegistrar myIndex = SearchableOptionsRegistrar.getInstance();
664 Set<Configurable> myFiltered = null;
665 ConfigurableHit myHits;
667 boolean myUpdateEnabled = true;
669 public boolean shouldBeShowing(final SimpleNode value) {
670 if (myFiltered == null) return true;
672 if (value instanceof OptionsTree.EditorNode) {
673 final OptionsTree.EditorNode node = (OptionsTree.EditorNode)value;
674 return myFiltered.contains(node.getConfigurable()) || isChildOfNameHit(node);
677 return true;
680 private boolean isChildOfNameHit(OptionsTree.EditorNode node) {
681 if (myHits != null) {
682 OptionsTree.Base eachParent = node;
683 while (eachParent != null) {
684 if (eachParent instanceof OptionsTree.EditorNode) {
685 final OptionsTree.EditorNode eachEditorNode = (OptionsTree.EditorNode)eachParent;
686 if (myHits.getNameFullHits().contains(eachEditorNode.myConfigurable)) return true;
688 eachParent = (OptionsTree.Base)eachParent.getParent();
691 return false;
694 return false;
697 public ActionCallback refilterFor(String text, boolean adjustSelection, final boolean now) {
698 try {
699 myUpdateEnabled = false;
700 mySearch.setText(text);
702 finally {
703 myUpdateEnabled = true;
706 return update(DocumentEvent.EventType.CHANGE, adjustSelection, now);
709 public ActionCallback update(DocumentEvent.EventType type, boolean adjustSeection, boolean now) {
710 if (!myUpdateEnabled) return new ActionCallback.Rejected();
712 final String text = mySearch.getText();
713 if (getFilterText().length() == 0) {
714 myContext.setHoldingFilter(false);
715 myFiltered = null;
716 } else {
717 myContext.setHoldingFilter(true);
718 myHits = myIndex.getConfigurables(myGroups, type, myFiltered, text, myProject);
719 myFiltered = myHits.getAll();
722 if (myFiltered != null && myFiltered.isEmpty()) {
723 mySearch.getTextEditor().setBackground(LightColors.RED);
724 } else {
725 mySearch.getTextEditor().setBackground(UIUtil.getTextFieldBackground());
729 final Configurable current = getContext().getCurrentConfigurable();
731 boolean shouldMoveSelection = true;
733 if (myHits != null && myHits.getNameFullHits().contains(current)) {
734 shouldMoveSelection = false;
737 if (shouldMoveSelection && (myFiltered == null || myFiltered.contains(current))) {
738 shouldMoveSelection = false;
741 Configurable toSelect = null;
742 if (shouldMoveSelection && myHits != null) {
743 if (!myHits.getNameHits().isEmpty()) {
744 toSelect = suggestToSelect(myHits.getNameHits(), myHits.getNameFullHits());
745 } else if (!myHits.getContentHits().isEmpty()) {
746 toSelect = suggestToSelect(myHits.getContentHits(), null);
750 updateSpotlight(false);
752 final ActionCallback callback = fireUpdate(adjustSeection ? myTree.findNodeFor(toSelect) : null, adjustSeection, now);
754 myFilterFocumentWasChanged = true;
756 return callback;
759 private boolean isEmptyParent(Configurable configurable) {
760 return configurable instanceof SearchableConfigurable.Parent && !((SearchableConfigurable.Parent)configurable).hasOwnContent();
763 @Nullable
764 private Configurable suggestToSelect(Set<Configurable> set, Set<Configurable> fullHits) {
765 Configurable candidate = null;
766 for (Configurable each : set) {
767 if (fullHits != null && fullHits.contains(each)) return each;
768 if (!isEmptyParent(each) && candidate == null) {
769 candidate = each;
773 return candidate;
778 public ActionCallback navigateTo(@Nullable final Place place, final boolean requestFocus) {
779 final Configurable config = (Configurable)place.getPath("configurable");
780 final String filter = (String)place.getPath("filter");
782 final ActionCallback result = new ActionCallback();
784 myFilter.refilterFor(filter, false, true).doWhenDone(new Runnable() {
785 public void run() {
786 myTree.select(config).notifyWhenDone(result);
790 return result;
793 public void queryPlace(@NotNull final Place place) {
794 final Configurable current = getContext().getCurrentConfigurable();
795 place.putPath("configurable", current);
796 place.putPath("filter", getFilterText());
798 if (current instanceof Place.Navigator) {
799 ((Place.Navigator)current).queryPlace(place);
803 public void dispose() {
804 final PropertiesComponent props = PropertiesComponent.getInstance(myProject);
805 props.setValue(MAIN_SPLITTER_PROPORTION, String.valueOf(myMainSplitter.getProportion()));
806 props.setValue(DETAILS_SPLITTER_PROPORTION, String.valueOf(myContentWrapper.myLastSplitterProproprtion));
807 props.setValue(SEARCH_VISIBLE, Boolean.valueOf(isFilterFieldVisible()).toString());
809 Toolkit.getDefaultToolkit().removeAWTEventListener(this);
812 final Set<Configurable> configurables = new HashSet<Configurable>();
813 configurables.addAll(myConfigurable2Content.keySet());
814 configurables.addAll(myConfigurable2LoadCallback.keySet());
815 for (Configurable each : configurables) {
816 each.disposeUIResources();
819 Disposer.clearOwnFields(this);
822 public OptionsEditorContext getContext() {
823 return myContext;
826 private class MyColleague extends OptionsEditorColleague.Adapter {
827 public ActionCallback onSelected(final Configurable configurable, final Configurable oldConfigurable) {
828 return processSelected(configurable, oldConfigurable);
831 @Override
832 public ActionCallback onModifiedRemoved(final Configurable configurable) {
833 return updateIfCurrent(configurable);
836 @Override
837 public ActionCallback onModifiedAdded(final Configurable configurable) {
838 return updateIfCurrent(configurable);
841 @Override
842 public ActionCallback onErrorsChanged() {
843 return updateIfCurrent(getContext().getCurrentConfigurable());
846 private ActionCallback updateIfCurrent(final Configurable configurable) {
847 if (getContext().getCurrentConfigurable() == configurable && configurable != null) {
848 updateDetails();
849 final ConfigurableContent content = myConfigurable2Content.get(configurable);
850 content.updateBannerActions();
851 return new ActionCallback.Done();
852 } else {
853 return new ActionCallback.Rejected();
858 public void flushModifications() {
859 fireModification(getContext().getCurrentConfigurable());
862 public boolean canApply() {
863 return !getContext().getModified().isEmpty();
866 public void eventDispatched(final AWTEvent event) {
867 if (event.getID() == MouseEvent.MOUSE_PRESSED || event.getID() == MouseEvent.MOUSE_RELEASED) {
868 final MouseEvent me = (MouseEvent)event;
869 if (SwingUtilities.isDescendingFrom(me.getComponent(), myContentWrapper) || isPopupOverEditor(me.getComponent())) {
870 queueModificationCheck(getContext().getCurrentConfigurable());
873 else if (event.getID() == KeyEvent.KEY_PRESSED || event.getID() == KeyEvent.KEY_RELEASED) {
874 final KeyEvent ke = (KeyEvent)event;
875 if (SwingUtilities.isDescendingFrom(ke.getComponent(), myContentWrapper)) {
876 queueModificationCheck(getContext().getCurrentConfigurable());
881 private void queueModificationCheck(final Configurable configurable) {
882 myModificationChecker.queue(new Update(this) {
883 public void run() {
884 checkModified(configurable);
887 @Override
888 public boolean isExpired() {
889 return getContext().getCurrentConfigurable() != configurable;
894 private boolean isPopupOverEditor(Component c) {
895 final Window wnd = SwingUtilities.getWindowAncestor(c);
896 return wnd instanceof JWindow && myWindow != null && wnd.getParent() == myWindow;
899 private static class MySearchField extends SearchTextField {
901 private boolean myDelegatingNow;
903 private MySearchField() {
904 super(false);
905 addKeyListener(new KeyAdapter() {});
908 @Override
909 protected boolean preprocessEventForTextField(final KeyEvent e) {
910 if (getTextEditor().isFocusOwner() && !myDelegatingNow) {
911 try {
912 myDelegatingNow = true;
913 final KeyStroke stroke = KeyStroke.getKeyStrokeForEvent(e);
914 boolean treeNavigation = stroke.getModifiers() == 0 && (stroke.getKeyCode() == KeyEvent.VK_UP || stroke.getKeyCode() == KeyEvent.VK_DOWN);
916 final Object action = getTextEditor().getInputMap().get(stroke);
917 if (action == null || treeNavigation) {
918 onTextKeyEvent(e);
919 return true;
922 finally {
923 myDelegatingNow = false;
927 return false;
931 protected void onTextKeyEvent(final KeyEvent e) {
936 private class SpotlightPainter extends AbstractPainter {
937 Map<Configurable, String> myConfigurableToLastOption = new HashMap<Configurable, String>();
939 GlassPanel myGP = new GlassPanel(myOwnDetails.getContentGutter());
940 boolean myVisible;
942 public void executePaint(final Component component, final Graphics2D g) {
943 if (myVisible && myGP.isVisible()) {
944 myGP.paintSpotlight(g, myOwnDetails.getContentGutter());
948 public boolean updateForCurrentConfigurable() {
949 final Configurable current = getContext().getCurrentConfigurable();
951 if (current != null && !myConfigurable2Content.containsKey(current)) return false;
953 String text = getFilterText();
955 try {
956 final boolean sameText =
957 myConfigurableToLastOption.containsKey(current) && text.equals(myConfigurableToLastOption.get(current));
960 if (current == null) {
961 myVisible = false;
962 myGP.clear();
963 return true;
966 SearchableConfigurable searchable;
967 if (current instanceof SearchableConfigurable) {
968 searchable = (SearchableConfigurable)current;
969 } else {
970 searchable = new SearachableWrappper(current);
973 myGP.clear();
975 final Runnable runnable = SearchUtil.lightOptions(searchable, myContentWrapper, text, myGP);
976 if (runnable != null) {
977 myVisible = true;
978 runnable.run();
980 boolean pushFilteringFurther = true;
981 if (sameText) {
982 pushFilteringFurther = false;
983 } else {
984 if (myFilter.myHits != null) {
985 pushFilteringFurther = !myFilter.myHits.getNameHits().contains(current);
989 final Runnable ownSearch = searchable.enableSearch(text);
990 if (pushFilteringFurther && ownSearch != null) {
991 ownSearch.run();
993 fireNeedsRepaint(myOwnDetails.getComponent());
994 } else {
995 myVisible = false;
998 finally {
999 myConfigurableToLastOption.put(current, text);
1002 return true;
1006 @Override
1007 public boolean needsRepaint() {
1008 return true;
1012 private String getFilterText() {
1013 return mySearch.getText() != null ? mySearch.getText().trim() : "";
1016 private static class SearachableWrappper implements SearchableConfigurable {
1017 private final Configurable myConfigurable;
1019 private SearachableWrappper(final Configurable configurable) {
1020 myConfigurable = configurable;
1023 public String getId() {
1024 return myConfigurable.getClass().getName();
1027 public Runnable enableSearch(final String option) {
1028 return null;
1031 @Nls
1032 public String getDisplayName() {
1033 return myConfigurable.getDisplayName();
1036 public Icon getIcon() {
1037 return myConfigurable.getIcon();
1040 public String getHelpTopic() {
1041 return myConfigurable.getHelpTopic();
1044 public JComponent createComponent() {
1045 return myConfigurable.createComponent();
1048 public boolean isModified() {
1049 return myConfigurable.isModified();
1052 public void apply() throws ConfigurationException {
1053 myConfigurable.apply();
1056 public void reset() {
1057 myConfigurable.reset();
1060 public void disposeUIResources() {
1061 myConfigurable.disposeUIResources();
1065 private abstract static class ConfigurableContent {
1066 abstract void set(ContentWrapper wrapper);
1068 abstract boolean isShowing();
1070 abstract void setBannerActions(Action[] actions);
1072 abstract void updateBannerActions();
1074 abstract void setText(final String[] bannerText);
1077 private class Simple extends ConfigurableContent {
1079 JComponent myComponent;
1080 Configurable myConfigurable;
1082 Simple(final Configurable configurable) {
1083 myConfigurable = configurable;
1084 myComponent = configurable.createComponent();
1087 void set(final ContentWrapper wrapper) {
1088 myOwnDetails.setDetailsModeEnabled(true);
1089 wrapper.setContent(myComponent, getContext().getErrors().get(myConfigurable), !(myConfigurable instanceof Configurable.NoScroll));
1092 boolean isShowing() {
1093 return myComponent != null && myComponent.isShowing();
1096 void setBannerActions(final Action[] actions) {
1097 myOwnDetails.setBannerActions(actions);
1100 void updateBannerActions() {
1101 myOwnDetails.updateBannerActions();
1104 void setText(final String[] bannerText) {
1105 myOwnDetails.setText(bannerText);
1109 private class Details extends ConfigurableContent {
1110 MasterDetails myConfigurable;
1111 DetailsComponent myDetails;
1112 JComponent myMaster;
1113 JComponent myToolbar;
1115 Details(final MasterDetails configurable) {
1116 myConfigurable = configurable;
1117 myConfigurable.initUi();
1118 myDetails = myConfigurable.getDetails();
1119 myMaster = myConfigurable.getMaster();
1120 myToolbar = myConfigurable.getToolbar();
1123 void set(final ContentWrapper wrapper) {
1124 myOwnDetails.setDetailsModeEnabled(false);
1125 myDetails.setPrefix(getBannerText((Configurable)myConfigurable));
1126 wrapper.setContent(myMaster, myToolbar, myDetails, getContext().getErrors().get(myConfigurable));
1129 void setBannerActions(final Action[] actions) {
1130 myDetails.setBannerActions(actions);
1133 boolean isShowing() {
1134 return myDetails.getComponent().isShowing();
1137 void updateBannerActions() {
1138 myDetails.updateBannerActions();
1141 void setText(final String[] bannerText) {
1142 myDetails.update();
1146 public void clearFilter() {
1147 mySearch.setText("");
1150 public void setHistory(final History history) {