diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/tree/CategoryNode.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/tree/CategoryNode.java index b81566f8f9..70339f27b7 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/tree/CategoryNode.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/tree/CategoryNode.java @@ -91,6 +91,10 @@ public class CategoryNode extends DataTypeTreeNode { return -1; // CategoryNodes are always come before ****everything else**** } + @Override + public int hashCode() { + return name.hashCode(); + } /** * @see java.lang.Object#equals(java.lang.Object) */ @@ -164,8 +168,14 @@ public class CategoryNode extends DataTypeTreeNode { } CategoryNode node = new CategoryNode(newCategory, filterState); - List allChildrenList = getChildren(); - int index = Collections.binarySearch(allChildrenList, node); + List children = getChildren(); + int index = Collections.binarySearch(children, node); + if (index >= 0) { + // if a node with that name exists, then we don't need to add one for the new category + if (node.getName().equals(children.get(index).getName())) { + return; + } + } if (index < 0) { index = -index - 1; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/util/DataTypeTreeDeleteTask.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/util/DataTypeTreeDeleteTask.java index 5a98de8b3d..b31a210450 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/util/DataTypeTreeDeleteTask.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/util/DataTypeTreeDeleteTask.java @@ -31,13 +31,17 @@ import ghidra.util.task.TaskMonitor; public class DataTypeTreeDeleteTask extends Task { + // if the total number of nodes is small, we won't need to collapse the tree before deleting + // the nodes to avoid excess tree events + private static final int NODE_COUNT_FOR_COLLAPSING_TREE = 100; private Map> nodesByArchive; private DataTypeManagerPlugin plugin; + private int nodeCount; public DataTypeTreeDeleteTask(DataTypeManagerPlugin plugin, List nodes) { super("Delete Nodes", true, true, true); this.plugin = plugin; - + nodeCount = nodes.size(); nodes = filterList(nodes); nodesByArchive = groupNodeByArchive(nodes); @@ -105,7 +109,9 @@ public class DataTypeTreeDeleteTask extends Task { DataTypeArchiveGTree tree = provider.getGTree(); GTreeState treeState = tree.getTreeState(); try { - collapseArchives(tree); + if (nodeCount > NODE_COUNT_FOR_COLLAPSING_TREE) { + collapseArchives(tree); + } Set>> entries = nodesByArchive.entrySet(); for (Entry> entry : entries) { diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/GTreeNode.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/GTreeNode.java index cb520e2596..86889f0e37 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/GTreeNode.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/GTreeNode.java @@ -16,7 +16,6 @@ package docking.widgets.tree; import java.util.*; -import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Stream; import javax.swing.Icon; @@ -36,15 +35,42 @@ import util.CollectionUtils; *

* All methods in this class that mutate the children node must perform that operation in * the swing thread. + *

+ * To create a simple GTreeNode where nodes will be added immediately + * using the addNode() methods, simply extend this class and implement the following methods: + *

    + *
  • getName()
  • + *
  • getToolTip()
  • + *
  • isLeaf()
  • + *
  • getIcon()
  • + *
+ * + * Usage Notes: + *
    + *
  • The equals() method: The GTree has the ability to remember expanded and + * selected states. This will only work if the nodes in the saved state can be matched + * with the nodes in the GTree. Java will do this by using the equals() method. + * There is a potential problem with this usage. If nodes within the GTree get rebuilt ( + * i.e., new nodes are created), then, by default, the expanded and selected state + * feature will be unable to find the correct nodes, since the default equals() + * method on GTreeNode performs a comparison based upon instances. To fix this problem, + * the {@link #equals()} method has been implemented such that nodes are considered equal if they have + * the same name. The {@link #hashCode()} method will return the hash of the name. The name + * attribute was chosen because it should be the most unique and descriptive piece of information + * available in a generic GTreeNode. + *


    + *

    + * There are two situations where the {@link #equals(Object)} and {@link #hashCode()} using the + * name are insufficient. One is if your tree implementation allows nodes with the same name + * with the same parent. The other possible situation is if your nodes can change their name, + * which may confuse the tree. If either of these situations apply, just override the + * {@link #equals(Object)} and {@link #hashCode()} methods to make them more robust. + *


    + *

  • + *
*/ + public abstract class GTreeNode extends CoreGTreeNode implements Comparable { - private static AtomicLong NEXT_ID = new AtomicLong(); - - private final long id; - - protected GTreeNode() { - id = NEXT_ID.incrementAndGet(); - } @Override protected List generateChildren() { @@ -166,10 +192,15 @@ public abstract class GTreeNode extends CoreGTreeNode implements Comparable