Fixed open program dialog to not repeatedly load the root node

This commit is contained in:
dragonmacher 2025-02-25 09:14:56 -05:00
parent 2eff37f655
commit 1d5da6dae1
9 changed files with 268 additions and 165 deletions

View file

@ -57,8 +57,14 @@ import resources.Icons;
/**
* Class for creating a JTree that supports filtering, threading, and a progress bar.
* <p>
* Note: when calling methods on this class to select nodes, if those nodes are threaded, or extend
* from {@link GTreeSlowLoadingNode}, then you must first expand the paths you wish to select. You
* can do this by calling {@link #expandAndSelectPaths(List)}. The various select methods of this
* class will not expand nodes, but they will trigger children to be loaded. If those nodes are not
* threaded, then the tree will add and expand the children by default. When using threaded nodes,
* the delay in loading prevents the tree from correctly expanding the paths.
*/
public class GTree extends JPanel implements BusyListener {
private static final Color BACKGROUND = new GColor("color.bg.tree");
private AutoScrollTree tree;
@ -475,26 +481,36 @@ public class GTree extends JPanel implements BusyListener {
}
public void expandPath(GTreeNode node) {
expandPaths(new TreePath[] { node.getTreePath() });
expandPaths(List.of(node.getTreePath()));
}
public void expandPath(TreePath path) {
expandPaths(new TreePath[] { path });
expandPaths(List.of(path));
}
public void expandPaths(TreePath[] paths) {
runTask(new GTreeExpandPathsTask(this, Arrays.asList(paths)));
expandPaths(Arrays.asList(paths));
}
public void expandPaths(List<TreePath> pathsList) {
TreePath[] treePaths = pathsList.toArray(new TreePath[pathsList.size()]);
expandPaths(treePaths);
public void expandPaths(List<TreePath> paths) {
runTask(new GTreeExpandPathsTask(this, paths));
}
public void clearSelectionPaths() {
runTask(new GTreeClearSelectionTask(this, tree));
}
/**
* Expands and then selects the given paths. You must use this method if your tree is using
* {@link GTreeSlowLoadingNode}s. Otherwise, if the given paths are not expanded, then the
* select will not work. More info at the class javadoc.
*
* @param paths the paths
*/
public void expandAndSelectPaths(List<TreePath> paths) {
setSelectionPaths(paths, true, EventOrigin.API_GENERATED);
}
public void setSelectedNode(GTreeNode node) {
setSelectionPaths(new TreePath[] { node.getTreePath() });
}
@ -568,8 +584,24 @@ public class GTree extends JPanel implements BusyListener {
runTask(new GTreeSelectNodeByNameTask(this, tree, namePath, EventOrigin.API_GENERATED));
}
public void setSelectionPaths(TreePath[] path, EventOrigin origin) {
runTask(new GTreeSelectPathsTask(this, tree, Arrays.asList(path), origin));
public void setSelectionPaths(TreePath[] paths, EventOrigin origin) {
setSelectionPaths(Arrays.asList(paths), false, origin);
}
/**
* Selects the given paths, expanding them first if requested.
*
* @param paths the paths to select
* @param expandPaths true to expand the paths first; this is only needed for multi-threaded
* nodes. Non-threaded nodes should use false, as it increase performance.
* @param origin the event type; use {@link EventOrigin#API_GENERATED} if unsure
*/
public void setSelectionPaths(List<TreePath> paths, boolean expandPaths, EventOrigin origin) {
if (expandPaths) {
expandPaths(paths);
}
runTask(new GTreeSelectPathsTask(this, tree, paths, origin));
}
public boolean isCollapsed(TreePath path) {

View file

@ -4,9 +4,9 @@
* 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.
@ -15,10 +15,12 @@
*/
package docking.widgets.tree;
import java.util.Collections;
import java.util.List;
import java.util.*;
import org.apache.commons.collections4.IteratorUtils;
import docking.widgets.tree.internal.InProgressGTreeNode;
import ghidra.util.Msg;
import ghidra.util.Swing;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
@ -124,4 +126,14 @@ public abstract class GTreeSlowLoadingNode extends GTreeLazyNode {
}
}
}
@Override
public Iterator<GTreeNode> iterator(boolean depthFirst) {
if (Swing.isSwingThread()) {
Msg.warn(this, "Threaded tree nodes cannot be iterated on the Swing thread. " +
"Change the call to this method to be run in a background task");
return IteratorUtils.emptyIterator();
}
return super.iterator(depthFirst);
}
}

View file

@ -4,9 +4,9 @@
* 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.
@ -17,8 +17,7 @@ package docking.widgets.tree;
import static org.junit.Assert.*;
import java.util.ArrayList;
import java.util.List;
import java.util.*;
import javax.swing.*;
@ -151,7 +150,27 @@ public class GTreeSlowLoadingNodeTest extends AbstractDockingTest {
waitForTree();
Swing.runNow(() -> children = nonLeaf1.getChildren());
assertTrue("Did not find children for: " + nonLeaf1, nonLeaf1.getChildCount() > 1);
}
@Test
public void testIterator() {
gTree.setRootNode(new TestRootNode(5000));
waitForTree();
GTreeNode rootNode = gTree.getModelRoot();
GTreeNode slowChild = rootNode.getChild(0); // slow; threaded
assertNotNull(slowChild);
Swing.runNow(() -> children = slowChild.getChildren());
assertEquals(1, children.size());
assertTrue(children.get(0) instanceof InProgressGTreeNode);
Iterator<GTreeNode> it = runSwing(() -> slowChild.iterator(true));
assertFalse(it.hasNext()); // empty when called on the Swing thread
it = slowChild.iterator(true);
assertTrue(it.hasNext()); // not empty on non-Swing thread
}
//==================================================================================================
@ -235,11 +254,11 @@ public class GTreeSlowLoadingNodeTest extends AbstractDockingTest {
private class TestRootNode extends GTreeNode {
TestRootNode(int loadDelayMillis) {
List<GTreeNode> children = new ArrayList<>();
children.add(new TestSlowLoadingNode(loadDelayMillis, 1));
children.add(new TestLeafNode());
children.add(new TestSlowLoadingNode(loadDelayMillis, 1));
setChildren(children);
List<GTreeNode> newChildren = new ArrayList<>();
newChildren.add(new TestSlowLoadingNode(loadDelayMillis, 1));
newChildren.add(new TestLeafNode());
newChildren.add(new TestSlowLoadingNode(loadDelayMillis, 1));
setChildren(newChildren);
}
@Override
@ -289,18 +308,18 @@ public class GTreeSlowLoadingNodeTest extends AbstractDockingTest {
}
int childCount = getRandomInt(MIN_CHILD_COUNT, MAX_CHILD_COUNT);
List<GTreeNode> children = new ArrayList<>();
List<GTreeNode> newChildren = new ArrayList<>();
for (int i = 0; i < childCount; i++) {
monitor.checkCancelled();
int value = getRandomInt(0, 1);
if (value == 0) {
children.add(new TestSlowLoadingNode(loadDelayMillis, depth + 1));
newChildren.add(new TestSlowLoadingNode(loadDelayMillis, depth + 1));
}
else {
children.add(new TestLeafNode());
newChildren.add(new TestLeafNode());
}
}
return children;
return newChildren;
}
@Override