Merge remote-tracking branch 'origin/GP-4361_Dan_modelTreeContext--SQUASHED'

This commit is contained in:
Ryan Kurtz 2024-03-01 13:50:48 -05:00
commit 5fc6105f0d
3 changed files with 302 additions and 136 deletions

View file

@ -0,0 +1,78 @@
/* ###
* 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.awt.event.*;
import javax.swing.event.TreeExpansionEvent;
public interface Adapters {
interface FocusListener extends java.awt.event.FocusListener {
@Override
default void focusGained(FocusEvent e) {
}
@Override
default void focusLost(FocusEvent e) {
}
}
interface KeyListener extends java.awt.event.KeyListener {
@Override
default void keyPressed(KeyEvent e) {
}
@Override
default void keyReleased(KeyEvent e) {
}
@Override
default void keyTyped(KeyEvent e) {
}
}
interface MouseListener extends java.awt.event.MouseListener {
@Override
default void mouseClicked(MouseEvent e) {
}
@Override
default void mouseEntered(MouseEvent e) {
}
@Override
default void mouseExited(MouseEvent e) {
}
@Override
default void mousePressed(MouseEvent e) {
}
@Override
default void mouseReleased(MouseEvent e) {
}
}
interface TreeExpansionListener extends javax.swing.event.TreeExpansionListener {
@Override
default void treeCollapsed(TreeExpansionEvent event) {
}
@Override
default void treeExpanded(TreeExpansionEvent event) {
}
}
}

View file

@ -25,8 +25,8 @@ import java.util.function.Function;
import java.util.stream.Collectors;
import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeExpansionListener;
import javax.swing.tree.TreePath;
import docking.*;
@ -35,6 +35,7 @@ import docking.action.ToggleDockingAction;
import docking.action.builder.ActionBuilder;
import docking.action.builder.ToggleActionBuilder;
import docking.widgets.table.RangeCursorTableHeaderRenderer.SeekListener;
import docking.widgets.tree.support.GTreeSelectionEvent;
import docking.widgets.tree.support.GTreeSelectionEvent.EventOrigin;
import generic.theme.GColor;
import generic.theme.GIcon;
@ -266,10 +267,9 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
DebuggerObjectActionContext myActionContext;
private final CellActivationListener elementActivationListener =
table -> activatedElementsTable();
private final CellActivationListener attributeActivationListener =
table -> activatedAttributesTable();
final ObjectsTreeListener objectsTreeListener = new ObjectsTreeListener();
final ElementsTableListener elementsTableListener = new ElementsTableListener();
final AttributesTableListener attributesTableListener = new AttributesTableListener();
private final SeekListener seekListener = pos -> {
long snap = Math.round(pos);
@ -430,6 +430,197 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
mainPanel.revalidate();
}
protected class ObjectsTreeListener implements Adapters.FocusListener,
Adapters.TreeExpansionListener, Adapters.MouseListener, Adapters.KeyListener {
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
activateObjectSelectedInTree();
e.consume(); // lest is select the next row down
}
}
@Override
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 2 && e.getButton() == MouseEvent.BUTTON1) {
activateObjectSelectedInTree();
e.consume();
}
}
@Override
public void treeExpanded(TreeExpansionEvent event) {
if (EventQueue.getCurrentEvent() instanceof InputEvent inputEvent &&
!inputEvent.isShiftDown()) {
refreshTargetChildren(event.getPath());
}
}
@Override
public void focusGained(FocusEvent e) {
setContextFromSelection();
}
public void selectionChanged(GTreeSelectionEvent evt) {
setContextFromSelection();
List<AbstractNode> sel = objectsTreePanel.getSelectedItems();
if (sel.size() == 1) {
TraceObjectValue value = sel.get(0).getValue();
setPath(value == null ? TraceObjectKeyPath.of() : value.getCanonicalPath(),
objectsTreePanel, EventOrigin.INTERNAL_GENERATED);
}
}
private void setContextFromSelection() {
myActionContext = computeContext(false);
contextChanged();
}
/*test access*/
DebuggerObjectActionContext computeContext(boolean ignoreFocus) {
if (!objectsTreePanel.tree.tree().isFocusOwner() && !ignoreFocus) {
return null;
}
Trace trace = current.getTrace();
if (trace == null) {
return null;
}
if (trace.getObjectManager().getRootObject() == null) {
return null;
}
List<AbstractNode> sel = objectsTreePanel.getSelectedItems();
if (sel.isEmpty()) {
return null;
}
return new DebuggerObjectActionContext(sel.stream()
.map(n -> n.getValue())
.filter(o -> o != null) // Root for no trace would return null
.collect(Collectors.toList()),
DebuggerModelProvider.this, objectsTreePanel);
}
}
protected class ElementsTableListener
implements Adapters.FocusListener, CellActivationListener {
@Override
public void cellActivated(JTable table) {
ValueRow row = elementsTablePanel.getSelectedItem();
if (row == null) {
return;
}
if (!(row instanceof ObjectRow objectRow)) {
return;
}
activatePath(objectRow.getTraceObject().getCanonicalPath());
}
@Override
public void focusGained(FocusEvent e) {
setContextFromSelection();
}
public void selectionChanged(ListSelectionEvent evt) {
if (evt.getValueIsAdjusting()) {
return;
}
setContextFromSelection();
List<ValueRow> sel = elementsTablePanel.getSelectedItems();
if (sel.size() != 1) {
attributesTablePanel.setQuery(ModelQuery.attributesOf(path));
return;
}
TraceObjectValue value = sel.get(0).getValue();
if (!value.isObject()) {
return;
}
TraceObject object = value.getChild();
attributesTablePanel.setQuery(ModelQuery.attributesOf(object.getCanonicalPath()));
}
private void setContextFromSelection() {
myActionContext = computeContext(false);
contextChanged();
}
/*test access*/
DebuggerObjectActionContext computeContext(boolean ignoreFocus) {
if (!elementsTablePanel.table.isFocusOwner() && !ignoreFocus) {
return null;
}
Trace trace = current.getTrace();
if (trace == null) {
return null;
}
if (trace.getObjectManager().getRootObject() == null) {
return null;
}
List<ValueRow> sel = elementsTablePanel.getSelectedItems();
if (sel.isEmpty()) {
return null;
}
return new DebuggerObjectActionContext(sel.stream()
.map(r -> r.getValue())
.collect(Collectors.toList()),
DebuggerModelProvider.this, elementsTablePanel);
}
}
protected class AttributesTableListener
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());
}
@Override
public void focusGained(FocusEvent e) {
setContextFromSelection();
}
public void selectionChanged(ListSelectionEvent evt) {
if (evt.getValueIsAdjusting()) {
return;
}
setContextFromSelection();
}
private void setContextFromSelection() {
myActionContext = computeContext(false);
contextChanged();
}
/*test access*/
DebuggerObjectActionContext computeContext(boolean ignoreFocus) {
if (!attributesTablePanel.table.isFocusOwner() && !ignoreFocus) {
return null;
}
Trace trace = current.getTrace();
if (trace == null) {
return null;
}
if (trace.getObjectManager().getRootObject() == null) {
return null;
}
List<PathRow> sel = attributesTablePanel.getSelectedItems();
if (sel.isEmpty()) {
return null;
}
return new DebuggerObjectActionContext(sel.stream()
.map(r -> Objects.requireNonNull(r.getPath().getLastEntry()))
.collect(Collectors.toList()),
DebuggerModelProvider.this, attributesTablePanel);
}
}
protected void buildMainPanel() {
mainPanel = new JPanel(new BorderLayout());
pathField = new MyTextField();
@ -487,114 +678,20 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
queryPanel.add(pathField, BorderLayout.CENTER);
queryPanel.add(goButton, BorderLayout.EAST);
objectsTreePanel.addTreeSelectionListener(evt -> {
Trace trace = current.getTrace();
if (trace == null) {
return;
}
if (trace.getObjectManager().getRootObject() == null) {
return;
}
List<AbstractNode> sel = objectsTreePanel.getSelectedItems();
if (!sel.isEmpty()) {
myActionContext = new DebuggerObjectActionContext(sel.stream()
.map(n -> n.getValue())
.filter(o -> o != null) // Root for no trace would return null
.collect(Collectors.toList()),
this, objectsTreePanel);
}
else {
myActionContext = null;
}
contextChanged();
if (sel.size() != 1) {
return;
}
TraceObjectValue value = sel.get(0).getValue();
setPath(value == null ? TraceObjectKeyPath.of() : value.getCanonicalPath(),
objectsTreePanel, EventOrigin.INTERNAL_GENERATED);
});
objectsTreePanel.tree.addTreeExpansionListener(new TreeExpansionListener() {
@Override
public void treeExpanded(TreeExpansionEvent expEvent) {
if (EventQueue.getCurrentEvent() instanceof InputEvent event &&
!event.isShiftDown()) {
refreshTargetChildren(expEvent.getPath());
}
}
@Override
public void treeCollapsed(TreeExpansionEvent event) {
// I don't care
}
});
objectsTreePanel.tree.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 2 && e.getButton() == MouseEvent.BUTTON1) {
activateObjectSelectedInTree();
e.consume();
}
}
});
objectsTreePanel.tree.tree().addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
activateObjectSelectedInTree();
e.consume(); // lest is select the next row down
}
}
});
elementsTablePanel.addSelectionListener(evt -> {
if (evt.getValueIsAdjusting()) {
return;
}
List<ValueRow> sel = elementsTablePanel.getSelectedItems();
if (!sel.isEmpty()) {
myActionContext = new DebuggerObjectActionContext(sel.stream()
.map(r -> r.getValue())
.collect(Collectors.toList()),
this, elementsTablePanel);
}
else {
myActionContext = null;
}
contextChanged();
if (sel.size() != 1) {
attributesTablePanel.setQuery(ModelQuery.attributesOf(path));
return;
}
TraceObjectValue value = sel.get(0).getValue();
if (!value.isObject()) {
return;
}
TraceObject object = value.getChild();
attributesTablePanel.setQuery(ModelQuery.attributesOf(object.getCanonicalPath()));
});
attributesTablePanel.addSelectionListener(evt -> {
if (evt.getValueIsAdjusting()) {
return;
}
List<PathRow> sel = attributesTablePanel.getSelectedItems();
if (!sel.isEmpty()) {
myActionContext = new DebuggerObjectActionContext(sel.stream()
.map(r -> Objects.requireNonNull(r.getPath().getLastEntry()))
.collect(Collectors.toList()),
this, attributesTablePanel);
}
else {
myActionContext = null;
}
contextChanged();
});
elementsTablePanel.addCellActivationListener(elementActivationListener);
attributesTablePanel.addCellActivationListener(attributeActivationListener);
objectsTreePanel.addTreeSelectionListener(objectsTreeListener::selectionChanged);
objectsTreePanel.tree.tree().addFocusListener(objectsTreeListener);
objectsTreePanel.tree.addTreeExpansionListener(objectsTreeListener);
objectsTreePanel.tree.addMouseListener(objectsTreeListener);
objectsTreePanel.tree.tree().addKeyListener(objectsTreeListener);
elementsTablePanel.addSelectionListener(elementsTableListener::selectionChanged);
elementsTablePanel.table.addFocusListener(elementsTableListener);
elementsTablePanel.addCellActivationListener(elementsTableListener);
elementsTablePanel.addSeekListener(seekListener);
attributesTablePanel.addSelectionListener(attributesTableListener::selectionChanged);
attributesTablePanel.table.addFocusListener(attributesTableListener);
attributesTablePanel.addCellActivationListener(attributesTableListener);
attributesTablePanel.addSeekListener(seekListener);
rebuildPanels();
@ -663,6 +760,17 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
@Override
public ActionContext getActionContext(MouseEvent event) {
if (event != null) {
if (event.getComponent() == objectsTreePanel.tree.tree()) {
return objectsTreeListener.computeContext(true);
}
else if (event.getComponent() == elementsTablePanel.table) {
return elementsTableListener.computeContext(true);
}
else if (event.getComponent() == attributesTablePanel.table) {
return attributesTableListener.computeContext(true);
}
}
if (myActionContext != null) {
return myActionContext;
}
@ -705,29 +813,6 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
.buildAndInstallLocal(this);
}
private void activatedElementsTable() {
ValueRow row = elementsTablePanel.getSelectedItem();
if (row == null) {
return;
}
if (!(row instanceof ObjectRow objectRow)) {
return;
}
activatePath(objectRow.getTraceObject().getCanonicalPath());
}
private void activatedAttributesTable() {
PathRow row = attributesTablePanel.getSelectedItem();
if (row == null) {
return;
}
Object value = row.getValue();
if (!(value instanceof TraceObject object)) {
return;
}
activatePath(object.getCanonicalPath());
}
private void activatedCloneWindow() {
DebuggerModelProvider clone = plugin.createDisconnectedProvider();
SaveState configState = new SaveState();

View file

@ -25,6 +25,7 @@ import org.jdom.JDOMException;
import org.junit.*;
import db.Transaction;
import docking.ActionContext;
import docking.widgets.table.*;
import docking.widgets.table.ColumnSortState.SortDirection;
import docking.widgets.tree.GTree;
@ -641,8 +642,10 @@ public class DebuggerModelProviderTest extends AbstractGhidraHeadedDebuggerTest
selectAttribute("_next");
waitForSwing();
assertEnabled(modelProvider, modelProvider.actionFollowLink);
performAction(modelProvider.actionFollowLink, modelProvider, true);
ActionContext ctx =
runSwing(() -> modelProvider.attributesTableListener.computeContext(true));
assertTrue(runSwing(() -> modelProvider.actionFollowLink.isEnabledForContext(ctx)));
performAction(modelProvider.actionFollowLink, ctx, true);
TraceObjectKeyPath thread3Path = TraceObjectKeyPath.parse("Processes[0].Threads[3]");
assertPathIs(thread3Path, 0, 5);