diff --git a/Ghidra/Features/Base/src/main/java/ghidra/framework/main/DataTreeDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/framework/main/DataTreeDialog.java index dfeac5b97f..53dd6ae165 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/framework/main/DataTreeDialog.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/framework/main/DataTreeDialog.java @@ -277,6 +277,11 @@ public class DataTreeDialog extends DialogComponentProvider pendingNameText = name; } + /** + * Sets a domain folder as the initially selected folder when the dialog is first shown. + * + * @param folder {@link DomainFolder} to select when showing the dialog + */ public void setSelectedFolder(DomainFolder folder) { pendingDomainFolder = folder; } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/main/DataTreeDialogTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/main/DataTreeDialogTest.java index 9a69f702e5..d0a768d832 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/main/DataTreeDialogTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/main/DataTreeDialogTest.java @@ -17,29 +17,34 @@ package ghidra.framework.main; import static org.junit.Assert.*; +import java.util.*; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Collectors; import javax.swing.*; import javax.swing.tree.TreeModel; +import javax.swing.tree.TreePath; import org.junit.*; import docking.widgets.tree.GTree; import docking.widgets.tree.GTreeNode; -import ghidra.framework.main.datatree.ProjectDataTreePanel; +import ghidra.framework.main.datatree.*; import ghidra.framework.model.*; import ghidra.program.database.ProgramBuilder; import ghidra.program.model.listing.Program; import ghidra.test.AbstractGhidraHeadedIntegrationTest; import ghidra.test.TestEnv; -import ghidra.util.task.TaskMonitorAdapter; +import ghidra.util.task.TaskMonitor; public class DataTreeDialogTest extends AbstractGhidraHeadedIntegrationTest { private TestEnv env; private FrontEndTool frontEndTool; private DataTreeDialog dialog; - private String[] names = new String[] { "tNotepadA", "tNotepadB", "tNotepadC", "tNotepadD" }; + private List names = + List.of("notepad", "XNotepad", "tNotepadA", "tNotepadB", "tNotepadC", "tNotepadD"); + private GTree gtree; /** * Constructor for DataTreeDialogTest. @@ -55,19 +60,26 @@ public class DataTreeDialogTest extends AbstractGhidraHeadedIntegrationTest { frontEndTool = env.getFrontEndTool(); env.showFrontEndTool(); + createBlankProgramsInProject(names); + } - DomainFolder rootFolder = env.getProject().getProjectData().getRootFolder(); + private List createBlankProgramsInProject(List paths) throws Exception { + List result = new ArrayList<>(); ProgramBuilder builder = new ProgramBuilder("notepad", ProgramBuilder._TOY_BE); Program p = builder.getProgram(); - rootFolder.createFile("notepad", p, TaskMonitorAdapter.DUMMY_MONITOR); - rootFolder.createFile("XNotepad", p, TaskMonitorAdapter.DUMMY_MONITOR); - for (String name : names) { - rootFolder.createFile(name, p, TaskMonitorAdapter.DUMMY_MONITOR); + DomainFolder rootFolder = env.getProject().getProjectData().getRootFolder(); + for (String pathFilename : paths) { + int lastSlash = pathFilename.lastIndexOf('/'); + String path = (lastSlash >= 0) ? pathFilename.substring(0, lastSlash) : ""; + String filename = + (lastSlash >= 0) ? pathFilename.substring(lastSlash + 1) : pathFilename; + DomainFolder domainFolder = ProjectDataUtils.createDomainFolderPath(rootFolder, path); + result.add(domainFolder.createFile(filename, p, TaskMonitor.DUMMY)); } builder.dispose(); - - waitForPostedSwingRunnables(); + waitForSwing(); + return result; } @After @@ -78,16 +90,20 @@ public class DataTreeDialogTest extends AbstractGhidraHeadedIntegrationTest { @Test public void testFilters() { - showFiltered(); + showFiltered("tN"); JTree tree = getJTree(); + List expectedFilteredNames = names.stream() + .filter(s -> s.startsWith("tN")) + .sorted() + .collect(Collectors.toList()); TreeModel model = tree.getModel(); GTreeNode root = (GTreeNode) model.getRoot(); - assertEquals(names.length, root.getChildCount()); - for (int i = 0; i < names.length; i++) { + assertEquals(expectedFilteredNames.size(), root.getChildCount()); + for (int i = 0; i < expectedFilteredNames.size(); i++) { GTreeNode child = root.getChild(i); - assertEquals(names[i], child.toString()); + assertEquals(expectedFilteredNames.get(i), child.toString()); } } @@ -230,6 +246,65 @@ public class DataTreeDialogTest extends AbstractGhidraHeadedIntegrationTest { assertNameHasText(false); } + @Test + public void testSelectFiles() throws Exception { + List createdFiles = createBlankProgramsInProject( + List.of("/dir1/dir2/file1", "/dir1/dir2a/dir3a/file2", "/file3")); + show(DataTreeDialog.OPEN); + + Set selectedProjectElements = new HashSet<>(); + ProjectDataTreePanel projectDataTreePanel = getProjectDataTreePanel(); + projectDataTreePanel.addTreeSelectionListener( + e -> { + for (TreePath treePath : e.getPaths()) { + Object leafNode = treePath.getLastPathComponent(); + if (leafNode instanceof DomainFileNode) { + selectedProjectElements.add(((DomainFileNode) leafNode).getDomainFile()); + } +// else if (leafNode instanceof DomainFolderNode) { +// selectedProjectElements +// .add(((DomainFolderNode) leafNode).getDomainFolder()); +// } + } + }); + + projectDataTreePanel.selectDomainFiles(Set.of(createdFiles.get(0), createdFiles.get(1))); + waitForSwing(); + + assertEquals(selectedProjectElements.size(), 2); + assertTrue(selectedProjectElements.contains(createdFiles.get(0))); + assertTrue(selectedProjectElements.contains(createdFiles.get(1))); + } + + @Test + public void testSelectFolder() throws Exception { + List createdFiles = createBlankProgramsInProject( + List.of("/dir1/dir2/file1", "/dir1/dir2a/dir3a/file2", "/file3")); + show(DataTreeDialog.OPEN); + + Set selectedProjectElements = new HashSet<>(); + ProjectDataTreePanel projectDataTreePanel = getProjectDataTreePanel(); + projectDataTreePanel.addTreeSelectionListener( + e -> { + for (TreePath treePath : e.getPaths()) { + Object leafNode = treePath.getLastPathComponent(); +// if (leafNode instanceof DomainFileNode) { +// selectedProjectElements.add(((DomainFileNode) leafNode).getDomainFile()); +// } + if (leafNode instanceof DomainFolderNode) { + selectedProjectElements + .add(((DomainFolderNode) leafNode).getDomainFolder()); + } + } + }); + + projectDataTreePanel.selectDomainFolder(createdFiles.get(0).getParent()); + waitForTree(getGTree()); + waitForSwing(); + + assertEquals(selectedProjectElements.size(), 1); + assertTrue(selectedProjectElements.contains(createdFiles.get(0).getParent())); + } //================================================================================================== // Private //================================================================================================== @@ -329,7 +404,7 @@ public class DataTreeDialogTest extends AbstractGhidraHeadedIntegrationTest { dialog.showComponent(); }); - waitForPostedSwingRunnables(); + waitForSwing(); assertNotNull(dialog); } @@ -341,19 +416,23 @@ public class DataTreeDialogTest extends AbstractGhidraHeadedIntegrationTest { dialog.showComponent(); }); - waitForPostedSwingRunnables(); + waitForSwing(); waitForTree(getGTree()); assertNotNull(dialog); } - private void showFiltered() { + private DomainFileFilter createStartsWithFilter(String startsWith) { + return (df) -> df.getName().startsWith(startsWith); + } + + private void showFiltered(String startsWith) { SwingUtilities.invokeLater(() -> { dialog = new DataTreeDialog(frontEndTool.getToolFrame(), "Test Data Tree Dialog", - DataTreeDialog.OPEN, new MyDomainFileFilter()); + DataTreeDialog.OPEN, createStartsWithFilter(startsWith)); dialog.showComponent(); }); - waitForPostedSwingRunnables(); + waitForSwing(); assertNotNull(dialog); } @@ -363,23 +442,16 @@ public class DataTreeDialogTest extends AbstractGhidraHeadedIntegrationTest { return (GTree) getInstanceField("tree", treePanel); } + private ProjectDataTreePanel getProjectDataTreePanel() { + ProjectDataTreePanel treePanel = + (ProjectDataTreePanel) getInstanceField("treePanel", dialog); + return treePanel; + } + private JTree getJTree() { JTree tree = findComponent(dialog.getComponent(), JTree.class); assertNotNull(tree); return tree; } - private class MyDomainFileFilter implements DomainFileFilter { - /* (non-Javadoc) - * @see ghidra.framework.model.DomainFileFilter#accept(ghidra.framework.model.DomainFile) - */ - @Override - public boolean accept(DomainFile df) { - if (df.getName().startsWith("tN")) { - return true; - } - return false; - } - } - } diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/datatree/DomainFileNode.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/datatree/DomainFileNode.java index da25f9a712..2e89a33d6a 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/datatree/DomainFileNode.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/datatree/DomainFileNode.java @@ -87,7 +87,7 @@ public class DomainFileNode extends GTreeNode implements Cuttable { return false; } DomainFileNode node = (DomainFileNode) obj; - if (domainFile == node.domainFile) { + if (domainFile.equals(node.domainFile)) { return true; } return false; diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/datatree/DomainFolderNode.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/datatree/DomainFolderNode.java index e4b139852a..f9dd5cf94e 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/datatree/DomainFolderNode.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/datatree/DomainFolderNode.java @@ -159,7 +159,7 @@ public class DomainFolderNode extends GTreeLazyNode implements Cuttable { return false; } DomainFolderNode node = (DomainFolderNode) obj; - if (domainFolder == node.domainFolder) { + if (domainFolder.equals(node.domainFolder)) { return true; } return false; diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/datatree/ProjectDataTreePanel.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/datatree/ProjectDataTreePanel.java index 4179b23a90..7671fafffc 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/datatree/ProjectDataTreePanel.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/datatree/ProjectDataTreePanel.java @@ -29,16 +29,13 @@ import docking.ActionContext; import docking.ComponentProvider; import docking.help.Help; import docking.help.HelpService; -import docking.widgets.tree.*; -import docking.widgets.tree.support.DepthFirstIterator; +import docking.widgets.tree.GTreeNode; import docking.widgets.tree.support.GTreeSelectionListener; import ghidra.framework.main.FrontEndPlugin; import ghidra.framework.main.FrontEndTool; import ghidra.framework.model.*; import ghidra.framework.plugintool.PluginTool; import ghidra.util.HelpLocation; -import ghidra.util.exception.CancelledException; -import ghidra.util.task.TaskMonitor; /** * Panel that contains a DataTree for showing project data. @@ -150,45 +147,39 @@ public class ProjectDataTreePanel extends JPanel { } public void selectDomainFolder(DomainFolder domainFolder) { - Iterator it = root.iterator(true); - while (it.hasNext()) { - GTreeNode child = it.next(); - if (child instanceof DomainFolderNode) { - DomainFolder nodeFolder = ((DomainFolderNode) child).getDomainFolder(); - if (nodeFolder.equals(domainFolder)) { - tree.expandPath(child); - tree.setSelectedNode(child); - return; - } - } + TreePath treePath = getTreePath(domainFolder); + tree.setSelectionPath(treePath); + } + + private List getTreePaths(Set files) { + List results = new ArrayList<>(); + for (DomainFile file : files) { + results.add(getTreePath(file)); } + return results; } - public void selectDomainFiles(final Set files) { - tree.runTask(new SelectDomainFilesTask(tree, files)); - } - - private void doSelectDomainFiles(Set files) { - - List nodes = getNodesForFiles(files); - tree.setSelectedNodes(nodes); - } - - private List getNodesForFiles(Set files) { - List nodes = new ArrayList<>(); - DepthFirstIterator it = new DepthFirstIterator(root); - while (it.hasNext()) { - GTreeNode node = it.next(); - if (node instanceof DomainFileNode) { - DomainFile nodeFile = ((DomainFileNode) node).getDomainFile(); - if (files.contains(nodeFile)) { - // it was in the list, add the the nodes list - nodes.add(node); - } - } + private TreePath getTreePath(DomainFile domainFile) { + DomainFileNode node = new DomainFileNode(domainFile); + DomainFolder parent = domainFile.getParent(); + if (parent != null) { + return getTreePath(parent).pathByAddingChild(node); } + return new TreePath(node); + } - return nodes; + private TreePath getTreePath(DomainFolder domainFolder) { + DomainFolder parent = domainFolder.getParent(); + if (parent != null) { + return getTreePath(parent).pathByAddingChild(new DomainFolderNode(domainFolder, null)); + } + return new TreePath(root); + + } + + public void selectDomainFiles(Set files) { + List treePaths = getTreePaths(files); + tree.setSelectionPaths(treePaths); } public void selectDomainFile(DomainFile domainFile) { @@ -516,18 +507,4 @@ public class ProjectDataTreePanel extends JPanel { } } - private class SelectDomainFilesTask extends GTreeTask { - - private final Set files; - - public SelectDomainFilesTask(GTree tree, Set files) { - super(tree); - this.files = files; - } - - @Override - public void run(TaskMonitor monitor) throws CancelledException { - doSelectDomainFiles(files); - } - } }