Fixed open program dialog to not repeatedly load the root node

This commit is contained in:
dragonmacher 2025-02-25 09:14:56 -05:00
parent 2eff37f655
commit 1d5da6dae1
9 changed files with 268 additions and 165 deletions

View file

@ -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();
});
}
}
}

View file

@ -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");

View file

@ -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();

View file

@ -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) {

View file

@ -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);
}
}

View file

@ -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

View file

@ -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);
});

View file

@ -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);

View file

@ -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) {