summaryrefslogtreecommitdiff
path: root/platform/platform-impl/src/com/intellij/openapi/options/newEditor/SettingsTreeView.java
diff options
context:
space:
mode:
Diffstat (limited to 'platform/platform-impl/src/com/intellij/openapi/options/newEditor/SettingsTreeView.java')
-rw-r--r--platform/platform-impl/src/com/intellij/openapi/options/newEditor/SettingsTreeView.java864
1 files changed, 864 insertions, 0 deletions
diff --git a/platform/platform-impl/src/com/intellij/openapi/options/newEditor/SettingsTreeView.java b/platform/platform-impl/src/com/intellij/openapi/options/newEditor/SettingsTreeView.java
new file mode 100644
index 000000000000..9269da7ab967
--- /dev/null
+++ b/platform/platform-impl/src/com/intellij/openapi/options/newEditor/SettingsTreeView.java
@@ -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);
+ }
+ }
+ }
+}