mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-04 10:19:23 +02:00
Merge remote-tracking branch 'origin/GP-5375-dragonmacher-gtree-get-child--SQUASHED'
This commit is contained in:
commit
c8937df382
6 changed files with 155 additions and 199 deletions
|
@ -50,6 +50,11 @@ public class KeyEntryPanel extends JPanel {
|
|||
add(clearButton);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestFocus() {
|
||||
keyEntryField.requestFocus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the text field used by this class
|
||||
* @return the text field
|
||||
|
|
|
@ -25,8 +25,7 @@ import java.awt.event.*;
|
|||
import java.io.PrintWriter;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.function.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.swing.*;
|
||||
|
@ -316,6 +315,7 @@ public class GTree extends JPanel implements BusyListener {
|
|||
updateModelFilter();
|
||||
}
|
||||
|
||||
// adds the given node to the view when the tree is filtered, regardless of filter match
|
||||
private void ignoreFilter(GTreeNode node) {
|
||||
if (!isFiltered()) {
|
||||
return;
|
||||
|
@ -1086,43 +1086,122 @@ public class GTree extends JPanel implements BusyListener {
|
|||
}
|
||||
|
||||
// Waits for the given model node, passing it to the consumer when available
|
||||
private void getModelNode(GTreeNode parent, String childName, Consumer<GTreeNode> consumer) {
|
||||
private void getModelNode(GTreeNode parent, Predicate<GTreeNode> matches,
|
||||
Consumer<GTreeNode> consumer) {
|
||||
|
||||
// check for null here to preserve the stack, as the code below is asynchronous
|
||||
Objects.requireNonNull(parent);
|
||||
Objects.requireNonNull(childName);
|
||||
Objects.requireNonNull(matches);
|
||||
Objects.requireNonNull(consumer);
|
||||
|
||||
int expireMs = 3000;
|
||||
Supplier<GTreeNode> supplier = () -> {
|
||||
GTreeNode modelParent = getModelNode(parent);
|
||||
if (modelParent != null) {
|
||||
return modelParent.getChild(childName);
|
||||
if (modelParent == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<GTreeNode> children = modelParent.getChildren();
|
||||
for (GTreeNode node : children) {
|
||||
if (matches.test(node)) {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
ExpiringSwingTimer.get(supplier, expireMs, consumer);
|
||||
}
|
||||
|
||||
// Waits for the given view node, passing it to the consumer when available
|
||||
private void getViewNode(GTreeNode parent, String childName, Consumer<GTreeNode> consumer) {
|
||||
private void getViewNode(GTreeNode parent, Predicate<GTreeNode> matches,
|
||||
Consumer<GTreeNode> consumer) {
|
||||
|
||||
// check for null here to preserve the stack, as the code below is asynchronous
|
||||
Objects.requireNonNull(parent);
|
||||
Objects.requireNonNull(childName);
|
||||
Objects.requireNonNull(matches);
|
||||
Objects.requireNonNull(consumer);
|
||||
|
||||
int expireMs = 3000;
|
||||
Supplier<GTreeNode> supplier = () -> {
|
||||
GTreeNode viewParent = getViewNode(parent);
|
||||
if (viewParent != null) {
|
||||
return viewParent.getChild(childName);
|
||||
if (viewParent == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<GTreeNode> children = viewParent.getChildren();
|
||||
for (GTreeNode node : children) {
|
||||
if (matches.test(node)) {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
ExpiringSwingTimer.get(supplier, expireMs, consumer);
|
||||
}
|
||||
|
||||
/**
|
||||
* A specialized method that will get the child node from the given parent node when it becomes
|
||||
* available to the model. This method will ensure that the matching child passes any current
|
||||
* filter in order for the child to appear in the tree. This effect is temporary and will be
|
||||
* undone when next the filter changes.
|
||||
*
|
||||
* <p>
|
||||
* This method is intended to be used by clients using an asynchronous node model, where new
|
||||
* nodes will get created by application-level events. Such clients may wish to perform work
|
||||
* when newly created nodes become available. This method simplifies the concurrent nature of
|
||||
* the GTree, asynchronous nodes and the processing of asynchronous application-level events by
|
||||
* providing a callback mechanism for clients. <b>This method is non-blocking.</b>
|
||||
*
|
||||
* <p>
|
||||
* Note: this method assumes that the given parent node is in the view and not filtered out of
|
||||
* the view. This method makes no attempt to ensure the given parent node passes any existing
|
||||
* filter.
|
||||
*
|
||||
* <p>
|
||||
* Note: this method will not wait forever for the given node to appear. It will eventually give
|
||||
* up if the node never arrives.
|
||||
*
|
||||
* @param parent the model's parent node. If the view's parent node is passed, it will be
|
||||
* translated to the model node.
|
||||
* @param matches the predicate that returns true when the given node is the desired node
|
||||
* @param consumer the consumer callback to which the child node will be given when available
|
||||
*/
|
||||
public void whenNodeIsReady(GTreeNode parent, Predicate<GTreeNode> matches,
|
||||
Consumer<GTreeNode> consumer) {
|
||||
|
||||
/*
|
||||
|
||||
If the GTree were to use Java's CompletableStage API, then the code below
|
||||
could be written thusly:
|
||||
|
||||
tree.getNewNode(modelParent, newName)
|
||||
.thenCompose(newModelChild -> {
|
||||
tree.ignoreFilter(newModelChild);
|
||||
return tree.getNewNode(viewParent, newName);
|
||||
))
|
||||
.thenAccept(consumer);
|
||||
|
||||
*/
|
||||
|
||||
// ensure we operate on the model node which will always have the given child not the view
|
||||
// node, which may have its child filtered
|
||||
GTreeNode modelParent = getModelNode(parent);
|
||||
if (modelParent == null) {
|
||||
Msg.error(this,
|
||||
"Attempted to show a node with an invalid parent.\n\tParent: " + parent);
|
||||
return;
|
||||
}
|
||||
|
||||
getModelNode(modelParent, matches, newModelChildren -> {
|
||||
// force the filter to accept the new node
|
||||
ignoreFilter(newModelChildren);
|
||||
|
||||
// Wait for the view to update from any filtering that may take place
|
||||
getViewNode(modelParent, matches, consumer);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* A specialized method that will get the child node from the given parent node when it becomes
|
||||
* available to the model. This method will ensure that the named child passes any current
|
||||
|
@ -1144,50 +1223,31 @@ public class GTree extends JPanel implements BusyListener {
|
|||
* <p>
|
||||
* Note: this method will not wait forever for the given node to appear. It will eventually give
|
||||
* up if the node never arrives.
|
||||
*
|
||||
* <p>
|
||||
* Note: if your parent node allows duplicate nodes then this method may not match the correct
|
||||
* node. If that is the case, then use
|
||||
* {@link #whenNodeIsReady(GTreeNode, Predicate, Consumer)}.
|
||||
*
|
||||
* @param parent the model's parent node. If the view's parent node is passed, it will be
|
||||
* translated to the model node.
|
||||
* @param childName the name of the desired child
|
||||
* @param consumer the consumer callback to which the child node will be given when available
|
||||
*/
|
||||
public void forceNewNodeIntoView(GTreeNode parent, String childName,
|
||||
Consumer<GTreeNode> consumer) {
|
||||
|
||||
/*
|
||||
|
||||
If the GTree were to use Java's CompletableStage API, then the code below
|
||||
could be written thusly:
|
||||
|
||||
tree.getNewNode(modelParent, newName)
|
||||
.thenCompose(newModelChild -> {
|
||||
tree.ignoreFilter(newModelChild);
|
||||
return tree.getNewNode(viewParent, newName);
|
||||
))
|
||||
.thenAccept(consumer);
|
||||
|
||||
*/
|
||||
|
||||
// ensure we operate on the model node which will always have the given child not the view
|
||||
// node, which may have its child filtered
|
||||
GTreeNode modelParent = getModelNode(parent);
|
||||
if (modelParent == null) {
|
||||
Msg.error(this, "Attempted to show a node with an invalid parent.\n\tParent: " +
|
||||
parent + "\n\tchild: " + childName);
|
||||
return;
|
||||
}
|
||||
getModelNode(modelParent, childName, newModelChild -> {
|
||||
// force the filter to accept the new node
|
||||
ignoreFilter(newModelChild);
|
||||
|
||||
// Wait for the view to update from any filtering that may take place
|
||||
getViewNode(modelParent, childName, consumer);
|
||||
});
|
||||
public void whenNodeIsReady(GTreeNode parent, String childName, Consumer<GTreeNode> consumer) {
|
||||
Predicate<GTreeNode> nameMatches = n -> n.getName().equals(childName);
|
||||
whenNodeIsReady(parent, nameMatches, consumer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests that the node with the given name, in the given parent, be edited. This operation is
|
||||
* asynchronous. This request will be buffered as needed to wait for the given node to be added
|
||||
* to the parent, up to a timeout period.
|
||||
* <p>
|
||||
* Note: if there are multiple nodes by the given name under the given parent, then no editing
|
||||
* will take place. In that case, you can instead use {@link #startEditing(GTreeNode)}, if you
|
||||
* have the node. If you have duplicates and do not yet have the node, then you will need to
|
||||
* create your own mechanism for waiting for the desired node and then starting the edit.
|
||||
*
|
||||
* @param parent the parent node
|
||||
* @param childName the name of the child to edit
|
||||
|
@ -1206,9 +1266,10 @@ 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.
|
||||
//
|
||||
Predicate<GTreeNode> nameMatches = n -> n.getName().equals(childName);
|
||||
GTreeNode modelParent = getModelNode(parent);
|
||||
forceNewNodeIntoView(modelParent, childName, viewNode -> {
|
||||
runTask(new GTreeStartEditingTask(GTree.this, tree, viewNode));
|
||||
whenNodeIsReady(modelParent, nameMatches, editNode -> {
|
||||
runTask(new GTreeStartEditingTask(GTree.this, tree, editNode));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
@ -185,9 +185,15 @@ public abstract class GTreeNode extends CoreGTreeNode implements Comparable<GTre
|
|||
|
||||
/**
|
||||
* Returns the child node of this node with the given name.
|
||||
* <p>
|
||||
* WARNING: If this node supports duplicate named children, then the node returned by this
|
||||
* method is arbitrary, depending upon how the nodes are arranged in the parent's list. If you
|
||||
* know duplicates are not allowed, then calling this method is safe. Otherwise, you should
|
||||
* instead use {@link #getChildren(String)}.
|
||||
*
|
||||
* @param name the name of the child to be returned
|
||||
* @return the child with the given name
|
||||
* @see #getChildren(String)
|
||||
*/
|
||||
public GTreeNode getChild(String name) {
|
||||
for (GTreeNode node : children()) {
|
||||
|
@ -198,12 +204,32 @@ public abstract class GTreeNode extends CoreGTreeNode implements Comparable<GTre
|
|||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets any children under this parent with the given name.
|
||||
* <p>
|
||||
* Note: if you know this parent node does not allow duplicates, then you can use
|
||||
* {@link #getChild(String)} instead of this method.
|
||||
*
|
||||
* @param name the name of the children to be returned
|
||||
* @return the matching children
|
||||
*/
|
||||
public List<GTreeNode> getChildren(String name) {
|
||||
List<GTreeNode> results = new ArrayList<>();
|
||||
for (GTreeNode node : children()) {
|
||||
if (name.equals(node.getName())) {
|
||||
results.add(node);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the child node of this node with the given name which satisfies predicate filter.
|
||||
*
|
||||
* @param name the name of the child to be returned
|
||||
* @param filter predicate filter
|
||||
* @return the child with the given name
|
||||
* @see #getChildren(String)
|
||||
*/
|
||||
public GTreeNode getChild(String name, Predicate<GTreeNode> filter) {
|
||||
for (GTreeNode node : 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.
|
||||
|
@ -60,7 +60,7 @@ public class GTreeStartEditingTask extends GTreeTask {
|
|||
@Override
|
||||
public void editingCanceled(ChangeEvent e) {
|
||||
cellEditor.removeCellEditorListener(this);
|
||||
reselectNode();
|
||||
tree.setSelectedNode(editNode); // reselect the node on cancel
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -68,16 +68,11 @@ public class GTreeStartEditingTask extends GTreeTask {
|
|||
String newName = Objects.toString(cellEditor.getCellEditorValue());
|
||||
cellEditor.removeCellEditorListener(this);
|
||||
|
||||
tree.forceNewNodeIntoView(modelParent, newName, newViewChild -> {
|
||||
tree.setSelectedNode(newViewChild);
|
||||
// note: this call only works when the parent cannot have duplicate named nodes
|
||||
tree.whenNodeIsReady(modelParent, newName, newNode -> {
|
||||
tree.setSelectedNode(newNode);
|
||||
});
|
||||
}
|
||||
|
||||
private void reselectNode() {
|
||||
String name = editNode.getName();
|
||||
GTreeNode newModelChild = modelParent.getChild(name);
|
||||
tree.setSelectedNode(newModelChild);
|
||||
}
|
||||
});
|
||||
|
||||
tree.setNodeEditable(editNode);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue