mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-05-25 00:10:48 +08:00
Merge remote-tracking branch 'origin/GP-1931_ghidravore_speeding_up_select_files_in_project_data_tree--SQUASHED'
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
+104
-32
@@ -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<String> 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<DomainFile> createBlankProgramsInProject(List<String> paths) throws Exception {
|
||||
List<DomainFile> 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<String> 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<DomainFile> createdFiles = createBlankProgramsInProject(
|
||||
List.of("/dir1/dir2/file1", "/dir1/dir2a/dir3a/file2", "/file3"));
|
||||
show(DataTreeDialog.OPEN);
|
||||
|
||||
Set<DomainFile> 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<DomainFile> createdFiles = createBlankProgramsInProject(
|
||||
List.of("/dir1/dir2/file1", "/dir1/dir2a/dir3a/file2", "/file3"));
|
||||
show(DataTreeDialog.OPEN);
|
||||
|
||||
Set<DomainFolder> 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+1
-1
@@ -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;
|
||||
|
||||
+1
-1
@@ -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;
|
||||
|
||||
+29
-52
@@ -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<GTreeNode> 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<TreePath> getTreePaths(Set<DomainFile> files) {
|
||||
List<TreePath> results = new ArrayList<>();
|
||||
for (DomainFile file : files) {
|
||||
results.add(getTreePath(file));
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
public void selectDomainFiles(final Set<DomainFile> files) {
|
||||
tree.runTask(new SelectDomainFilesTask(tree, files));
|
||||
}
|
||||
|
||||
private void doSelectDomainFiles(Set<DomainFile> files) {
|
||||
|
||||
List<GTreeNode> nodes = getNodesForFiles(files);
|
||||
tree.setSelectedNodes(nodes);
|
||||
}
|
||||
|
||||
private List<GTreeNode> getNodesForFiles(Set<DomainFile> files) {
|
||||
List<GTreeNode> 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<DomainFile> files) {
|
||||
List<TreePath> 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<DomainFile> files;
|
||||
|
||||
public SelectDomainFilesTask(GTree tree, Set<DomainFile> files) {
|
||||
super(tree);
|
||||
this.files = files;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(TaskMonitor monitor) throws CancelledException {
|
||||
doSelectDomainFiles(files);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user