ActionCallback (final @NotNull 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 ActionCallback.DONE; final int size = tree.getRowCount(); if (size == 0) { tree.clearSelection(); return ActionCallback.DONE; } if (top < 0){ top = 0; } if (bottom >= size){ bottom = size - 1; } if (row >= tree.getRowCount()) return 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 = () -> { 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 || !scroll) { selectRunnable.run(); return ActionCallback.DONE; } final Rectangle rowBounds = tree.getRowBounds(row); if (rowBounds == null) return 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)) { selectRunnable.run(); return ActionCallback.DONE; } final Component comp = tree.getCellRenderer().getTreeCellRendererComponent(tree, path.getLastPathComponent(), true, true, false, row, false); if (comp instanceof SimpleColoredComponent renderer) { final Dimension scrollableSize = renderer.computePreferredSize(true); bounds.width = scrollableSize.width; } final ActionCallback callback = new ActionCallback(); selectRunnable.run(); 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 = tree.getVisibleRect().x; } if (LOG.isTraceEnabled()) LOG.debug("tree scroll: ", path); tree.scrollRectToVisible(bounds); // try to scroll later when the tree is ready Object property = tree.getClientProperty(TREE_UTIL_SCROLL_TIME_STAMP); long stamp = property instanceof Long ? (Long)property + 1L : Long.MIN_VALUE; tree.putClientProperty(TREE_UTIL_SCROLL_TIME_STAMP, stamp); // store relative offset because the row can be moved during the tree updating int offset = rowBounds.y - bounds.y; AbstractTreeBuilder builder = AbstractTreeBuilder.getBuilderFor(tree); scrollToVisible(tree, path, bounds, offset, stamp, callback::setDone, builder, 3); return callback; }