mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 06:05:01 +07:00
New tree implementation for Settings.
This commit is contained in:
+85
-20
@@ -49,6 +49,7 @@ import com.intellij.ui.navigation.History;
|
||||
import com.intellij.ui.navigation.Place;
|
||||
import com.intellij.ui.speedSearch.ElementFilter;
|
||||
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;
|
||||
@@ -87,6 +88,7 @@ public class OptionsEditor extends JPanel implements DataProvider, Place.Navigat
|
||||
private final History myHistory = new History(this);
|
||||
|
||||
private final OptionsTree myTree;
|
||||
private final SettingsTreeView myTreeView;
|
||||
private final MySearchField mySearch;
|
||||
private final Splitter myMainSplitter;
|
||||
//[back/forward] JComponent myToolbarComponent;
|
||||
@@ -126,7 +128,12 @@ public class OptionsEditor extends JPanel implements DataProvider, Place.Navigat
|
||||
mySearch = new MySearchField() {
|
||||
@Override
|
||||
protected void onTextKeyEvent(final KeyEvent e) {
|
||||
myTree.processTextEvent(e);
|
||||
if (myTreeView != null) {
|
||||
myTreeView.myTree.processKeyEvent(e);
|
||||
}
|
||||
else {
|
||||
myTree.processTextEvent(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -144,12 +151,12 @@ public class OptionsEditor extends JPanel implements DataProvider, Place.Navigat
|
||||
}
|
||||
});
|
||||
|
||||
myTree = new OptionsTree(myProject, groups, getContext()) {
|
||||
final KeyListener listener = new KeyListener() {
|
||||
@Override
|
||||
protected void onTreeKeyEvent(final KeyEvent e) {
|
||||
public void keyTyped(KeyEvent event) {
|
||||
myFilterDocumentWasChanged = false;
|
||||
try {
|
||||
mySearch.keyEventToTextField(e);
|
||||
mySearch.keyEventToTextField(event);
|
||||
}
|
||||
finally {
|
||||
if (myFilterDocumentWasChanged && !isFilterFieldVisible()) {
|
||||
@@ -157,10 +164,33 @@ public class OptionsEditor extends JPanel implements DataProvider, Place.Navigat
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
getContext().addColleague(myTree);
|
||||
Disposer.register(this, myTree);
|
||||
@Override
|
||||
public void keyPressed(KeyEvent event) {
|
||||
keyTyped(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keyReleased(KeyEvent event) {
|
||||
keyTyped(event);
|
||||
}
|
||||
};
|
||||
if (Registry.is("ide.file.settings.tree.new")) {
|
||||
myTreeView = new SettingsTreeView(listener, getContext(), groups);
|
||||
myTree = null;
|
||||
}
|
||||
else {
|
||||
myTreeView = null;
|
||||
myTree = new OptionsTree(myProject, groups, getContext()) {
|
||||
@Override
|
||||
protected void onTreeKeyEvent(final KeyEvent e) {
|
||||
listener.keyTyped(e);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
getContext().addColleague(myTreeView != null ? myTreeView : myTree);
|
||||
Disposer.register(this, myTreeView != null ? myTreeView : myTree);
|
||||
mySearch.addDocumentListener(new DocumentAdapter() {
|
||||
@Override
|
||||
protected void textChanged(DocumentEvent e) {
|
||||
@@ -198,7 +228,8 @@ public class OptionsEditor extends JPanel implements DataProvider, Place.Navigat
|
||||
@Override
|
||||
public Dimension getMinimumSize() {
|
||||
Dimension dimension = super.getMinimumSize();
|
||||
dimension.width = Math.max(myTree.getMinimumSize().width, mySearchWrapper.getPreferredSize().width);
|
||||
JComponent component = myTreeView != null ? myTreeView : myTree;
|
||||
dimension.width = Math.max(component.getMinimumSize().width, mySearchWrapper.getPreferredSize().width);
|
||||
return dimension;
|
||||
}
|
||||
};
|
||||
@@ -211,7 +242,7 @@ public class OptionsEditor extends JPanel implements DataProvider, Place.Navigat
|
||||
*/
|
||||
|
||||
myLeftSide.add(mySearchWrapper, BorderLayout.NORTH);
|
||||
myLeftSide.add(myTree, BorderLayout.CENTER);
|
||||
myLeftSide.add(myTreeView != null ? myTreeView : myTree, BorderLayout.CENTER);
|
||||
|
||||
setLayout(new BorderLayout());
|
||||
|
||||
@@ -233,9 +264,19 @@ public class OptionsEditor extends JPanel implements DataProvider, Place.Navigat
|
||||
mySpotlightUpdate = new MergingUpdateQueue("OptionsSpotlight", 200, false, this, this, this);
|
||||
|
||||
if (preselectedConfigurable != null) {
|
||||
myTree.select(preselectedConfigurable);
|
||||
if (myTreeView != null) {
|
||||
myTreeView.select(preselectedConfigurable);
|
||||
}
|
||||
else {
|
||||
myTree.select(preselectedConfigurable);
|
||||
}
|
||||
} else {
|
||||
myTree.selectFirst();
|
||||
if (myTreeView != null) {
|
||||
myTreeView.selectFirst();
|
||||
}
|
||||
else {
|
||||
myTree.selectFirst();
|
||||
}
|
||||
}
|
||||
|
||||
Toolkit.getDefaultToolkit().addAWTEventListener(this,
|
||||
@@ -295,12 +336,16 @@ public class OptionsEditor extends JPanel implements DataProvider, Place.Navigat
|
||||
@Deprecated
|
||||
@Nullable
|
||||
public <T extends Configurable> T findConfigurable(Class<T> configurableClass) {
|
||||
return myTree.findConfigurable(configurableClass);
|
||||
return myTreeView != null
|
||||
? myTreeView.findConfigurable(configurableClass)
|
||||
: myTree.findConfigurable(configurableClass);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public SearchableConfigurable findConfigurableById(@NotNull String configurableId) {
|
||||
return myTree.findConfigurableById(configurableId);
|
||||
return myTreeView != null
|
||||
? myTreeView.findConfigurableById(configurableId)
|
||||
: myTree.findConfigurableById(configurableId);
|
||||
}
|
||||
|
||||
public ActionCallback clearSearchAndSelect(Configurable configurable) {
|
||||
@@ -318,7 +363,9 @@ public class OptionsEditor extends JPanel implements DataProvider, Place.Navigat
|
||||
|
||||
public ActionCallback select(Configurable configurable, final String text) {
|
||||
myFilter.refilterFor(text, false, true);
|
||||
return myTree.select(configurable);
|
||||
return myTreeView != null
|
||||
? myTreeView.select(configurable)
|
||||
: myTree.select(configurable);
|
||||
}
|
||||
|
||||
private float readProportion(final float defaultValue, final String propertyName) {
|
||||
@@ -367,7 +414,10 @@ public class OptionsEditor extends JPanel implements DataProvider, Place.Navigat
|
||||
myOwnDetails.setContent(myContentWrapper);
|
||||
myOwnDetails.setBannerMinHeight(mySearchWrapper.getHeight());
|
||||
myOwnDetails.setText(getBannerText(configurable));
|
||||
if (Registry.is("ide.file.settings.order.new")) {
|
||||
if (myTreeView != null) {
|
||||
myOwnDetails.forProject(myTreeView.findConfigurableProject(configurable));
|
||||
}
|
||||
else if (Registry.is("ide.file.settings.order.new")) {
|
||||
myOwnDetails.forProject(myTree.getConfigurableProject(configurable));
|
||||
}
|
||||
|
||||
@@ -385,7 +435,8 @@ public class OptionsEditor extends JPanel implements DataProvider, Place.Navigat
|
||||
checkModified(oldConfigurable);
|
||||
checkModified(configurable);
|
||||
|
||||
if (myTree.myBuilder.getSelectedElements().size() == 0) {
|
||||
FilteringTreeBuilder builder = myTreeView != null ? myTreeView.myBuilder : myTree.myBuilder;
|
||||
if (builder.getSelectedElements().size() == 0) {
|
||||
select(configurable).notify(result);
|
||||
} else {
|
||||
result.setDone();
|
||||
@@ -507,6 +558,9 @@ public class OptionsEditor extends JPanel implements DataProvider, Place.Navigat
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -795,7 +849,12 @@ public class OptionsEditor extends JPanel implements DataProvider, Place.Navigat
|
||||
getContext().fireErrorsChanged(errors, null);
|
||||
|
||||
if (!errors.isEmpty()) {
|
||||
myTree.select(errors.keySet().iterator().next());
|
||||
if (myTreeView != null) {
|
||||
myTreeView.select(errors.keySet().iterator().next());
|
||||
}
|
||||
else {
|
||||
myTree.select(errors.keySet().iterator().next());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -835,7 +894,7 @@ public class OptionsEditor extends JPanel implements DataProvider, Place.Navigat
|
||||
return myFiltered.contains(node.getConfigurable()) || isChildOfNameHit(node);
|
||||
}
|
||||
|
||||
return true;
|
||||
return SettingsTreeView.isFiltered(myFiltered, myHits, value);
|
||||
}
|
||||
|
||||
private boolean isChildOfNameHit(OptionsTree.EditorNode node) {
|
||||
@@ -929,7 +988,8 @@ public class OptionsEditor extends JPanel implements DataProvider, Place.Navigat
|
||||
myLastSelected = current;
|
||||
}
|
||||
|
||||
final ActionCallback callback = fireUpdate(adjustSelection ? myTree.findNodeFor(toSelect) : null, adjustSelection, now);
|
||||
SimpleNode node = !adjustSelection ? null : myTreeView != null ? myTreeView.findNode(toSelect) : myTree.findNodeFor(toSelect);
|
||||
final ActionCallback callback = fireUpdate(node, adjustSelection, now);
|
||||
|
||||
myFilterDocumentWasChanged = true;
|
||||
|
||||
@@ -965,7 +1025,12 @@ public class OptionsEditor extends JPanel implements DataProvider, Place.Navigat
|
||||
myFilter.refilterFor(filter, false, true).doWhenDone(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
myTree.select(config).notifyWhenDone(result);
|
||||
if (myTreeView != null) {
|
||||
myTreeView.select(config).notifyWhenDone(result);
|
||||
}
|
||||
else {
|
||||
myTree.select(config).notifyWhenDone(result);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
+864
@@ -0,0 +1,864 @@
|
||||
/*
|
||||
* 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.icons.AllIcons;
|
||||
import com.intellij.ide.ui.search.ConfigurableHit;
|
||||
import com.intellij.ide.util.treeView.NodeDescriptor;
|
||||
import com.intellij.openapi.Disposable;
|
||||
import com.intellij.openapi.options.*;
|
||||
import com.intellij.openapi.options.ex.ConfigurableWrapper;
|
||||
import com.intellij.openapi.options.ex.NodeConfigurable;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.ActionCallback;
|
||||
import com.intellij.openapi.util.Disposer;
|
||||
import com.intellij.ui.*;
|
||||
import com.intellij.ui.treeStructure.*;
|
||||
import com.intellij.ui.treeStructure.filtered.FilteringTreeBuilder;
|
||||
import com.intellij.ui.treeStructure.filtered.FilteringTreeStructure;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.ui.GraphicsUtil;
|
||||
import com.intellij.util.ui.UIUtil;
|
||||
import com.intellij.util.ui.tree.TreeUtil;
|
||||
import com.intellij.util.ui.update.MergingUpdateQueue;
|
||||
import com.intellij.util.ui.update.Update;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.TreeExpansionEvent;
|
||||
import javax.swing.event.TreeExpansionListener;
|
||||
import javax.swing.event.TreeSelectionEvent;
|
||||
import javax.swing.event.TreeSelectionListener;
|
||||
import javax.swing.plaf.TreeUI;
|
||||
import javax.swing.plaf.basic.BasicTreeUI;
|
||||
import javax.swing.tree.DefaultMutableTreeNode;
|
||||
import javax.swing.tree.TreePath;
|
||||
import javax.swing.tree.TreeSelectionModel;
|
||||
|
||||
/**
|
||||
* @author Sergey.Malenkov
|
||||
*/
|
||||
final class SettingsTreeView extends JComponent implements Disposable, OptionsEditorColleague {
|
||||
final SimpleTree myTree;
|
||||
final FilteringTreeBuilder myBuilder;
|
||||
|
||||
private final OptionsEditorContext myContext;
|
||||
private final MyRoot myRoot;
|
||||
private final JScrollPane myScroller;
|
||||
private JLabel mySeparator;
|
||||
private final MyRenderer myRenderer = new MyRenderer();
|
||||
private final IdentityHashMap<Configurable, MyNode> myConfigurableToNodeMap = new IdentityHashMap<Configurable, MyNode>();
|
||||
private final MergingUpdateQueue myQueue = new MergingUpdateQueue("OptionsTree", 150, false, this, this, this).setRestartTimerOnAdd(true);
|
||||
|
||||
private Configurable myQueuedConfigurable;
|
||||
|
||||
SettingsTreeView(final KeyListener listener, OptionsEditorContext context, ConfigurableGroup... groups) {
|
||||
myContext = context;
|
||||
myRoot = new MyRoot(groups);
|
||||
|
||||
myTree = new MyTree();
|
||||
myTree.getInputMap().clear();
|
||||
TreeUtil.installActions(myTree);
|
||||
|
||||
myTree.setOpaque(true);
|
||||
myTree.setBorder(BorderFactory.createEmptyBorder(0, 1, 0, 0));
|
||||
|
||||
myTree.setRowHeight(-1);
|
||||
myTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
|
||||
|
||||
myTree.setCellRenderer(myRenderer);
|
||||
myTree.setRootVisible(false);
|
||||
myTree.setShowsRootHandles(false);
|
||||
|
||||
myScroller = ScrollPaneFactory.createScrollPane(myTree);
|
||||
myScroller.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
|
||||
add(myScroller);
|
||||
|
||||
myTree.addComponentListener(new ComponentAdapter() {
|
||||
@Override
|
||||
public void componentResized(ComponentEvent e) {
|
||||
myBuilder.revalidateTree();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void componentMoved(ComponentEvent e) {
|
||||
myBuilder.revalidateTree();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void componentShown(ComponentEvent e) {
|
||||
myBuilder.revalidateTree();
|
||||
}
|
||||
});
|
||||
|
||||
myTree.getSelectionModel().addTreeSelectionListener(new TreeSelectionListener() {
|
||||
public void valueChanged(TreeSelectionEvent event) {
|
||||
MyNode node = extractNode(event.getNewLeadSelectionPath());
|
||||
select(node == null ? null : node.myConfigurable);
|
||||
}
|
||||
});
|
||||
|
||||
myTree.addKeyListener(new KeyListener() {
|
||||
public void keyTyped(KeyEvent event) {
|
||||
if (listener != null && isValid(event)) {
|
||||
listener.keyTyped(event);
|
||||
}
|
||||
}
|
||||
|
||||
public void keyPressed(KeyEvent event) {
|
||||
if (listener != null && isValid(event)) {
|
||||
listener.keyPressed(event);
|
||||
}
|
||||
}
|
||||
|
||||
public void keyReleased(KeyEvent event) {
|
||||
if (listener != null && isValid(event)) {
|
||||
listener.keyReleased(event);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isValid(KeyEvent event) {
|
||||
return null == myTree.getInputMap().get(KeyStroke.getKeyStrokeForEvent(event));
|
||||
}
|
||||
});
|
||||
myBuilder = new MyBuilder(new SimpleTreeStructure.Impl(myRoot));
|
||||
myBuilder.setFilteringMerge(300, null);
|
||||
Disposer.register(this, myBuilder);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
String[] getPathNames(Configurable configurable) {
|
||||
ArrayDeque<String> path = new ArrayDeque<String>();
|
||||
MyNode node = myConfigurableToNodeMap.get(configurable);
|
||||
while (node != null) {
|
||||
path.push(node.myDisplayName);
|
||||
SimpleNode parent = node.getParent();
|
||||
node = parent instanceof MyNode
|
||||
? (MyNode)parent
|
||||
: null;
|
||||
}
|
||||
return ArrayUtil.toStringArray(path);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
SimpleNode findNode(Configurable toSelect) {
|
||||
return myConfigurableToNodeMap.get(toSelect);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
SearchableConfigurable findConfigurableById(@NotNull String id) {
|
||||
for (Configurable configurable : myConfigurableToNodeMap.keySet()) {
|
||||
if (configurable instanceof SearchableConfigurable) {
|
||||
SearchableConfigurable searchable = (SearchableConfigurable)configurable;
|
||||
if (id.equals(searchable.getId())) {
|
||||
return searchable;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
<T extends UnnamedConfigurable> T findConfigurable(@NotNull Class<T> type) {
|
||||
for (UnnamedConfigurable configurable : myConfigurableToNodeMap.keySet()) {
|
||||
if (configurable instanceof ConfigurableWrapper) {
|
||||
ConfigurableWrapper wrapper = (ConfigurableWrapper)configurable;
|
||||
configurable = wrapper.getConfigurable();
|
||||
}
|
||||
if (type.isInstance(configurable)) {
|
||||
return type.cast(configurable);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
Project findConfigurableProject(@Nullable Configurable configurable) {
|
||||
if (configurable instanceof ConfigurableWrapper) {
|
||||
ConfigurableWrapper wrapper = (ConfigurableWrapper)configurable;
|
||||
return wrapper.getExtensionPoint().getProject();
|
||||
}
|
||||
return findConfigurableProject(myConfigurableToNodeMap.get(configurable));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Project findConfigurableProject(@Nullable MyNode node) {
|
||||
if (node != null) {
|
||||
Configurable configurable = node.myConfigurable;
|
||||
if (configurable instanceof ConfigurableWrapper) {
|
||||
ConfigurableWrapper wrapper = (ConfigurableWrapper)configurable;
|
||||
return wrapper.getExtensionPoint().getProject();
|
||||
}
|
||||
SimpleNode parent = node.getParent();
|
||||
if (parent instanceof MyNode) {
|
||||
return findConfigurableProject((MyNode)parent);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private ConfigurableGroup findConfigurableGroupAt(int x, int y) {
|
||||
TreePath path = myTree.getClosestPathForLocation(x - myTree.getX(), y - myTree.getY());
|
||||
while (path != null) {
|
||||
MyNode node = extractNode(path);
|
||||
if (node == null) {
|
||||
return null;
|
||||
}
|
||||
if (node.myComposite instanceof ConfigurableGroup) {
|
||||
return (ConfigurableGroup)node.myComposite;
|
||||
}
|
||||
path = path.getParentPath();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static MyNode extractNode(@Nullable Object object) {
|
||||
if (object instanceof TreePath) {
|
||||
TreePath path = (TreePath)object;
|
||||
object = path.getLastPathComponent();
|
||||
}
|
||||
if (object instanceof DefaultMutableTreeNode) {
|
||||
DefaultMutableTreeNode node = (DefaultMutableTreeNode)object;
|
||||
object = node.getUserObject();
|
||||
}
|
||||
if (object instanceof FilteringTreeStructure.FilteringNode) {
|
||||
FilteringTreeStructure.FilteringNode node = (FilteringTreeStructure.FilteringNode)object;
|
||||
object = node.getDelegate();
|
||||
}
|
||||
return object instanceof MyNode
|
||||
? (MyNode)object
|
||||
: null;
|
||||
}
|
||||
|
||||
static boolean isFiltered(Set<Configurable> configurables, ConfigurableHit hits, SimpleNode value) {
|
||||
if (value instanceof MyNode && !configurables.contains(((MyNode)value).myConfigurable)) {
|
||||
if (hits != null) {
|
||||
configurables = hits.getNameFullHits();
|
||||
while (value != null) {
|
||||
if (value instanceof MyNode) {
|
||||
if (configurables.contains(((MyNode)value).myConfigurable)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
value = value.getParent();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doLayout() {
|
||||
myScroller.setBounds(0, 0, getWidth(), getHeight());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paint(Graphics g) {
|
||||
super.paint(g);
|
||||
|
||||
if (mySeparator == null) {
|
||||
mySeparator = new JLabel();
|
||||
mySeparator.setFont(UIUtil.getLabelFont());
|
||||
mySeparator.setFont(getFont().deriveFont(Font.BOLD));
|
||||
}
|
||||
ConfigurableGroup group = findConfigurableGroupAt(0, 5 + mySeparator.getFont().getSize());
|
||||
if (group != null && group == findConfigurableGroupAt(0, -5)) {
|
||||
int offset = UIUtil.isUnderNativeMacLookAndFeel() ? 1 : 3;
|
||||
mySeparator.setBorder(BorderFactory.createEmptyBorder(offset, 18, offset, 3));
|
||||
mySeparator.setText(group.getDisplayName());
|
||||
|
||||
Rectangle bounds = myScroller.getViewport().getBounds();
|
||||
int height = mySeparator.getPreferredSize().height;
|
||||
if (bounds.height > height) {
|
||||
bounds.height = height;
|
||||
}
|
||||
g.setColor(myTree.getBackground());
|
||||
if (g instanceof Graphics2D) {
|
||||
int h = bounds.height / 4;
|
||||
int y = bounds.y + bounds.height - h;
|
||||
g.fillRect(bounds.x, bounds.y, bounds.width, bounds.height - h);
|
||||
((Graphics2D)g).setPaint(UIUtil.getGradientPaint(
|
||||
0, y, g.getColor(),
|
||||
0, y + h, ColorUtil.toAlpha(g.getColor(), 0)));
|
||||
g.fillRect(bounds.x, y, bounds.width, h + h);
|
||||
}
|
||||
else {
|
||||
g.fillRect(bounds.x, bounds.y, bounds.width, bounds.height);
|
||||
}
|
||||
mySeparator.setSize(bounds.width - 1, bounds.height);
|
||||
mySeparator.paint(g.create(bounds.x + 1, bounds.y, bounds.width - 1, bounds.height));
|
||||
}
|
||||
}
|
||||
|
||||
void selectFirst() {
|
||||
for (ConfigurableGroup eachGroup : myRoot.myGroups) {
|
||||
Configurable[] kids = eachGroup.getConfigurables();
|
||||
if (kids.length > 0) {
|
||||
select(kids[0]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ActionCallback select(@Nullable final Configurable configurable) {
|
||||
if (myBuilder.isSelectionBeingAdjusted()) {
|
||||
return new ActionCallback.Rejected();
|
||||
}
|
||||
final ActionCallback callback = new ActionCallback();
|
||||
myQueuedConfigurable = configurable;
|
||||
myQueue.queue(new Update(this) {
|
||||
public void run() {
|
||||
if (configurable == myQueuedConfigurable) {
|
||||
if (configurable == null) {
|
||||
fireSelected(null, callback);
|
||||
}
|
||||
else {
|
||||
myBuilder.getReady(this).doWhenDone(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (configurable != myQueuedConfigurable) return;
|
||||
|
||||
MyNode editorNode = myConfigurableToNodeMap.get(configurable);
|
||||
FilteringTreeStructure.FilteringNode editorUiNode = myBuilder.getVisibleNodeFor(editorNode);
|
||||
if (editorUiNode == null) return;
|
||||
|
||||
if (!myBuilder.getSelectedElements().contains(editorUiNode)) {
|
||||
myBuilder.select(editorUiNode, new Runnable() {
|
||||
public void run() {
|
||||
fireSelected(configurable, callback);
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
myBuilder.scrollSelectionToVisible(new Runnable() {
|
||||
public void run() {
|
||||
fireSelected(configurable, callback);
|
||||
}
|
||||
}, false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRejected() {
|
||||
super.setRejected();
|
||||
callback.setRejected();
|
||||
}
|
||||
});
|
||||
return callback;
|
||||
}
|
||||
|
||||
private void fireSelected(Configurable configurable, ActionCallback callback) {
|
||||
myContext.fireSelected(configurable, this).doWhenProcessed(callback.createSetDoneRunnable());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
myQueuedConfigurable = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActionCallback onSelected(@Nullable Configurable configurable, Configurable oldConfigurable) {
|
||||
return select(configurable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActionCallback onModifiedAdded(Configurable configurable) {
|
||||
myTree.repaint();
|
||||
return new ActionCallback.Done();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActionCallback onModifiedRemoved(Configurable configurable) {
|
||||
myTree.repaint();
|
||||
return new ActionCallback.Done();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActionCallback onErrorsChanged() {
|
||||
return new ActionCallback.Done();
|
||||
}
|
||||
|
||||
private final class MyRoot extends CachingSimpleNode {
|
||||
private final ConfigurableGroup[] myGroups;
|
||||
|
||||
private MyRoot(ConfigurableGroup[] groups) {
|
||||
super(null);
|
||||
myGroups = groups;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SimpleNode[] buildChildren() {
|
||||
if (myGroups == null || myGroups.length == 0) {
|
||||
return NO_CHILDREN;
|
||||
}
|
||||
SimpleNode[] result = new SimpleNode[myGroups.length];
|
||||
for (int i = 0; i < myGroups.length; i++) {
|
||||
result[i] = new MyNode(this, myGroups[i]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private final class MyNode extends CachingSimpleNode {
|
||||
private final Configurable.Composite myComposite;
|
||||
private final Configurable myConfigurable;
|
||||
private final String myDisplayName;
|
||||
|
||||
private MyNode(CachingSimpleNode parent, Configurable configurable) {
|
||||
super(parent);
|
||||
myComposite = configurable instanceof Configurable.Composite ? (Configurable.Composite)configurable : null;
|
||||
myConfigurable = configurable;
|
||||
String name = configurable.getDisplayName();
|
||||
myDisplayName = name != null ? name.replace("\n", " ") : "{ " + configurable.getClass().getSimpleName() + " }";
|
||||
|
||||
myConfigurableToNodeMap.put(configurable, this);
|
||||
}
|
||||
|
||||
private MyNode(CachingSimpleNode parent, ConfigurableGroup group) {
|
||||
super(parent);
|
||||
myComposite = group;
|
||||
myConfigurable = null;
|
||||
String name = group.getDisplayName();
|
||||
myDisplayName = name != null ? name.replace("\n", " ") : "{ " + group.getClass().getSimpleName() + " }";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SimpleNode[] buildChildren() {
|
||||
if (myComposite == null) {
|
||||
return NO_CHILDREN;
|
||||
}
|
||||
Configurable[] configurables = myComposite.getConfigurables();
|
||||
if (configurables == null || configurables.length == 0) {
|
||||
return NO_CHILDREN;
|
||||
}
|
||||
SimpleNode[] result = new SimpleNode[configurables.length];
|
||||
for (int i = 0; i < configurables.length; i++) {
|
||||
result[i] = new MyNode(this, configurables[i]);
|
||||
if (myConfigurable != null) {
|
||||
myContext.registerKid(myConfigurable, configurables[i]);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAlwaysLeaf() {
|
||||
return myComposite == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWeight() {
|
||||
return WeightBasedComparator.UNDEFINED_WEIGHT;
|
||||
}
|
||||
}
|
||||
|
||||
private final class MyRenderer extends GroupedElementsRenderer.Tree {
|
||||
private JLabel myNodeIcon;
|
||||
private JLabel myProjectIcon;
|
||||
|
||||
protected JComponent createItemComponent() {
|
||||
myTextLabel = new ErrorLabel();
|
||||
return myTextLabel;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void layout() {
|
||||
myNodeIcon = new JLabel(" ", SwingConstants.RIGHT);
|
||||
myProjectIcon = new JLabel(" ", SwingConstants.LEFT);
|
||||
myProjectIcon.setOpaque(true);
|
||||
myRendererComponent.add(BorderLayout.NORTH, mySeparatorComponent);
|
||||
myRendererComponent.add(BorderLayout.CENTER, myComponent);
|
||||
myRendererComponent.add(BorderLayout.WEST, myNodeIcon);
|
||||
myRendererComponent.add(BorderLayout.EAST, myProjectIcon);
|
||||
}
|
||||
|
||||
public Component getTreeCellRendererComponent(JTree tree,
|
||||
Object value,
|
||||
boolean selected,
|
||||
boolean expanded,
|
||||
boolean leaf,
|
||||
int row,
|
||||
boolean focused) {
|
||||
myTextLabel.setOpaque(selected);
|
||||
myTextLabel.setFont(UIUtil.getLabelFont());
|
||||
|
||||
String text;
|
||||
boolean hasSeparatorAbove = false;
|
||||
int preferredForcedWidth = -1;
|
||||
|
||||
MyNode node = extractNode(value);
|
||||
if (node == null) {
|
||||
text = value.toString();
|
||||
}
|
||||
else {
|
||||
text = node.myDisplayName;
|
||||
// show groups in bold
|
||||
if (myRoot == node.getParent()) {
|
||||
hasSeparatorAbove = node != myRoot.getChildAt(0);
|
||||
myTextLabel.setFont(myTextLabel.getFont().deriveFont(Font.BOLD));
|
||||
}
|
||||
TreePath path = tree.getPathForRow(row);
|
||||
if (path == null) {
|
||||
if (value instanceof DefaultMutableTreeNode) {
|
||||
path = new TreePath(((DefaultMutableTreeNode)value).getPath());
|
||||
}
|
||||
}
|
||||
int forcedWidth = 2000;
|
||||
if (path != null && tree.isVisible()) {
|
||||
Rectangle visibleRect = tree.getVisibleRect();
|
||||
|
||||
int nestingLevel = tree.isRootVisible() ? path.getPathCount() - 1 : path.getPathCount() - 2;
|
||||
|
||||
int left = UIUtil.getTreeLeftChildIndent();
|
||||
int right = UIUtil.getTreeRightChildIndent();
|
||||
|
||||
Insets treeInsets = tree.getInsets();
|
||||
|
||||
int indent = (left + right) * nestingLevel + (treeInsets != null ? treeInsets.left + treeInsets.right : 0);
|
||||
|
||||
forcedWidth = visibleRect.width > 0 ? visibleRect.width - indent : forcedWidth;
|
||||
}
|
||||
preferredForcedWidth = forcedWidth - 4;
|
||||
}
|
||||
Component result = configureComponent(text, null, null, null, selected, hasSeparatorAbove, null, preferredForcedWidth);
|
||||
// update font color for modified configurables
|
||||
if (!selected && node != null) {
|
||||
Configurable configurable = node.myConfigurable;
|
||||
if (configurable != null) {
|
||||
if (myContext.getErrors().containsKey(configurable)) {
|
||||
myTextLabel.setForeground(JBColor.RED);
|
||||
}
|
||||
else if (myContext.getModified().contains(configurable)) {
|
||||
myTextLabel.setForeground(JBColor.BLUE);
|
||||
}
|
||||
}
|
||||
}
|
||||
// configure project icon
|
||||
Project project = null;
|
||||
if (node != null) {
|
||||
SimpleNode parent = node.getParent();
|
||||
if (parent instanceof MyNode) {
|
||||
if (myRoot == parent.getParent()) {
|
||||
project = findConfigurableProject(node); // show icon for top-level nodes
|
||||
if (node.myConfigurable instanceof NodeConfigurable) { // special case for custom subgroups (build.tools)
|
||||
Configurable[] configurables = ((NodeConfigurable)node.myConfigurable).getConfigurables();
|
||||
if (configurables != null) { // assume that all configurables have the same project
|
||||
project = findConfigurableProject(configurables[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (((MyNode)parent).myConfigurable instanceof NodeConfigurable) {
|
||||
if (((MyNode)node.getParent()).myConfigurable instanceof NodeConfigurable) {
|
||||
project = findConfigurableProject(node); // special case for custom subgroups
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (project != null) {
|
||||
myProjectIcon.setIcon(selected
|
||||
? AllIcons.General.ProjectConfigurableSelected
|
||||
: AllIcons.General.ProjectConfigurable);
|
||||
myProjectIcon.setToolTipText(OptionsBundle.message(project.isDefault()
|
||||
? "configurable.default.project.tooltip"
|
||||
: "configurable.current.project.tooltip"));
|
||||
myProjectIcon.setBackground(myTextLabel.getBackground());
|
||||
myProjectIcon.setVisible(true);
|
||||
}
|
||||
else {
|
||||
myProjectIcon.setVisible(false);
|
||||
}
|
||||
// configure node icon
|
||||
if (value instanceof DefaultMutableTreeNode) {
|
||||
DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode)value;
|
||||
TreePath treePath = new TreePath(treeNode.getPath());
|
||||
myNodeIcon.setIcon(myTree.getHandleIcon(treeNode, treePath));
|
||||
}
|
||||
else {
|
||||
myNodeIcon.setIcon(null);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
public boolean isUnderHandle(Point point) {
|
||||
Point handlePoint = SwingUtilities.convertPoint(myRendererComponent, point, myNodeIcon);
|
||||
Rectangle bounds = myNodeIcon.getBounds();
|
||||
return bounds.x < handlePoint.x && bounds.getMaxX() >= handlePoint.x;
|
||||
}
|
||||
}
|
||||
|
||||
private final class MyTree extends SimpleTree {
|
||||
@Override
|
||||
public String getToolTipText(MouseEvent event) {
|
||||
if (event != null) {
|
||||
Component component = getDeepestRendererComponentAt(event.getX(), event.getY());
|
||||
if (component instanceof JLabel) {
|
||||
JLabel label = (JLabel)component;
|
||||
if (label.getIcon() != null) {
|
||||
String text = label.getToolTipText();
|
||||
if (text != null) {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return super.getToolTipText(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean paintNodes() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean highlightSingleNode() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUI(TreeUI ui) {
|
||||
TreeUI actualUI = ui;
|
||||
if (!(ui instanceof MyTreeUi)) {
|
||||
actualUI = new MyTreeUi();
|
||||
}
|
||||
super.setUI(actualUI);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isCustomUI() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configureUiHelper(TreeUIHelper helper) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getScrollableTracksViewportWidth() {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void processKeyEvent(KeyEvent e) {
|
||||
TreePath path = myTree.getSelectionPath();
|
||||
if (path != null) {
|
||||
if (e.getKeyCode() == KeyEvent.VK_LEFT) {
|
||||
if (isExpanded(path)) {
|
||||
collapsePath(path);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
|
||||
if (isCollapsed(path)) {
|
||||
expandPath(path);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
super.processKeyEvent(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void processMouseEvent(MouseEvent e) {
|
||||
MyTreeUi ui = (MyTreeUi)myTree.getUI();
|
||||
boolean toggleNow = MouseEvent.MOUSE_RELEASED == e.getID()
|
||||
&& UIUtil.isActionClick(e, MouseEvent.MOUSE_RELEASED)
|
||||
&& !ui.isToggleEvent(e);
|
||||
|
||||
if (toggleNow || MouseEvent.MOUSE_PRESSED == e.getID()) {
|
||||
TreePath path = getPathForLocation(e.getX(), e.getY());
|
||||
if (path != null) {
|
||||
Rectangle bounds = getPathBounds(path);
|
||||
if (bounds != null && path.getLastPathComponent() instanceof DefaultMutableTreeNode) {
|
||||
DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent();
|
||||
boolean selected = isPathSelected(path);
|
||||
boolean expanded = isExpanded(path);
|
||||
Component comp =
|
||||
myRenderer.getTreeCellRendererComponent(this, node, selected, expanded, node.isLeaf(), getRowForPath(path), isFocusOwner());
|
||||
|
||||
comp.setBounds(bounds);
|
||||
comp.validate();
|
||||
|
||||
Point point = new Point(e.getX() - bounds.x, e.getY() - bounds.y);
|
||||
if (myRenderer.isUnderHandle(point)) {
|
||||
if (toggleNow) {
|
||||
ui.toggleExpandState(path);
|
||||
}
|
||||
e.consume();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
super.processMouseEvent(e);
|
||||
}
|
||||
|
||||
private final class MyTreeUi extends BasicTreeUI {
|
||||
|
||||
@Override
|
||||
public void toggleExpandState(TreePath path) {
|
||||
super.toggleExpandState(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isToggleEvent(MouseEvent event) {
|
||||
return super.isToggleEvent(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean shouldPaintExpandControl(TreePath path,
|
||||
int row,
|
||||
boolean isExpanded,
|
||||
boolean hasBeenExpanded,
|
||||
boolean isLeaf) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void paintHorizontalPartOfLeg(Graphics g,
|
||||
Rectangle clipBounds,
|
||||
Insets insets,
|
||||
Rectangle bounds,
|
||||
TreePath path,
|
||||
int row,
|
||||
boolean isExpanded,
|
||||
boolean hasBeenExpanded,
|
||||
boolean isLeaf) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void paintVerticalPartOfLeg(Graphics g, Rectangle clipBounds, Insets insets, TreePath path) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paint(Graphics g, JComponent c) {
|
||||
GraphicsUtil.setupAntialiasing(g);
|
||||
super.paint(g, c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class MyBuilder extends FilteringTreeBuilder {
|
||||
|
||||
List<Object> myToExpandOnResetFilter;
|
||||
boolean myRefilteringNow;
|
||||
boolean myWasHoldingFilter;
|
||||
|
||||
public MyBuilder(SimpleTreeStructure structure) {
|
||||
super(myTree, myContext.getFilter(), structure, new WeightBasedComparator(false));
|
||||
myTree.addTreeExpansionListener(new TreeExpansionListener() {
|
||||
public void treeExpanded(TreeExpansionEvent event) {
|
||||
invalidateExpansions();
|
||||
}
|
||||
|
||||
public void treeCollapsed(TreeExpansionEvent event) {
|
||||
invalidateExpansions();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void invalidateExpansions() {
|
||||
if (!myRefilteringNow) {
|
||||
myToExpandOnResetFilter = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isSelectable(Object object) {
|
||||
return object instanceof MyNode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAutoExpandNode(NodeDescriptor nodeDescriptor) {
|
||||
return myContext.isHoldingFilter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isToEnsureSelectionOnFocusGained() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ActionCallback refilterNow(Object preferredSelection, boolean adjustSelection) {
|
||||
final List<Object> toRestore = new ArrayList<Object>();
|
||||
if (myContext.isHoldingFilter() && !myWasHoldingFilter && myToExpandOnResetFilter == null) {
|
||||
myToExpandOnResetFilter = myBuilder.getUi().getExpandedElements();
|
||||
}
|
||||
else if (!myContext.isHoldingFilter() && myWasHoldingFilter && myToExpandOnResetFilter != null) {
|
||||
toRestore.addAll(myToExpandOnResetFilter);
|
||||
myToExpandOnResetFilter = null;
|
||||
}
|
||||
|
||||
myWasHoldingFilter = myContext.isHoldingFilter();
|
||||
|
||||
ActionCallback result = super.refilterNow(preferredSelection, adjustSelection);
|
||||
myRefilteringNow = true;
|
||||
return result.doWhenDone(new Runnable() {
|
||||
public void run() {
|
||||
myRefilteringNow = false;
|
||||
if (!myContext.isHoldingFilter() && getSelectedElements().isEmpty()) {
|
||||
restoreExpandedState(toRestore);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void restoreExpandedState(List<Object> toRestore) {
|
||||
TreePath[] selected = myTree.getSelectionPaths();
|
||||
if (selected == null) {
|
||||
selected = new TreePath[0];
|
||||
}
|
||||
|
||||
List<TreePath> toCollapse = new ArrayList<TreePath>();
|
||||
|
||||
for (int eachRow = 0; eachRow < myTree.getRowCount(); eachRow++) {
|
||||
if (!myTree.isExpanded(eachRow)) continue;
|
||||
|
||||
TreePath eachVisiblePath = myTree.getPathForRow(eachRow);
|
||||
if (eachVisiblePath == null) continue;
|
||||
|
||||
Object eachElement = myBuilder.getElementFor(eachVisiblePath.getLastPathComponent());
|
||||
if (toRestore.contains(eachElement)) continue;
|
||||
|
||||
|
||||
for (TreePath eachSelected : selected) {
|
||||
if (!eachVisiblePath.isDescendant(eachSelected)) {
|
||||
toCollapse.add(eachVisiblePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (TreePath each : toCollapse) {
|
||||
myTree.collapsePath(each);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -394,6 +394,7 @@ console.too.much.text.buffer.ratio.description=Used for disabling of console pro
|
||||
when there is too much of text to process.\n\
|
||||
The ratio is used against the console cycle buffer size (idea.cycle.buffer.size/theRatio=maxTextLength).
|
||||
ide.file.settings.order.new=false
|
||||
ide.file.settings.tree.new=false
|
||||
ide.new.project.settings=true
|
||||
ide.new.project.settings.description=Temporary key for new project settings dialog UI
|
||||
|
||||
|
||||
Reference in New Issue
Block a user