| /* |
| * 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.util.ui.tree; |
| |
| import com.intellij.ide.util.treeView.AbstractTreeBuilder; |
| import com.intellij.openapi.application.Application; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.ActionCallback; |
| import com.intellij.openapi.util.Comparing; |
| import com.intellij.openapi.wm.IdeFocusManager; |
| import com.intellij.ui.ListScrollingUtil; |
| import com.intellij.ui.SimpleColoredComponent; |
| import com.intellij.ui.awt.RelativePoint; |
| import com.intellij.ui.treeStructure.Tree; |
| import com.intellij.util.Range; |
| import com.intellij.util.ui.UIUtil; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import javax.swing.*; |
| import javax.swing.plaf.basic.BasicTreeUI; |
| import javax.swing.tree.*; |
| import java.awt.*; |
| import java.awt.event.ActionEvent; |
| import java.awt.event.KeyEvent; |
| import java.util.*; |
| import java.util.List; |
| |
| public final class TreeUtil { |
| private static final Logger LOG = Logger.getInstance("#com.intellij.util.ui.tree.TreeUtil"); |
| @NonNls @NotNull private static final String TREE_UTIL_SCROLL_TIME_STAMP = "TreeUtil.scrollTimeStamp"; |
| |
| private TreeUtil() {} |
| |
| /** |
| * @param tree JTree to collect expanded paths from. |
| * @param paths output parameter. |
| */ |
| public static void collectExpandedPaths(@NotNull final JTree tree, @NotNull final List<TreePath> paths){ |
| final TreeModel model = tree.getModel(); |
| final Object root = model.getRoot(); |
| LOG.assertTrue(root != null); |
| |
| collectExpandedPathsImpl(tree, paths, new TreePath(root)); |
| } |
| |
| @NotNull |
| public static List<TreePath> collectExpandedPaths(@NotNull final JTree tree){ |
| final ArrayList<TreePath> result = new ArrayList<TreePath>(); |
| final Object root = tree.getModel().getRoot(); |
| final TreePath rootPath = new TreePath(root); |
| result.addAll(collectExpandedPaths(tree, rootPath)); |
| return result; |
| } |
| |
| @NotNull |
| public static <T> List<T> collectSelectedObjectsOfType(@NotNull JTree tree, @NotNull Class<T> clazz) { |
| final TreePath[] selections = tree.getSelectionPaths(); |
| if (selections != null) { |
| final ArrayList<T> result = new ArrayList<T>(); |
| for (TreePath selection : selections) { |
| final DefaultMutableTreeNode node = (DefaultMutableTreeNode)selection.getLastPathComponent(); |
| final Object userObject = node.getUserObject(); |
| if (clazz.isInstance(userObject)) { |
| //noinspection unchecked |
| result.add((T)userObject); |
| } |
| } |
| return result; |
| } |
| return Collections.emptyList(); |
| |
| } |
| |
| @NotNull |
| public static List<TreePath> collectExpandedPaths(@NotNull final JTree tree, @NotNull TreePath path){ |
| final ArrayList<TreePath> result = new ArrayList<TreePath>(); |
| if (!tree.isExpanded(path)) return result; |
| final Object lastPathComponent = path.getLastPathComponent(); |
| final TreeModel model = tree.getModel(); |
| if (model.isLeaf(lastPathComponent)) { |
| result.add(path); |
| } else { |
| boolean pathWasAdded = false; |
| for(int i = model.getChildCount(lastPathComponent) - 1; i >= 0 ; i--){ |
| final TreePath childPath = path.pathByAddingChild(model.getChild(lastPathComponent, i)); |
| if (model.isLeaf(lastPathComponent)) { |
| if (!pathWasAdded) { |
| result.add(path); |
| pathWasAdded= true; |
| } |
| } |
| else if (tree.isExpanded(childPath)) { |
| result.addAll(collectExpandedPaths(tree, childPath)); |
| } else { |
| if (!pathWasAdded) { |
| result.add(path); |
| pathWasAdded= true; |
| } |
| } |
| } |
| |
| } |
| return result; |
| } |
| |
| private static boolean collectExpandedPathsImpl(@NotNull final JTree tree, @NotNull final Collection<TreePath> paths, @NotNull final TreePath path){ |
| final TreeModel model = tree.getModel(); |
| final Object lastPathComponent = path.getLastPathComponent(); |
| if(model.isLeaf(lastPathComponent)){ |
| return false; |
| } |
| |
| boolean hasExpandedChildren = false; |
| |
| for(int i = model.getChildCount(lastPathComponent) - 1; i >= 0 ; i--){ |
| hasExpandedChildren |= collectExpandedPathsImpl(tree, paths, path.pathByAddingChild(model.getChild(lastPathComponent, i))); |
| } |
| |
| if(!hasExpandedChildren){ |
| paths.add(path); |
| return true; |
| } |
| else{ |
| return false; |
| } |
| } |
| |
| /** |
| * Expands specified paths. |
| * @param tree JTree to apply expansion status to |
| * @param paths to expand. See {@link #collectExpandedPaths(javax.swing.JTree, java.util.List)} |
| */ |
| public static void restoreExpandedPaths(@NotNull final JTree tree, @NotNull final List<TreePath> paths){ |
| for(int i = paths.size() - 1; i >= 0; i--){ |
| tree.expandPath(paths.get(i)); |
| } |
| } |
| |
| @NotNull |
| public static TreePath getPath(final TreeNode aRootNode, @NotNull final TreeNode aNode) { |
| final List<TreeNode> pathStack = new ArrayList<TreeNode>(); |
| addEach(aRootNode, aNode, pathStack); |
| |
| final Object[] pathElements = new Object[pathStack.size()]; |
| |
| for (int i = pathStack.size() - 1; i >= 0; i--) { |
| pathElements[pathStack.size() - i - 1] = pathStack.get(i); |
| } |
| |
| return new TreePath(pathElements); |
| } |
| |
| public static boolean isAncestor(final TreeNode ancestor, final TreeNode node) { |
| TreeNode parent = node; |
| while (parent != null) { |
| if (parent == ancestor) return true; |
| parent = parent.getParent(); |
| } |
| return false; |
| } |
| |
| private static boolean isAncestor(@NotNull final TreePath ancestor, @NotNull final TreePath path) { |
| if (path.getPathCount() < ancestor.getPathCount()) return false; |
| for (int i = 0; i < ancestor.getPathCount(); i++) |
| if (!path.getPathComponent(i).equals(ancestor.getPathComponent(i))) return false; |
| return true; |
| } |
| |
| private static boolean isDescendants(@NotNull final TreePath path, @NotNull final TreePath[] paths) { |
| for (final TreePath ancestor : paths) { |
| if (isAncestor(ancestor, path)) return true; |
| } |
| return false; |
| } |
| |
| @NotNull |
| public static TreePath getPathFromRoot(TreeNode node) { |
| final ArrayList<TreeNode> path = new ArrayList<TreeNode>(); |
| do { |
| path.add(node); |
| node = node.getParent(); |
| } while (node != null); |
| Collections.reverse(path); |
| return new TreePath(path.toArray()); |
| } |
| |
| @Nullable |
| public static TreeNode findNodeWithObject(final Object object, @NotNull final TreeModel model, final Object parent) { |
| for (int i = 0; i < model.getChildCount(parent); i++) { |
| final DefaultMutableTreeNode childNode = (DefaultMutableTreeNode) model.getChild(parent, i); |
| if (childNode.getUserObject().equals(object)) return childNode; |
| } |
| return null; |
| } |
| |
| /** |
| * Removes last component in the current selection path. |
| * @param tree to remove selected node from. |
| */ |
| public static void removeSelected(@NotNull final JTree tree) { |
| TreePath[] paths = tree.getSelectionPaths(); |
| if (paths == null) { |
| return; |
| } |
| for (TreePath path : paths) { |
| removeLastPathComponent((DefaultTreeModel) tree.getModel(), path).restoreSelection(tree); |
| } |
| } |
| |
| public static void removeLastPathComponent(@NotNull final JTree tree, @NotNull final TreePath pathToBeRemoved){ |
| removeLastPathComponent((DefaultTreeModel)tree.getModel(), pathToBeRemoved).restoreSelection(tree); |
| } |
| |
| @Nullable |
| public static DefaultMutableTreeNode findNodeWithObject(@NotNull final DefaultMutableTreeNode aRoot, final Object aObject) { |
| if (Comparing.equal(aRoot.getUserObject(), aObject)) { |
| return aRoot; |
| } else { |
| for (int i = 0; i < aRoot.getChildCount(); i++) { |
| final DefaultMutableTreeNode candidate = findNodeWithObject((DefaultMutableTreeNode) aRoot.getChildAt(i), aObject); |
| if (null != candidate) { |
| return candidate; |
| } |
| } |
| return null; |
| } |
| } |
| |
| @NotNull |
| public static TreePath findCommonPath(@NotNull final TreePath[] treePaths) { |
| LOG.assertTrue(areComponentsEqual(treePaths, 0)); |
| TreePath result = new TreePath(treePaths[0].getPathComponent(0)); |
| int pathIndex = 1; |
| while (areComponentsEqual(treePaths, pathIndex)) { |
| result = result.pathByAddingChild(treePaths[0].getPathComponent(pathIndex)); |
| pathIndex++; |
| } |
| return result; |
| } |
| |
| @NotNull |
| public static ActionCallback selectFirstNode(@NotNull JTree tree) { |
| TreePath selectionPath = getFirstNodePath(tree); |
| return selectPath(tree, selectionPath); |
| } |
| |
| @NotNull |
| public static TreePath getFirstNodePath(@NotNull JTree tree) { |
| final TreeModel model = tree.getModel(); |
| final Object root = model.getRoot(); |
| TreePath selectionPath = new TreePath(root); |
| if (!tree.isRootVisible() && model.getChildCount(root) > 0) { |
| selectionPath = selectionPath.pathByAddingChild(model.getChild(root, 0)); |
| } |
| return selectionPath; |
| } |
| |
| @NotNull |
| public static TreePath getFirstLeafNodePath(@NotNull JTree tree) { |
| final TreeModel model = tree.getModel(); |
| Object root = model.getRoot(); |
| TreePath selectionPath = new TreePath(root); |
| while (model.getChildCount(root) > 0) { |
| final Object child = model.getChild(root, 0); |
| selectionPath = selectionPath.pathByAddingChild(child); |
| root = child; |
| } |
| return selectionPath; |
| } |
| |
| private static void addEach(final TreeNode aRootNode, @NotNull final TreeNode aNode, @NotNull final List<TreeNode> aPathStack) { |
| aPathStack.add(aNode); |
| |
| if (aNode != aRootNode) { |
| addEach(aRootNode, aNode.getParent(), aPathStack); |
| } |
| } |
| |
| @NotNull |
| private static IndexTreePathState removeLastPathComponent(@NotNull final DefaultTreeModel model, @NotNull final TreePath pathToBeRemoved) { |
| final IndexTreePathState selectionState = new IndexTreePathState(pathToBeRemoved); |
| if (((MutableTreeNode) pathToBeRemoved.getLastPathComponent()).getParent() == null) return selectionState; |
| model.removeNodeFromParent((MutableTreeNode) pathToBeRemoved.getLastPathComponent()); |
| return selectionState; |
| } |
| |
| |
| private static boolean areComponentsEqual(@NotNull final TreePath[] paths, final int componentIndex) { |
| if (paths[0].getPathCount() <= componentIndex) return false; |
| final Object pathComponent = paths[0].getPathComponent(componentIndex); |
| for (final TreePath treePath : paths) { |
| if (treePath.getPathCount() <= componentIndex) return false; |
| if (!pathComponent.equals(treePath.getPathComponent(componentIndex))) return false; |
| } |
| return true; |
| } |
| |
| @NotNull |
| private static TreePath[] removeDuplicates(@NotNull final TreePath[] paths) { |
| final ArrayList<TreePath> result = new ArrayList<TreePath>(); |
| for (final TreePath path : paths) { |
| if (!result.contains(path)) result.add(path); |
| } |
| return result.toArray(new TreePath[result.size()]); |
| } |
| |
| @NotNull |
| public static TreePath[] selectMaximals(@Nullable final TreePath[] paths) { |
| if (paths == null) return new TreePath[0]; |
| final TreePath[] noDuplicates = removeDuplicates(paths); |
| final ArrayList<TreePath> result = new ArrayList<TreePath>(); |
| for (final TreePath path : noDuplicates) { |
| final ArrayList<TreePath> otherPaths = new ArrayList<TreePath>(Arrays.asList(noDuplicates)); |
| otherPaths.remove(path); |
| if (!isDescendants(path, otherPaths.toArray(new TreePath[otherPaths.size()]))) result.add(path); |
| } |
| return result.toArray(new TreePath[result.size()]); |
| } |
| |
| public static void sort(@NotNull final DefaultTreeModel model, final Comparator comparator) { |
| sort((DefaultMutableTreeNode) model.getRoot(), comparator); |
| } |
| |
| public static void sort(@NotNull final DefaultMutableTreeNode node, final Comparator comparator) { |
| final List<TreeNode> children = childrenToArray(node); |
| Collections.sort(children, comparator); |
| node.removeAllChildren(); |
| addChildrenTo(node, children); |
| for (int i = 0; i < node.getChildCount(); i++) { |
| sort((DefaultMutableTreeNode) node.getChildAt(i), comparator); |
| } |
| } |
| |
| public static void addChildrenTo(@NotNull final MutableTreeNode node, @NotNull final List<TreeNode> children) { |
| for (final Object aChildren : children) { |
| final MutableTreeNode child = (MutableTreeNode)aChildren; |
| node.insert(child, node.getChildCount()); |
| } |
| } |
| |
| public static boolean traverse(@NotNull final TreeNode node, @NotNull final Traverse traverse) { |
| final int childCount = node.getChildCount(); |
| for (int i = 0; i < childCount; i++){ |
| if (!traverse(node.getChildAt(i), traverse)) return false; |
| } |
| return traverse.accept(node); |
| } |
| |
| public static boolean traverseDepth(@NotNull final TreeNode node, @NotNull final Traverse traverse) { |
| if (!traverse.accept(node)) return false; |
| final int childCount = node.getChildCount(); |
| for (int i = 0; i < childCount; i++) |
| if (!traverseDepth(node.getChildAt(i), traverse)) return false; |
| return true; |
| } |
| |
| @NotNull |
| public static ActionCallback selectPath(@NotNull final JTree tree, final TreePath path) { |
| return selectPath(tree, path, true); |
| } |
| |
| @NotNull |
| public static ActionCallback selectPath(@NotNull final JTree tree, final TreePath path, boolean center) { |
| tree.makeVisible(path); |
| if (center) { |
| return showRowCentred(tree, tree.getRowForPath(path)); |
| } else { |
| final int row = tree.getRowForPath(path); |
| return showAndSelect(tree, row - ListScrollingUtil.ROW_PADDING, row + ListScrollingUtil.ROW_PADDING, row, -1); |
| } |
| } |
| |
| @NotNull |
| public static ActionCallback moveDown(@NotNull final JTree tree) { |
| final int size = tree.getRowCount(); |
| int row = tree.getLeadSelectionRow(); |
| if (row < size - 1) { |
| row++; |
| return showAndSelect(tree, row, row + 2, row, getSelectedRow(tree), false, true, true); |
| } else { |
| return new ActionCallback.Done(); |
| } |
| } |
| |
| @NotNull |
| public static ActionCallback moveUp(@NotNull final JTree tree) { |
| int row = tree.getLeadSelectionRow(); |
| if (row > 0) { |
| row--; |
| return showAndSelect(tree, row - 2, row, row, getSelectedRow(tree), false, true, true); |
| } else { |
| return new ActionCallback.Done(); |
| } |
| } |
| |
| @NotNull |
| public static ActionCallback movePageUp(@NotNull final JTree tree) { |
| final int visible = getVisibleRowCount(tree); |
| if (visible <= 0){ |
| return moveHome(tree); |
| } |
| final int decrement = visible - 1; |
| final int row = Math.max(getSelectedRow(tree) - decrement, 0); |
| final int top = getFirstVisibleRow(tree) - decrement; |
| final int bottom = top + visible - 1; |
| return showAndSelect(tree, top, bottom, row, getSelectedRow(tree)); |
| } |
| |
| @NotNull |
| public static ActionCallback movePageDown(@NotNull final JTree tree) { |
| final int visible = getVisibleRowCount(tree); |
| if (visible <= 0){ |
| return moveEnd(tree); |
| } |
| final int size = tree.getRowCount(); |
| final int increment = visible - 1; |
| final int index = Math.min(getSelectedRow(tree) + increment, size - 1); |
| final int top = getFirstVisibleRow(tree) + increment; |
| final int bottom = top + visible - 1; |
| return showAndSelect(tree, top, bottom, index, getSelectedRow(tree)); |
| } |
| |
| @NotNull |
| private static ActionCallback moveHome(@NotNull final JTree tree) { |
| return showRowCentred(tree, 0); |
| } |
| |
| @NotNull |
| private static ActionCallback moveEnd(@NotNull final JTree tree) { |
| return showRowCentred(tree, tree.getRowCount() - 1); |
| } |
| |
| @NotNull |
| private static ActionCallback showRowCentred(@NotNull final JTree tree, final int row) { |
| return showRowCentered(tree, row, true); |
| } |
| |
| @NotNull |
| public static ActionCallback showRowCentered(@NotNull final JTree tree, final int row, final boolean centerHorizontally) { |
| return showRowCentered(tree, row, centerHorizontally, true); |
| } |
| |
| @NotNull |
| public static ActionCallback showRowCentered(@NotNull final JTree tree, final int row, final boolean centerHorizontally, boolean scroll) { |
| final int visible = getVisibleRowCount(tree); |
| |
| final int top = visible > 0 ? row - (visible - 1)/ 2 : row; |
| final int bottom = visible > 0 ? top + visible - 1 : row; |
| return showAndSelect(tree, top, bottom, row, -1, false, scroll, false); |
| } |
| |
| @NotNull |
| public static ActionCallback showAndSelect(@NotNull final JTree tree, int top, int bottom, final int row, final int previous) { |
| return showAndSelect(tree, top, bottom, row, previous, false); |
| } |
| |
| @NotNull |
| public static ActionCallback showAndSelect(@NotNull final JTree tree, int top, int bottom, final int row, final int previous, boolean addToSelection) { |
| return showAndSelect(tree, top, bottom, row, previous, addToSelection, true, false); |
| } |
| |
| @NotNull |
| public static ActionCallback showAndSelect(@NotNull final JTree tree, int top, int bottom, final int row, final int previous, final boolean addToSelection, final boolean scroll) { |
| return showAndSelect(tree, top, bottom, row, previous, addToSelection, scroll, false); |
| } |
| |
| @NotNull |
| public static ActionCallback showAndSelect(@NotNull final JTree tree, int top, int bottom, final int row, final int previous, final boolean addToSelection, final boolean scroll, final boolean resetSelection) { |
| final TreePath path = tree.getPathForRow(row); |
| |
| if (path == null) return new ActionCallback.Done(); |
| |
| final int size = tree.getRowCount(); |
| if (size == 0) { |
| tree.clearSelection(); |
| return new ActionCallback.Done(); |
| } |
| if (top < 0){ |
| top = 0; |
| } |
| if (bottom >= size){ |
| bottom = size - 1; |
| } |
| |
| if (row >= tree.getRowCount()) return new ActionCallback.Done(); |
| |
| boolean okToScroll = true; |
| if (tree.isShowing()) { |
| if (!tree.isValid()) { |
| tree.validate(); |
| } |
| } else { |
| Application app = ApplicationManager.getApplication(); |
| if (app != null && app.isUnitTestMode()) { |
| okToScroll = false; |
| } |
| } |
| |
| Runnable selectRunnable = new Runnable() { |
| @Override |
| public void run() { |
| if (!tree.isRowSelected(row)) { |
| if (addToSelection) { |
| tree.getSelectionModel().addSelectionPath(tree.getPathForRow(row)); |
| } else { |
| tree.setSelectionRow(row); |
| } |
| } else if (resetSelection) { |
| if (!addToSelection) { |
| tree.setSelectionRow(row); |
| } |
| } |
| } |
| }; |
| |
| |
| if (!okToScroll) { |
| selectRunnable.run(); |
| return new ActionCallback.Done(); |
| } |
| |
| |
| final Rectangle rowBounds = tree.getRowBounds(row); |
| if (rowBounds == null) return new ActionCallback.Done(); |
| |
| Rectangle topBounds = tree.getRowBounds(top); |
| if (topBounds == null) { |
| topBounds = rowBounds; |
| } |
| |
| Rectangle bottomBounds = tree.getRowBounds(bottom); |
| if (bottomBounds == null) { |
| bottomBounds = rowBounds; |
| } |
| |
| Rectangle bounds = topBounds.union(bottomBounds); |
| bounds.x = rowBounds.x; |
| bounds.width = rowBounds.width; |
| |
| final Rectangle visible = tree.getVisibleRect(); |
| if (visible.contains(bounds)) { |
| bounds = null; |
| } else { |
| final Component comp = |
| tree.getCellRenderer().getTreeCellRendererComponent(tree, path.getLastPathComponent(), true, true, false, row, false); |
| |
| if (comp instanceof SimpleColoredComponent) { |
| final SimpleColoredComponent renderer = (SimpleColoredComponent)comp; |
| final Dimension scrollableSize = renderer.computePreferredSize(true); |
| bounds.width = scrollableSize.width; |
| } |
| } |
| |
| final ActionCallback callback = new ActionCallback(); |
| |
| |
| selectRunnable.run(); |
| |
| if (bounds != null) { |
| final Range<Integer> range = getExpandControlRange(tree, path); |
| if (range != null) { |
| int delta = bounds.x - range.getFrom().intValue(); |
| bounds.x -= delta; |
| bounds.width -= delta; |
| } |
| |
| if (visible.width < bounds.width) { |
| bounds.width = visible.width; |
| } |
| |
| if (tree instanceof Tree && !((Tree)tree).isHorizontalAutoScrollingEnabled()) { |
| bounds.x = 0; |
| } |
| |
| final Rectangle b1 = bounds; |
| final Runnable runnable = new Runnable() { |
| @Override |
| public void run() { |
| if (scroll) { |
| AbstractTreeBuilder builder = AbstractTreeBuilder.getBuilderFor(tree); |
| if (builder != null) { |
| builder.getReady(TreeUtil.class).doWhenDone(new Runnable() { |
| @Override |
| public void run() { |
| tree.scrollRectToVisible(b1); |
| } |
| }); |
| callback.setDone(); |
| } else { |
| tree.scrollRectToVisible(b1); |
| |
| Long ts = (Long)tree.getClientProperty(TREE_UTIL_SCROLL_TIME_STAMP); |
| if (ts == null) { |
| ts = 0L; |
| } |
| ts = ts.longValue() + 1; |
| tree.putClientProperty(TREE_UTIL_SCROLL_TIME_STAMP, ts); |
| |
| final long targetValue = ts.longValue(); |
| |
| SwingUtilities.invokeLater(new Runnable() { |
| @Override |
| public void run() { |
| Long actual = (Long)tree.getClientProperty(TREE_UTIL_SCROLL_TIME_STAMP); |
| if (actual == null || targetValue < actual.longValue()) return; |
| |
| if (!tree.getVisibleRect().contains(b1)) { |
| tree.scrollRectToVisible(b1); |
| } |
| callback.setDone(); |
| } |
| }); |
| } |
| } |
| callback.setDone(); |
| } |
| }; |
| |
| runnable.run(); |
| |
| } else { |
| callback.setDone(); |
| } |
| |
| return callback; |
| } |
| |
| |
| // this method returns FIRST selected row but not LEAD |
| private static int getSelectedRow(@NotNull final JTree tree) { |
| return tree.getRowForPath(tree.getSelectionPath()); |
| } |
| |
| private static int getFirstVisibleRow(@NotNull final JTree tree) { |
| final Rectangle visible = tree.getVisibleRect(); |
| int row = -1; |
| for (int i=0; i < tree.getRowCount(); i++) { |
| final Rectangle bounds = tree.getRowBounds(i); |
| if (visible.y <= bounds.y && visible.y + visible.height >= bounds.y + bounds.height) { |
| row = i; |
| break; |
| } |
| } |
| return row; |
| } |
| |
| private static int getVisibleRowCount(@NotNull final JTree tree) { |
| final Rectangle visible = tree.getVisibleRect(); |
| |
| if (visible == null) return 0; |
| |
| int count = 0; |
| for (int i=0; i < tree.getRowCount(); i++) { |
| final Rectangle bounds = tree.getRowBounds(i); |
| if (bounds == null) continue; |
| if (visible.y <= bounds.y && visible.y + visible.height >= bounds.y + bounds.height) { |
| count++; |
| } |
| } |
| return count; |
| } |
| |
| @SuppressWarnings({"HardCodedStringLiteral"}) |
| public static void installActions(@NotNull final JTree tree) { |
| tree.getActionMap().put("scrollUpChangeSelection", new AbstractAction() { |
| @Override |
| public void actionPerformed(final ActionEvent e) { |
| movePageUp(tree); |
| } |
| }); |
| tree.getActionMap().put("scrollDownChangeSelection", new AbstractAction() { |
| @Override |
| public void actionPerformed(final ActionEvent e) { |
| movePageDown(tree); |
| } |
| }); |
| tree.getActionMap().put("selectPrevious", new AbstractAction() { |
| @Override |
| public void actionPerformed(final ActionEvent e) { |
| moveUp(tree); |
| } |
| }); |
| tree.getActionMap().put("selectNext", new AbstractAction() { |
| @Override |
| public void actionPerformed(final ActionEvent e) { |
| moveDown(tree); |
| } |
| }); |
| copyAction(tree, "selectLast", "selectLastChangeLead"); |
| copyAction(tree, "selectFirst", "selectFirstChangeLead"); |
| |
| InputMap inputMap = tree.getInputMap(JComponent.WHEN_FOCUSED); |
| UIUtil.maybeInstall(inputMap, "scrollUpChangeSelection", KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, 0)); |
| UIUtil.maybeInstall(inputMap, "scrollDownChangeSelection", KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, 0)); |
| UIUtil.maybeInstall(inputMap, "selectNext", KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0)); |
| UIUtil.maybeInstall(inputMap, "selectPrevious", KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0)); |
| UIUtil.maybeInstall(inputMap, "selectLast", KeyStroke.getKeyStroke(KeyEvent.VK_END, 0)); |
| UIUtil.maybeInstall(inputMap, "selectFirst", KeyStroke.getKeyStroke(KeyEvent.VK_HOME, 0)); |
| } |
| |
| private static void copyAction(@NotNull final JTree tree, String original, String copyTo) { |
| final Action action = tree.getActionMap().get(original); |
| if (action != null) { |
| tree.getActionMap().put(copyTo, action); |
| } |
| } |
| |
| public static void collapseAll(@NotNull final JTree tree, final int keepSelectionLevel) { |
| final TreePath leadSelectionPath = tree.getLeadSelectionPath(); |
| // Collapse all |
| int row = tree.getRowCount() - 1; |
| while (row >= 0) { |
| tree.collapseRow(row); |
| row--; |
| } |
| final DefaultMutableTreeNode root = (DefaultMutableTreeNode)tree.getModel().getRoot(); |
| tree.expandPath(new TreePath(root)); |
| if (leadSelectionPath != null) { |
| final Object[] path = leadSelectionPath.getPath(); |
| final Object[] pathToSelect = new Object[path.length > keepSelectionLevel && keepSelectionLevel >= 0 ? keepSelectionLevel : path.length]; |
| System.arraycopy(path, 0, pathToSelect, 0, pathToSelect.length); |
| if (pathToSelect.length == 0) return; |
| selectPath(tree, new TreePath(pathToSelect)); |
| } |
| } |
| |
| public static void selectNode(@NotNull final JTree tree, final TreeNode node) { |
| selectPath(tree, getPathFromRoot(node)); |
| } |
| |
| public static void moveSelectedRow(@NotNull final JTree tree, final int direction){ |
| final TreePath selectionPath = tree.getSelectionPath(); |
| final DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode)selectionPath.getLastPathComponent(); |
| final DefaultMutableTreeNode parent = (DefaultMutableTreeNode)treeNode.getParent(); |
| final int idx = parent.getIndex(treeNode); |
| ((DefaultTreeModel)tree.getModel()).removeNodeFromParent(treeNode); |
| ((DefaultTreeModel)tree.getModel()).insertNodeInto(treeNode, parent, idx + direction); |
| selectNode(tree, treeNode); |
| } |
| |
| @NotNull |
| public static ArrayList<TreeNode> childrenToArray(@NotNull final TreeNode node) { |
| //ApplicationManager.getApplication().assertIsDispatchThread(); |
| final ArrayList<TreeNode> result = new ArrayList<TreeNode>(); |
| for(int i = 0; i < node.getChildCount(); i++){ |
| TreeNode child = node.getChildAt(i); |
| LOG.assertTrue(child != null); |
| result.add(child); |
| } |
| return result; |
| } |
| |
| public static void expandRootChildIfOnlyOne(@Nullable final JTree tree) { |
| if (tree == null) return; |
| final Runnable runnable = new Runnable() { |
| @Override |
| public void run() { |
| final DefaultMutableTreeNode root = (DefaultMutableTreeNode)tree.getModel().getRoot(); |
| tree.expandPath(new TreePath(new Object[]{root})); |
| if (root.getChildCount() == 1) { |
| TreeNode firstChild = root.getFirstChild(); |
| tree.expandPath(new TreePath(new Object[]{root, firstChild})); |
| } |
| } |
| }; |
| UIUtil.invokeLaterIfNeeded(runnable); |
| } |
| |
| public static void expandAll(@NotNull final JTree tree) { |
| tree.expandPath(new TreePath(tree.getModel().getRoot())); |
| int oldRowCount = 0; |
| do { |
| int rowCount = tree.getRowCount(); |
| if (rowCount == oldRowCount) break; |
| oldRowCount = rowCount; |
| for (int i = 0; i < rowCount; i++) { |
| tree.expandRow(i); |
| } |
| } |
| while (true); |
| } |
| |
| /** |
| * Expands n levels of the tree counting from the root |
| * @param tree to expand nodes of |
| * @param levels depths of the expantion |
| */ |
| public static void expand(@NotNull JTree tree, int levels) { |
| expand(tree, new TreePath(tree.getModel().getRoot()), levels); |
| } |
| |
| private static void expand(@NotNull JTree tree, @NotNull TreePath path, int levels) { |
| if (levels == 0) return; |
| tree.expandPath(path); |
| TreeNode node = (TreeNode)path.getLastPathComponent(); |
| Enumeration children = node.children(); |
| while (children.hasMoreElements()) { |
| expand(tree, path.pathByAddingChild(children.nextElement()) , levels - 1); |
| } |
| } |
| |
| @NotNull |
| public static ActionCallback selectInTree(DefaultMutableTreeNode node, boolean requestFocus, @NotNull JTree tree) { |
| return selectInTree(node, requestFocus, tree, true); |
| } |
| |
| @NotNull |
| public static ActionCallback selectInTree(@Nullable DefaultMutableTreeNode node, boolean requestFocus, @NotNull JTree tree, boolean center) { |
| if (node == null) return new ActionCallback.Done(); |
| |
| final TreePath treePath = new TreePath(node.getPath()); |
| tree.expandPath(treePath); |
| if (requestFocus) { |
| tree.requestFocus(); |
| } |
| return selectPath(tree, treePath, center); |
| } |
| |
| @NotNull |
| public static ActionCallback selectInTree(Project project, @Nullable DefaultMutableTreeNode node, boolean requestFocus, @NotNull JTree tree, boolean center) { |
| if (node == null) return new ActionCallback.Done(); |
| |
| final TreePath treePath = new TreePath(node.getPath()); |
| tree.expandPath(treePath); |
| if (requestFocus) { |
| ActionCallback result = new ActionCallback(2); |
| IdeFocusManager.getInstance(project).requestFocus(tree, true).notifyWhenDone(result); |
| selectPath(tree, treePath, center).notifyWhenDone(result); |
| return result; |
| } |
| return selectPath(tree, treePath, center); |
| } |
| |
| @NotNull |
| public static List<TreePath> collectSelectedPaths(@NotNull final JTree tree, @NotNull final TreePath treePath) { |
| final ArrayList<TreePath> result = new ArrayList<TreePath>(); |
| final TreePath[] selections = tree.getSelectionPaths(); |
| if (selections != null) { |
| for (TreePath selection : selections) { |
| if (treePath.isDescendant(selection)) { |
| result.add(selection); |
| } |
| } |
| } |
| return result; |
| } |
| |
| public static void unselect(@NotNull JTree tree, @NotNull final DefaultMutableTreeNode node) { |
| final TreePath rootPath = new TreePath(node.getPath()); |
| final TreePath[] selectionPaths = tree.getSelectionPaths(); |
| if (selectionPaths != null) { |
| for (TreePath selectionPath : selectionPaths) { |
| if (selectionPath.getPathCount() > rootPath.getPathCount() && rootPath.isDescendant(selectionPath)) { |
| tree.removeSelectionPath(selectionPath); |
| } |
| } |
| } |
| } |
| |
| @Nullable |
| public static Range<Integer> getExpandControlRange(@NotNull final JTree aTree, @Nullable final TreePath path) { |
| TreeModel treeModel = aTree.getModel(); |
| |
| final BasicTreeUI basicTreeUI = (BasicTreeUI)aTree.getUI(); |
| Icon expandedIcon = basicTreeUI.getExpandedIcon(); |
| |
| |
| Range<Integer> box = null; |
| if (path != null && !treeModel.isLeaf(path.getLastPathComponent())) { |
| int boxWidth; |
| Insets i = aTree.getInsets(); |
| |
| if (expandedIcon != null) { |
| boxWidth = expandedIcon.getIconWidth(); |
| } |
| else { |
| boxWidth = 8; |
| } |
| |
| int boxLeftX = i != null ? i.left : 0; |
| |
| boolean leftToRight = aTree.getComponentOrientation().isLeftToRight(); |
| int depthOffset = getDepthOffset(aTree); |
| int totalChildIndent = basicTreeUI.getLeftChildIndent() + basicTreeUI.getRightChildIndent(); |
| |
| if (leftToRight) { |
| boxLeftX += (path.getPathCount() + depthOffset - 2) * totalChildIndent + basicTreeUI.getLeftChildIndent() - |
| boxWidth / 2; |
| } |
| int boxRightX = boxLeftX + boxWidth; |
| |
| box = new Range<Integer>(boxLeftX, boxRightX); |
| } |
| return box; |
| } |
| |
| public static int getDepthOffset(@NotNull JTree aTree) { |
| if (aTree.isRootVisible()) { |
| return aTree.getShowsRootHandles() ? 1 : 0; |
| } |
| else { |
| return aTree.getShowsRootHandles() ? 0 : -1; |
| } |
| } |
| |
| @NotNull |
| public static RelativePoint getPointForSelection(@NotNull JTree aTree) { |
| final int[] rows = aTree.getSelectionRows(); |
| if (rows == null || rows.length == 0) { |
| return RelativePoint.getCenterOf(aTree); |
| } |
| return getPointForRow(aTree, rows[rows.length - 1]); |
| } |
| |
| @NotNull |
| public static RelativePoint getPointForRow(@NotNull JTree aTree, int aRow) { |
| return getPointForPath(aTree, aTree.getPathForRow(aRow)); |
| } |
| |
| @NotNull |
| public static RelativePoint getPointForPath(@NotNull JTree aTree, TreePath path) { |
| final Rectangle rowBounds = aTree.getPathBounds(path); |
| rowBounds.x += 20; |
| return getPointForBounds(aTree, rowBounds); |
| } |
| |
| @NotNull |
| public static RelativePoint getPointForBounds(JComponent aComponent, @NotNull final Rectangle aBounds) { |
| return new RelativePoint(aComponent, new Point(aBounds.x, (int)aBounds.getMaxY())); |
| } |
| |
| public static boolean isOverSelection(@NotNull final JTree tree, @NotNull final Point point) { |
| TreePath path = tree.getPathForLocation(point.x, point.y); |
| return path != null && tree.getSelectionModel().isPathSelected(path); |
| } |
| |
| public static void dropSelectionButUnderPoint(@NotNull JTree tree, @NotNull Point treePoint) { |
| final TreePath toRetain = tree.getPathForLocation(treePoint.x, treePoint.y); |
| if (toRetain == null) return; |
| |
| TreePath[] selection = tree.getSelectionModel().getSelectionPaths(); |
| selection = selection == null ? new TreePath[0] : selection; |
| for (TreePath each : selection) { |
| if (toRetain.equals(each)) continue; |
| tree.getSelectionModel().removeSelectionPath(each); |
| } |
| } |
| |
| public interface Traverse{ |
| boolean accept(Object node); |
| } |
| |
| public static void ensureSelection(@NotNull JTree tree) { |
| final TreePath[] paths = tree.getSelectionPaths(); |
| |
| if (paths != null) { |
| for (TreePath each : paths) { |
| if (tree.getRowForPath(each) >= 0 && tree.isVisible(each)) { |
| return; |
| } |
| } |
| } |
| |
| for (int eachRow = 0; eachRow < tree.getRowCount(); eachRow++) { |
| TreePath eachPath = tree.getPathForRow(eachRow); |
| if (eachPath != null && tree.isVisible(eachPath)) { |
| tree.setSelectionPath(eachPath); |
| break; |
| } |
| } |
| } |
| |
| public static int indexedBinarySearch(@NotNull TreeNode parent, @NotNull TreeNode key, Comparator comparator) { |
| int low = 0; |
| int high = parent.getChildCount() - 1; |
| |
| while (low <= high) { |
| int mid = (low + high) / 2; |
| TreeNode treeNode = parent.getChildAt(mid); |
| int cmp = comparator.compare(treeNode, key); |
| if (cmp < 0) { |
| low = mid + 1; |
| } |
| else if (cmp > 0) { |
| high = mid - 1; |
| } |
| else { |
| return mid; // key found |
| } |
| } |
| return -(low + 1); // key not found |
| } |
| } |