diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/Adapters.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/Adapters.java new file mode 100644 index 0000000000..6ce74aacc0 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/Adapters.java @@ -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) { + } + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/DebuggerModelProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/DebuggerModelProvider.java index f422e41c57..9486f0d05d 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/DebuggerModelProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/model/DebuggerModelProvider.java @@ -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 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 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 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 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 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 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 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 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(); diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/model/DebuggerModelProviderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/model/DebuggerModelProviderTest.java index be78e675ed..4740f54337 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/model/DebuggerModelProviderTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/model/DebuggerModelProviderTest.java @@ -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);