Merge remote-tracking branch 'origin/GP-2186-dragonmacher-dt-editing-npe--SQUASHED'

This commit is contained in:
Ryan Kurtz 2022-10-08 00:45:07 -04:00
commit c4c363b6b4
12 changed files with 192 additions and 86 deletions

View File

@ -45,8 +45,7 @@ import ghidra.program.model.symbol.*;
import ghidra.program.util.*;
import ghidra.util.*;
import ghidra.util.exception.*;
import ghidra.util.task.SwingUpdateManager;
import ghidra.util.task.TaskMonitor;
import ghidra.util.task.*;
import resources.ResourceManager;
public class SymbolTreeProvider extends ComponentProviderAdapter {
@ -76,7 +75,7 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
*/
private List<GTreeTask> bufferedTasks = new ArrayList<>();
private SwingUpdateManager domainChangeUpdateManager = new SwingUpdateManager(1000,
SwingUpdateManager.DEFAULT_MAX_DELAY, "Symbol Tree Provider", () -> {
AbstractSwingUpdateManager.DEFAULT_MAX_DELAY, "Symbol Tree Provider", () -> {
if (bufferedTasks.isEmpty()) {
return;
@ -230,12 +229,12 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
String createGroup = "0Create";
int createGroupIndex = 0;
DockingAction createNamespaceAction = new CreateNamespaceAction(plugin, createGroup,
Integer.toString(createGroupIndex++));
DockingAction createClassAction = new CreateClassAction(plugin, createGroup,
Integer.toString(createGroupIndex++));
DockingAction convertToClassAction = new ConvertToClassAction(plugin, createGroup,
Integer.toString(createGroupIndex++));
DockingAction createNamespaceAction =
new CreateNamespaceAction(plugin, createGroup, Integer.toString(createGroupIndex++));
DockingAction createClassAction =
new CreateClassAction(plugin, createGroup, Integer.toString(createGroupIndex++));
DockingAction convertToClassAction =
new ConvertToClassAction(plugin, createGroup, Integer.toString(createGroupIndex++));
DockingAction renameAction = new RenameAction(plugin);
DockingAction cutAction = new CutAction(plugin, this);
@ -414,6 +413,11 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
}
private void rebuildTree() {
// If we do not cancel the edit here, then an open edits will instead be committed. It
// seems safer to cancel an edit rather than to commit it without asking.
tree.cancelEditing();
SymbolTreeRootNode node = (SymbolTreeRootNode) tree.getModelRoot();
node.setChildren(null);
tree.refilterLater();
@ -434,8 +438,7 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
private void addTask(GTreeTask task) {
// Note: if we want to call this method from off the Swing thread, then we have to
// synchronize on the list that we are adding to here.
Swing.assertSwingThread(
"Adding tasks must be done on the Swing thread," +
Swing.assertSwingThread("Adding tasks must be done on the Swing thread," +
"since they are put into a list that is processed on the Swing thread. ");
bufferedTasks.add(task);

View File

@ -19,21 +19,35 @@ import java.awt.Component;
import javax.swing.*;
import docking.DockingUtils;
import docking.UndoRedoKeeper;
import ghidra.program.model.symbol.Symbol;
class SymbolEditor extends DefaultCellEditor {
private JTextField symbolField = null;
private UndoRedoKeeper undoRedoKeeper;
SymbolEditor() {
super(new JTextField());
symbolField = (JTextField) super.getComponent();
symbolField.setBorder(BorderFactory.createEmptyBorder());
undoRedoKeeper = DockingUtils.installUndoRedo(symbolField);
}
@Override
public Object getCellEditorValue() {
return symbolField.getText().trim();
public boolean stopCellEditing() {
if (super.stopCellEditing()) {
undoRedoKeeper.clear();
return true;
}
return false;
}
@Override
public void cancelCellEditing() {
super.cancelCellEditing();
undoRedoKeeper.clear();
}
@Override

View File

@ -172,10 +172,18 @@ public class DockingUtils {
return (modifiers & osSpecificMask) == osSpecificMask;
}
/**
* Installs key binding support for undo/redo operations on the given text component.
*
* <p>Note: the edits are tracked by adding a listener to the document of the given text
* component. If that document is changed, then undo/redo will stop working.
*
* @param textComponent the text component
* @return the object that allows the client to track the undo/redo state
*/
public static UndoRedoKeeper installUndoRedo(JTextComponent textComponent) {
Document document = textComponent.getDocument();
final UndoRedoKeeper undoRedoKeeper = new UndoRedoKeeper();
document.addUndoableEditListener(e -> {
UndoableEdit edit = e.getEdit();
@ -189,9 +197,11 @@ public class DockingUtils {
KeyStroke keyStrokeForEvent = KeyStroke.getKeyStrokeForEvent(e);
if (REDO_KEYSTROKE.equals(keyStrokeForEvent)) {
undoRedoKeeper.redo();
e.consume(); // consume to prevent other listeners from processing a second time
}
else if (UNDO_KEYSTROKE.equals(keyStrokeForEvent)) {
undoRedoKeeper.undo();
e.consume(); // consume to prevent other listeners from processing a second time
}
}
});

View File

@ -22,6 +22,7 @@ import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import docking.DockingUtils;
import ghidra.util.SystemUtilities;
import ghidra.util.datastruct.WeakDataStructureFactory;
import ghidra.util.datastruct.WeakSet;
@ -120,6 +121,8 @@ public class FilterTextField extends JPanel {
});
add(layeredPane, BorderLayout.NORTH);
DockingUtils.installUndoRedo(textField);
}
private void notifyEnterPressed() {

View File

@ -1,6 +1,5 @@
/* ###
* 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.
@ -18,14 +17,36 @@ package docking.widgets.table;
import javax.swing.*;
import docking.DockingUtils;
import docking.UndoRedoKeeper;
public class GTableTextCellEditor extends DefaultCellEditor {
private static final Object TABLE_FOCUS_CELL_HIGHLIGHT_BORDER =
"Table.focusCellHighlightBorder";
private UndoRedoKeeper undoRedoKeeper;
public GTableTextCellEditor(JTextField textField) {
super(textField);
setClickCountToStart(2);
textField.setBorder(UIManager.getBorder(TABLE_FOCUS_CELL_HIGHLIGHT_BORDER));
undoRedoKeeper = DockingUtils.installUndoRedo(textField);
}
@Override
public boolean stopCellEditing() {
if (super.stopCellEditing()) {
undoRedoKeeper.clear();
return true;
}
return false;
}
@Override
public void cancelCellEditing() {
super.cancelCellEditing();
undoRedoKeeper.clear();
}
}

View File

@ -174,7 +174,7 @@ public class ColumnConstraintSet<R, T> {
return saveState;
}
/**
/*
* Returns an HTML representation of this constraint set in a tabular form. It will be used
* inside the HTML representation of the entire filter. See {@link ColumnBasedTableFilter#getHtmlRepresentation()}
* for a description of the table format.

View File

@ -62,7 +62,7 @@ public abstract class AbstractColumnConstraintEditor<T> implements ColumnConstra
* This expects the UI to have been constructed.
*
* @see #getValue()
* @return
* @return the value
*/
protected abstract ColumnConstraint<T> getValueFromComponent();

View File

@ -26,6 +26,7 @@ import javax.swing.*;
import org.apache.commons.lang3.StringUtils;
import docking.DockingUtils;
import docking.widgets.DropDownTextField;
import docking.widgets.DropDownTextFieldDataModel;
import docking.widgets.list.GListCellRenderer;
@ -61,6 +62,7 @@ public class AutocompletingStringConstraintEditor extends DataLoadingConstraintE
textField = new DropDownTextField<>(autocompleter, 100);
textField.setIgnoreEnterKeyPress(true);
textField.getDocument().addUndoableEditListener(e -> valueChanged());
DockingUtils.installUndoRedo(textField);
panel.add(textField, BorderLayout.NORTH);
textField.addActionListener(e -> textField.closeDropDownWindow());
@ -139,9 +141,8 @@ public class AutocompletingStringConstraintEditor extends DataLoadingConstraintE
return Collections.emptyList();
}
searchText = searchText.trim();
lastConstraint =
(StringColumnConstraint) currentConstraint.parseConstraintValue(searchText,
columnDataSource.getTableDataSource());
lastConstraint = (StringColumnConstraint) currentConstraint
.parseConstraintValue(searchText, columnDataSource.getTableDataSource());
// Use a Collator to support languages other than English.
Collator collator = Collator.getInstance();
@ -222,8 +223,7 @@ public class AutocompletingStringConstraintEditor extends DataLoadingConstraintE
// escape all unescaped '\' and '$' chars, as Match.appendReplacement() will treat
// them as regex characters
String quoted = Matcher.quoteReplacement(group);
String replacement =
HTMLUtilities.colorString(color, HTMLUtilities.bold(quoted));
String replacement = HTMLUtilities.colorString(color, HTMLUtilities.bold(quoted));
matcher.appendReplacement(sb, replacement);
}
matcher.appendTail(sb);

View File

@ -19,6 +19,7 @@ import java.awt.*;
import javax.swing.*;
import docking.DockingUtils;
import docking.widgets.label.GDHtmlLabel;
import docking.widgets.table.constraint.ColumnConstraint;
import docking.widgets.table.constraint.StringColumnConstraint;
@ -50,6 +51,8 @@ public class StringConstraintEditor extends AbstractColumnConstraintEditor<Strin
textField = new JTextField();
textField.getDocument().addUndoableEditListener(e -> valueChanged());
DockingUtils.installUndoRedo(textField);
panel.add(textField, BorderLayout.CENTER);
infoLabel = new GDHtmlLabel("abc"); // temporary text in the label so that it sizes properly
@ -77,7 +80,7 @@ public class StringConstraintEditor extends AbstractColumnConstraintEditor<Strin
@Override
protected void updateInfoMessage(boolean isValid) {
// uses &nbsp to presever the labels height.
// uses &nbsp to preserve the label's height
String status = formatStatus(isValid ? "&nbsp;" : errorMessage, true);
infoLabel.setText(status);
}

View File

@ -15,8 +15,8 @@
*/
package docking.widgets.tree;
import static docking.widgets.tree.support.GTreeSelectionEvent.EventOrigin.USER_GENERATED;
import static ghidra.util.SystemUtilities.runSwingNow;
import static docking.widgets.tree.support.GTreeSelectionEvent.EventOrigin.*;
import static ghidra.util.SystemUtilities.*;
import java.awt.*;
import java.awt.dnd.Autoscroll;
@ -1009,6 +1009,11 @@ 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) {
// check for null here to preserve the stack, as the code below is asynchronous
Objects.requireNonNull(parent);
Objects.requireNonNull(childName);
Objects.requireNonNull(consumer);
int expireMs = 3000;
Supplier<GTreeNode> supplier = () -> {
GTreeNode modelParent = getModelNode(parent);
@ -1023,6 +1028,11 @@ public class GTree extends JPanel implements BusyListener {
// Waits for the given view node, passing it to the consumer when available
private void getViewNode(GTreeNode parent, String childName, 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(consumer);
int expireMs = 3000;
Supplier<GTreeNode> supplier = () -> {
GTreeNode viewParent = getViewNode(parent);
@ -1081,6 +1091,11 @@ public class GTree extends JPanel implements BusyListener {
// 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);
@ -1324,6 +1339,10 @@ public class GTree extends JPanel implements BusyListener {
tree.stopEditing();
}
public void cancelEditing() {
tree.cancelEditing();
}
public void setNodeEditable(GTreeNode child) {
// for now only subclasses of GTree will set a node editable.
}

View File

@ -1,6 +1,5 @@
/* ###
* 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.
@ -18,20 +17,54 @@ package docking.widgets.tree.support;
import java.awt.Component;
import javax.swing.JTree;
import javax.swing.*;
import javax.swing.tree.DefaultTreeCellEditor;
import javax.swing.tree.DefaultTreeCellRenderer;
import docking.DockingUtils;
import docking.UndoRedoKeeper;
import docking.widgets.tree.GTreeNode;
public class GTreeCellEditor extends DefaultTreeCellEditor {
private UndoRedoKeeper undoRedoKeeper;
public GTreeCellEditor(JTree tree, DefaultTreeCellRenderer renderer) {
super(tree, renderer);
if (realEditor instanceof DefaultCellEditor) {
Component c = ((DefaultCellEditor) realEditor).getComponent();
if (c instanceof JTextField) {
JTextField tf = (JTextField) c;
undoRedoKeeper = DockingUtils.installUndoRedo(tf);
}
}
}
@Override
public Component getTreeCellEditorComponent(JTree jTree, Object value,
boolean isSelected, boolean expanded, boolean leaf, int row) {
public boolean stopCellEditing() {
if (super.stopCellEditing()) {
clearUndoRedo();
return true;
}
return false;
}
@Override
public void cancelCellEditing() {
super.cancelCellEditing();
clearUndoRedo();
}
private void clearUndoRedo() {
if (undoRedoKeeper != null) {
undoRedoKeeper.clear();
}
}
@Override
public Component getTreeCellEditorComponent(JTree jTree, Object value, boolean isSelected,
boolean expanded, boolean leaf, int row) {
GTreeNode node = (GTreeNode) value;
if (node.isLeaf()) {
@ -41,8 +74,8 @@ public class GTreeCellEditor extends DefaultTreeCellEditor {
renderer.setOpenIcon(node.getIcon(true));
renderer.setClosedIcon(node.getIcon(false));
}
return super.getTreeCellEditorComponent(jTree, value, isSelected, expanded,
leaf, row);
return super.getTreeCellEditorComponent(jTree, value, isSelected, expanded, leaf, row);
}
}