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
;
40 import javax
.swing
.event
.DocumentEvent
;
41 import javax
.swing
.event
.DocumentListener
;
43 import java
.awt
.event
.*;
44 import java
.beans
.PropertyChangeEvent
;
45 import java
.beans
.PropertyChangeListener
;
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
) {
96 myFilter
= new Filter();
97 myContext
= new OptionsEditorContext(myFilter
);
99 mySearch
= new MySearchField() {
101 protected void onTextKeyEvent(final KeyEvent e
) {
102 myTree
.processTextEvent(e
);
105 myTree
= new OptionsTree(myProject
, groups
, getContext()) {
107 protected void onTreeKeyEvent(final KeyEvent e
) {
108 myFilterFocumentWasChanged
= false;
110 mySearch
.keyEventToTextField(e
);
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);
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() {
149 public void navigationFinished(final Place from, final Place to) {
150 UIUtil.invokeLaterIfNeeded(new Runnable() {
152 if (myToolbarComponent.isShowing()) {
153 myToolbar.updateActionsImmediately();
162 myLeftSide
= new JPanel(new BorderLayout());
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
);
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) {
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
;
232 final String p
= PropertiesComponent
.getInstance(myProject
).getValue(propertyName
);
234 proportion
= Float
.valueOf(p
);
237 catch (NumberFormatException e
) {
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
);
257 getUiFor(configurable
).doWhenDone(new EdtRunnable() {
258 public void runEdt() {
259 final Configurable current
= getContext().getCurrentConfigurable();
260 if (current
!= configurable
) {
261 result
.setRejected();
265 myHistory
.pushQueryPlace();
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
);
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() {
309 initConfigurable(configurable
).notifyWhenDone(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
));
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() {
341 myConfigurable2Content
.put(configurable
, content
.get());
350 private void updateSpotlight(boolean now
) {
352 final boolean success
= mySpotlightPainter
.updateForCurrentConfigurable();
354 updateSpotlight(false);
357 mySpotlightUpdate
.queue(new Update(this) {
359 final boolean success
= mySpotlightPainter
.updateForCurrentConfigurable();
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()];
372 for (int i
= list
.size() - 1; i
>=0; i
--) {
373 result
[add
++] = list
.get(i
).getDisplayName().replace('\n', ' ');
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) {
400 parent
= getContext().getParentConfigurable(parent
);
403 result
.addAll(getContext().getChildren(nearestParent
));
412 private void fireModificationForItem(final Configurable configurable
) {
413 if (configurable
!= null) {
414 if (!myConfigurable2Content
.containsKey(configurable
) && isParentWithContent(configurable
)) {
416 ApplicationManager
.getApplication().executeOnPooledThread(new Runnable() {
418 initConfigurable(configurable
).doWhenDone(new Runnable() {
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();
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
);
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
);
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
);
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;
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
);
559 add(c
, BorderLayout
.CENTER
);
564 myErrorLabel
.setText(UIUtil
.toHtml(e
.getMessage()));
565 add(myErrorLabel
, BorderLayout
.NORTH
);
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;
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;
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
);
613 return NullableComponent
.Check
.isNull(myMaster
);
618 public void reset(Configurable configurable
, boolean notify
) {
619 configurable
.reset();
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
) {
631 if (!each
.isModified()) {
632 getContext().fireModifiedRemoved(each
, null);
635 catch (ConfigurationException 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())) {
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
);
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();
697 public ActionCallback
refilterFor(String text
, boolean adjustSelection
, final boolean now
) {
699 myUpdateEnabled
= false;
700 mySearch
.setText(text
);
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);
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
);
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;
759 private boolean isEmptyParent(Configurable configurable
) {
760 return configurable
instanceof SearchableConfigurable
.Parent
&& !((SearchableConfigurable
.Parent
)configurable
).hasOwnContent();
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) {
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() {
786 myTree
.select(config
).notifyWhenDone(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() {
826 private class MyColleague
extends OptionsEditorColleague
.Adapter
{
827 public ActionCallback
onSelected(final Configurable configurable
, final Configurable oldConfigurable
) {
828 return processSelected(configurable
, oldConfigurable
);
832 public ActionCallback
onModifiedRemoved(final Configurable configurable
) {
833 return updateIfCurrent(configurable
);
837 public ActionCallback
onModifiedAdded(final Configurable configurable
) {
838 return updateIfCurrent(configurable
);
842 public ActionCallback
onErrorsChanged() {
843 return updateIfCurrent(getContext().getCurrentConfigurable());
846 private ActionCallback
updateIfCurrent(final Configurable configurable
) {
847 if (getContext().getCurrentConfigurable() == configurable
&& configurable
!= null) {
849 final ConfigurableContent content
= myConfigurable2Content
.get(configurable
);
850 content
.updateBannerActions();
851 return new ActionCallback
.Done();
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) {
884 checkModified(configurable
);
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() {
905 addKeyListener(new KeyAdapter() {});
909 protected boolean preprocessEventForTextField(final KeyEvent e
) {
910 if (getTextEditor().isFocusOwner() && !myDelegatingNow
) {
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
) {
923 myDelegatingNow
= 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());
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();
956 final boolean sameText
=
957 myConfigurableToLastOption
.containsKey(current
) && text
.equals(myConfigurableToLastOption
.get(current
));
960 if (current
== null) {
966 SearchableConfigurable searchable
;
967 if (current
instanceof SearchableConfigurable
) {
968 searchable
= (SearchableConfigurable
)current
;
970 searchable
= new SearachableWrappper(current
);
975 final Runnable runnable
= SearchUtil
.lightOptions(searchable
, myContentWrapper
, text
, myGP
);
976 if (runnable
!= null) {
980 boolean pushFilteringFurther
= true;
982 pushFilteringFurther
= false;
984 if (myFilter
.myHits
!= null) {
985 pushFilteringFurther
= !myFilter
.myHits
.getNameHits().contains(current
);
989 final Runnable ownSearch
= searchable
.enableSearch(text
);
990 if (pushFilteringFurther
&& ownSearch
!= null) {
993 fireNeedsRepaint(myOwnDetails
.getComponent());
999 myConfigurableToLastOption
.put(current
, text
);
1007 public boolean needsRepaint() {
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
) {
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
) {
1146 public void clearFilter() {
1147 mySearch
.setText("");
1150 public void setHistory(final History history
) {