blob: c1e55a1419a2e36bb75ab6affdf12e5b49b410fa [file] [log] [blame]
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.openapi.options.newEditor;
import com.intellij.AbstractBundle;
import com.intellij.CommonBundle;
import com.intellij.ide.ui.laf.darcula.ui.DarculaTextBorder;
import com.intellij.ide.ui.laf.darcula.ui.DarculaTextFieldUI;
import com.intellij.ide.ui.search.SearchUtil;
import com.intellij.ide.util.PropertiesComponent;
import com.intellij.internal.statistic.UsageTrigger;
import com.intellij.internal.statistic.beans.ConvertUsagesUtil;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.actionSystem.ex.AnActionListener;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ex.ApplicationEx;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.options.*;
import com.intellij.openapi.options.ex.ConfigurableWrapper;
import com.intellij.openapi.options.ex.GlassPanel;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.*;
import com.intellij.openapi.util.ActionCallback;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.EdtRunnable;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.wm.IdeGlassPaneUtil;
import com.intellij.ui.LightColors;
import com.intellij.ui.OnePixelSplitter;
import com.intellij.ui.ScrollPaneFactory;
import com.intellij.ui.SearchTextField;
import com.intellij.ui.components.labels.LinkLabel;
import com.intellij.ui.components.panels.NonOpaquePanel;
import com.intellij.ui.components.panels.Wrapper;
import com.intellij.ui.navigation.History;
import com.intellij.ui.navigation.Place;
import com.intellij.ui.treeStructure.SimpleNode;
import com.intellij.ui.treeStructure.filtered.FilteringTreeBuilder;
import com.intellij.util.ui.UIUtil;
import com.intellij.util.ui.update.Activatable;
import com.intellij.util.ui.update.MergingUpdateQueue;
import com.intellij.util.ui.update.UiNotifyConnector;
import com.intellij.util.ui.update.Update;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import java.awt.*;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.*;
import java.util.List;
public class OptionsEditor extends JPanel implements DataProvider, Place.Navigator, Disposable, AWTEventListener {
public static DataKey<OptionsEditor> KEY = DataKey.create("options.editor");
private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.options.newEditor.OptionsEditor");
@NonNls private static final String MAIN_SPLITTER_PROPORTION = "options.splitter.main.proportions";
@NonNls private static final String DETAILS_SPLITTER_PROPORTION = "options.splitter.details.proportions";
@NonNls private static final String NOT_A_NEW_COMPONENT = "component.was.already.instantiated";
private final History myHistory = new History(this);
private final OptionsTree myTree;
private final SettingsTreeView myTreeView;
private final SearchTextField mySearch;
private final Splitter myMainSplitter;
//[back/forward] JComponent myToolbarComponent;
private final DetailsComponent myOwnDetails =
new DetailsComponent().setEmptyContentText("Select configuration element in the tree to edit its settings");
private final ContentWrapper myContentWrapper = new ContentWrapper();
private final Map<Configurable, ConfigurableContent> myConfigurable2Content = new HashMap<Configurable, ConfigurableContent>();
private final Map<Configurable, ActionCallback> myConfigurable2LoadCallback = new HashMap<Configurable, ActionCallback>();
private final MergingUpdateQueue myModificationChecker;
private final SpotlightPainter mySpotlightPainter = new SpotlightPainter();
private final MergingUpdateQueue mySpotlightUpdate;
private final LoadingDecorator myLoadingDecorator;
private final SettingsFilter myFilter;
private final Wrapper mySearchWrapper = new Wrapper();
private final JPanel myLeftSide;
//[back/forward] private ActionToolbar myToolbar;
private Window myWindow;
private final PropertiesComponent myProperties;
private volatile boolean myDisposed;
private final KeyListener myTreeKeyListener = new KeyListener() {
@Override
public void keyPressed(KeyEvent event) {
keyTyped(event);
}
@Override
public void keyReleased(KeyEvent event) {
keyTyped(event);
}
@Override
public void keyTyped(KeyEvent event) {
Object source = event.getSource();
if (source instanceof JTree) {
JTree tree = (JTree)source;
if (tree.getInputMap().get(KeyStroke.getKeyStrokeForEvent(event)) == null) {
myFilter.myDocumentWasChanged = false;
try {
mySearch.keyEventToTextField(event);
}
finally {
if (myFilter.myDocumentWasChanged && !isFilterFieldVisible()) {
setFilterFieldVisible(true, false, false);
}
}
}
}
}
};
public OptionsEditor(Project project, ConfigurableGroup[] groups, Configurable preselectedConfigurable) {
myProperties = PropertiesComponent.getInstance(project);
mySearch = new MySearchField() {
@Override
protected void onTextKeyEvent(final KeyEvent e) {
if (myTreeView != null) {
myTreeView.myTree.processKeyEvent(e);
}
else {
myTree.processTextEvent(e);
}
}
};
myFilter = new SettingsFilter(project, groups, mySearch) {
@Override
Configurable getConfigurable(SimpleNode node) {
if (node instanceof OptionsTree.EditorNode) {
return ((OptionsTree.EditorNode)node).getConfigurable();
}
return SettingsTreeView.getConfigurable(node);
}
@Override
SimpleNode findNode(Configurable configurable) {
return myTreeView != null
? myTreeView.findNode(configurable)
: myTree.findNodeFor(configurable);
}
@Override
void updateSpotlight(boolean now) {
if (!now) {
mySpotlightUpdate.queue(new Update(this) {
@Override
public void run() {
if (!mySpotlightPainter.updateForCurrentConfigurable()) {
updateSpotlight(false);
}
}
});
}
else if (!mySpotlightPainter.updateForCurrentConfigurable()) {
updateSpotlight(false);
}
}
};
if (Registry.is("ide.new.settings.dialog")) {
myTreeView = new SettingsTreeView(myFilter, groups);
myTreeView.myTree.addKeyListener(myTreeKeyListener);
myTree = null;
}
else {
myTreeView = null;
myTree = new OptionsTree(myFilter, groups);
myTree.addKeyListener(myTreeKeyListener);
}
getContext().addColleague(myTreeView != null ? myTreeView : myTree);
Disposer.register(this, myTreeView != null ? myTreeView : myTree);
/* [back/forward]
final DefaultActionGroup toolbarActions = new DefaultActionGroup();
toolbarActions.add(new BackAction(myTree));
toolbarActions.add(new ForwardAction(myTree));
toolbarActions.addSeparator();
toolbarActions.add(new ShowSearchFieldAction(this));
myToolbar = ActionManager.getInstance().createActionToolbar(ActionPlaces.UNKNOWN, toolbarActions, true);
myToolbar.setTargetComponent(this);
myToolbarComponent = myToolbar.getComponent();
myHistory.addListener(new HistoryListener.Adapter() {
@Override
public void navigationFinished(final Place from, final Place to) {
UIUtil.invokeLaterIfNeeded(new Runnable() {
public void run() {
if (myToolbarComponent.isShowing()) {
myToolbar.updateActionsImmediately();
}
}
});
}
}, this);
*/
myLeftSide = new JPanel(new BorderLayout()) {
@Override
public Dimension getMinimumSize() {
Dimension dimension = super.getMinimumSize();
JComponent component = myTreeView != null ? myTreeView : myTree;
dimension.width = Math.max(component.getMinimumSize().width, mySearchWrapper.getPreferredSize().width);
return dimension;
}
};
/* [back/forward]
final NonOpaquePanel toolbarPanel = new NonOpaquePanel(new BorderLayout());
toolbarPanel.add(myToolbarComponent, BorderLayout.WEST);
toolbarPanel.add(mySearchWrapper, BorderLayout.CENTER);
*/
myLeftSide.add(mySearchWrapper, BorderLayout.NORTH);
myLeftSide.add(myTreeView != null ? myTreeView : myTree, BorderLayout.CENTER);
setLayout(new BorderLayout());
myMainSplitter = Registry.is("ide.new.settings.dialog") ? new OnePixelSplitter(false) : new Splitter(false);
myMainSplitter.setFirstComponent(myLeftSide);
myLoadingDecorator = new LoadingDecorator(myOwnDetails.getComponent(), this, 150);
myMainSplitter.setSecondComponent(myLoadingDecorator.getComponent());
myMainSplitter.setProportion(readProportion(0.3f, MAIN_SPLITTER_PROPORTION));
myContentWrapper.mySplitter.setProportion(readProportion(0.2f, DETAILS_SPLITTER_PROPORTION));
add(myMainSplitter, BorderLayout.CENTER);
MyColleague colleague = new MyColleague();
getContext().addColleague(colleague);
mySpotlightUpdate = new MergingUpdateQueue("OptionsSpotlight", 200, false, this, this, this);
if (preselectedConfigurable != null) {
selectInTree(preselectedConfigurable);
}
else {
if (myTreeView != null) {
myTreeView.selectFirst();
}
else {
myTree.selectFirst();
}
}
Toolkit.getDefaultToolkit().addAWTEventListener(this,
AWTEvent.MOUSE_EVENT_MASK | AWTEvent.KEY_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK);
ActionManager.getInstance().addAnActionListener(new AnActionListener() {
@Override
public void beforeActionPerformed(AnAction action, DataContext dataContext, AnActionEvent event) {
}
@Override
public void afterActionPerformed(AnAction action, DataContext dataContext, AnActionEvent event) {
queueModificationCheck();
}
@Override
public void beforeEditorTyping(char c, DataContext dataContext) {
}
}, this);
myModificationChecker = new MergingUpdateQueue("OptionsModificationChecker", 1000, false, this, this, this);
IdeGlassPaneUtil.installPainter(myOwnDetails.getContentGutter(), mySpotlightPainter, this);
/*
String visible = PropertiesComponent.getInstance(myProject).getValue(SEARCH_VISIBLE);
if (visible == null) {
visible = "true";
}
*/
setFilterFieldVisible(true, false, false);
new UiNotifyConnector.Once(this, new Activatable() {
@Override
public void showNotify() {
myWindow = SwingUtilities.getWindowAncestor(OptionsEditor.this);
}
@Override
public void hideNotify() {
}
});
}
private ActionCallback selectInTree(Configurable configurable) {
return myTreeView != null
? myTreeView.select(configurable)
: myTree.select(configurable);
}
/** @see #select(com.intellij.openapi.options.Configurable) */
@Deprecated
public ActionCallback select(Class<? extends Configurable> configurableClass) {
final Configurable configurable = findConfigurable(configurableClass);
if (configurable == null) {
return new ActionCallback.Rejected();
}
return select(configurable);
}
/** @see #findConfigurableById(String) */
@Deprecated
@Nullable
public <T extends Configurable> T findConfigurable(Class<T> configurableClass) {
return myTreeView != null
? myTreeView.findConfigurable(configurableClass)
: myTree.findConfigurable(configurableClass);
}
@Nullable
public SearchableConfigurable findConfigurableById(@NotNull String configurableId) {
return myTreeView != null
? myTreeView.findConfigurableById(configurableId)
: myTree.findConfigurableById(configurableId);
}
public ActionCallback clearSearchAndSelect(Configurable configurable) {
clearFilter();
return select(configurable, "");
}
public ActionCallback select(Configurable configurable) {
if (myFilter.getFilterText().isEmpty()) {
return select(configurable, "");
}
else {
return myFilter.update(true, true);
}
}
public ActionCallback select(Configurable configurable, final String text) {
myFilter.update(text, false, true);
return selectInTree(configurable);
}
private float readProportion(final float defaultValue, final String propertyName) {
float proportion = defaultValue;
try {
final String p = myProperties.getValue(propertyName);
if (p != null) {
proportion = Float.valueOf(p);
}
}
catch (NumberFormatException e) {
LOG.debug(e);
}
return proportion;
}
private ActionCallback processSelected(final Configurable configurable, final Configurable oldConfigurable) {
if (isShowing(configurable)) return new ActionCallback.Done();
final ActionCallback result = new ActionCallback();
if (configurable == null) {
myOwnDetails.setContent(null);
myFilter.updateSpotlight(true);
checkModified(oldConfigurable);
result.setDone();
} else {
getUiFor(configurable).doWhenDone(new EdtRunnable() {
@Override
public void runEdt() {
if (myDisposed) return;
final Configurable current = getContext().getCurrentConfigurable();
if (current != configurable) {
result.setRejected();
return;
}
myHistory.pushQueryPlace();
updateDetails();
myOwnDetails.setContent(myContentWrapper);
myOwnDetails.setBannerMinHeight(mySearchWrapper.getHeight());
myOwnDetails.setText(getBannerText(configurable));
if (myTreeView != null) {
myOwnDetails.forProject(myTreeView.findConfigurableProject(configurable));
}
else if (Registry.is("ide.new.settings.dialog")) {
myOwnDetails.forProject(myTree.getConfigurableProject(configurable));
}
final ConfigurableContent content = myConfigurable2Content.get(current);
content.setText(getBannerText(configurable));
content.setBannerActions(new Action[] {new ResetAction(configurable)});
content.updateBannerActions();
myLoadingDecorator.stopLoading();
myFilter.updateSpotlight(false);
checkModified(oldConfigurable);
checkModified(configurable);
FilteringTreeBuilder builder = myTreeView != null ? myTreeView.myBuilder : myTree.myBuilder;
if (builder.getSelectedElements().size() == 0) {
select(configurable).notify(result);
} else {
result.setDone();
}
}
});
}
return result;
}
private static void assertIsDispatchThread() {
ApplicationManager.getApplication().assertIsDispatchThread();
}
private ActionCallback getUiFor(final Configurable configurable) {
assertIsDispatchThread();
if (myDisposed) {
return new ActionCallback.Rejected();
}
final ActionCallback result = new ActionCallback();
if (!myConfigurable2Content.containsKey(configurable)) {
final ActionCallback readyCallback = myConfigurable2LoadCallback.get(configurable);
if (readyCallback != null) {
return readyCallback;
}
myConfigurable2LoadCallback.put(configurable, result);
myLoadingDecorator.startLoading(false);
final Application app = ApplicationManager.getApplication();
Runnable action = new Runnable() {
@Override
public void run() {
app.runReadAction(new Runnable() {
@Override
public void run() {
((ApplicationEx)app).runEdtSafeAction(new Runnable() {
@Override
public void run() {
if (myFilter.myProject.isDisposed()) {
result.setRejected();
}
else {
initConfigurable(configurable).notifyWhenDone(result);
}
}
});
}
});
}
};
if (app.isUnitTestMode()) {
action.run();
}
else {
app.executeOnPooledThread(action);
}
}
else {
result.setDone();
}
return result;
}
private ActionCallback initConfigurable(@NotNull final Configurable configurable) {
final ActionCallback result = new ActionCallback();
final ConfigurableContent content;
if (configurable instanceof MasterDetails) {
content = new Details((MasterDetails)configurable);
}
else {
content = new Simple(configurable);
}
if (!myConfigurable2Content.containsKey(configurable)) {
if (configurable instanceof Place.Navigator) {
((Place.Navigator)configurable).setHistory(myHistory);
}
configurable.reset();
}
UIUtil.invokeLaterIfNeeded(new Runnable() {
@Override
public void run() {
if (myDisposed) return;
myConfigurable2Content.put(configurable, content);
result.setDone();
}
});
return result;
}
private String[] getBannerText(Configurable configurable) {
if (myTreeView != null) {
return myTreeView.getPathNames(configurable);
}
final List<Configurable> list = myTree.getPathToRoot(configurable);
final String[] result = new String[list.size()];
int add = 0;
for (int i = list.size() - 1; i >=0; i--) {
result[add++] = list.get(i).getDisplayName().replace('\n', ' ');
}
return result;
}
private void checkModified(final Configurable configurable) {
fireModification(configurable);
}
private void fireModification(final Configurable actual) {
Collection<Configurable> toCheck = collectAllParentsAndSiblings(actual);
for (Configurable configurable : toCheck) {
fireModificationForItem(configurable);
}
}
private Collection<Configurable> collectAllParentsAndSiblings(final Configurable actual) {
ArrayList<Configurable> result = new ArrayList<Configurable>();
Configurable nearestParent = getContext().getParentConfigurable(actual);
if (nearestParent != null) {
Configurable parent = nearestParent;
while (parent != null) {
result.add(parent);
parent = getContext().getParentConfigurable(parent);
}
result.addAll(getContext().getChildren(nearestParent));
} else {
result.add(actual);
}
return result;
}
private void fireModificationForItem(final Configurable configurable) {
if (configurable != null) {
if (!myConfigurable2Content.containsKey(configurable) && isParentWithContent(configurable)) {
ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
@Override
public void run() {
ApplicationManager.getApplication().runReadAction(new Runnable() {
@Override
public void run() {
if (myDisposed) return;
initConfigurable(configurable).doWhenDone(new Runnable() {
@Override
public void run() {
if (myDisposed) return;
fireModificationInt(configurable);
}
});
}
});
}
});
}
else if (myConfigurable2Content.containsKey(configurable)) {
fireModificationInt(configurable);
}
}
}
private static boolean isParentWithContent(final Configurable configurable) {
return configurable instanceof SearchableConfigurable.Parent &&
((SearchableConfigurable.Parent)configurable).hasOwnContent();
}
private void fireModificationInt(final Configurable configurable) {
if (configurable.isModified()) {
getContext().fireModifiedAdded(configurable, null);
} else if (!configurable.isModified() && !getContext().getErrors().containsKey(configurable)) {
getContext().fireModifiedRemoved(configurable, null);
}
}
private void updateDetails() {
final Configurable current = getContext().getCurrentConfigurable();
assert current != null;
final ConfigurableContent content = myConfigurable2Content.get(current);
content.set(myContentWrapper);
}
private boolean isShowing(Configurable configurable) {
final ConfigurableContent content = myConfigurable2Content.get(configurable);
return content != null && content.isShowing();
}
@Nullable
public String getHelpTopic() {
Configurable current = getContext().getCurrentConfigurable();
while (current != null) {
String topic = current.getHelpTopic();
if (topic != null) return topic;
current = getContext().getParentConfigurable(current);
}
return null;
}
public boolean isFilterFieldVisible() {
return mySearch.getParent() == mySearchWrapper;
}
public void setFilterFieldVisible(final boolean visible, boolean requestFocus, boolean checkFocus) {
if (isFilterFieldVisible() && checkFocus && requestFocus && !isSearchFieldFocused()) {
UIUtil.requestFocus(mySearch);
return;
}
mySearchWrapper.setContent(visible ? mySearch : null);
myLeftSide.revalidate();
myLeftSide.repaint();
if (visible && requestFocus) {
UIUtil.requestFocus(mySearch);
}
}
public boolean isSearchFieldFocused() {
return mySearch.getTextEditor().isFocusOwner();
}
private class ResetAction extends AbstractAction {
Configurable myConfigurable;
ResetAction(final Configurable configurable) {
myConfigurable = configurable;
putValue(NAME, "Reset");
putValue(SHORT_DESCRIPTION, "Rollback changes for this configuration element");
}
@Override
public void actionPerformed(final ActionEvent e) {
reset(myConfigurable, true);
checkModified(myConfigurable);
}
@Override
public boolean isEnabled() {
return myFilter.myContext.isModified(myConfigurable) || getContext().getErrors().containsKey(myConfigurable);
}
}
private static class ContentWrapper extends NonOpaquePanel {
private final JLabel myErrorLabel;
private JComponent mySimpleContent;
private ConfigurationException myException;
private JComponent myMaster;
private JComponent myToolbar;
private DetailsComponent myDetails;
private final Splitter mySplitter = new Splitter(false);
private JPanel myLeft = new JPanel(new BorderLayout());
public float myLastSplitterProportion;
private ContentWrapper() {
setLayout(new BorderLayout());
myErrorLabel = new JLabel();
myErrorLabel.setOpaque(true);
myErrorLabel.setBackground(LightColors.RED);
myLeft = new JPanel(new BorderLayout());
mySplitter.addPropertyChangeListener(Splitter.PROP_PROPORTION, new PropertyChangeListener() {
@Override
public void propertyChange(final PropertyChangeEvent evt) {
myLastSplitterProportion = ((Float)evt.getNewValue()).floatValue();
}
});
}
void setContent(JComponent c, ConfigurationException e, boolean scrollable) {
if (c != null && mySimpleContent == c && myException == e) return;
removeAll();
if (c != null) {
if (scrollable) {
JScrollPane scroll = ScrollPaneFactory.createScrollPane(c);
scroll.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
scroll.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
scroll.getVerticalScrollBar().setUnitIncrement(10);
scroll.setBorder(null);
add(scroll, BorderLayout.CENTER);
}
else {
add(c, BorderLayout.CENTER);
}
}
if (e != null) {
myErrorLabel.setText(UIUtil.toHtml(e.getMessage()));
add(myErrorLabel, BorderLayout.NORTH);
}
mySimpleContent = c;
myException = e;
myMaster = null;
myToolbar = null;
myDetails = null;
mySplitter.setFirstComponent(null);
mySplitter.setSecondComponent(null);
}
void setContent(JComponent master, JComponent toolbar, DetailsComponent details, ConfigurationException e) {
if (myMaster == master && myToolbar == toolbar && myDetails == details && myException == e) return;
myMaster = master;
myToolbar = toolbar;
myDetails = details;
myException = e;
removeAll();
myLeft.removeAll();
myLeft.add(myToolbar, BorderLayout.NORTH);
myLeft.add(myMaster, BorderLayout.CENTER);
myDetails.setBannerMinHeight(myToolbar.getPreferredSize().height);
mySplitter.setFirstComponent(myLeft);
mySplitter.setSecondComponent(myDetails.getComponent());
mySplitter.setProportion(myLastSplitterProportion);
add(mySplitter, BorderLayout.CENTER);
mySimpleContent = null;
}
@Override
public boolean isNull() {
final boolean superNull = super.isNull();
if (superNull) return superNull;
if (myMaster == null) {
return NullableComponent.Check.isNull(mySimpleContent);
} else {
return NullableComponent.Check.isNull(myMaster);
}
}
}
public void reset(Configurable configurable, boolean notify) {
configurable.reset();
if (notify) {
getContext().fireReset(configurable);
}
}
public void apply() {
Map<Configurable, ConfigurationException> errors = new LinkedHashMap<Configurable, ConfigurationException>();
final Set<Configurable> modified = getContext().getModified();
for (Configurable each : modified) {
try {
each.apply();
UsageTrigger.trigger("ide.settings." + ConvertUsagesUtil.escapeDescriptorName(each.getDisplayName()));
if (!each.isModified()) {
getContext().fireModifiedRemoved(each, null);
}
}
catch (ConfigurationException e) {
errors.put(each, e);
LOG.debug(e);
}
}
getContext().fireErrorsChanged(errors, null);
if (!errors.isEmpty()) {
selectInTree(errors.keySet().iterator().next());
}
}
@Override
public Object getData(@NonNls final String dataId) {
if (KEY.is(dataId)) {
return this;
}
return History.KEY.is(dataId) ? myHistory : null;
}
public JComponent getPreferredFocusedComponent() {
return myTreeView != null ? myTreeView.myTree : mySearch;//myTree.getTree();
}
@Override
public Dimension getPreferredSize() {
return new Dimension(1200, 768);
}
@Override
public ActionCallback navigateTo(@Nullable final Place place, final boolean requestFocus) {
final Configurable config = (Configurable)place.getPath("configurable");
final String filter = (String)place.getPath("filter");
final ActionCallback result = new ActionCallback();
myFilter.update(filter, false, true).doWhenDone(new Runnable() {
@Override
public void run() {
selectInTree(config).notifyWhenDone(result);
}
});
return result;
}
@Override
public void queryPlace(@NotNull final Place place) {
final Configurable current = getContext().getCurrentConfigurable();
place.putPath("configurable", current);
place.putPath("filter", myFilter.getFilterText());
if (current instanceof Place.Navigator) {
((Place.Navigator)current).queryPlace(place);
}
}
@Override
public void dispose() {
assertIsDispatchThread();
if (myDisposed) {
return;
}
myDisposed = true;
myProperties.setValue(MAIN_SPLITTER_PROPORTION, String.valueOf(myMainSplitter.getProportion()));
myProperties.setValue(DETAILS_SPLITTER_PROPORTION, String.valueOf(myContentWrapper.myLastSplitterProportion));
Toolkit.getDefaultToolkit().removeAWTEventListener(this);
final Set<Configurable> configurables = new HashSet<Configurable>();
configurables.addAll(myConfigurable2Content.keySet());
configurables.addAll(myConfigurable2LoadCallback.keySet());
for (final Configurable each : configurables) {
ActionCallback loadCb = myConfigurable2LoadCallback.get(each);
if (loadCb != null) {
loadCb.doWhenProcessed(new Runnable() {
@Override
public void run() {
assertIsDispatchThread();
each.disposeUIResources();
}
});
} else {
each.disposeUIResources();
}
}
Disposer.clearOwnFields(this);
}
public OptionsEditorContext getContext() {
return myFilter.myContext;
}
private class MyColleague extends OptionsEditorColleague.Adapter {
@Override
public ActionCallback onSelected(final Configurable configurable, final Configurable oldConfigurable) {
return processSelected(configurable, oldConfigurable);
}
@Override
public ActionCallback onModifiedRemoved(final Configurable configurable) {
return updateIfCurrent(configurable);
}
@Override
public ActionCallback onModifiedAdded(final Configurable configurable) {
return updateIfCurrent(configurable);
}
@Override
public ActionCallback onErrorsChanged() {
return updateIfCurrent(getContext().getCurrentConfigurable());
}
private ActionCallback updateIfCurrent(final Configurable configurable) {
if (getContext().getCurrentConfigurable() == configurable && configurable != null) {
updateDetails();
final ConfigurableContent content = myConfigurable2Content.get(configurable);
content.updateBannerActions();
return new ActionCallback.Done();
} else {
return new ActionCallback.Rejected();
}
}
}
public void flushModifications() {
fireModification(getContext().getCurrentConfigurable());
}
public boolean canApply() {
return !getContext().getModified().isEmpty();
}
@Override
public void eventDispatched(final AWTEvent event) {
if (event.getID() == MouseEvent.MOUSE_PRESSED || event.getID() == MouseEvent.MOUSE_RELEASED || event.getID() == MouseEvent.MOUSE_DRAGGED) {
final MouseEvent me = (MouseEvent)event;
if (SwingUtilities.isDescendingFrom(me.getComponent(), SwingUtilities.getWindowAncestor(myContentWrapper)) || isPopupOverEditor(me.getComponent())) {
queueModificationCheck();
myFilter.setHoldingFilter(false);
}
}
else if (event.getID() == KeyEvent.KEY_PRESSED || event.getID() == KeyEvent.KEY_RELEASED) {
final KeyEvent ke = (KeyEvent)event;
if (SwingUtilities.isDescendingFrom(ke.getComponent(), myContentWrapper)) {
queueModificationCheck();
}
}
}
private void queueModificationCheck() {
final Configurable configurable = getContext().getCurrentConfigurable();
myModificationChecker.queue(new Update(this) {
@Override
public void run() {
checkModified(configurable);
}
@Override
public boolean isExpired() {
return getContext().getCurrentConfigurable() != configurable;
}
});
}
private boolean isPopupOverEditor(Component c) {
final Window wnd = SwingUtilities.getWindowAncestor(c);
return (wnd instanceof JWindow || wnd instanceof JDialog && ((JDialog)wnd).getModalityType() == Dialog.ModalityType.MODELESS) && myWindow != null && wnd.getParent() == myWindow;
}
private static class MySearchField extends SearchTextField {
private boolean myDelegatingNow;
private MySearchField() {
super(false);
addKeyListener(new KeyAdapter() {});
if (Registry.is("ide.new.settings.dialog")) {
final JTextField editor = getTextEditor();
if (!SystemInfo.isMac) {
editor.putClientProperty("JTextField.variant", "search");
if (!(editor.getUI() instanceof DarculaTextFieldUI)) {
editor.setUI((DarculaTextFieldUI)DarculaTextFieldUI.createUI(editor));
editor.setBorder(new DarculaTextBorder());
}
}
setBackground(UIUtil.getSidePanelColor());
setBorder(new EmptyBorder(5, 10, 2, 10));
}
}
@Override
protected boolean isSearchControlUISupported() {
return true;
}
@Override
protected boolean preprocessEventForTextField(final KeyEvent e) {
final KeyStroke stroke = KeyStroke.getKeyStrokeForEvent(e);
if (!myDelegatingNow) {
if ("pressed ESCAPE".equals(stroke.toString()) && getText().length() > 0) {
setText(""); // reset filter on ESC
return true;
}
if (getTextEditor().isFocusOwner()) {
try {
myDelegatingNow = true;
boolean treeNavigation =
stroke.getModifiers() == 0 && (stroke.getKeyCode() == KeyEvent.VK_UP || stroke.getKeyCode() == KeyEvent.VK_DOWN);
if ("pressed ENTER".equals(stroke.toString())) {
return true; // avoid closing dialog on ENTER
}
final Object action = getTextEditor().getInputMap().get(stroke);
if (action == null || treeNavigation) {
onTextKeyEvent(e);
return true;
}
}
finally {
myDelegatingNow = false;
}
}
}
return false;
}
protected void onTextKeyEvent(final KeyEvent e) {
}
}
private class SpotlightPainter extends AbstractPainter {
Map<Configurable, String> myConfigurableToLastOption = new HashMap<Configurable, String>();
GlassPanel myGP = new GlassPanel(myOwnDetails.getContentGutter());
boolean myVisible;
@Override
public void executePaint(final Component component, final Graphics2D g) {
if (myVisible && myGP.isVisible()) {
myGP.paintSpotlight(g, myOwnDetails.getContentGutter());
}
}
public boolean updateForCurrentConfigurable() {
final Configurable current = getContext().getCurrentConfigurable();
if (current != null && !myConfigurable2Content.containsKey(current)) {
return ApplicationManager.getApplication().isUnitTestMode();
}
String text = myFilter.getFilterText();
try {
final boolean sameText =
myConfigurableToLastOption.containsKey(current) && text.equals(myConfigurableToLastOption.get(current));
if (current == null) {
myVisible = false;
myGP.clear();
return true;
}
SearchableConfigurable searchable;
if (current instanceof SearchableConfigurable) {
searchable = (SearchableConfigurable)current;
} else {
searchable = new SearachableWrappper(current);
}
myGP.clear();
final Runnable runnable = SearchUtil.lightOptions(searchable, myContentWrapper, text, myGP);
if (runnable != null) {
myVisible = true;//myContext.isHoldingFilter();
runnable.run();
boolean pushFilteringFurther = !sameText && !myFilter.contains(current);
final Runnable ownSearch = searchable.enableSearch(text);
if (pushFilteringFurther && ownSearch != null) {
ownSearch.run();
}
fireNeedsRepaint(myOwnDetails.getComponent());
} else {
myVisible = false;
}
}
finally {
myConfigurableToLastOption.put(current, text);
}
return true;
}
@Override
public boolean needsRepaint() {
return true;
}
}
private static class SearachableWrappper implements SearchableConfigurable {
private final Configurable myConfigurable;
private SearachableWrappper(final Configurable configurable) {
myConfigurable = configurable;
}
@Override
@NotNull
public String getId() {
return myConfigurable.getClass().getName();
}
@Override
public Runnable enableSearch(final String option) {
return null;
}
@Override
@Nls
public String getDisplayName() {
return myConfigurable.getDisplayName();
}
@Override
public String getHelpTopic() {
return myConfigurable.getHelpTopic();
}
@Override
public JComponent createComponent() {
return myConfigurable.createComponent();
}
@Override
public boolean isModified() {
return myConfigurable.isModified();
}
@Override
public void apply() throws ConfigurationException {
myConfigurable.apply();
}
@Override
public void reset() {
myConfigurable.reset();
}
@Override
public void disposeUIResources() {
myConfigurable.disposeUIResources();
}
}
private abstract static class ConfigurableContent {
abstract void set(ContentWrapper wrapper);
abstract boolean isShowing();
abstract void setBannerActions(Action[] actions);
abstract void updateBannerActions();
abstract void setText(final String[] bannerText);
}
/**
* Returns default view for the specified configurable.
* It uses the configurable identifier to retrieve description.
*
* @param searchable the configurable that does not have any view
* @return default view for the specified configurable
*/
private JComponent createDefaultComponent(SearchableConfigurable searchable) {
JPanel panel = new JPanel(new BorderLayout(0, 9));
try {
panel.add(BorderLayout.NORTH, new JLabel(getDefaultDescription(searchable)));
}
catch (AssertionError error) {
return null; // description is not set
}
if (searchable instanceof Configurable.Composite) {
JPanel box = new JPanel();
box.setLayout(new BoxLayout(box, BoxLayout.Y_AXIS));
panel.add(BorderLayout.CENTER, box);
Configurable.Composite composite = (Configurable.Composite)searchable;
for (final Configurable configurable : composite.getConfigurables()) {
LinkLabel label = new LinkLabel(configurable.getDisplayName(), null) {
@Override
public void doClick() {
select(configurable, null);
}
};
label.setBorder(BorderFactory.createEmptyBorder(1, 17, 1, 1));
box.add(label);
}
}
return panel;
}
@NotNull
private static String getDefaultDescription(SearchableConfigurable configurable) {
String key = configurable.getId() + ".settings.description";
if (configurable instanceof ConfigurableWrapper) {
ConfigurableWrapper wrapper = (ConfigurableWrapper) configurable;
ConfigurableEP ep = wrapper.getExtensionPoint();
ResourceBundle resourceBundle = AbstractBundle.getResourceBundle(ep.bundle, ep.getPluginDescriptor().getPluginClassLoader());
return CommonBundle.message(resourceBundle, key);
}
return OptionsBundle.message(key);
}
private class Simple extends ConfigurableContent {
JComponent myComponent;
Configurable myConfigurable;
Simple(final Configurable configurable) {
myConfigurable = configurable;
myComponent = configurable.createComponent();
if (myComponent == null && configurable instanceof SearchableConfigurable) {
myComponent = createDefaultComponent((SearchableConfigurable)configurable);
}
if (myComponent != null) {
final Object clientProperty = myComponent.getClientProperty(NOT_A_NEW_COMPONENT);
if (clientProperty != null && ApplicationManager.getApplication().isInternal()) {
LOG.warn("Settings component for " + configurable.getClass()+ " must be created anew, not reused, in createComponent() and destroyed in disposeUIResources()");
}
else {
myComponent.putClientProperty(NOT_A_NEW_COMPONENT, Boolean.TRUE);
}
}
}
@Override
void set(final ContentWrapper wrapper) {
myOwnDetails.setDetailsModeEnabled(true);
wrapper.setContent(myComponent, getContext().getErrors().get(myConfigurable), !ConfigurableWrapper.isNoScroll(myConfigurable));
}
@Override
boolean isShowing() {
return myComponent != null && myComponent.isShowing();
}
@Override
void setBannerActions(final Action[] actions) {
myOwnDetails.setBannerActions(actions);
}
@Override
void updateBannerActions() {
myOwnDetails.updateBannerActions();
}
@Override
void setText(final String[] bannerText) {
myOwnDetails.setText(bannerText);
}
}
private class Details extends ConfigurableContent {
MasterDetails myConfigurable;
DetailsComponent myDetails;
JComponent myMaster;
JComponent myToolbar;
Details(final MasterDetails configurable) {
myConfigurable = configurable;
myConfigurable.initUi();
myDetails = myConfigurable.getDetails();
myMaster = myConfigurable.getMaster();
myToolbar = myConfigurable.getToolbar();
}
@Override
void set(final ContentWrapper wrapper) {
myOwnDetails.setDetailsModeEnabled(false);
myDetails.setPrefix(getBannerText((Configurable)myConfigurable));
wrapper.setContent(myMaster, myToolbar, myDetails, getContext().getErrors().get(myConfigurable));
}
@Override
void setBannerActions(final Action[] actions) {
myDetails.setBannerActions(actions);
}
@Override
boolean isShowing() {
return myDetails.getComponent().isShowing();
}
@Override
void updateBannerActions() {
myDetails.updateBannerActions();
}
@Override
void setText(final String[] bannerText) {
myDetails.update();
}
}
public void clearFilter() {
mySearch.setText("");
}
@Override
public void setHistory(final History history) {
}
}