mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-04 10:19:23 +02:00
Fixed open program dialog to not repeatedly load the root node
This commit is contained in:
parent
2eff37f655
commit
1d5da6dae1
9 changed files with 268 additions and 165 deletions
|
@ -32,16 +32,19 @@ import docking.event.mouse.GMouseListenerAdapter;
|
|||
import docking.widgets.combobox.GComboBox;
|
||||
import docking.widgets.label.GDLabel;
|
||||
import docking.widgets.label.GLabel;
|
||||
import docking.widgets.tree.GTree;
|
||||
import docking.widgets.tree.GTreeTask;
|
||||
import docking.widgets.tree.support.GTreeSelectionEvent;
|
||||
import docking.widgets.tree.support.GTreeSelectionListener;
|
||||
import ghidra.framework.main.datatree.DialogProjectTreeContext;
|
||||
import ghidra.framework.main.datatree.ProjectDataTreePanel;
|
||||
import ghidra.framework.main.datatree.*;
|
||||
import ghidra.framework.main.projectdata.actions.*;
|
||||
import ghidra.framework.model.*;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.Swing;
|
||||
import ghidra.util.exception.AssertException;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.layout.PairLayout;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* Base dialog for choosing DomainFiles. Provides and manages the base data tree panel. Subclasses
|
||||
|
@ -68,7 +71,6 @@ public abstract class AbstractDataTreeDialog extends DialogComponentProvider
|
|||
private DataTreeDialogType type;
|
||||
private Component parent;
|
||||
|
||||
private String searchString;
|
||||
private boolean cancelled = false;
|
||||
|
||||
private ProjectDataExpandAction<DialogProjectTreeContext> expandAction;
|
||||
|
@ -183,13 +185,20 @@ public abstract class AbstractDataTreeDialog extends DialogComponentProvider
|
|||
show();
|
||||
}
|
||||
|
||||
public GTree getTree() {
|
||||
return treePanel.getDataTree();
|
||||
}
|
||||
|
||||
public String getNameText() {
|
||||
return nameField.getText();
|
||||
}
|
||||
|
||||
public void setNameText(String name) {
|
||||
nameField.setText(name.trim());
|
||||
nameField.selectAll();
|
||||
// We need to run this code in a task since the tree may already be processing other tasks
|
||||
// that would override this setting when they are run. But putting this task in the queue,
|
||||
// we get the correct UI update ordering.
|
||||
DataTree tree = treePanel.getDataTree();
|
||||
tree.runTask(new SetNameTextTask(tree, name));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -327,13 +336,6 @@ public abstract class AbstractDataTreeDialog extends DialogComponentProvider
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Select the root folder in the tree.
|
||||
*/
|
||||
public void selectRootDataFolder() {
|
||||
Swing.runLater(() -> treePanel.selectRootDataFolder());
|
||||
}
|
||||
|
||||
/**
|
||||
* Select a folder in the tree.
|
||||
* @param folder the folder to select
|
||||
|
@ -369,13 +371,14 @@ public abstract class AbstractDataTreeDialog extends DialogComponentProvider
|
|||
|
||||
// data tree panel must be created before the combo box
|
||||
JPanel dataTreePanel = createDataTreePanel();
|
||||
ProjectData pd = project.getProjectData();
|
||||
treePanel.setProjectData(project.getName(), pd);
|
||||
treePanel.selectRootDataFolder();
|
||||
|
||||
if (type == CHOOSE_FOLDER) {
|
||||
// this allows users to press the OK button to choose the root folder
|
||||
treePanel.selectRootDataFolder();
|
||||
}
|
||||
|
||||
if (type == OPEN) {
|
||||
JPanel comboPanel = createComboBoxPanel();
|
||||
|
||||
panel.add(comboPanel, BorderLayout.NORTH);
|
||||
populateProjectModel();
|
||||
}
|
||||
|
@ -578,7 +581,7 @@ public abstract class AbstractDataTreeDialog extends DialogComponentProvider
|
|||
}
|
||||
|
||||
public void setSearchText(String s) {
|
||||
if (searchString != null) {
|
||||
if (s != null) {
|
||||
treePanel.findAndSelect(s);
|
||||
}
|
||||
}
|
||||
|
@ -604,4 +607,21 @@ public abstract class AbstractDataTreeDialog extends DialogComponentProvider
|
|||
}
|
||||
}
|
||||
|
||||
private class SetNameTextTask extends GTreeTask {
|
||||
|
||||
private String text;
|
||||
|
||||
SetNameTextTask(GTree gTree, String text) {
|
||||
super(gTree);
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(TaskMonitor monitor) throws CancelledException {
|
||||
runOnSwingThread(() -> {
|
||||
nameField.setText(text.trim());
|
||||
nameField.selectAll();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
* 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.
|
||||
|
@ -118,6 +118,7 @@ public abstract class AbstractValueIntegrationTest extends AbstractGhidraHeadedI
|
|||
runSwing(() -> {
|
||||
dataTreeDialog.selectDomainFile(file);
|
||||
});
|
||||
waitForTree(dataTreeDialog.getTree());
|
||||
waitForSwing();
|
||||
pressButtonByText(dataTreeDialog, "OK");
|
||||
|
||||
|
@ -131,6 +132,7 @@ public abstract class AbstractValueIntegrationTest extends AbstractGhidraHeadedI
|
|||
runSwing(() -> {
|
||||
dataTreeDialog.selectFolder(folder);
|
||||
});
|
||||
waitForTree(dataTreeDialog.getTree());
|
||||
waitForSwing();
|
||||
pressButtonByText(dataTreeDialog, "OK");
|
||||
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
* 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.
|
||||
|
@ -36,6 +36,7 @@ import ghidra.program.database.ProgramBuilder;
|
|||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
||||
import ghidra.test.TestEnv;
|
||||
import ghidra.util.Swing;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class DataTreeDialogTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
|
@ -45,15 +46,6 @@ public class DataTreeDialogTest extends AbstractGhidraHeadedIntegrationTest {
|
|||
private DataTreeDialog dialog;
|
||||
private List<String> names =
|
||||
List.of("notepad", "XNotepad", "tNotepadA", "tNotepadB", "tNotepadC", "tNotepadD");
|
||||
private GTree gtree;
|
||||
|
||||
/**
|
||||
* Constructor for DataTreeDialogTest.
|
||||
* @param arg0
|
||||
*/
|
||||
public DataTreeDialogTest() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
|
@ -85,6 +77,7 @@ public class DataTreeDialogTest extends AbstractGhidraHeadedIntegrationTest {
|
|||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
closeAllWindows();
|
||||
env.dispose();
|
||||
}
|
||||
|
||||
|
@ -172,7 +165,14 @@ public class DataTreeDialogTest extends AbstractGhidraHeadedIntegrationTest {
|
|||
|
||||
@Test
|
||||
public void testOKButtonAlwaysEnabled_Type_CHOOSE_FOLDER() {
|
||||
// no initial selection--button disabled
|
||||
//
|
||||
// This tests that when choosing a folder, the root is selected by default so that users can
|
||||
// simply press OK to pick a folder quickly.
|
||||
//
|
||||
// **Also, the tree remembers folder selections and files selections, with the file's parent
|
||||
// being the selected folder. It appears that you cannot get rid of the dialog's notion of
|
||||
// the current folder. This tests that.
|
||||
//
|
||||
show(CHOOSE_FOLDER);
|
||||
assertOK(true);
|
||||
|
||||
|
@ -253,28 +253,17 @@ public class DataTreeDialogTest extends AbstractGhidraHeadedIntegrationTest {
|
|||
List.of("/dir1/dir2/file1", "/dir1/dir2a/dir3a/file2", "/file3"));
|
||||
show(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();
|
||||
DomainFile file1 = createdFiles.get(0);
|
||||
DomainFile file2 = createdFiles.get(1);
|
||||
projectDataTreePanel.selectDomainFiles(Set.of(file1, file2));
|
||||
waitForTree(getGTree());
|
||||
|
||||
assertEquals(selectedProjectElements.size(), 2);
|
||||
assertTrue(selectedProjectElements.contains(createdFiles.get(0)));
|
||||
assertTrue(selectedProjectElements.contains(createdFiles.get(1)));
|
||||
Set<DomainFile> selectedData = getSelectedFiles();
|
||||
assertEquals(selectedData.size(), 2);
|
||||
assertTrue(selectedData.contains(file1));
|
||||
assertTrue(selectedData.contains(file2));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -283,33 +272,46 @@ public class DataTreeDialogTest extends AbstractGhidraHeadedIntegrationTest {
|
|||
List.of("/dir1/dir2/file1", "/dir1/dir2a/dir3a/file2", "/file3"));
|
||||
show(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());
|
||||
DomainFile file1 = createdFiles.get(0);
|
||||
DomainFolder dir2 = file1.getParent();
|
||||
projectDataTreePanel.selectDomainFolder(dir2);
|
||||
waitForTree(getGTree());
|
||||
waitForSwing();
|
||||
|
||||
assertEquals(selectedProjectElements.size(), 1);
|
||||
assertTrue(selectedProjectElements.contains(createdFiles.get(0).getParent()));
|
||||
Set<DomainFolder> selectedData = getSelectedFolders();
|
||||
assertEquals(selectedData.size(), 1);
|
||||
assertTrue("'dir2' not selected", selectedData.contains(dir2));
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// Private
|
||||
//==================================================================================================
|
||||
|
||||
private Set<DomainFolder> getSelectedFolders() {
|
||||
Set<DomainFolder> set = new HashSet<>();
|
||||
TreePath[] paths = getGTree().getSelectionPaths();
|
||||
for (TreePath path : paths) {
|
||||
Object leafNode = path.getLastPathComponent();
|
||||
if (leafNode instanceof DomainFolderNode folderNode) {
|
||||
set.add(folderNode.getDomainFolder());
|
||||
}
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
private Set<DomainFile> getSelectedFiles() {
|
||||
Set<DomainFile> set = new HashSet<>();
|
||||
TreePath[] paths = getGTree().getSelectionPaths();
|
||||
for (TreePath path : paths) {
|
||||
Object leafNode = path.getLastPathComponent();
|
||||
if (leafNode instanceof DomainFileNode fileNode) {
|
||||
set.add(fileNode.getDomainFile());
|
||||
}
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
private void deselectFolder() {
|
||||
clearSelection();
|
||||
}
|
||||
|
@ -400,9 +402,8 @@ public class DataTreeDialogTest extends AbstractGhidraHeadedIntegrationTest {
|
|||
}
|
||||
|
||||
private void show(DataTreeDialogType type) {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
Swing.runLater(() -> {
|
||||
dialog = new DataTreeDialog(frontEndTool.getToolFrame(), "Test Data Tree Dialog", type);
|
||||
|
||||
dialog.showComponent();
|
||||
});
|
||||
waitForSwing();
|
||||
|
@ -410,9 +411,8 @@ public class DataTreeDialogTest extends AbstractGhidraHeadedIntegrationTest {
|
|||
}
|
||||
|
||||
private void show(DataTreeDialogType type, final String name) {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
Swing.runLater(() -> {
|
||||
dialog = new DataTreeDialog(frontEndTool.getToolFrame(), "Test Data Tree Dialog", type);
|
||||
|
||||
dialog.setNameText(name);
|
||||
dialog.showComponent();
|
||||
});
|
||||
|
@ -423,7 +423,7 @@ public class DataTreeDialogTest extends AbstractGhidraHeadedIntegrationTest {
|
|||
}
|
||||
|
||||
private void showFiltered(final String startsWith) {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
Swing.runLater(() -> {
|
||||
dialog = new DataTreeDialog(frontEndTool.getToolFrame(), "Test Data Tree Dialog",
|
||||
OPEN, f -> f.getName().startsWith(startsWith));
|
||||
dialog.showComponent();
|
||||
|
|
|
@ -57,8 +57,14 @@ import resources.Icons;
|
|||
|
||||
/**
|
||||
* Class for creating a JTree that supports filtering, threading, and a progress bar.
|
||||
* <p>
|
||||
* Note: when calling methods on this class to select nodes, if those nodes are threaded, or extend
|
||||
* from {@link GTreeSlowLoadingNode}, then you must first expand the paths you wish to select. You
|
||||
* can do this by calling {@link #expandAndSelectPaths(List)}. The various select methods of this
|
||||
* class will not expand nodes, but they will trigger children to be loaded. If those nodes are not
|
||||
* threaded, then the tree will add and expand the children by default. When using threaded nodes,
|
||||
* the delay in loading prevents the tree from correctly expanding the paths.
|
||||
*/
|
||||
|
||||
public class GTree extends JPanel implements BusyListener {
|
||||
private static final Color BACKGROUND = new GColor("color.bg.tree");
|
||||
private AutoScrollTree tree;
|
||||
|
@ -475,26 +481,36 @@ public class GTree extends JPanel implements BusyListener {
|
|||
}
|
||||
|
||||
public void expandPath(GTreeNode node) {
|
||||
expandPaths(new TreePath[] { node.getTreePath() });
|
||||
expandPaths(List.of(node.getTreePath()));
|
||||
}
|
||||
|
||||
public void expandPath(TreePath path) {
|
||||
expandPaths(new TreePath[] { path });
|
||||
expandPaths(List.of(path));
|
||||
}
|
||||
|
||||
public void expandPaths(TreePath[] paths) {
|
||||
runTask(new GTreeExpandPathsTask(this, Arrays.asList(paths)));
|
||||
expandPaths(Arrays.asList(paths));
|
||||
}
|
||||
|
||||
public void expandPaths(List<TreePath> pathsList) {
|
||||
TreePath[] treePaths = pathsList.toArray(new TreePath[pathsList.size()]);
|
||||
expandPaths(treePaths);
|
||||
public void expandPaths(List<TreePath> paths) {
|
||||
runTask(new GTreeExpandPathsTask(this, paths));
|
||||
}
|
||||
|
||||
public void clearSelectionPaths() {
|
||||
runTask(new GTreeClearSelectionTask(this, tree));
|
||||
}
|
||||
|
||||
/**
|
||||
* Expands and then selects the given paths. You must use this method if your tree is using
|
||||
* {@link GTreeSlowLoadingNode}s. Otherwise, if the given paths are not expanded, then the
|
||||
* select will not work. More info at the class javadoc.
|
||||
*
|
||||
* @param paths the paths
|
||||
*/
|
||||
public void expandAndSelectPaths(List<TreePath> paths) {
|
||||
setSelectionPaths(paths, true, EventOrigin.API_GENERATED);
|
||||
}
|
||||
|
||||
public void setSelectedNode(GTreeNode node) {
|
||||
setSelectionPaths(new TreePath[] { node.getTreePath() });
|
||||
}
|
||||
|
@ -568,8 +584,24 @@ public class GTree extends JPanel implements BusyListener {
|
|||
runTask(new GTreeSelectNodeByNameTask(this, tree, namePath, EventOrigin.API_GENERATED));
|
||||
}
|
||||
|
||||
public void setSelectionPaths(TreePath[] path, EventOrigin origin) {
|
||||
runTask(new GTreeSelectPathsTask(this, tree, Arrays.asList(path), origin));
|
||||
public void setSelectionPaths(TreePath[] paths, EventOrigin origin) {
|
||||
setSelectionPaths(Arrays.asList(paths), false, origin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects the given paths, expanding them first if requested.
|
||||
*
|
||||
* @param paths the paths to select
|
||||
* @param expandPaths true to expand the paths first; this is only needed for multi-threaded
|
||||
* nodes. Non-threaded nodes should use false, as it increase performance.
|
||||
* @param origin the event type; use {@link EventOrigin#API_GENERATED} if unsure
|
||||
*/
|
||||
public void setSelectionPaths(List<TreePath> paths, boolean expandPaths, EventOrigin origin) {
|
||||
|
||||
if (expandPaths) {
|
||||
expandPaths(paths);
|
||||
}
|
||||
runTask(new GTreeSelectPathsTask(this, tree, paths, origin));
|
||||
}
|
||||
|
||||
public boolean isCollapsed(TreePath path) {
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
* 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.
|
||||
|
@ -15,10 +15,12 @@
|
|||
*/
|
||||
package docking.widgets.tree;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
|
||||
import org.apache.commons.collections4.IteratorUtils;
|
||||
|
||||
import docking.widgets.tree.internal.InProgressGTreeNode;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.Swing;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
@ -124,4 +126,14 @@ public abstract class GTreeSlowLoadingNode extends GTreeLazyNode {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<GTreeNode> iterator(boolean depthFirst) {
|
||||
if (Swing.isSwingThread()) {
|
||||
Msg.warn(this, "Threaded tree nodes cannot be iterated on the Swing thread. " +
|
||||
"Change the call to this method to be run in a background task");
|
||||
return IteratorUtils.emptyIterator();
|
||||
}
|
||||
return super.iterator(depthFirst);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
* 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.
|
||||
|
@ -17,8 +17,7 @@ package docking.widgets.tree;
|
|||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
|
@ -151,7 +150,27 @@ public class GTreeSlowLoadingNodeTest extends AbstractDockingTest {
|
|||
waitForTree();
|
||||
Swing.runNow(() -> children = nonLeaf1.getChildren());
|
||||
assertTrue("Did not find children for: " + nonLeaf1, nonLeaf1.getChildCount() > 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIterator() {
|
||||
|
||||
gTree.setRootNode(new TestRootNode(5000));
|
||||
waitForTree();
|
||||
|
||||
GTreeNode rootNode = gTree.getModelRoot();
|
||||
GTreeNode slowChild = rootNode.getChild(0); // slow; threaded
|
||||
assertNotNull(slowChild);
|
||||
|
||||
Swing.runNow(() -> children = slowChild.getChildren());
|
||||
assertEquals(1, children.size());
|
||||
assertTrue(children.get(0) instanceof InProgressGTreeNode);
|
||||
|
||||
Iterator<GTreeNode> it = runSwing(() -> slowChild.iterator(true));
|
||||
assertFalse(it.hasNext()); // empty when called on the Swing thread
|
||||
|
||||
it = slowChild.iterator(true);
|
||||
assertTrue(it.hasNext()); // not empty on non-Swing thread
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
|
@ -235,11 +254,11 @@ public class GTreeSlowLoadingNodeTest extends AbstractDockingTest {
|
|||
private class TestRootNode extends GTreeNode {
|
||||
|
||||
TestRootNode(int loadDelayMillis) {
|
||||
List<GTreeNode> children = new ArrayList<>();
|
||||
children.add(new TestSlowLoadingNode(loadDelayMillis, 1));
|
||||
children.add(new TestLeafNode());
|
||||
children.add(new TestSlowLoadingNode(loadDelayMillis, 1));
|
||||
setChildren(children);
|
||||
List<GTreeNode> newChildren = new ArrayList<>();
|
||||
newChildren.add(new TestSlowLoadingNode(loadDelayMillis, 1));
|
||||
newChildren.add(new TestLeafNode());
|
||||
newChildren.add(new TestSlowLoadingNode(loadDelayMillis, 1));
|
||||
setChildren(newChildren);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -289,18 +308,18 @@ public class GTreeSlowLoadingNodeTest extends AbstractDockingTest {
|
|||
}
|
||||
|
||||
int childCount = getRandomInt(MIN_CHILD_COUNT, MAX_CHILD_COUNT);
|
||||
List<GTreeNode> children = new ArrayList<>();
|
||||
List<GTreeNode> newChildren = new ArrayList<>();
|
||||
for (int i = 0; i < childCount; i++) {
|
||||
monitor.checkCancelled();
|
||||
int value = getRandomInt(0, 1);
|
||||
if (value == 0) {
|
||||
children.add(new TestSlowLoadingNode(loadDelayMillis, depth + 1));
|
||||
newChildren.add(new TestSlowLoadingNode(loadDelayMillis, depth + 1));
|
||||
}
|
||||
else {
|
||||
children.add(new TestLeafNode());
|
||||
newChildren.add(new TestLeafNode());
|
||||
}
|
||||
}
|
||||
return children;
|
||||
return newChildren;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
* 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.
|
||||
|
@ -318,7 +318,7 @@ public class FrontEndPlugin extends Plugin
|
|||
r.run();
|
||||
}
|
||||
else {
|
||||
SwingUtilities.invokeLater(r);
|
||||
Swing.runLater(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -415,12 +415,12 @@ public class FrontEndPlugin extends Plugin
|
|||
|
||||
@Override
|
||||
public void viewedProjectAdded(URL projectView) {
|
||||
SwingUtilities.invokeLater(() -> rebuildRecentMenus());
|
||||
Swing.runLater(() -> rebuildRecentMenus());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void viewedProjectRemoved(URL projectView) {
|
||||
SwingUtilities.invokeLater(() -> rebuildRecentMenus());
|
||||
Swing.runLater(() -> rebuildRecentMenus());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -536,7 +536,7 @@ public class FrontEndPlugin extends Plugin
|
|||
void selectFiles(final Set<DomainFile> files) {
|
||||
// Do this later in case any of the given files are newly created, which means that the
|
||||
// GUIs may have not yet been notified.
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
Swing.runLater(() -> {
|
||||
// there was a delete bug; make the set unmodifiable to catch this earlier
|
||||
Set<DomainFile> unmodifiableFiles = Collections.unmodifiableSet(files);
|
||||
if (dataTablePanel.isCapacityExceeded()) {
|
||||
|
@ -552,7 +552,7 @@ public class FrontEndPlugin extends Plugin
|
|||
void selectFolder(final DomainFolder folder) {
|
||||
// Do this later in case any of the given files are newly created, which means that the
|
||||
// GUIs may have not yet been notified.
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
Swing.runLater(() -> {
|
||||
projectDataPanel.showTree();
|
||||
dataTreePanel.selectDomainFolder(folder);
|
||||
});
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
* 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.
|
||||
|
@ -20,16 +20,18 @@ import java.util.*;
|
|||
|
||||
import javax.swing.Icon;
|
||||
|
||||
import docking.widgets.tree.GTreeLazyNode;
|
||||
import docking.widgets.tree.GTreeNode;
|
||||
import docking.widgets.tree.GTreeSlowLoadingNode;
|
||||
import ghidra.framework.model.*;
|
||||
import ghidra.util.*;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
import resources.ResourceManager;
|
||||
|
||||
/**
|
||||
* Class to represent a node in the Data tree.
|
||||
*/
|
||||
public class DomainFolderNode extends GTreeLazyNode implements Cuttable {
|
||||
public class DomainFolderNode extends GTreeSlowLoadingNode implements Cuttable {
|
||||
|
||||
private static final Icon ENABLED_OPEN_FOLDER = DomainFolder.OPEN_FOLDER_ICON;
|
||||
private static final Icon ENABLED_CLOSED_FOLDER = DomainFolder.CLOSED_FOLDER_ICON;
|
||||
|
@ -127,30 +129,33 @@ public class DomainFolderNode extends GTreeLazyNode implements Cuttable {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected List<GTreeNode> generateChildren() {
|
||||
public List<GTreeNode> generateChildren(TaskMonitor monitor) throws CancelledException {
|
||||
|
||||
List<GTreeNode> children = new ArrayList<>();
|
||||
if (domainFolder != null && !domainFolder.isEmpty()) {
|
||||
if (domainFolder == null || domainFolder.isEmpty()) {
|
||||
return children;
|
||||
}
|
||||
|
||||
// NOTE: isEmpty() is used to avoid multiple failed connection attempts on this folder
|
||||
// NOTE: isEmpty() is used to avoid multiple failed connection attempts on this folder
|
||||
|
||||
DomainFolder[] folders = domainFolder.getFolders();
|
||||
for (DomainFolder folder : folders) {
|
||||
children.add(new DomainFolderNode(folder, filter));
|
||||
DomainFolder[] folders = domainFolder.getFolders();
|
||||
for (DomainFolder folder : folders) {
|
||||
monitor.checkCancelled();
|
||||
children.add(new DomainFolderNode(folder, filter));
|
||||
}
|
||||
|
||||
DomainFile[] files = domainFolder.getFiles();
|
||||
for (DomainFile domainFile : files) {
|
||||
monitor.checkCancelled();
|
||||
if (domainFile.isLinkFile() && filter != null && filter.followLinkedFolders()) {
|
||||
DomainFolder folder = domainFile.followLink();
|
||||
if (folder != null) {
|
||||
children.add(new DomainFolderNode(folder, filter));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
DomainFile[] files = domainFolder.getFiles();
|
||||
for (DomainFile domainFile : files) {
|
||||
if (domainFile.isLinkFile() && filter != null && filter.followLinkedFolders()) {
|
||||
DomainFolder folder = domainFile.followLink();
|
||||
if (folder != null) {
|
||||
children.add(new DomainFolderNode(folder, filter));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (filter == null || filter.accept(domainFile)) {
|
||||
children.add(new DomainFileNode(domainFile));
|
||||
}
|
||||
if (filter == null || filter.accept(domainFile)) {
|
||||
children.add(new DomainFileNode(domainFile));
|
||||
}
|
||||
}
|
||||
Collections.sort(children);
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
* 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.
|
||||
|
@ -27,13 +27,15 @@ import javax.swing.tree.TreeSelectionModel;
|
|||
|
||||
import docking.ActionContext;
|
||||
import docking.ComponentProvider;
|
||||
import docking.widgets.tree.GTreeNode;
|
||||
import docking.widgets.tree.*;
|
||||
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;
|
||||
import help.Help;
|
||||
import help.HelpService;
|
||||
|
||||
|
@ -100,6 +102,10 @@ public class ProjectDataTreePanel extends JPanel {
|
|||
* @param projectData data that has the root folder for the project
|
||||
*/
|
||||
public void setProjectData(String projectName, ProjectData projectData) {
|
||||
if (this.projectData == projectData) {
|
||||
return; // this can happen during setup if listeners get activated
|
||||
}
|
||||
|
||||
if (this.projectData != null) {
|
||||
this.projectData.removeDomainFolderChangeListener(changeMgr);
|
||||
}
|
||||
|
@ -138,19 +144,6 @@ public class ProjectDataTreePanel extends JPanel {
|
|||
oldRoot.removeAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Select the root data folder (not root node in the tree which
|
||||
* shows the project name).
|
||||
*/
|
||||
public void selectRootDataFolder() {
|
||||
tree.setSelectionPath(root.getTreePath());
|
||||
}
|
||||
|
||||
public void selectDomainFolder(DomainFolder domainFolder) {
|
||||
TreePath treePath = getTreePath(domainFolder);
|
||||
tree.setSelectionPath(treePath);
|
||||
}
|
||||
|
||||
private List<TreePath> getTreePaths(Set<DomainFile> files) {
|
||||
List<TreePath> results = new ArrayList<>();
|
||||
for (DomainFile file : files) {
|
||||
|
@ -177,24 +170,25 @@ public class ProjectDataTreePanel extends JPanel {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* Select the root data folder (not root node in the tree which shows the project name).
|
||||
*/
|
||||
public void selectRootDataFolder() {
|
||||
tree.setSelectionPath(root.getTreePath());
|
||||
}
|
||||
|
||||
public void selectDomainFolder(DomainFolder domainFolder) {
|
||||
TreePath treePath = getTreePath(domainFolder);
|
||||
tree.expandAndSelectPaths(List.of(treePath));
|
||||
}
|
||||
|
||||
public void selectDomainFiles(Set<DomainFile> files) {
|
||||
List<TreePath> treePaths = getTreePaths(files);
|
||||
tree.setSelectionPaths(treePaths);
|
||||
tree.expandAndSelectPaths(treePaths);
|
||||
}
|
||||
|
||||
public void selectDomainFile(DomainFile domainFile) {
|
||||
Iterator<GTreeNode> it = root.iterator(true);
|
||||
while (it.hasNext()) {
|
||||
GTreeNode child = it.next();
|
||||
if (child instanceof DomainFileNode) {
|
||||
DomainFile nodeFile = ((DomainFileNode) child).getDomainFile();
|
||||
if (nodeFile.equals(domainFile)) {
|
||||
tree.expandPath(child);
|
||||
tree.setSelectedNode(child);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
selectDomainFiles(Set.of(domainFile));
|
||||
}
|
||||
|
||||
public void setHelpLocation(HelpLocation helpLocation) {
|
||||
|
@ -487,11 +481,34 @@ public class ProjectDataTreePanel extends JPanel {
|
|||
* @param s node name
|
||||
*/
|
||||
public void findAndSelect(String s) {
|
||||
if (projectData.getFileCount() < MAX_PROJECT_SIZE_TO_SEARCH) {
|
||||
tree.expandTree(root);
|
||||
FindAndSelectTask task = new FindAndSelectTask(tree, s);
|
||||
tree.runTask(task);
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// Inner Classes
|
||||
//==================================================================================================
|
||||
|
||||
private class FindAndSelectTask extends GTreeTask {
|
||||
|
||||
private String text;
|
||||
|
||||
FindAndSelectTask(GTree gTree, String text) {
|
||||
super(gTree);
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(TaskMonitor monitor) throws CancelledException {
|
||||
|
||||
if (projectData.getFileCount() > MAX_PROJECT_SIZE_TO_SEARCH) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (Iterator<GTreeNode> it = root.iterator(true); it.hasNext();) {
|
||||
monitor.checkCancelled();
|
||||
GTreeNode node = it.next();
|
||||
if (node.getName().equals(s)) {
|
||||
if (node.getName().equals(text)) {
|
||||
tree.setSelectedNode(node);
|
||||
return;
|
||||
}
|
||||
|
@ -499,10 +516,6 @@ public class ProjectDataTreePanel extends JPanel {
|
|||
}
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// Inner Classes
|
||||
//==================================================================================================
|
||||
|
||||
private class MyMouseListener extends MouseAdapter {
|
||||
@Override
|
||||
public void mousePressed(MouseEvent e) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue