Merge remote-tracking branch 'origin/GP-4293_Dan_doubleClickModelActions--SQUASHED'

This commit is contained in:
Ryan Kurtz 2024-04-03 13:02:31 -04:00
commit 611aae64ae
30 changed files with 470 additions and 144 deletions

View file

@ -23,6 +23,7 @@ import ghidra.debug.api.breakpoint.LogicalBreakpoint;
import ghidra.debug.api.breakpoint.LogicalBreakpoint.State;
import ghidra.pcode.exec.SleighUtils;
import ghidra.program.model.address.Address;
import ghidra.program.util.ProgramLocation;
import ghidra.trace.model.breakpoint.TraceBreakpoint;
import ghidra.trace.model.thread.TraceThread;
@ -82,6 +83,10 @@ public class BreakpointLocationRow {
return loc.getMinAddress();
}
public ProgramLocation getProgramLocation() {
return new ProgramLocation(loc.getTrace().getProgramView(), getAddress());
}
public String getTraceName() {
return loc.getTrace().getName();
}

View file

@ -1153,7 +1153,7 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter
}
traceManager.activateTrace(trace);
}
listingService.goTo(row.getAddress(), true);
listingService.goTo(row.getProgramLocation(), true);
}
protected void createActions() {

View file

@ -267,8 +267,8 @@ public class DebuggerLegacyRegionsPanel extends JPanel {
int selectedRow = regionTable.getSelectedRow();
int selectedColumn = regionTable.getSelectedColumn();
Object value = regionTable.getValueAt(selectedRow, selectedColumn);
if (value instanceof Address) {
listingService.goTo((Address) value, true);
if (value instanceof Address address) {
listingService.goTo(address, true);
}
}
}

View file

@ -187,7 +187,7 @@ public class DebuggerRegionsPanel extends AbstractObjectsTableBasedPanel<TraceOb
}
@Override
protected ObjectTableModel createModel(Plugin plugin) {
protected ObjectTableModel createModel() {
return new RegionTableModel(plugin);
}

View file

@ -26,27 +26,25 @@ import javax.swing.event.ListSelectionListener;
import docking.ComponentProvider;
import ghidra.app.plugin.core.debug.gui.model.AbstractQueryTablePanel.CellActivationListener;
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueProperty;
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueRow;
import ghidra.app.services.DebuggerListingService;
import ghidra.app.services.DebuggerTraceManagerService;
import ghidra.debug.api.model.DebuggerObjectActionContext;
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
import ghidra.framework.plugintool.AutoService;
import ghidra.framework.plugintool.Plugin;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.annotation.AutoServiceConsumed;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRange;
import ghidra.program.util.ProgramSelection;
import ghidra.trace.model.Trace;
import ghidra.trace.model.target.TraceObject;
import ghidra.trace.model.target.TraceObjectInterface;
import ghidra.trace.model.target.*;
public abstract class AbstractObjectsTableBasedPanel<U extends TraceObjectInterface>
extends ObjectsTablePanel implements ListSelectionListener, CellActivationListener {
extends ObjectsTablePanel
implements ListSelectionListener, CellActivationListener, ObjectDefaultActionsMixin {
private final ComponentProvider provider;
private final Class<U> objType;
@AutoServiceConsumed
protected DebuggerTraceManagerService traceManager;
@AutoServiceConsumed
protected DebuggerListingService listingService;
@SuppressWarnings("unused")
@ -121,23 +119,32 @@ public abstract class AbstractObjectsTableBasedPanel<U extends TraceObjectInterf
@Override
public void cellActivated(JTable table) {
if (listingService == null) {
if (performElementCellDefaultAction(table)) {
return;
}
int row = table.getSelectedRow();
int col = table.getSelectedColumn();
Object value = table.getValueAt(row, col);
if (!(value instanceof ValueProperty<?> property)) {
performValueRowDefaultAction(getSelectedItem());
}
@Override
public DebuggerCoordinates getCurrent() {
return current;
}
@Override
public PluginTool getTool() {
return plugin.getTool();
}
@Override
public void activatePath(TraceObjectKeyPath path) {
if (current.getTrace() == null) {
return;
}
Object propVal = property.getValue();
if (propVal instanceof Address address) {
listingService.goTo(address, true);
try {
traceManager.activate(current.pathNonCanonical(path));
}
else if (propVal instanceof AddressRange range) {
listingService.setCurrentSelection(
new ProgramSelection(range.getMinAddress(), range.getMaxAddress()));
listingService.goTo(range.getMinAddress(), true);
catch (IllegalArgumentException e) {
plugin.getTool().setStatusInfo(e.getMessage(), true);
}
}
}

View file

@ -41,6 +41,7 @@ public abstract class AbstractQueryTablePanel<T, M extends AbstractQueryTableMod
void cellActivated(JTable table);
}
protected final Plugin plugin;
protected final M tableModel;
protected final GhidraTable table;
protected final GhidraTableFilterPanel<T> filterPanel;
@ -54,7 +55,9 @@ public abstract class AbstractQueryTablePanel<T, M extends AbstractQueryTableMod
public AbstractQueryTablePanel(Plugin plugin) {
super(new BorderLayout());
tableModel = createModel(plugin);
this.plugin = plugin;
tableModel = createModel();
table = new GhidraTable(tableModel);
filterPanel = new GhidraTableFilterPanel<>(table, tableModel);
@ -80,7 +83,7 @@ public abstract class AbstractQueryTablePanel<T, M extends AbstractQueryTableMod
});
}
protected abstract M createModel(Plugin plugin);
protected abstract M createModel();
protected void coordinatesChanged() {
// Extension point

View file

@ -45,11 +45,11 @@ import ghidra.app.plugin.core.debug.gui.DebuggerResources.CloneWindowAction;
import ghidra.app.plugin.core.debug.gui.MultiProviderSaveBehavior.SaveableProvider;
import ghidra.app.plugin.core.debug.gui.control.TargetActionTask;
import ghidra.app.plugin.core.debug.gui.model.AbstractQueryTablePanel.CellActivationListener;
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ObjectRow;
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueRow;
import ghidra.app.plugin.core.debug.gui.model.ObjectTreeModel.*;
import ghidra.app.plugin.core.debug.gui.model.ObjectTreeModel.RootNode;
import ghidra.app.plugin.core.debug.gui.model.PathTableModel.PathRow;
import ghidra.app.services.DebuggerListingService;
import ghidra.app.services.DebuggerTraceManagerService;
import ghidra.debug.api.model.DebuggerObjectActionContext;
import ghidra.debug.api.target.ActionName;
@ -233,6 +233,8 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
@AutoServiceConsumed
protected DebuggerTraceManagerService traceManager;
@AutoServiceConsumed
protected DebuggerListingService listingService;
@SuppressWarnings("unused")
private final AutoService.Wiring autoServiceWiring;
@ -431,8 +433,49 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
mainPanel.revalidate();
}
protected class ObjectsTreeListener implements Adapters.FocusListener,
protected void activatePath(TraceObjectKeyPath path) {
if (current.getTrace() == null) {
return;
}
try {
traceManager.activate(current.pathNonCanonical(path));
}
catch (IllegalArgumentException e) {
plugin.getTool().setStatusInfo(e.getMessage(), true);
}
}
protected class MyMixin implements ObjectDefaultActionsMixin {
@Override
public DebuggerCoordinates getCurrent() {
return current;
}
@Override
public PluginTool getTool() {
return plugin.getTool();
}
@Override
public void activatePath(TraceObjectKeyPath path) {
DebuggerModelProvider.this.activatePath(path);
}
}
protected class ObjectsTreeListener extends MyMixin implements Adapters.FocusListener,
Adapters.TreeExpansionListener, Adapters.MouseListener, Adapters.KeyListener {
private void activateObjectSelectedInTree() {
List<AbstractNode> sel = objectsTreePanel.getSelectedItems();
if (sel.size() != 1) {
// TODO: Multiple paths? PathMatcher can do it, just have to parse
// Just leave whatever was there.
return;
}
TraceObjectValue value = sel.get(0).getValue();
performDefaultAction(value);
}
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
@ -501,18 +544,14 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
}
}
protected class ElementsTableListener
protected class ElementsTableListener extends MyMixin
implements Adapters.FocusListener, CellActivationListener {
@Override
public void cellActivated(JTable table) {
ValueRow row = elementsTablePanel.getSelectedItem();
if (row == null) {
if (performElementCellDefaultAction(table)) {
return;
}
if (!(row instanceof ObjectRow objectRow)) {
return;
}
activatePath(objectRow.getTraceObject().getCanonicalPath());
performValueRowDefaultAction(elementsTablePanel.getSelectedItem());
}
@Override
@ -567,19 +606,11 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
}
}
protected class AttributesTableListener
protected class AttributesTableListener extends MyMixin
implements Adapters.FocusListener, CellActivationListener {
@Override
public void cellActivated(JTable table) {
PathRow row = attributesTablePanel.getSelectedItem();
if (row == null) {
return;
}
Object value = row.getValue();
if (!(value instanceof TraceObject object)) {
return;
}
activatePath(object.getCanonicalPath());
performPathRowDefaultAction(attributesTablePanel.getSelectedItem());
}
@Override
@ -746,19 +777,6 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
}
}
private void activateObjectSelectedInTree() {
List<AbstractNode> sel = objectsTreePanel.getSelectedItems();
if (sel.size() != 1) {
// TODO: Multiple paths? PathMatcher can do it, just have to parse
// Just leave whatever was there.
return;
}
TraceObjectValue value = sel.get(0).getValue();
if (value != null && value.getValue() instanceof TraceObject child) {
activatePath(child.getCanonicalPath());
}
}
@Override
public ActionContext getActionContext(MouseEvent event) {
if (event != null) {
@ -970,26 +988,6 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
}
}
protected void activatePath(TraceObjectKeyPath path) {
Trace trace = current.getTrace();
if (trace != null) {
TraceObject object = trace.getObjectManager().getObjectByCanonicalPath(path);
if (object != null) {
traceManager.activateObject(object);
return;
}
object = trace.getObjectManager()
.getObjectsByPath(Lifespan.at(current.getSnap()), path)
.findFirst()
.orElse(null);
if (object != null) {
traceManager.activateObject(object);
return;
}
plugin.getTool().setStatusInfo("No such object at path " + path, true);
}
}
protected void setPath(TraceObjectKeyPath path, JComponent source, EventOrigin origin) {
if (Objects.equals(this.path, path) && getTreeSelection() != null) {
return;

View file

@ -0,0 +1,169 @@
/* ###
* IP: GHIDRA
*
* 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.debug.gui.model;
import java.util.*;
import javax.swing.JTable;
import ghidra.app.plugin.core.debug.gui.control.TargetActionTask;
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueProperty;
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueRow;
import ghidra.app.plugin.core.debug.gui.model.PathTableModel.PathRow;
import ghidra.app.services.DebuggerListingService;
import ghidra.dbg.target.*;
import ghidra.debug.api.model.DebuggerSingleObjectPathActionContext;
import ghidra.debug.api.target.ActionName;
import ghidra.debug.api.target.Target;
import ghidra.debug.api.target.Target.ActionEntry;
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRange;
import ghidra.program.util.ProgramLocation;
import ghidra.program.util.ProgramSelection;
import ghidra.trace.model.target.*;
import ghidra.util.Msg;
public interface ObjectDefaultActionsMixin {
PluginTool getTool();
DebuggerCoordinates getCurrent();
void activatePath(TraceObjectKeyPath path);
default void toggleObject(TraceObject object) {
if (!getCurrent().isAliveAndPresent()) {
return;
}
Target target = getCurrent().getTarget();
Map<String, ActionEntry> actions = target.collectActions(ActionName.TOGGLE,
new DebuggerSingleObjectPathActionContext(object.getCanonicalPath()));
ActionEntry action = actions.values()
.stream()
.filter(e -> !e.requiresPrompt())
.sorted(Comparator.comparing(e -> -e.specificity()))
.findFirst()
.orElse(null);
if (action == null) {
Msg.error(this, "No suitable toggle action for " + object);
return;
}
TargetActionTask.runAction(getTool(), "Toggle", action);
}
default void goToAddress(DebuggerListingService listingService, Address address) {
ProgramLocation loc = new ProgramLocation(getCurrent().getView(), address);
listingService.goTo(loc, true);
}
default void goToAddress(Address address) {
DebuggerListingService listingService = getTool().getService(DebuggerListingService.class);
if (listingService == null) {
return;
}
goToAddress(listingService, address);
}
default void goToRange(AddressRange range) {
DebuggerListingService listingService = getTool().getService(DebuggerListingService.class);
if (listingService == null) {
return;
}
listingService.setCurrentSelection(
new ProgramSelection(range.getMinAddress(), range.getMaxAddress()));
goToAddress(listingService, range.getMinAddress());
}
default boolean performElementCellDefaultAction(JTable table) {
int row = table.getSelectedRow();
int col = table.getSelectedColumn();
Object cellValue = table.getValueAt(row, col);
if (cellValue instanceof ValueProperty<?> property) {
Object propValue = property.getValue();
if (performDefaultAction(propValue)) {
return true;
}
}
return false;
}
default boolean performValueRowDefaultAction(ValueRow row) {
if (row == null) {
return false;
}
return performDefaultAction(row.getValue());
}
default boolean performPathRowDefaultAction(PathRow row) {
if (row == null) {
return false;
}
return performDefaultAction(row.getValue());
}
default boolean performDefaultAction(TraceObjectValue value) {
if (value == null) {
return false;
}
return performDefaultAction(value.getValue());
}
default boolean performDefaultAction(TraceObject object) {
Set<Class<? extends TargetObject>> interfaces = object.getTargetSchema().getInterfaces();
if (interfaces.contains(TargetActivatable.class)) {
activatePath(object.getCanonicalPath());
return true;
}
/**
* Should I check aliveAndPresent() here? If I do, behavior changes when target is dead,
* which might be unexpected.
*/
if (interfaces.contains(TargetTogglable.class)) {
toggleObject(object);
return true;
}
long snap = getCurrent().getSnap();
TraceObjectValue valAddress = object.getAttribute(snap, "_address");
if (valAddress != null && valAddress.getValue() instanceof Address address) {
goToAddress(address);
return true;
}
TraceObjectValue valRange = object.getAttribute(snap, "_range");
if (valRange != null && valRange.getValue() instanceof AddressRange range) {
goToRange(range);
return true;
}
return false;
}
default boolean performDefaultAction(Object value) {
if (value instanceof Address address) {
goToAddress(address);
return true;
}
if (value instanceof AddressRange range) {
goToRange(range);
return true;
}
if (value instanceof TraceObject object) {
return performDefaultAction(object);
}
return false;
}
}

View file

@ -69,7 +69,7 @@ public class ObjectsTablePanel extends AbstractQueryTablePanel<ValueRow, ObjectT
}
@Override
protected ObjectTableModel createModel(Plugin plugin) {
protected ObjectTableModel createModel() {
return new ObjectTableModel(plugin);
}

View file

@ -24,7 +24,7 @@ public class PathsTablePanel extends AbstractQueryTablePanel<PathRow, PathTableM
}
@Override
protected PathTableModel createModel(Plugin plugin) {
protected PathTableModel createModel() {
return new PathTableModel(plugin);
}
}

View file

@ -229,8 +229,8 @@ public class DebuggerLegacyModulesPanel extends JPanel {
int selectedRow = moduleTable.getSelectedRow();
int selectedColumn = moduleTable.getSelectedColumn();
Object value = moduleTable.getValueAt(selectedRow, selectedColumn);
if (value instanceof Address) {
provider.listingService.goTo((Address) value, true);
if (value instanceof Address address) {
provider.listingService.goTo(address, true);
}
}
}

View file

@ -273,8 +273,8 @@ public class DebuggerLegacySectionsPanel extends JPanel {
int selectedRow = sectionTable.getSelectedRow();
int selectedColumn = sectionTable.getSelectedColumn();
Object value = sectionTable.getValueAt(selectedRow, selectedColumn);
if (value instanceof Address) {
provider.listingService.goTo((Address) value, true);
if (value instanceof Address address) {
provider.listingService.goTo(address, true);
}
}
}

View file

@ -232,7 +232,7 @@ public class DebuggerModulesPanel extends AbstractObjectsTableBasedPanel<TraceOb
}
@Override
protected ObjectTableModel createModel(Plugin plugin) {
protected ObjectTableModel createModel() {
return new ModuleTableModel(plugin);
}

View file

@ -211,7 +211,7 @@ public class DebuggerSectionsPanel extends AbstractObjectsTableBasedPanel<TraceO
}
@Override
protected ObjectTableModel createModel(Plugin plugin) {
protected ObjectTableModel createModel() {
return new SectionTableModel(plugin);
}

View file

@ -63,6 +63,7 @@ import ghidra.program.model.data.*;
import ghidra.program.model.lang.*;
import ghidra.program.model.listing.Data;
import ghidra.program.model.util.CodeUnitInsertionException;
import ghidra.program.util.ProgramLocation;
import ghidra.trace.model.*;
import ghidra.trace.model.guest.TracePlatform;
import ghidra.trace.model.listing.*;
@ -639,7 +640,8 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
if (listingService == null) {
return;
}
listingService.goTo(address, true);
ProgramLocation loc = new ProgramLocation(current.getView(), address);
listingService.goTo(loc, true);
}).build());
}
return result;
@ -658,7 +660,8 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter
if (address == null) {
return;
}
listingService.goTo(address, true);
ProgramLocation loc = new ProgramLocation(current.getView(), address);
listingService.goTo(loc, true);
}
@Override

View file

@ -23,7 +23,6 @@ import javax.swing.event.ListSelectionListener;
import docking.widgets.table.AbstractDynamicTableColumn;
import docking.widgets.table.TableColumnDescriptor;
import ghidra.app.plugin.core.debug.gui.model.*;
import ghidra.app.plugin.core.debug.gui.model.AbstractQueryTablePanel.CellActivationListener;
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueRow;
import ghidra.app.plugin.core.debug.gui.model.columns.TraceValueKeyColumn;
import ghidra.app.plugin.core.debug.gui.model.columns.TraceValueObjectAttributeColumn;
@ -46,7 +45,7 @@ import ghidra.trace.model.target.TraceObject;
import ghidra.trace.model.target.TraceObjectValue;
public class DebuggerStackPanel extends AbstractObjectsTableBasedPanel<TraceObjectStackFrame>
implements ListSelectionListener, CellActivationListener {
implements ListSelectionListener {
private static class FrameLevelColumn extends TraceValueKeyColumn {
@Override
@ -137,7 +136,7 @@ public class DebuggerStackPanel extends AbstractObjectsTableBasedPanel<TraceObje
}
@Override
protected ObjectTableModel createModel(Plugin plugin) {
protected ObjectTableModel createModel() {
return new StackTableModel(plugin);
}
@ -165,7 +164,11 @@ public class DebuggerStackPanel extends AbstractObjectsTableBasedPanel<TraceObje
@Override
public void cellActivated(JTable table) {
// No super
/**
* Override, because PC columns is fairly wide and representative of the stack frame.
* Likely, when the user double-clicks, they mean to activate the frame, even if it happens
* to be in that column. Simply going to the address will confuse and/or disappoint.
*/
ValueRow item = getSelectedItem();
if (item != null) {
traceManager.activateObject(item.getValue().getChild());

View file

@ -17,7 +17,6 @@ package ghidra.app.plugin.core.debug.gui.thread;
import java.util.List;
import javax.swing.JTable;
import javax.swing.event.ListSelectionEvent;
import docking.widgets.table.AbstractDynamicTableColumn;
@ -258,7 +257,7 @@ public class DebuggerThreadsPanel extends AbstractObjectsTableBasedPanel<TraceOb
}
@Override
protected ObjectTableModel createModel(Plugin plugin) {
protected ObjectTableModel createModel() {
return new ThreadTableModel(plugin);
}
@ -293,15 +292,6 @@ public class DebuggerThreadsPanel extends AbstractObjectsTableBasedPanel<TraceOb
trySelectCurrentThread();
}
@Override
public void cellActivated(JTable table) {
// No super
ValueRow item = getSelectedItem();
if (item != null) {
traceManager.activateObject(item.getValue().getChild());
}
}
@Override
public void valueChanged(ListSelectionEvent e) {
super.valueChanged(e);

View file

@ -483,7 +483,8 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter
return;
}
if (address.isMemoryAddress()) {
listingService.goTo(address, true);
ProgramLocation loc = new ProgramLocation(current.getView(), address);
listingService.goTo(loc, true);
return;
}
}

View file

@ -370,6 +370,14 @@ public class TraceRecorderTarget extends AbstractTarget {
return Map.of();
}
@Override
protected Map<String, ActionEntry> collectToggleActions(ActionContext context) {
return collectIfaceActions(context, TargetTogglable.class, "Toggle",
ActionName.TOGGLE, "Toggle the object",
togglable -> true,
togglable -> togglable.toggle(!togglable.isEnabled()));
}
@Override
public Trace getTrace() {
return recorder.getTrace();

View file

@ -198,7 +198,8 @@ public abstract class AbstractTarget implements Target {
collectStepOverActions(context),
collectStepOutActions(context),
collectStepExtActions(context),
collectRefreshActions(context))
collectRefreshActions(context),
collectToggleActions(context))
.flatMap(m -> m.entrySet().stream())
.collect(Collectors.toMap(Entry::getKey, Entry::getValue));
}
@ -219,6 +220,8 @@ public abstract class AbstractTarget implements Target {
protected abstract Map<String, ActionEntry> collectRefreshActions(ActionContext context);
protected abstract Map<String, ActionEntry> collectToggleActions(ActionContext context);
@Override
public Map<String, ActionEntry> collectActions(ActionName name, ActionContext context) {
if (name == null) {
@ -251,6 +254,9 @@ public abstract class AbstractTarget implements Target {
else if (ActionName.REFRESH.equals(name)) {
return collectRefreshActions(context);
}
else if (ActionName.TOGGLE.equals(name)) {
return collectToggleActions(context);
}
Msg.warn(this, "Unrecognized action name: " + name);
return Map.of();
}