IJPL-159035 refactor SettingsFilter

GitOrigin-RevId: 9a0bbd9b4a2fe83daac024b968220ef863161cb8
This commit is contained in:
Vladimir Krivosheev
2024-07-29 14:46:25 +02:00
committed by intellij-monorepo-bot
parent b86c156997
commit 6d64659fc3
7 changed files with 304 additions and 336 deletions

View File

@@ -16311,8 +16311,12 @@ f:com.intellij.openapi.options.newEditor.SettingsDialogFactory$Companion
- f:getInstance():com.intellij.openapi.options.newEditor.SettingsDialogFactory
a:com.intellij.openapi.options.newEditor.SettingsFilter
- com.intellij.ui.speedSearch.ElementFilter$Active$Impl
- isEmptyFilter():Z
- f:contains(com.intellij.openapi.options.Configurable):Z
- f:isEmptyFilter():Z
- f:reload():V
- f:setFilterText(java.lang.String):V
- shouldBeShowing(com.intellij.ui.treeStructure.SimpleNode):Z
- f:update(java.lang.String):V
c:com.intellij.openapi.options.newEditor.SettingsTreeView
- javax.swing.JComponent
- com.intellij.openapi.Disposable

View File

@@ -12,7 +12,7 @@ import java.awt.*;
@ApiStatus.Internal
public abstract class AbstractEditor extends JPanel implements Disposable {
volatile boolean myDisposed;
volatile boolean isDisposed;
AbstractEditor(@NotNull Disposable parent) {
super(new BorderLayout());
@@ -22,8 +22,8 @@ public abstract class AbstractEditor extends JPanel implements Disposable {
@Override
public final void dispose() {
if (!myDisposed) {
myDisposed = true;
if (!isDisposed) {
isDisposed = true;
disposeOnce();
}
}

View File

@@ -21,10 +21,7 @@ import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.ActionCallback;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.text.HtmlChunk;
import com.intellij.ui.ComponentUtil;
import com.intellij.ui.LightColors;
import com.intellij.ui.RelativeFont;
import com.intellij.ui.UIBundle;
import com.intellij.ui.*;
import com.intellij.util.ObjectUtils;
import com.intellij.util.messages.MessageBusConnection;
import com.intellij.util.ui.JBUI;
@@ -45,10 +42,6 @@ import java.awt.event.MouseEvent;
import java.util.Arrays;
import static com.intellij.openapi.options.newEditor.ConfigurablesListPanelKt.createConfigurablesListPanel;
import static com.intellij.ui.ScrollPaneFactory.createScrollPane;
import static java.awt.Toolkit.getDefaultToolkit;
import static javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER;
import static javax.swing.SwingUtilities.isDescendingFrom;
class ConfigurableEditor extends AbstractEditor implements AnActionListener, AWTEventListener {
private final MergingUpdateQueue myQueue = new MergingUpdateQueue("SettingsModification", 1000, false, this, this, this);
@@ -103,7 +96,7 @@ class ConfigurableEditor extends AbstractEditor implements AnActionListener, AWT
MessageBusConnection messageBus = ApplicationManager.getApplication().getMessageBus().connect(this);
messageBus.subscribe(AnActionListener.TOPIC, this);
messageBus.subscribe(ExternalUpdateRequest.TOPIC, conf -> updateCurrent(conf, false));
getDefaultToolkit().addAWTEventListener(this, AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK | AWTEvent.KEY_EVENT_MASK);
Toolkit.getDefaultToolkit().addAWTEventListener(this, AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK | AWTEvent.KEY_EVENT_MASK);
if (configurable != null) {
myConfigurable = configurable;
myCardPanel.select(configurable, true).doWhenDone(() -> postUpdateCurrent(configurable));
@@ -113,7 +106,7 @@ class ConfigurableEditor extends AbstractEditor implements AnActionListener, AWT
@Override
void disposeOnce() {
getDefaultToolkit().removeAWTEventListener(this);
Toolkit.getDefaultToolkit().removeAWTEventListener(this);
myCardPanel.removeAll();
}
@@ -174,13 +167,13 @@ class ConfigurableEditor extends AbstractEditor implements AnActionListener, AWT
switch (event.getID()) {
case MouseEvent.MOUSE_PRESSED, MouseEvent.MOUSE_RELEASED, MouseEvent.MOUSE_DRAGGED -> {
MouseEvent me = (MouseEvent)event;
if (isDescendingFrom(me.getComponent(), this) || isPopupOverEditor(me.getComponent())) {
if (SwingUtilities.isDescendingFrom(me.getComponent(), this) || isPopupOverEditor(me.getComponent())) {
requestUpdate();
}
}
case KeyEvent.KEY_PRESSED, KeyEvent.KEY_RELEASED -> {
KeyEvent ke = (KeyEvent)event;
if (isDescendingFrom(ke.getComponent(), this)) {
if (SwingUtilities.isDescendingFrom(ke.getComponent(), this)) {
requestUpdate();
}
}
@@ -240,7 +233,7 @@ class ConfigurableEditor extends AbstractEditor implements AnActionListener, AWT
}
final @NotNull Promise<? super Object> select(final Configurable configurable) {
assert !myDisposed : "Already disposed";
assert !isDisposed : "Already disposed";
long startTime = System.currentTimeMillis();
final boolean loadedFromCache = myCardPanel.getValue(configurable, false) != null;
ActionCallback callback = myCardPanel.select(configurable, false);
@@ -303,8 +296,8 @@ class ConfigurableEditor extends AbstractEditor implements AnActionListener, AWT
content.add(BorderLayout.CENTER, createConfigurablesListPanel(description, Arrays.asList(compositeGroup.getConfigurables()), this));
}
JScrollPane pane = createScrollPane(content, true);
pane.setHorizontalScrollBarPolicy(HORIZONTAL_SCROLLBAR_NEVER);
JScrollPane pane = ScrollPaneFactory.createScrollPane(content, true);
pane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
return pane;
}

View File

@@ -40,36 +40,36 @@ import static com.intellij.openapi.actionSystem.IdeActions.ACTION_FIND;
public class SettingsDialog extends DialogWrapper implements UiCompatibleDataProvider {
public static final String DIMENSION_KEY = "SettingsEditor";
private final String myDimensionServiceKey;
private final AbstractEditor myEditor;
private final boolean myApplyButtonNeeded;
private final boolean myResetButtonNeeded;
private final String dimensionServiceKey;
private final AbstractEditor editor;
private final boolean isApplyButtonNeeded;
private final boolean isResetButtonNeeded;
private final JLabel myHintLabel = new JLabel();
public SettingsDialog(Project project, String key, @NotNull Configurable configurable, boolean showApplyButton, boolean showResetButton) {
super(project, true);
myDimensionServiceKey = key;
myEditor = new SingleSettingEditor(myDisposable, configurable);
myApplyButtonNeeded = showApplyButton;
myResetButtonNeeded = showResetButton;
dimensionServiceKey = key;
editor = new SingleSettingEditor(myDisposable, configurable);
isApplyButtonNeeded = showApplyButton;
isResetButtonNeeded = showResetButton;
init(configurable, project);
}
public SettingsDialog(@NotNull Component parent, String key, @NotNull Configurable configurable, boolean showApplyButton, boolean showResetButton) {
super(parent, true);
myDimensionServiceKey = key;
myEditor = new SingleSettingEditor(myDisposable, configurable);
myApplyButtonNeeded = showApplyButton;
myResetButtonNeeded = showResetButton;
dimensionServiceKey = key;
editor = new SingleSettingEditor(myDisposable, configurable);
isApplyButtonNeeded = showApplyButton;
isResetButtonNeeded = showResetButton;
init(configurable, null);
}
public SettingsDialog(@NotNull Project project, @NotNull List<? extends ConfigurableGroup> groups, @Nullable Configurable configurable, @Nullable String filter) {
super(project, true);
myDimensionServiceKey = DIMENSION_KEY;
myEditor = new SettingsEditor(myDisposable, project, groups, configurable, filter, this::treeViewFactory, this::spotlightPainterFactory);
myApplyButtonNeeded = true;
myResetButtonNeeded = false;
dimensionServiceKey = DIMENSION_KEY;
editor = new SettingsEditor(myDisposable, project, groups, configurable, filter, this::treeViewFactory, this::spotlightPainterFactory);
isApplyButtonNeeded = true;
isResetButtonNeeded = false;
init(null, project);
}
@@ -79,15 +79,15 @@ public class SettingsDialog extends DialogWrapper implements UiCompatibleDataPro
@Nullable Configurable configurable,
@Nullable String filter) {
super(project, parentComponent, true, IdeModalityType.IDE);
myDimensionServiceKey = DIMENSION_KEY;
myEditor = new SettingsEditor(myDisposable, project, groups, configurable, filter, this::treeViewFactory, this::spotlightPainterFactory);
myApplyButtonNeeded = true;
myResetButtonNeeded = false;
dimensionServiceKey = DIMENSION_KEY;
editor = new SettingsEditor(myDisposable, project, groups, configurable, filter, this::treeViewFactory, this::spotlightPainterFactory);
isApplyButtonNeeded = true;
isResetButtonNeeded = false;
init(null, project);
}
protected final AbstractEditor getEditor() {
return myEditor;
return editor;
}
protected @NotNull SettingsTreeView treeViewFactory(@NotNull SettingsFilter filter, @NotNull List<? extends ConfigurableGroup> groups) {
@@ -132,17 +132,17 @@ public class SettingsDialog extends DialogWrapper implements UiCompatibleDataPro
@Override
public void uiDataSnapshot(@NotNull DataSink sink) {
DataSink.uiDataSnapshot(sink, myEditor);
DataSink.uiDataSnapshot(sink, editor);
}
@Override
protected String getDimensionServiceKey() {
return myDimensionServiceKey;
return dimensionServiceKey;
}
@Override
public JComponent getPreferredFocusedComponent() {
return myEditor.getPreferredFocusedComponent();
return editor.getPreferredFocusedComponent();
}
@Override
@@ -152,7 +152,7 @@ public class SettingsDialog extends DialogWrapper implements UiCompatibleDataPro
@Override
protected JComponent createCenterPanel() {
return myEditor;
return editor;
}
@Override
@@ -166,8 +166,8 @@ public class SettingsDialog extends DialogWrapper implements UiCompatibleDataPro
@SuppressWarnings("unused") // used in Rider
protected void tryAddOptionsListener(OptionsEditorColleague colleague) {
if (myEditor instanceof SettingsEditor) {
((SettingsEditor)myEditor).addOptionsListener(colleague);
if (editor instanceof SettingsEditor) {
((SettingsEditor)editor).addOptionsListener(colleague);
}
}
@@ -176,12 +176,12 @@ public class SettingsDialog extends DialogWrapper implements UiCompatibleDataPro
ArrayList<Action> actions = new ArrayList<>();
actions.add(getOKAction());
actions.add(getCancelAction());
Action apply = myEditor.getApplyAction();
if (apply != null && myApplyButtonNeeded) {
Action apply = editor.getApplyAction();
if (apply != null && isApplyButtonNeeded) {
actions.add(new ApplyActionWrapper(apply));
}
Action reset = myEditor.getResetAction();
if (reset != null && myResetButtonNeeded) {
Action reset = editor.getResetAction();
if (reset != null && isResetButtonNeeded) {
actions.add(reset);
}
if (getHelpId() != null) {
@@ -192,7 +192,7 @@ public class SettingsDialog extends DialogWrapper implements UiCompatibleDataPro
@Override
protected @Nullable String getHelpId() {
return myEditor.getHelpTopic();
return editor.getHelpTopic();
}
@Override
@@ -205,7 +205,7 @@ public class SettingsDialog extends DialogWrapper implements UiCompatibleDataPro
if (window != null) {
UIUtil.stopFocusedEditing(window);
}
if (myEditor.apply()) {
if (editor.apply()) {
if (scheduleSave) {
SaveAndSyncHandler.getInstance().scheduleSave(new SaveAndSyncHandler.SaveTask(null, /* forceSavingAllSettings = */ true));
}
@@ -216,7 +216,7 @@ public class SettingsDialog extends DialogWrapper implements UiCompatibleDataPro
@Override
public void doCancelAction(AWTEvent source) {
if (source instanceof KeyEvent || source instanceof ActionEvent) {
if (!myEditor.cancel(source)) {
if (!editor.cancel(source)) {
return;
}
}
@@ -257,7 +257,7 @@ public class SettingsDialog extends DialogWrapper implements UiCompatibleDataPro
@Override
public void actionPerformed(ActionEvent e) {
delegate.actionPerformed(e);
ApplicationManager.getApplication().getMessageBus().syncPublisher(SettingsDialogListener.getTOPIC()).afterApply(myEditor);
ApplicationManager.getApplication().getMessageBus().syncPublisher(SettingsDialogListener.getTOPIC()).afterApply(editor);
}
@Override

View File

@@ -60,9 +60,9 @@ public final class SettingsEditor extends AbstractEditor implements UiDataProvid
private final PropertiesComponent myProperties;
private final Settings mySettings;
private final SettingsSearch mySearch;
private final SettingsFilter myFilter;
private final SettingsTreeView myTreeView;
private final ConfigurableEditor myEditor;
private final SettingsFilter filter;
private final SettingsTreeView treeView;
private final ConfigurableEditor editor;
private final OnePixelSplitter mySplitter;
private final SpotlightPainter mySpotlightPainter;
private final LoadingDecorator myLoadingDecorator;
@@ -85,20 +85,20 @@ public final class SettingsEditor extends AbstractEditor implements UiDataProvid
mySettings = new Settings(groups) {
@Override
protected @NotNull Promise<? super Object> selectImpl(Configurable configurable) {
myFilter.update(null);
return myTreeView.select(configurable);
SettingsEditor.this.filter.update(null);
return treeView.select(configurable);
}
@Override
protected @Nullable Configurable getConfigurableWithInitializedUiComponentImpl(@Nullable Configurable configurable,
boolean initializeUiComponentIfNotYet) {
JComponent content = myEditor.getContent(configurable);
JComponent content = editor.getContent(configurable);
if (!initializeUiComponentIfNotYet || content != null) {
return content == null ? null : configurable;
}
myEditor.readContent(configurable); // calls Configurable.createComponent() and Configurable.reset()
// calls Configurable.createComponent() and Configurable.reset()
editor.readContent(configurable);
return configurable;
}
@@ -109,23 +109,24 @@ public final class SettingsEditor extends AbstractEditor implements UiDataProvid
@Override
protected void setSearchText(String search) {
myFilter.update(search);
SettingsEditor.this.filter.update(search);
}
@Override
public void revalidate() {
myEditor.requestUpdate();
editor.requestUpdate();
}
};
mySearch = new SettingsSearch() {
@Override
void onTextKeyEvent(KeyEvent event) {
myTreeView.getTree().processKeyEvent(event);
treeView.getTree().processKeyEvent(event);
}
};
JPanel searchPanel = new JPanel(new VerticalLayout(0));
searchPanel.add(VerticalLayout.CENTER, mySearch);
myFilter = new SettingsFilter(project, groups, mySearch) {
this.filter = new SettingsFilter(project, groups, mySearch) {
@Override
protected Configurable getConfigurable(SimpleNode node) {
return SettingsTreeView.getConfigurable(node);
@@ -133,12 +134,12 @@ public final class SettingsEditor extends AbstractEditor implements UiDataProvid
@Override
protected SimpleNode findNode(Configurable configurable) {
return myTreeView.findNode(configurable);
return treeView.findNode(configurable);
}
@Override
protected void updateSpotlight(boolean now) {
if (!myDisposed && mySpotlightPainter != null) {
if (!isDisposed && mySpotlightPainter != null) {
if (!now) {
mySpotlightPainter.updateLater();
}
@@ -148,7 +149,7 @@ public final class SettingsEditor extends AbstractEditor implements UiDataProvid
}
}
};
myFilter.myContext.addColleague(new OptionsEditorColleague() {
this.filter.context.addColleague(new OptionsEditorColleague() {
@Override
public @NotNull Promise<? super Object> onSelected(@Nullable Configurable configurable, Configurable oldConfigurable) {
if (configurable != null) {
@@ -157,7 +158,7 @@ public final class SettingsEditor extends AbstractEditor implements UiDataProvid
myLoadingDecorator.startLoading(false);
}
checkModified(oldConfigurable);
Promise<? super Object> result = myEditor.select(configurable);
Promise<? super Object> result = editor.select(configurable);
result.onSuccess(it -> {
updateController(configurable);
//requestFocusToEditor(); // TODO
@@ -178,11 +179,11 @@ public final class SettingsEditor extends AbstractEditor implements UiDataProvid
@Override
public @NotNull Promise<? super Object> onErrorsChanged() {
return updateIfCurrent(myFilter.myContext.getCurrentConfigurable());
return updateIfCurrent(SettingsEditor.this.filter.context.getCurrentConfigurable());
}
private @NotNull Promise<? super Object> updateIfCurrent(@Nullable Configurable configurable) {
if (configurable != null && configurable == myFilter.myContext.getCurrentConfigurable()) {
if (configurable != null && configurable == SettingsEditor.this.filter.context.getCurrentConfigurable()) {
updateStatus(configurable);
return Promises.resolvedPromise();
}
@@ -191,27 +192,27 @@ public final class SettingsEditor extends AbstractEditor implements UiDataProvid
}
}
});
myTreeView = factory.createTreeView(myFilter, groups);
myTreeView.getTree().addKeyListener(mySearch);
myEditor = new ConfigurableEditor(this, null) {
treeView = factory.createTreeView(this.filter, groups);
treeView.getTree().addKeyListener(mySearch);
editor = new ConfigurableEditor(this, null) {
@Override
boolean apply() {
checkModified(myFilter.myContext.getCurrentConfigurable());
if (myFilter.myContext.getModified().isEmpty()) {
checkModified(SettingsEditor.this.filter.context.getCurrentConfigurable());
if (SettingsEditor.this.filter.context.getModified().isEmpty()) {
return true;
}
Map<Configurable, ConfigurationException> map = new LinkedHashMap<>();
for (Configurable configurable : myFilter.myContext.getModified()) {
for (Configurable configurable : SettingsEditor.this.filter.context.getModified()) {
ConfigurationException exception = ConfigurableEditor.apply(configurable);
if (exception != null) {
map.put(configurable, exception);
}
else if (!configurable.isModified()) {
myFilter.myContext.fireModifiedRemoved(configurable, null);
SettingsEditor.this.filter.context.fireModifiedRemoved(configurable, null);
}
}
mySearch.updateToolTipText();
myFilter.myContext.fireErrorsChanged(map, null);
SettingsEditor.this.filter.context.fireErrorsChanged(map, null);
if (!map.isEmpty()) {
Configurable targetConfigurable = map.keySet().iterator().next();
ConfigurationException exception = map.get(targetConfigurable);
@@ -219,17 +220,17 @@ public final class SettingsEditor extends AbstractEditor implements UiDataProvid
if (originator != null) {
targetConfigurable = originator;
}
myTreeView.select(targetConfigurable);
treeView.select(targetConfigurable);
return false;
}
updateStatus(myFilter.myContext.getCurrentConfigurable());
updateStatus(SettingsEditor.this.filter.context.getCurrentConfigurable());
return true;
}
@Override
void updateCurrent(Configurable configurable, boolean reset) {
if (reset && configurable != null) {
myFilter.myContext.fireReset(configurable);
SettingsEditor.this.filter.context.fireReset(configurable);
}
checkModified(configurable);
}
@@ -239,17 +240,17 @@ public final class SettingsEditor extends AbstractEditor implements UiDataProvid
mySettings.select(configurable);
}
};
myEditor.setPreferredSize(JBUI.size(800, 600));
myLoadingDecorator = new LoadingDecorator(myEditor, this, 10, true);
editor.setPreferredSize(JBUI.size(800, 600));
myLoadingDecorator = new LoadingDecorator(editor, this, 10, true);
myLoadingDecorator.setOverlayBackground(LoadingDecorator.OVERLAY_BACKGROUND);
myBanner = new Banner(myEditor.getResetAction());
myBanner = new Banner(editor.getResetAction());
searchPanel.setBorder(JBUI.Borders.empty(7, 5, 6, 5));
myBanner.setBorder(JBUI.Borders.empty(11, 6, 0, 10));
mySearch.setBackground(UIUtil.SIDE_PANEL_BACKGROUND);
searchPanel.setBackground(UIUtil.SIDE_PANEL_BACKGROUND);
JComponent left = new JPanel(new BorderLayout());
left.add(BorderLayout.NORTH, searchPanel);
left.add(BorderLayout.CENTER, myTreeView);
left.add(BorderLayout.CENTER, treeView);
JComponent right = new JPanel(new BorderLayout());
right.add(BorderLayout.NORTH, withHistoryToolbar(myBanner));
right.add(BorderLayout.CENTER, myLoadingDecorator.getComponent());
@@ -263,10 +264,10 @@ public final class SettingsEditor extends AbstractEditor implements UiDataProvid
mySplitter.getDivider().setOpaque(false);
}
mySpotlightPainter = spotlightPainterFactory.createSpotlightPainter(project, myEditor, this, (painter) -> {
Configurable currentConfigurable = myFilter.myContext.getCurrentConfigurable();
if (myTreeView.getTree().hasFocus() || mySearch.getTextEditor().hasFocus()) {
painter.update(myFilter, currentConfigurable, myEditor.getContent(currentConfigurable));
mySpotlightPainter = spotlightPainterFactory.createSpotlightPainter(project, editor, this, (painter) -> {
Configurable currentConfigurable = this.filter.context.getCurrentConfigurable();
if (treeView.getTree().hasFocus() || mySearch.getTextEditor().hasFocus()) {
painter.update(this.filter, currentConfigurable, editor.getContent(currentConfigurable));
}
});
add(BorderLayout.CENTER, mySplitter);
@@ -279,13 +280,13 @@ public final class SettingsEditor extends AbstractEditor implements UiDataProvid
}
}
myTreeView.select(configurable).onProcessed(it -> myFilter.update(filter));
treeView.select(configurable).onProcessed(it -> this.filter.update(filter));
Disposer.register(this, myTreeView);
Disposer.register(this, treeView);
installSpotlightRemover();
//noinspection CodeBlock2Expr
mySearch.getTextEditor().addActionListener(event -> {
myTreeView.select(myFilter.myContext.getCurrentConfigurable()).onProcessed(o -> requestFocusToEditor());
treeView.select(this.filter.context.getCurrentConfigurable()).onProcessed(o -> requestFocusToEditor());
});
for (ConfigurableGroup group : groups) {
@@ -298,24 +299,24 @@ public final class SettingsEditor extends AbstractEditor implements UiDataProvid
@ApiStatus.Internal
public void select(Configurable configurable) {
myTreeView.select(configurable);
myEditor.select(configurable);
treeView.select(configurable);
editor.select(configurable);
updateController(configurable);
}
@ApiStatus.Internal
public @NotNull SettingsTreeView getTreeView() {
return myTreeView;
return treeView;
}
private @NotNull MutableConfigurableGroup.Listener createReloadListener(List<? extends ConfigurableGroup> groups) {
return new MutableConfigurableGroup.Listener() {
@Override
public void handleUpdate() {
Configurable selected = myEditor.getConfigurable();
Configurable selected = editor.getConfigurable();
String id = selected instanceof SearchableConfigurable ? ((SearchableConfigurable)selected).getId() : null;
myEditor.reload();
myFilter.reload();
editor.reload();
filter.reload();
myControllers.clear();
myLastController = null;
@@ -323,8 +324,8 @@ public final class SettingsEditor extends AbstractEditor implements UiDataProvid
if (candidate == null) {
candidate = ConfigurableVisitor.findById(PluginManagerConfigurable.ID, groups);
}
myEditor.init(candidate, false);
myTreeView.reloadWithSelection(candidate);
editor.init(candidate, false);
treeView.reloadWithSelection(candidate);
mySettings.reload();
invalidate();
repaint();
@@ -333,7 +334,7 @@ public final class SettingsEditor extends AbstractEditor implements UiDataProvid
}
private void requestFocusToEditor() {
JComponent component = myEditor.getPreferredFocusedComponent();
JComponent component = editor.getPreferredFocusedComponent();
if (component != null) {
IdeFocusManager.findInstanceByComponent(component).requestFocus(component, true);
}
@@ -344,7 +345,7 @@ public final class SettingsEditor extends AbstractEditor implements UiDataProvid
@Override
public void focusLost(FocusEvent e) {
final Component comp = e.getOppositeComponent();
if (comp == mySearch.getTextEditor() || comp == myTreeView.getTree()) {
if (comp == mySearch.getTextEditor() || comp == treeView.getTree()) {
return;
}
mySpotlightPainter.update(null, null, null);
@@ -357,7 +358,7 @@ public final class SettingsEditor extends AbstractEditor implements UiDataProvid
}
}
};
myTreeView.getTree().addFocusListener(spotlightRemover);
treeView.getTree().addFocusListener(spotlightRemover);
mySearch.getTextEditor().addFocusListener(spotlightRemover);
}
@@ -406,7 +407,7 @@ public final class SettingsEditor extends AbstractEditor implements UiDataProvid
@Override
Action getApplyAction() {
return myEditor.getApplyAction();
return editor.getApplyAction();
}
@Override
@@ -416,29 +417,29 @@ public final class SettingsEditor extends AbstractEditor implements UiDataProvid
@Override
String getHelpTopic() {
Configurable configurable = myFilter.myContext.getCurrentConfigurable();
Configurable configurable = filter.context.getCurrentConfigurable();
while (configurable != null) {
String topic = configurable.getHelpTopic();
if (topic != null) {
return topic;
}
configurable = myFilter.myContext.getParentConfigurable(configurable);
configurable = filter.context.getParentConfigurable(configurable);
}
return "preferences";
}
@Override
boolean apply() {
return myEditor.apply();
return editor.apply();
}
@Override
boolean cancel(AWTEvent source) {
if (source instanceof KeyEvent && myFilter.myContext.isHoldingFilter()) {
if (source instanceof KeyEvent && filter.context.isHoldingFilter()) {
mySearch.setText("");
return false;
}
for (Configurable configurable : myFilter.myContext.getModified()) {
for (Configurable configurable : filter.context.getModified()) {
configurable.cancel();
}
return super.cancel(source);
@@ -446,30 +447,30 @@ public final class SettingsEditor extends AbstractEditor implements UiDataProvid
@Override
JComponent getPreferredFocusedComponent() {
return myTreeView != null ? myTreeView.getTree() : myEditor;
return treeView != null ? treeView.getTree() : editor;
}
@Nullable
Collection<@NlsContexts.ConfigurableName String> getPathNames() {
return myTreeView == null ? null : myTreeView.getPathNames(myFilter.myContext.getCurrentConfigurable());
return treeView == null ? null : treeView.getPathNames(filter.context.getCurrentConfigurable());
}
public void addOptionsListener(OptionsEditorColleague colleague) {
myFilter.myContext.addColleague(colleague);
filter.context.addColleague(colleague);
}
void updateStatus(Configurable configurable) {
myFilter.updateSpotlight(configurable == null);
if (myEditor != null) {
ConfigurationException exception = myFilter.myContext.getErrors().get(configurable);
myEditor.getApplyAction().setEnabled(!myFilter.myContext.getModified().isEmpty());
myEditor.getResetAction().setEnabled(myFilter.myContext.isModified(configurable) || exception != null);
myEditor.setError(exception);
myEditor.revalidate();
filter.updateSpotlight(configurable == null);
if (editor != null) {
ConfigurationException exception = filter.context.getErrors().get(configurable);
editor.getApplyAction().setEnabled(!filter.context.getModified().isEmpty());
editor.getResetAction().setEnabled(filter.context.isModified(configurable) || exception != null);
editor.setError(exception);
editor.revalidate();
}
if (configurable != null) {
EdtScheduler.getInstance().schedule(300, () -> {
if (!myDisposed && mySpotlightPainter != null) {
if (!isDisposed && mySpotlightPainter != null) {
mySpotlightPainter.updateNow();
}
});
@@ -477,9 +478,9 @@ public final class SettingsEditor extends AbstractEditor implements UiDataProvid
}
private void updateController(@Nullable Configurable configurable) {
Project project = myTreeView.findConfigurableProject(configurable);
Project project = treeView.findConfigurableProject(configurable);
myBanner.setProjectText(project != null ? getProjectText(project) : null);
myBanner.setText(myTreeView.getPathNames(configurable));
myBanner.setText(treeView.getPathNames(configurable));
if (myLastController != null) {
myLastController.setBanner(null);
@@ -494,10 +495,10 @@ public final class SettingsEditor extends AbstractEditor implements UiDataProvid
}
void checkModified(Configurable configurable) {
Configurable parent = myFilter.myContext.getParentConfigurable(configurable);
Configurable parent = filter.context.getParentConfigurable(configurable);
if (ConfigurableWrapper.hasOwnContent(parent)) {
checkModifiedForItem(parent);
for (Configurable child : myFilter.myContext.getChildren(parent)) {
for (Configurable child : filter.context.getChildren(parent)) {
checkModifiedForItem(child);
}
}
@@ -509,9 +510,9 @@ public final class SettingsEditor extends AbstractEditor implements UiDataProvid
private void checkModifiedForItem(final Configurable configurable) {
if (configurable != null) {
JComponent component = myEditor.getContent(configurable);
JComponent component = editor.getContent(configurable);
if (component == null && ConfigurableWrapper.hasOwnContent(configurable)) {
component = myEditor.readContent(configurable);
component = editor.readContent(configurable);
}
if (component != null) {
checkModifiedInternal(configurable);
@@ -521,10 +522,10 @@ public final class SettingsEditor extends AbstractEditor implements UiDataProvid
private void checkModifiedInternal(Configurable configurable) {
if (configurable.isModified()) {
myFilter.myContext.fireModifiedAdded(configurable, null);
filter.context.fireModifiedAdded(configurable, null);
}
else if (!myFilter.myContext.getErrors().containsKey(configurable)) {
myFilter.myContext.fireModifiedRemoved(configurable, null);
else if (!filter.context.getErrors().containsKey(configurable)) {
filter.context.fireModifiedRemoved(configurable, null);
}
}

View File

@@ -1,279 +1,249 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.openapi.options.newEditor;
package com.intellij.openapi.options.newEditor
import com.intellij.ide.ui.search.ConfigurableHit;
import com.intellij.ide.ui.search.SearchableOptionsRegistrar;
import com.intellij.ide.ui.search.SearchableOptionsRegistrarImpl;
import com.intellij.internal.statistic.collectors.fus.ui.SettingsCounterUsagesCollector;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.options.Configurable;
import com.intellij.openapi.options.ConfigurableGroup;
import com.intellij.openapi.options.SearchableConfigurable;
import com.intellij.openapi.options.UnnamedConfigurable;
import com.intellij.openapi.options.ex.ConfigurableWrapper;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.wm.IdeFocusManager;
import com.intellij.ui.DocumentAdapter;
import com.intellij.ui.LightColors;
import com.intellij.ui.SearchTextField;
import com.intellij.ui.speedSearch.ElementFilter;
import com.intellij.ui.treeStructure.SimpleNode;
import com.intellij.util.Alarm;
import com.intellij.util.concurrency.AppExecutorUtil;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import com.intellij.ide.ui.search.ConfigurableHit
import com.intellij.ide.ui.search.SearchableOptionsRegistrar
import com.intellij.ide.ui.search.SearchableOptionsRegistrarImpl
import com.intellij.internal.statistic.collectors.fus.ui.SettingsCounterUsagesCollector
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.ModalityState
import com.intellij.openapi.components.serviceIfCreated
import com.intellij.openapi.options.Configurable
import com.intellij.openapi.options.ConfigurableGroup
import com.intellij.openapi.options.SearchableConfigurable
import com.intellij.openapi.options.UnnamedConfigurable
import com.intellij.openapi.options.ex.ConfigurableWrapper
import com.intellij.openapi.project.Project
import com.intellij.openapi.wm.IdeFocusManager
import com.intellij.ui.DocumentAdapter
import com.intellij.ui.LightColors
import com.intellij.ui.SearchTextField
import com.intellij.ui.speedSearch.ElementFilter
import com.intellij.ui.treeStructure.SimpleNode
import com.intellij.util.Alarm
import com.intellij.util.concurrency.AppExecutorUtil
import com.intellij.util.ui.UIUtil
import org.jetbrains.annotations.ApiStatus
import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
import javax.swing.event.DocumentEvent
import javax.swing.event.DocumentEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.List;
import java.util.Set;
abstract class SettingsFilter @ApiStatus.Internal protected constructor(
project: Project?,
groups: List<ConfigurableGroup>,
search: SearchTextField
) : ElementFilter.Active.Impl<SimpleNode?>() {
@JvmField
internal val context: OptionsEditorContext = OptionsEditorContext()
private val project: Project?
public abstract class SettingsFilter extends ElementFilter.Active.Impl<SimpleNode> {
final OptionsEditorContext myContext = new OptionsEditorContext();
private final @Nullable Project myProject;
private val search: SearchTextField
private val groups: List<ConfigurableGroup>
private final SearchTextField mySearch;
private final List<? extends ConfigurableGroup> myGroups;
private var filtered: Set<Configurable>? = null
private var hits: ConfigurableHit? = null
private Set<Configurable> myFiltered;
private ConfigurableHit myHits;
private var isUpdateRejected = false
private var isLastSelected: Configurable? = null
private boolean myUpdateRejected;
private Configurable myLastSelected;
@Volatile
private var searchableOptionRegistrar: SearchableOptionsRegistrar? = null
private val updatingAlarm = Alarm(if (project == null) ApplicationManager.getApplication() else project)
private volatile SearchableOptionsRegistrar searchableOptionRegistrar;
private final Alarm myUpdatingAlarm;
@ApiStatus.Internal
protected SettingsFilter(@Nullable Project project, @NotNull List<? extends ConfigurableGroup> groups, SearchTextField search) {
myUpdatingAlarm = new Alarm(project != null ? project : ApplicationManager.getApplication());
SearchableOptionsRegistrarImpl optionRegistrar =
(SearchableOptionsRegistrarImpl)ApplicationManager.getApplication().getServiceIfCreated(SearchableOptionsRegistrar.class);
if (optionRegistrar == null || !optionRegistrar.isInitialized()) {
init {
val optionRegistrar = serviceIfCreated<SearchableOptionsRegistrar>() as SearchableOptionsRegistrarImpl?
if (optionRegistrar == null || !optionRegistrar.isInitialized) {
// if not yet computed, preload it to ensure that will be no delay on user typing
AppExecutorUtil.getAppExecutorService().execute(() -> {
SearchableOptionsRegistrarImpl r = (SearchableOptionsRegistrarImpl)SearchableOptionsRegistrar.getInstance();
r.initialize();
AppExecutorUtil.getAppExecutorService().execute {
val r = SearchableOptionsRegistrar.getInstance() as SearchableOptionsRegistrarImpl
r.initialize()
// must be set only after initializing (to avoid concurrent modifications)
searchableOptionRegistrar = r;
ApplicationManager.getApplication().invokeLater(() -> {
update(DocumentEvent.EventType.CHANGE, false, true);
}, ModalityState.any(), project == null ? ApplicationManager.getApplication().getDisposed() : project.getDisposed());
});
searchableOptionRegistrar = r
ApplicationManager.getApplication().invokeLater(Runnable {
update(type = DocumentEvent.EventType.CHANGE, adjustSelection = false, now = true)
}, ModalityState.any(), if (project == null) ApplicationManager.getApplication().getDisposed() else project.getDisposed())
}
}
else {
searchableOptionRegistrar = optionRegistrar;
searchableOptionRegistrar = optionRegistrar
}
myProject = project;
myGroups = groups;
mySearch = search;
mySearch.addDocumentListener(new DocumentAdapter() {
@Override
protected void textChanged(@NotNull DocumentEvent event) {
update(event.getType(), true, false);
this@SettingsFilter.project = project
this@SettingsFilter.groups = groups
this@SettingsFilter.search = search
search.addDocumentListener(object : DocumentAdapter() {
override fun textChanged(event: DocumentEvent) {
update(type = event.type, adjustSelection = true, now = false)
// request focus if needed on changing the filter text
IdeFocusManager manager = IdeFocusManager.findInstanceByComponent(mySearch);
if (manager.getFocusedDescendantFor(mySearch) == null) {
manager.requestFocus(mySearch, true);
val manager = IdeFocusManager.findInstanceByComponent(this@SettingsFilter.search)
if (manager.getFocusedDescendantFor(this@SettingsFilter.search) == null) {
manager.requestFocus(this@SettingsFilter.search, true)
}
}
});
mySearch.getTextEditor().addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent event) {
if (!mySearch.getText().isEmpty()) {
if (!myContext.isHoldingFilter()) {
setHoldingFilter(true);
})
search.textEditor.addMouseListener(object : MouseAdapter() {
override fun mousePressed(event: MouseEvent?) {
if (!search.text.isEmpty()) {
if (!context.isHoldingFilter) {
setHoldingFilter(true)
}
if (!mySearch.getTextEditor().isFocusOwner()) {
mySearch.selectText();
if (!search.textEditor.isFocusOwner) {
search.selectText()
}
}
}
});
})
}
@ApiStatus.Internal
protected abstract Configurable getConfigurable(SimpleNode node);
protected abstract fun getConfigurable(node: SimpleNode?): Configurable?
@ApiStatus.Internal
protected abstract SimpleNode findNode(Configurable configurable);
protected abstract fun findNode(configurable: Configurable?): SimpleNode?
@ApiStatus.Internal
protected abstract void updateSpotlight(boolean now);
protected abstract fun updateSpotlight(now: Boolean)
@Override
public boolean shouldBeShowing(SimpleNode node) {
if (myFiltered != null) {
Configurable configurable = getConfigurable(node);
if (configurable != null) {
if (!myFiltered.contains(configurable)) {
if (myHits != null) {
Set<Configurable> configurables = myHits.getNameFullHits();
while (node != null) {
if (configurable != null) {
if (configurables.contains(configurable)) {
return true;
}
}
node = node.getParent();
configurable = getConfigurable(node);
override fun shouldBeShowing(node: SimpleNode?): Boolean {
var node = node
val filtered = filtered ?: return true
var configurable = getConfigurable(node)
if (configurable != null) {
if (!filtered.contains(configurable)) {
if (hits != null) {
val configurables = hits!!.nameFullHits
while (node != null) {
if (configurable != null && configurables.contains(configurable)) {
return true
}
node = node.parent
configurable = getConfigurable(node)
}
return false;
}
return false
}
}
return true;
return true
}
void setFilterText(@NotNull String text) {
mySearch.setText(text);
fun setFilterText(text: String) {
search.text = text
}
public boolean isEmptyFilter() {
return StringUtil.isEmpty(mySearch.getText());
}
fun isEmptyFilter(): Boolean = search.text.isNullOrEmpty()
@NotNull
@ApiStatus.Internal
public String getFilterText() {
String text = mySearch.getText();
return text == null ? "" : text.trim();
}
fun getFilterText(): String = search.text?.trim() ?: ""
@NotNull
@ApiStatus.Internal
public String getSpotlightFilterText() {
if (myHits != null) {
return myHits.getSpotlightFilter();
}
return getFilterText();
fun getSpotlightFilterText(): String = if (hits == null) getFilterText() else hits!!.spotlightFilter
private fun setHoldingFilter(holding: Boolean) {
context.isHoldingFilter = holding
updateSpotlight(false)
}
private void setHoldingFilter(boolean holding) {
myContext.setHoldingFilter(holding);
updateSpotlight(false);
}
fun contains(configurable: Configurable): Boolean = hits != null && hits!!.nameHits.contains(configurable)
boolean contains(@NotNull Configurable configurable) {
return myHits != null && myHits.getNameHits().contains(configurable);
}
void update(@Nullable String text) {
fun update(text: String?) {
try {
myUpdateRejected = true;
mySearch.setText(text);
isUpdateRejected = true
search.text = text
}
finally {
myUpdateRejected = false;
isUpdateRejected = false
}
update(DocumentEvent.EventType.CHANGE, false, true);
update(type = DocumentEvent.EventType.CHANGE, adjustSelection = false, now = true)
}
private void update(@NotNull DocumentEvent.EventType type, boolean adjustSelection, boolean now) {
myUpdatingAlarm.cancelAllRequests();
myUpdatingAlarm.addRequest(() -> {
SearchableOptionsRegistrar registrar = searchableOptionRegistrar;
if (registrar != null) update(registrar, type, adjustSelection, now);
}, 100, ModalityState.any());
private fun update(type: DocumentEvent.EventType, adjustSelection: Boolean, now: Boolean) {
updatingAlarm.cancelAllRequests()
updatingAlarm.addRequest(Runnable {
val registrar = searchableOptionRegistrar
if (registrar != null) update(registrar, type, adjustSelection, now)
}, 100, ModalityState.any())
}
private void update(@NotNull SearchableOptionsRegistrar optionRegistrar, @NotNull DocumentEvent.EventType type, boolean adjustSelection, boolean now) {
if (myUpdateRejected) {
return;
private fun update(optionRegistrar: SearchableOptionsRegistrar, type: DocumentEvent.EventType, adjustSelection: Boolean, now: Boolean) {
if (isUpdateRejected) {
return
}
String text = getFilterText();
val text = getFilterText()
if (text.isEmpty()) {
myContext.setHoldingFilter(false);
myHits = null;
myFiltered = null;
context.isHoldingFilter = false
hits = null
filtered = null
}
else {
myContext.setHoldingFilter(true);
myHits = optionRegistrar.getConfigurables(myGroups, type, null, text, myProject);
myFiltered = myHits.getAll();
context.isHoldingFilter = true
hits = optionRegistrar.getConfigurables(groups, type, null, text, project)
filtered = hits!!.all
}
mySearch.getTextEditor().setBackground(myFiltered != null && myFiltered.isEmpty()
? LightColors.RED
: UIUtil.getTextFieldBackground());
search.textEditor.setBackground(if (filtered != null && filtered!!.isEmpty())LightColors.RED else UIUtil.getTextFieldBackground())
Configurable current = myContext.getCurrentConfigurable();
val current = context.currentConfigurable
var shouldMoveSelection = hits == null || !hits!!.nameFullHits.contains(current) && !hits!!.contentHits.contains(current)
boolean shouldMoveSelection = myHits == null || !myHits.getNameFullHits().contains(current) &&
!myHits.getContentHits().contains(current);
if (shouldMoveSelection && type != DocumentEvent.EventType.INSERT && (myFiltered == null || myFiltered.contains(current))) {
shouldMoveSelection = false;
if (shouldMoveSelection && type != DocumentEvent.EventType.INSERT && (filtered == null || filtered!!.contains(current))) {
shouldMoveSelection = false
}
Configurable candidate = adjustSelection ? current : null;
if (shouldMoveSelection && myHits != null) {
if (!myHits.getNameHits().isEmpty()) {
candidate = findConfigurable(myHits.getNameHits(), myHits.getNameFullHits());
var candidate = if (adjustSelection) current else null
if (shouldMoveSelection && hits != null) {
if (!hits!!.nameHits.isEmpty()) {
candidate = findConfigurable(hits!!.nameHits, hits!!.nameFullHits)
}
else if (!myHits.getContentHits().isEmpty()) {
candidate = findConfigurable(myHits.getContentHits(), null);
else if (!hits!!.contentHits.isEmpty()) {
candidate = findConfigurable(hits!!.contentHits, null)
}
}
updateSpotlight(false);
updateSpotlight(false)
if ((myFiltered == null || !myFiltered.isEmpty()) && candidate == null && myLastSelected != null) {
candidate = myLastSelected;
myLastSelected = null;
if ((filtered == null || !filtered!!.isEmpty()) && candidate == null && isLastSelected != null) {
candidate = isLastSelected
isLastSelected = null
}
if (candidate == null && current != null) {
myLastSelected = current;
isLastSelected = current
}
if (myFiltered != null &&
candidate != null) {
SettingsCounterUsagesCollector.SEARCH.log(getUnnamedConfigurable(candidate).getClass(),
myFiltered.size(),
text.length());
if (filtered != null && candidate != null) {
SettingsCounterUsagesCollector.SEARCH.log(getUnnamedConfigurable(candidate).javaClass, filtered!!.size, text.length)
}
SimpleNode node = !adjustSelection ? null : findNode(candidate);
fireUpdate(node, adjustSelection, now);
val node = if (adjustSelection) findNode(candidate) else null
fireUpdate(node, adjustSelection, now)
}
private static @NotNull UnnamedConfigurable getUnnamedConfigurable(@NotNull Configurable candidate) {
return candidate instanceof ConfigurableWrapper ?
((ConfigurableWrapper)candidate).getConfigurable() :
candidate;
}
private static Configurable findConfigurable(Set<? extends Configurable> configurables, Set<? extends Configurable> hits) {
Configurable candidate = null;
for (Configurable configurable : configurables) {
if (hits != null && hits.contains(configurable)) {
return configurable;
}
if (candidate == null && !isEmptyParent(configurable)) {
candidate = configurable;
}
}
return candidate;
}
private static boolean isEmptyParent(Configurable configurable) {
SearchableConfigurable.Parent parent = ConfigurableWrapper.cast(SearchableConfigurable.Parent.class, configurable);
return parent != null && !parent.hasOwnContent();
}
void reload() {
myLastSelected = null;
myFiltered = null;
myHits = null;
mySearch.setText("");
myContext.reload();
fun reload() {
isLastSelected = null
filtered = null
hits = null
search.text = ""
context.reload()
}
}
private fun getUnnamedConfigurable(candidate: Configurable): UnnamedConfigurable {
return if (candidate is ConfigurableWrapper) candidate.getConfigurable() else candidate
}
private fun findConfigurable(configurables: Set<Configurable>, hits: Set<Configurable>?): Configurable? {
var candidate: Configurable? = null
for (configurable in configurables) {
if (hits != null && hits.contains(configurable)) {
return configurable
}
if (candidate == null && !isEmptyParent(configurable)) {
candidate = configurable
}
}
return candidate
}
private fun isEmptyParent(configurable: Configurable?): Boolean {
val parent = ConfigurableWrapper.cast(SearchableConfigurable.Parent::class.java, configurable)
return parent != null && !parent.hasOwnContent()
}

View File

@@ -460,7 +460,7 @@ public class SettingsTreeView extends JComponent implements Accessible, Disposab
}
private @NotNull Promise<? super Object> fireSelected(Configurable configurable) {
return myFilter.myContext.fireSelected(configurable, this);
return myFilter.context.fireSelected(configurable, this);
}
@Override
@@ -564,7 +564,7 @@ public class SettingsTreeView extends JComponent implements Accessible, Disposab
SimpleNode[] result = new SimpleNode[configurables.length];
for (int i = 0; i < configurables.length; i++) {
result[i] = new MyNode(this, configurables[i], myLevel + 1);
myFilter.myContext.registerKid(myConfigurable, configurables[i]);
myFilter.context.registerKid(myConfigurable, configurables[i]);
}
return result;
}
@@ -637,10 +637,10 @@ public class SettingsTreeView extends JComponent implements Accessible, Disposab
myTextLabel.setForeground(selected ? UIUtil.getTreeSelectionForeground(true) : UIUtil.getTreeForeground());
if (!selected && node != null) {
Configurable configurable = node.myConfigurable;
if (myFilter.myContext.getErrors().containsKey(configurable)) {
if (myFilter.context.getErrors().containsKey(configurable)) {
myTextLabel.setForeground(WRONG_CONTENT);
}
else if (myFilter.myContext.getModified().contains(configurable)) {
else if (myFilter.context.getModified().contains(configurable)) {
myTextLabel.setForeground(MODIFIED_CONTENT);
}
}