diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/GTree.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/GTree.java index 20fc91cc7c..ff74e5e16f 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/GTree.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/GTree.java @@ -107,6 +107,7 @@ public class GTree extends JPanel implements BusyListener { private GTreeFilter filter; private GTreeFilterProvider filterProvider; private SwingUpdateManager filterUpdateManager; + private Set ignoredNodes = new HashSet<>(); /** * Creates a GTree with the given root node. The created GTree will use a threaded model @@ -241,6 +242,7 @@ public class GTree extends JPanel implements BusyListener { } public void dispose() { + ignoredNodes.clear(); filterUpdateManager.dispose(); worker.dispose(); @@ -269,11 +271,23 @@ public class GTree extends JPanel implements BusyListener { } public void filterChanged() { + ignoredNodes.clear(); + updateModelFilter(); + } + + public void ignoreFilter(GTreeNode node) { + ignoredNodes.add(node); updateModelFilter(); } protected void updateModelFilter() { filter = filterProvider.getFilter(); + if (!ignoredNodes.isEmpty()) { + filter = new IgnoredNodesGtreeFilter(filter, ignoredNodes); + } + + // TODO why? + filterUpdateManager.stop(); if (lastFilterTask != null) { // it is safe to repeatedly call cancel @@ -964,10 +978,16 @@ public class GTree extends JPanel implements BusyListener { // the Swing thread. To deal with this, we use a construct that will run our request // once the given node has been added to the parent. // - BooleanSupplier isReady = () -> parent.getChild(childName) != null; + GTreeNode modelParent = getModelNodeForPath(parent.getTreePath()); + BooleanSupplier isReady = () -> modelParent.getChild(childName) != null; int expireMs = 3000; ExpiringSwingTimer.runWhen(isReady, expireMs, () -> { - runTask(new GTreeStartEditingTask(GTree.this, tree, parent, childName)); + if (isFiltered()) { + GTreeNode child = modelParent.getChild(childName); + ignoreFilter(child); + updateModelFilter(); + } + runTask(new GTreeStartEditingTask(GTree.this, tree, modelParent, childName)); }); } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/GTreeTask.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/GTreeTask.java index 58078d9c53..d595a81d17 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/GTreeTask.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/GTreeTask.java @@ -18,6 +18,7 @@ package docking.widgets.tree; import javax.swing.JTree; import javax.swing.tree.TreePath; +import ghidra.util.Msg; import ghidra.util.SystemUtilities; import ghidra.util.task.TaskMonitor; import ghidra.util.worker.PriorityJob; @@ -61,7 +62,9 @@ public abstract class GTreeTask extends PriorityJob { // note: call this on the Swing thread, since the Swing thread maintains the node state // (we have seen errors where the tree will return nodes that are in the process // of being disposed) + Msg.debug(this, "before translate: " + path); GTreeNode nodeForPath = SystemUtilities.runSwingNow(() -> tree.getViewNodeForPath(path)); + Msg.debug(this, "After translate, node = " + nodeForPath); if (nodeForPath != null) { return nodeForPath.getTreePath(); } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/support/IgnoredNodesGtreeFilter.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/support/IgnoredNodesGtreeFilter.java new file mode 100644 index 0000000000..3e6e22d0a4 --- /dev/null +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/support/IgnoredNodesGtreeFilter.java @@ -0,0 +1,48 @@ +/* ### + * IP: GHIDRA + * + * 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 docking.widgets.tree.support; + +import java.util.Set; + +import docking.widgets.tree.GTreeNode; + +/** + * GTreeFilter that allows for some nodes that are never filtered out. + */ +public class IgnoredNodesGtreeFilter implements GTreeFilter { + + private GTreeFilter filter; + private Set ignoredNodes; + + public IgnoredNodesGtreeFilter(GTreeFilter filter, Set ignoredNodes) { + this.filter = filter; + this.ignoredNodes = ignoredNodes; + } + + @Override + public boolean acceptsNode(GTreeNode node) { + if (ignoredNodes.contains(node)) { + return true; + } + return filter.acceptsNode(node); + } + + @Override + public boolean showFilterMatches() { + return filter.showFilterMatches(); + } + +} diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/tasks/GTreeStartEditingTask.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/tasks/GTreeStartEditingTask.java index 3c41a690e9..a4d6944400 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/tasks/GTreeStartEditingTask.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/tasks/GTreeStartEditingTask.java @@ -15,8 +15,7 @@ */ package docking.widgets.tree.tasks; -import java.util.HashSet; -import java.util.Set; +import java.util.*; import javax.swing.CellEditor; import javax.swing.JTree; @@ -30,23 +29,22 @@ import ghidra.util.SystemUtilities; import ghidra.util.exception.AssertException; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; -import util.CollectionUtils; public class GTreeStartEditingTask extends GTreeTask { - private final GTreeNode parent; + private final GTreeNode modelParent; private final String childName; private GTreeNode editNode; public GTreeStartEditingTask(GTree gTree, JTree jTree, GTreeNode parent, String childName) { super(gTree); - this.parent = parent; + this.modelParent = parent; this.childName = childName; } public GTreeStartEditingTask(GTree gTree, JTree jTree, GTreeNode editNode) { super(gTree); - this.parent = editNode.getParent(); + this.modelParent = tree.getModelNodeForPath(editNode.getParent().getTreePath()); this.childName = editNode.getName(); this.editNode = editNode; } @@ -67,17 +65,16 @@ public class GTreeStartEditingTask extends GTreeTask { } private void edit() { - + GTreeNode viewParent = tree.getViewNodeForPath(modelParent.getTreePath()); if (editNode == null) { - editNode = parent.getChild(childName); + editNode = viewParent.getChild(childName); if (editNode == null) { Msg.debug(this, "Can't find node \"" + childName + "\" to edit."); return; } } - TreePath path = editNode.getTreePath(); - final Set childrenBeforeEdit = new HashSet<>(parent.getChildren()); + final Set childrenBeforeEdit = new HashSet<>(viewParent.getChildren()); final CellEditor cellEditor = tree.getCellEditor(); cellEditor.addCellEditorListener(new CellEditorListener() { @@ -89,8 +86,10 @@ public class GTreeStartEditingTask extends GTreeTask { @Override public void editingStopped(ChangeEvent e) { + String newName = Objects.toString(cellEditor.getCellEditorValue()); cellEditor.removeCellEditorListener(this); - SystemUtilities.runSwingLater(this::reselectNodeHandlingPotentialChildChange); + SystemUtilities + .runSwingLater(() -> reselectNodeHandlingPotentialChildChange(newName)); } /** @@ -103,12 +102,12 @@ public class GTreeStartEditingTask extends GTreeTask { */ private void reselectNode() { String newName = editNode.getName(); - GTreeNode newChild = parent.getChild(newName); - if (newChild == null) { + GTreeNode newModelChild = modelParent.getChild(newName); + + if (newModelChild == null) { throw new AssertException("Unable to find new node by name: " + newName); } - - tree.setSelectedNode(newChild); + tree.setSelectedNode(tree.getViewNodeForPath(newModelChild.getTreePath())); } /** @@ -125,25 +124,25 @@ public class GTreeStartEditingTask extends GTreeTask { * the state of the edited node's parent, both before and after * the edit. */ - private void reselectNodeHandlingPotentialChildChange() { - SystemUtilities.runSwingLater(this::doReselectNodeHandlingPotentialChildChange); + private void reselectNodeHandlingPotentialChildChange(String newName) { + SystemUtilities + .runSwingLater(() -> doReselectNodeHandlingPotentialChildChange(newName)); } - private void doReselectNodeHandlingPotentialChildChange() { - Set childrenAfterEdit = new HashSet<>(parent.getChildren()); - if (childrenAfterEdit.equals(childrenBeforeEdit)) { - reselectNode(); // default re-select--the original child is still there - return; - } + private void doReselectNodeHandlingPotentialChildChange(String newName) { + GTreeNode newModelChild = modelParent.getChild(newName); + List children = modelParent.getChildren(); + Msg.debug(this, children.toString()); - // we have to figure out the new node to select - childrenAfterEdit.removeAll(childrenBeforeEdit); - if (childrenAfterEdit.size() != 1) { - return; // no way for us to figure out the correct child to edit - } + if (newModelChild != null) { + tree.ignoreFilter(newModelChild); - GTreeNode newChild = CollectionUtils.any(childrenAfterEdit); - tree.setSelectedNode(newChild); + tree.setSelectedNode(newModelChild); + Msg.debug(this, "new child not null"); + } + else { + Msg.debug(this, "child is null"); + } } }); diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/projectdata/actions/ProjectDataNewFolderAction.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/projectdata/actions/ProjectDataNewFolderAction.java index 9a853981cc..cc60f5817c 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/projectdata/actions/ProjectDataNewFolderAction.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/projectdata/actions/ProjectDataNewFolderAction.java @@ -33,7 +33,8 @@ import ghidra.util.Swing; import ghidra.util.exception.AssertException; import resources.ResourceManager; -public class ProjectDataNewFolderAction extends ContextSpecificAction { +public class ProjectDataNewFolderAction + extends ContextSpecificAction { private static Icon icon = ResourceManager.loadImage("images/folder_add.png"); @@ -66,8 +67,9 @@ public class ProjectDataNewFolderAction extends Co Swing.runLater(() -> { GTreeNode node = findNodeForFolder(tree, newFolder); if (node != null) { + tree.ignoreFilter(node); tree.setEditable(true); - tree.startEditing(node.getParent(), node.getName()); + tree.startEditing(node); } }); @@ -79,7 +81,7 @@ public class ProjectDataNewFolderAction extends Co return parentFolder.createFolder(name); } catch (InvalidNameException | IOException e) { - throw new AssertException("Unexpected Error creating new folder: "+name, e); + throw new AssertException("Unexpected Error creating new folder: " + name, e); } }