New tree implementation for Settings.

This commit is contained in:
Sergey Malenkov
2014-08-18 19:22:43 +04:00
parent a347027e74
commit b4208e624f
3 changed files with 950 additions and 20 deletions
@@ -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);
}
}
});
@@ -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