Skip to content

Commit c21bfa2

Browse files
committed
Merge remote-tracking branch 'origin/ghidravore_gtree_improvements'
2 parents decb362 + 4cab252 commit c21bfa2

File tree

5 files changed

+145
-9
lines changed

5 files changed

+145
-9
lines changed

Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/GTreeLazyNode.java

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@
2222
* Also, children of this node can be unloaded by calling {@link #unloadChildren()}. This
2323
* can be used by nodes in large trees to save memory by unloading children that are no longer
2424
* in the current tree view (collapsed). Of course, that decision would need to be balanced
25-
* against the extra time to reload the nodes in the event that a filter is applied.
25+
* against the extra time to reload the nodes in the event that a filter is applied. Also, if
26+
* some external event occurs that changes the set of children for a GTreeLazyNode, you can call
27+
* {@link #reload()} to refresh the node's children.
2628
*/
2729
public abstract class GTreeLazyNode extends GTreeNode {
2830

@@ -36,13 +38,29 @@ public abstract class GTreeLazyNode extends GTreeNode {
3638
/**
3739
* Sets this lazy node back to the "unloaded" state such that if
3840
* its children are accessed, it will reload its children as needed.
41+
* NOTE: This method does not trigger a call to {@link #fireNodeChanged(GTreeNode, GTreeNode)}
42+
* because doing so may trigger a call from the JTree that will immediately cause the node
43+
* to reload its children. If that is the effect you want, call {@link #reload()}.
3944
*/
4045
public void unloadChildren() {
4146
if (isLoaded()) {
4247
doSetChildren(null);
4348
}
4449
}
4550

51+
/**
52+
* Tells this node that its children are stale and that it needs to regenerate them. This will
53+
* unload any existing children and call {@link #fireNodeStructureChanged(GTreeNode)}, which will
54+
* inform the JTree that this node has changed. Then, when the JTree queries this node for
55+
* its children, the {@link #generateChildren()} will get called to populate the node.
56+
*/
57+
public void reload() {
58+
if (isLoaded()) {
59+
unloadChildren();
60+
fireNodeStructureChanged(this);
61+
}
62+
}
63+
4664
@Override
4765
public void addNode(GTreeNode node) {
4866
if (isLoaded()) {
@@ -66,9 +84,7 @@ public void addNodes(List<GTreeNode> nodes) {
6684

6785
@Override
6886
public void removeAll() {
69-
if (isLoaded()) {
70-
unloadChildren();
71-
}
87+
reload();
7288
}
7389

7490
@Override

Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/GTreeNode.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,42 @@ public void fireNodeChanged(GTreeNode parentNode, GTreeNode node) {
441441
Swing.runNow(() -> doFireNodeChanged());
442442
}
443443

444+
/**
445+
* Convenience method for expanding (opening) this node in the tree. If this node is not
446+
* currently attached to a visible tree, then this call does nothing
447+
*/
448+
public void expand() {
449+
GTree tree = getTree();
450+
if (tree != null) {
451+
tree.expandPath(this);
452+
}
453+
}
454+
455+
/**
456+
* Convenience method for collapsing (closing) this node in the tree. If this node is not
457+
* currently attached to a visible tree, then this call does nothing
458+
*/
459+
public void collapse() {
460+
GTree tree = getTree();
461+
if (tree != null) {
462+
tree.collapseAll(this);
463+
}
464+
}
465+
466+
/**
467+
* Convenience method determining if this node is expanded in a tree. If the node is not
468+
* currently attached to a visible tree, then this call returns false
469+
*
470+
* @return true if the node is expanded in a currently visible tree.
471+
*/
472+
public boolean isExpanded() {
473+
GTree tree = getTree();
474+
if (tree != null) {
475+
return tree.isExpanded(this.getTreePath());
476+
}
477+
return false;
478+
}
479+
444480
private GTreeNode[] getPathToRoot(GTreeNode node, int depth) {
445481
GTreeNode[] returnNodes;
446482

Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/GTreeRestoreTreeStateTask.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ public void run(TaskMonitor monitor) {
5656
monitor.setMessage("Restoring tree selection state");
5757
selectPathsInThisTask(state, monitor, true);
5858

59-
// this allows some tress to perform cleanup
59+
// this allows some trees to perform cleanup
6060
tree.expandedStateRestored(monitor);
6161
tree.clearFilterRestoreState();
6262
}

Ghidra/Framework/Docking/src/test.slow/java/docking/widgets/tree/GTreeTest.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,25 @@ public GTreeFilter getFilter() {
187187
assertNull("Found a node in the tree that should have been filtered out", node2);
188188
}
189189

190+
@Test
191+
public void testExpandCollapseNode() {
192+
GTreeNode node = findNodeInTree(NonLeafWithOneLevelOfChildrenNodeB.class.getSimpleName());
193+
assertTrue(!node.isExpanded());
194+
195+
node.expand();
196+
waitForTree();
197+
assertTrue(node.isExpanded());
198+
199+
node.collapse();
200+
waitForTree();
201+
assertTrue(!node.isExpanded());
202+
203+
GTreeNode root = node.getRoot();
204+
root.collapse();
205+
assertTrue(!root.isExpanded());
206+
assertTrue(gTree.getExpandedPaths().isEmpty());
207+
}
208+
190209
@Test
191210
public void testChangeFilterSettingsWithFilterTextInPlace() {
192211

Ghidra/Framework/Docking/src/test/java/docking/widgets/tree/GTreeNodeTest.java

Lines changed: 69 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,23 @@
2525
import org.junit.Before;
2626
import org.junit.Test;
2727

28+
import docking.test.AbstractDockingTest;
2829
import docking.widgets.tree.support.GTreeFilter;
30+
import ghidra.util.Swing;
2931
import ghidra.util.exception.CancelledException;
3032
import ghidra.util.task.TaskMonitor;
3133

34+
/**
35+
* Note: This test does not extend {@link AbstractDockingTest}. Extending that class sets up
36+
* {@link Swing#runNow(Runnable)} methods so that they actually run on the swing thread; otherwise
37+
* they run on the calling thread. Normally GTreeNode test would need that because the fire event
38+
* calls normally check that the events are being sent on the swing thread. In this file, all the
39+
* tests use {@link TestNode} or {@link LazyTestNode} which override the event sending methods to
40+
* instead put the events in a list so that the test can check that the correct events were generated.
41+
* Since the methods that check for being on the swing thread are overridden, we can get away with
42+
* not extending {@link AbstractDockingTest} and this allows the test to run about 100 times faster.
43+
*/
44+
3245
public class GTreeNodeTest {
3346
private List<TestEvent> events = new ArrayList<>();
3447
private GTreeNode root;
@@ -353,10 +366,10 @@ public void testIsRoot() {
353366
@Test
354367
public void testDispose() {
355368
root.dispose();
356-
assertTrue(!root.isLoaded());
369+
assertFalse(root.isLoaded());
357370
assertNull(node1.getParent());
358371
assertNull(node1_0.getParent());
359-
assertTrue(!node1.isLoaded());
372+
assertFalse(node1.isLoaded());
360373
}
361374

362375
@Test
@@ -385,11 +398,35 @@ public void testLoadAllOnSimpleTree() throws CancelledException {
385398
}
386399

387400
@Test
388-
public void testLoadAllOnLazyTre() throws CancelledException {
389-
GTreeNode node = new LazyGTestNode("test", 2);
401+
public void testLoadAllOnLazyTree() throws CancelledException {
402+
GTreeNode node = new LazyTestNode("test", 2);
390403
assertEquals(13, node.loadAll(TaskMonitor.DUMMY));
391404
}
392405

406+
@Test
407+
public void testUnloadOnLazyNode() throws CancelledException {
408+
GTreeLazyNode node = new LazyTestNode("test", 2);
409+
node.loadAll(TaskMonitor.DUMMY);
410+
assertTrue(node.isLoaded());
411+
412+
events.clear();
413+
node.unloadChildren();
414+
assertFalse(node.isLoaded());
415+
assertTrue(events.isEmpty());
416+
}
417+
418+
@Test
419+
public void testReloadOnLazyNode() throws CancelledException {
420+
GTreeLazyNode node = new LazyTestNode("test", 2);
421+
node.loadAll(TaskMonitor.DUMMY);
422+
assertTrue(node.isLoaded());
423+
424+
events.clear();
425+
node.reload();
426+
assertFalse(node.isLoaded());
427+
assertFalse(events.isEmpty());
428+
}
429+
393430
@Test
394431
public void testStreamDepthFirst() {
395432
List<GTreeNode> collect = root.stream(true).collect(Collectors.toList());
@@ -454,6 +491,33 @@ public boolean showFilterMatches() {
454491

455492
}
456493

494+
private class LazyTestNode extends LazyGTestNode {
495+
496+
LazyTestNode(String name, int depth) {
497+
super(name, depth);
498+
}
499+
500+
@Override
501+
public void doFireNodeStructureChanged() {
502+
events.add(new TestEvent(EventType.STRUCTURE_CHANGED, null, this, -1));
503+
}
504+
505+
@Override
506+
public void doFireNodeChanged() {
507+
events.add(new TestEvent(EventType.NODE_CHANGED, getParent(), this, -1));
508+
}
509+
510+
@Override
511+
protected void doFireNodeAdded(GTreeNode newNode) {
512+
events.add(new TestEvent(EventType.NODE_ADDED, this, newNode, -1));
513+
}
514+
515+
@Override
516+
protected void doFireNodeRemoved(GTreeNode removedNode, int index) {
517+
events.add(new TestEvent(EventType.NODE_REMOVED, this, removedNode, -1));
518+
}
519+
}
520+
457521
private class TestNode extends GTestNode {
458522
TestNode(String name) {
459523
super(name);
@@ -480,6 +544,7 @@ protected void doFireNodeRemoved(GTreeNode removedNode, int index) {
480544
}
481545
}
482546

547+
483548
enum EventType {
484549
STRUCTURE_CHANGED, NODE_CHANGED, NODE_ADDED, NODE_REMOVED
485550
}

0 commit comments

Comments
 (0)