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
|
@ -15,7 +15,6 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.app.plugin.core.datamgr.actions;
|
package ghidra.app.plugin.core.datamgr.actions;
|
||||||
|
|
||||||
import javax.swing.SwingUtilities;
|
|
||||||
import javax.swing.tree.TreePath;
|
import javax.swing.tree.TreePath;
|
||||||
|
|
||||||
import docking.ActionContext;
|
import docking.ActionContext;
|
||||||
|
@ -52,19 +51,15 @@ public class CreateProjectArchiveAction extends DockingAction {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void selectNewArchive(final Archive archive, final DataTypeArchiveGTree gTree) {
|
private void selectNewArchive(final Archive archive, final DataTypeArchiveGTree gTree) {
|
||||||
SwingUtilities.invokeLater(new Runnable() {
|
|
||||||
@Override
|
GTreeNode root = gTree.getViewRoot();
|
||||||
public void run() {
|
gTree.whenNodeIsReady(root, archive.getName(), archiveNode -> {
|
||||||
// start an edit on the new temporary node name
|
|
||||||
GTreeNode node = gTree.getViewRoot();
|
gTree.expandPath(root);
|
||||||
final GTreeNode child = node.getChild(archive.getName());
|
|
||||||
if (child != null) {
|
TreePath path = archiveNode.getTreePath();
|
||||||
gTree.expandPath(node);
|
|
||||||
TreePath path = child.getTreePath();
|
|
||||||
gTree.scrollPathToVisible(path);
|
gTree.scrollPathToVisible(path);
|
||||||
gTree.setSelectedNode(child);
|
gTree.setSelectedNode(archiveNode);
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,126 +0,0 @@
|
||||||
/* ###
|
|
||||||
* IP: GHIDRA
|
|
||||||
* REVIEWED: YES
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package ghidra.app.plugin.core.datamgr.editor;
|
|
||||||
|
|
||||||
import ghidra.app.plugin.core.datamgr.DataTypeManagerPlugin;
|
|
||||||
import ghidra.app.plugin.core.datamgr.tree.DataTypeNode;
|
|
||||||
import ghidra.program.model.data.DataType;
|
|
||||||
|
|
||||||
import java.awt.Component;
|
|
||||||
|
|
||||||
import javax.swing.*;
|
|
||||||
import javax.swing.event.CellEditorListener;
|
|
||||||
import javax.swing.event.ChangeEvent;
|
|
||||||
import javax.swing.tree.*;
|
|
||||||
|
|
||||||
import docking.widgets.tree.GTreeNode;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A implementation of {@link DefaultTreeCellEditor} that adds the ability to launch custom
|
|
||||||
* editors instead of the default editor. This class will also handle re-selecting the
|
|
||||||
* edited node after editing has successfully completed.
|
|
||||||
*/
|
|
||||||
public class DataTypesTreeCellEditor extends DefaultTreeCellEditor {
|
|
||||||
|
|
||||||
private final DataTypeManagerPlugin plugin;
|
|
||||||
private GTreeNode lastEditedNode;
|
|
||||||
|
|
||||||
public DataTypesTreeCellEditor(JTree tree, DefaultTreeCellRenderer renderer,
|
|
||||||
DataTypeManagerPlugin plugin) {
|
|
||||||
super(tree, renderer);
|
|
||||||
this.plugin = plugin;
|
|
||||||
|
|
||||||
// listener to re-select the edited node after editing is finished (for default editing)
|
|
||||||
addCellEditorListener(new CellEditorListener() {
|
|
||||||
@Override
|
|
||||||
public void editingCanceled(ChangeEvent e) {
|
|
||||||
lastEditedNode = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void editingStopped(ChangeEvent e) {
|
|
||||||
if (lastEditedNode != null) {
|
|
||||||
handleEditingFinished((CellEditor) e.getSource());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleEditingFinished(final CellEditor cellEditor) {
|
|
||||||
|
|
||||||
// this is called before the changes have been put into place and we
|
|
||||||
// need to wait until the
|
|
||||||
// node has been changed before attempting to select it
|
|
||||||
SwingUtilities.invokeLater(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
Object cellEditorValue = cellEditor.getCellEditorValue();
|
|
||||||
if (cellEditorValue == null || !(cellEditorValue instanceof String)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// reselect the cell that was edited
|
|
||||||
GTreeNode newNode = lastEditedNode.getChild(cellEditorValue.toString());
|
|
||||||
if (newNode == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
TreePath path = newNode.getTreePath();
|
|
||||||
tree.setSelectionPath(path);
|
|
||||||
tree.scrollPathToVisible(path);
|
|
||||||
lastEditedNode = null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Component getTreeCellEditorComponent(final JTree jTree, Object value,
|
|
||||||
boolean isSelected, boolean expanded, boolean leaf, int row) {
|
|
||||||
|
|
||||||
if (isCustom(value)) {
|
|
||||||
edit(value);
|
|
||||||
SwingUtilities.invokeLater(new Runnable() { // we are going to bring a stand-alone editor
|
|
||||||
@Override
|
|
||||||
public void run() { // the tree is not longer involved, so tell it
|
|
||||||
jTree.cancelEditing();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return renderer.getTreeCellRendererComponent(jTree, value, isSelected, expanded, leaf,
|
|
||||||
row, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
lastEditedNode = ((GTreeNode) value).getParent();
|
|
||||||
return super.getTreeCellEditorComponent(jTree, value, isSelected, expanded, leaf, row);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void edit(Object value) {
|
|
||||||
DataTypeNode dataTypeNode = (DataTypeNode) value;
|
|
||||||
if (dataTypeNode.isModifiable()) {
|
|
||||||
DataType dt = dataTypeNode.getDataType();
|
|
||||||
plugin.getEditorManager().edit(dt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isCustom(Object value) {
|
|
||||||
if (!(value instanceof DataTypeNode)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
DataTypeNode node = (DataTypeNode) value;
|
|
||||||
return node.hasCustomEditor();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -50,6 +50,11 @@ public class KeyEntryPanel extends JPanel {
|
||||||
add(clearButton);
|
add(clearButton);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void requestFocus() {
|
||||||
|
keyEntryField.requestFocus();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the text field used by this class
|
* Returns the text field used by this class
|
||||||
* @return the text field
|
* @return the text field
|
||||||
|
|
|
@ -25,8 +25,7 @@ import java.awt.event.*;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.*;
|
||||||
import java.util.function.Supplier;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
|
@ -316,6 +315,7 @@ public class GTree extends JPanel implements BusyListener {
|
||||||
updateModelFilter();
|
updateModelFilter();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// adds the given node to the view when the tree is filtered, regardless of filter match
|
||||||
private void ignoreFilter(GTreeNode node) {
|
private void ignoreFilter(GTreeNode node) {
|
||||||
if (!isFiltered()) {
|
if (!isFiltered()) {
|
||||||
return;
|
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
|
// 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
|
// check for null here to preserve the stack, as the code below is asynchronous
|
||||||
Objects.requireNonNull(parent);
|
Objects.requireNonNull(parent);
|
||||||
Objects.requireNonNull(childName);
|
Objects.requireNonNull(matches);
|
||||||
Objects.requireNonNull(consumer);
|
Objects.requireNonNull(consumer);
|
||||||
|
|
||||||
int expireMs = 3000;
|
int expireMs = 3000;
|
||||||
Supplier<GTreeNode> supplier = () -> {
|
Supplier<GTreeNode> supplier = () -> {
|
||||||
GTreeNode modelParent = getModelNode(parent);
|
GTreeNode modelParent = getModelNode(parent);
|
||||||
if (modelParent != null) {
|
if (modelParent == null) {
|
||||||
return modelParent.getChild(childName);
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<GTreeNode> children = modelParent.getChildren();
|
||||||
|
for (GTreeNode node : children) {
|
||||||
|
if (matches.test(node)) {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
ExpiringSwingTimer.get(supplier, expireMs, consumer);
|
ExpiringSwingTimer.get(supplier, expireMs, consumer);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Waits for the given view node, passing it to the consumer when available
|
// 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
|
// check for null here to preserve the stack, as the code below is asynchronous
|
||||||
Objects.requireNonNull(parent);
|
Objects.requireNonNull(parent);
|
||||||
Objects.requireNonNull(childName);
|
Objects.requireNonNull(matches);
|
||||||
Objects.requireNonNull(consumer);
|
Objects.requireNonNull(consumer);
|
||||||
|
|
||||||
int expireMs = 3000;
|
int expireMs = 3000;
|
||||||
Supplier<GTreeNode> supplier = () -> {
|
Supplier<GTreeNode> supplier = () -> {
|
||||||
GTreeNode viewParent = getViewNode(parent);
|
GTreeNode viewParent = getViewNode(parent);
|
||||||
if (viewParent != null) {
|
if (viewParent == null) {
|
||||||
return viewParent.getChild(childName);
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<GTreeNode> children = viewParent.getChildren();
|
||||||
|
for (GTreeNode node : children) {
|
||||||
|
if (matches.test(node)) {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
ExpiringSwingTimer.get(supplier, expireMs, consumer);
|
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
|
* 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
|
* available to the model. This method will ensure that the named child passes any current
|
||||||
|
@ -1145,49 +1224,30 @@ public class GTree extends JPanel implements BusyListener {
|
||||||
* Note: this method will not wait forever for the given node to appear. It will eventually give
|
* Note: this method will not wait forever for the given node to appear. It will eventually give
|
||||||
* up if the node never arrives.
|
* 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
|
* @param parent the model's parent node. If the view's parent node is passed, it will be
|
||||||
* translated to the model node.
|
* translated to the model node.
|
||||||
* @param childName the name of the desired child
|
* @param childName the name of the desired child
|
||||||
* @param consumer the consumer callback to which the child node will be given when available
|
* @param consumer the consumer callback to which the child node will be given when available
|
||||||
*/
|
*/
|
||||||
public void forceNewNodeIntoView(GTreeNode parent, String childName,
|
public void whenNodeIsReady(GTreeNode parent, String childName, Consumer<GTreeNode> consumer) {
|
||||||
Consumer<GTreeNode> consumer) {
|
Predicate<GTreeNode> nameMatches = n -> n.getName().equals(childName);
|
||||||
|
whenNodeIsReady(parent, nameMatches, 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);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Requests that the node with the given name, in the given parent, be edited. This operation is
|
* 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
|
* 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.
|
* 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 parent the parent node
|
||||||
* @param childName the name of the child to edit
|
* @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
|
// 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.
|
// once the given node has been added to the parent.
|
||||||
//
|
//
|
||||||
|
Predicate<GTreeNode> nameMatches = n -> n.getName().equals(childName);
|
||||||
GTreeNode modelParent = getModelNode(parent);
|
GTreeNode modelParent = getModelNode(parent);
|
||||||
forceNewNodeIntoView(modelParent, childName, viewNode -> {
|
whenNodeIsReady(modelParent, nameMatches, editNode -> {
|
||||||
runTask(new GTreeStartEditingTask(GTree.this, tree, viewNode));
|
runTask(new GTreeStartEditingTask(GTree.this, tree, editNode));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -185,9 +185,15 @@ public abstract class GTreeNode extends CoreGTreeNode implements Comparable<GTre
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the child node of this node with the given name.
|
* 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
|
* @param name the name of the child to be returned
|
||||||
* @return the child with the given name
|
* @return the child with the given name
|
||||||
|
* @see #getChildren(String)
|
||||||
*/
|
*/
|
||||||
public GTreeNode getChild(String name) {
|
public GTreeNode getChild(String name) {
|
||||||
for (GTreeNode node : children()) {
|
for (GTreeNode node : children()) {
|
||||||
|
@ -198,12 +204,32 @@ public abstract class GTreeNode extends CoreGTreeNode implements Comparable<GTre
|
||||||
return null;
|
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.
|
* 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 name the name of the child to be returned
|
||||||
* @param filter predicate filter
|
* @param filter predicate filter
|
||||||
* @return the child with the given name
|
* @return the child with the given name
|
||||||
|
* @see #getChildren(String)
|
||||||
*/
|
*/
|
||||||
public GTreeNode getChild(String name, Predicate<GTreeNode> filter) {
|
public GTreeNode getChild(String name, Predicate<GTreeNode> filter) {
|
||||||
for (GTreeNode node : children()) {
|
for (GTreeNode node : children()) {
|
||||||
|
|
|
@ -60,7 +60,7 @@ public class GTreeStartEditingTask extends GTreeTask {
|
||||||
@Override
|
@Override
|
||||||
public void editingCanceled(ChangeEvent e) {
|
public void editingCanceled(ChangeEvent e) {
|
||||||
cellEditor.removeCellEditorListener(this);
|
cellEditor.removeCellEditorListener(this);
|
||||||
reselectNode();
|
tree.setSelectedNode(editNode); // reselect the node on cancel
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -68,16 +68,11 @@ public class GTreeStartEditingTask extends GTreeTask {
|
||||||
String newName = Objects.toString(cellEditor.getCellEditorValue());
|
String newName = Objects.toString(cellEditor.getCellEditorValue());
|
||||||
cellEditor.removeCellEditorListener(this);
|
cellEditor.removeCellEditorListener(this);
|
||||||
|
|
||||||
tree.forceNewNodeIntoView(modelParent, newName, newViewChild -> {
|
// note: this call only works when the parent cannot have duplicate named nodes
|
||||||
tree.setSelectedNode(newViewChild);
|
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);
|
tree.setNodeEditable(editNode);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue