GP-5650: Replace 'Dynamic Update' checkbox in 'Instruction Info' with toolbar action.

This commit is contained in:
Dan 2025-08-27 16:15:29 +00:00
parent eb7dbaa04f
commit f2743a259e
6 changed files with 105 additions and 124 deletions

View file

@ -115,7 +115,7 @@
(Mnemonic, Number of Operands, Address, Flow Type, Delay Slot Depth, Prototype Hash, Input
Objects, Result Objects, Constructor line numbers, Instruction Bytes, etc.).</P>
<P>The Operand columns (<I>Op1</I>, <I>Op2</I>, etc.) display information about a particular
<P>The Operand columns (<I>Op0</I>, <I>Op1</I>, etc.) display information about a particular
operand.&nbsp; Each operand has a number of rows.&nbsp; At the end of the row is a
descriptive name for the information displayed on that row.</P>
@ -197,13 +197,13 @@
<P align="center">&nbsp;</P>
<BLOCKQUOTE>
<P>The <I>Dynamic Update</I> checkbox indicates whether the window should update when you
change the location in the Code Browser.&nbsp; By default, the checkbox is selected. As you
change your <A href="help/topics/CodeBrowserPlugin/CodeBrowser.htm#Location">location</A>
in the Code Browser, the window will be updated to show the info for the new
location.&nbsp; If you turn off the checkbox, the window does not update; the next time you
choose <I>Instruction Info</I>, a new tab is displayed in the <I>Instruction Info</I>
window.</P>
<P>The <img src="icon.navigate.in"> <I>Dynamic Update</I> toggle indicates whether the
window should update when you change the location in the Code Browser.&nbsp; By default, the
toggle is selected. As you change your
<A href="help/topics/CodeBrowserPlugin/CodeBrowser.htm#Location">location</A> in the Code
Browser, the window will be updated to show the info for the new location.&nbsp; If you turn
off the toggle, the window does not update; the next time you choose <I>Instruction
Info</I>, a new tab is displayed in the <I>Instruction Info</I> window.</P>
</BLOCKQUOTE>
<P class="providedbyplugin">&nbsp;</P>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Before After
Before After

View file

@ -15,12 +15,14 @@
*/
package ghidra.app.plugin.core.processors;
import java.awt.*;
import java.awt.BorderLayout;
import java.awt.Dimension;
import javax.swing.*;
import javax.swing.table.DefaultTableModel;
import docking.widgets.checkbox.GCheckBox;
import docking.action.ToggleDockingAction;
import docking.action.builder.ToggleActionBuilder;
import generic.theme.Gui;
import ghidra.app.plugin.processors.sleigh.SleighDebugLogger;
import ghidra.app.plugin.processors.sleigh.SleighDebugLogger.SleighDebugMode;
@ -33,6 +35,7 @@ import ghidra.program.model.listing.*;
import ghidra.program.util.InstructionUtils;
import ghidra.util.HelpLocation;
import ghidra.util.table.GhidraTable;
import resources.Icons;
/**
* Component provider to show the instruction info.
@ -48,7 +51,7 @@ class InstructionInfoProvider extends ComponentProviderAdapter implements Domain
private JTextArea instructionText;
private JTable opTable;
private JCheckBox dynamicUpdateCB;
private ToggleDockingAction dynamicUpdateAction;
private OperandModel operandModel;
private Address myAddr;
@ -57,10 +60,11 @@ class InstructionInfoProvider extends ComponentProviderAdapter implements Domain
super(plugin.getTool(), "Instruction Info", plugin.getName());
this.plugin = plugin;
buildMainPanel(isDynamic);
buildMainPanel();
setTransient();
setWindowMenuGroup("Instruction Info");
addToTool();
createActions(isDynamic);
}
@Override
@ -73,8 +77,18 @@ class InstructionInfoProvider extends ComponentProviderAdapter implements Domain
return new HelpLocation(plugin.getName(), "Show_Instruction_Info_Window");
}
private void createActions(boolean isDynamic) {
dynamicUpdateAction = new ToggleActionBuilder("Dynamic Update", plugin.getName())
.toolBarIcon(Icons.NAVIGATE_ON_INCOMING_EVENT_ICON)
.description("Update this panel with navigation")
.onAction(ctx -> dynamicStateChanged())
.selected(isDynamic)
.buildAndInstallLocal(this);
dynamicStateChanged();
}
boolean dynamicUpdateSelected() {
return dynamicUpdateCB.isSelected();
return dynamicUpdateAction.isSelected();
}
/**
@ -101,7 +115,7 @@ class InstructionInfoProvider extends ComponentProviderAdapter implements Domain
*
* @return JPanel the completed <CODE>Main Panel</CODE>
*/
protected JPanel buildMainPanel(boolean isDynamic) {
protected JPanel buildMainPanel() {
mainPanel = new JPanel(new BorderLayout());
@ -120,11 +134,6 @@ class InstructionInfoProvider extends ComponentProviderAdapter implements Domain
pane.setResizeWeight(.25);
mainPanel.add(pane, BorderLayout.CENTER);
dynamicUpdateCB = new GCheckBox("Dynamic Update", isDynamic);
dynamicUpdateCB.setAlignmentX(Component.CENTER_ALIGNMENT);
dynamicUpdateCB.addItemListener(e -> dynamicStateChanged());
mainPanel.add(dynamicUpdateCB, BorderLayout.SOUTH);
mainPanel.validate();
return mainPanel;
@ -194,8 +203,9 @@ class InstructionInfoProvider extends ComponentProviderAdapter implements Domain
setAddress(myAddr);
}
public void setNonDynamic() {
dynamicUpdateCB.setSelected(false);
public void setDynamic(boolean dynamic) {
dynamicUpdateAction.setSelected(dynamic);
dynamicStateChanged();
}
public Program getProgram() {
@ -225,6 +235,7 @@ class InstructionInfoProvider extends ComponentProviderAdapter implements Domain
/**
* Returns the number of columns in this data table.
*
* @return the number of columns in the model
*/
@Override
@ -234,10 +245,10 @@ class InstructionInfoProvider extends ComponentProviderAdapter implements Domain
/**
* Returns the column name.
* @return a name for this column using the string value of the
* appropriate member in <I>columnIdentfiers</I>. If <I>columnIdentfiers</I>
* is null or does not have and entry for this index return the default
* name provided by the superclass.
*
* @return a name for this column using the string value of the appropriate member in
* <I>columnIdentfiers</I>. If <I>columnIdentfiers</I> is null or does not have and
* entry for this index return the default name provided by the superclass.
*/
@Override
public String getColumnName(int column) {
@ -249,6 +260,7 @@ class InstructionInfoProvider extends ComponentProviderAdapter implements Domain
/**
* Returns the number of rows in this data table.
*
* @return the number of rows in the model
*/
@Override
@ -257,14 +269,12 @@ class InstructionInfoProvider extends ComponentProviderAdapter implements Domain
}
/**
* Returns an attribute value for the cell at <I>row</I>
* and <I>column</I>.
* Returns an attribute value for the cell at <I>row</I> and <I>column</I>.
*
* @param row the row whose value is to be looked up
* @param column the column whose value is to be looked up
* @return the value Object at the specified cell
* @exception ArrayIndexOutOfBoundsException if an invalid row or
* column was given.
* @exception ArrayIndexOutOfBoundsException if an invalid row or column was given.
*/
@Override
public Object getValueAt(int row, int column) {
@ -272,56 +282,39 @@ class InstructionInfoProvider extends ComponentProviderAdapter implements Domain
return null;
}
if (column == 0) {
switch (row) {
case 0:
return "Operand";
case 1:
return "Labeled";
case 2:
return "Type";
case 3:
return "Scalar";
case 4:
return "Address";
case 5:
return "Register";
case 6:
return "Op-Objects";
case 7:
return "Operand Mask";
case 8:
return "Masked Value";
}
return switch (row) {
case 0 -> "Operand";
case 1 -> "Labeled";
case 2 -> "Type";
case 3 -> "Scalar";
case 4 -> "Address";
case 5 -> "Register";
case 6 -> "Op-Objects";
case 7 -> "Operand Mask";
case 8 -> "Masked Value";
default -> "";
};
}
int opIndex = column - 1;
if (opIndex >= instruction.getNumOperands()) {
return "";
}
switch (row) {
case 0:
return instruction.getDefaultOperandRepresentation(opIndex);
case 1:
return CodeUnitFormat.DEFAULT.getOperandRepresentationList(instruction,
opIndex);
case 2:
return OperandType.toString(instruction.getOperandType(opIndex));
case 3:
return instruction.getScalar(opIndex);
case 4:
return switch (row) {
case 0 -> instruction.getDefaultOperandRepresentation(opIndex);
case 1 -> CodeUnitFormat.DEFAULT.getOperandRepresentationList(instruction, opIndex);
case 2 -> OperandType.toString(instruction.getOperandType(opIndex));
case 3 -> instruction.getScalar(opIndex);
case 4 -> {
Address addr = instruction.getAddress(opIndex);
return addr != null ? addr.toString(true) : "";
case 5:
return instruction.getRegister(opIndex);
case 6:
return getString(
InstructionUtils.getFormatedOperandObjects(instruction, opIndex));
case 7:
return debug != null ? debug.getFormattedInstructionMask(opIndex) : null;
case 8:
return debug != null ? debug.getFormattedMaskedValue(opIndex) : null;
yield addr != null ? addr.toString(true) : "";
}
return "";
case 5 -> instruction.getRegister(opIndex);
case 6 -> getString(
InstructionUtils.getFormatedOperandObjects(instruction, opIndex));
case 7 -> debug != null ? debug.getFormattedInstructionMask(opIndex) : null;
case 8 -> debug != null ? debug.getFormattedMaskedValue(opIndex) : null;
default -> "";
};
}
@Override

View file

@ -344,8 +344,8 @@ public class ShowInstructionInfoPlugin extends ProgramPlugin {
}
/**
* Subclass should override this method if it is interested in
* program location events.
* Subclass should override this method if it is interested in program location events.
*
* @param loc location could be null
*/
@Override
@ -463,7 +463,7 @@ public class ShowInstructionInfoPlugin extends ProgramPlugin {
}
else if (provider != connectedProvider && isDynamic) {
if (connectedProvider != null) {
connectedProvider.setNonDynamic();
connectedProvider.setDynamic(false);
}
disconnectedProviders.remove(provider);
connectedProvider = provider;

View file

@ -22,7 +22,8 @@ import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.*;
import javax.swing.JTable;
import javax.swing.JTextArea;
import org.junit.*;
@ -150,6 +151,7 @@ public class ShowInstructionInfoPluginTest extends AbstractGhidraHeadedIntegrati
DockingActionIf infoAction = getAction(plugin, "Show Instruction Info");
// show the window
performAction(infoAction, cb.getProvider(), true);
InstructionInfoProvider provider = waitForComponentProvider(InstructionInfoProvider.class);
// make sure we are at an invalid Instruction
ListingActionContext context = getCurrentContext();
@ -176,14 +178,8 @@ public class ShowInstructionInfoPluginTest extends AbstractGhidraHeadedIntegrati
componentProviderTablesHaveData());
// verify dynamic update has changed the window's contents
ComponentProvider componentProvider = getCurrentComponentProviderFromPlugin();
JComponent comp = componentProvider.getComponent();
final JCheckBox dynamicCheckBox = findComponent(comp, JCheckBox.class);
// make sure dynamic update is enabled
if (!dynamicCheckBox.isSelected()) {
runSwing(() -> dynamicCheckBox.doClick());
}
runSwing(() -> provider.setDynamic(true));
// change to another valid Instruction
currentInstruction = changeLocationToAddress("01000006");
@ -200,7 +196,7 @@ public class ShowInstructionInfoPluginTest extends AbstractGhidraHeadedIntegrati
verifyAddressWithTableModels(currentInstruction.getMinAddress(), true, true);
// turn off dynamic update
runSwing(() -> dynamicCheckBox.doClick());
runSwing(() -> provider.setDynamic(false));
// change to another valid Instruction
currentInstruction = changeLocationToAddress("01000009");
@ -223,7 +219,7 @@ public class ShowInstructionInfoPluginTest extends AbstractGhidraHeadedIntegrati
// place
// turn dynamic update back on
runSwing(() -> dynamicCheckBox.doClick());
runSwing(() -> provider.setDynamic(true));
// move to a valid location that has yet to be disassembled
currentInstruction = changeLocationToAddress("01000ffe");
@ -269,13 +265,9 @@ public class ShowInstructionInfoPluginTest extends AbstractGhidraHeadedIntegrati
DockingActionIf infoAction = getAction(plugin, "Show Instruction Info");
// show the window
performAction(infoAction, cb.getProvider(), true);
InstructionInfoProvider provider = waitForComponentProvider(InstructionInfoProvider.class);
ComponentProvider componentProvider = getCurrentComponentProviderFromPlugin();
JComponent comp = componentProvider.getComponent();
final JCheckBox dynamicCheckBox = findComponent(comp, JCheckBox.class);
// turn off the checkbox
runSwing(() -> dynamicCheckBox.setSelected(false));
runSwing(() -> provider.setDynamic(false));
changeLocationToAddress("01000006");
performAction(infoAction, cb.getProvider(), true);
@ -363,12 +355,10 @@ public class ShowInstructionInfoPluginTest extends AbstractGhidraHeadedIntegrati
}
/**
* Moves the program location to the given address and returns the
* instruction at that location.
* Moves the program location to the given address and returns the instruction at that location.
*
* @param addressString The address location to move to.
* @return The instruction at the new location or null if there is no
* instruction.
* @return The instruction at the new location or null if there is no instruction.
*/
private Instruction changeLocationToAddress(String addressString) throws Exception {
CodeBrowserPlugin cbp = env.getPlugin(CodeBrowserPlugin.class);
@ -395,18 +385,15 @@ public class ShowInstructionInfoPluginTest extends AbstractGhidraHeadedIntegrati
}
/**
* Tests the addresses of the table models of the "Instruction Info" dialog.
* The method will fail the current test if the result is not as
* expected by the caller of this method. For example, if
* {@code expectedSame} is true, then the method expects the values to
* be the same when compared with the given address and will fail if
* they are not. If {@code expectedSame} is false, then the method will
* fail if the test values are the same.
* Tests the addresses of the table models of the "Instruction Info" dialog. The method will
* fail the current test if the result is not as expected by the caller of this method. For
* example, if {@code expectedSame} is true, then the method expects the values to be the same
* when compared with the given address and will fail if they are not. If {@code expectedSame}
* is false, then the method will fail if the test values are the same.
*
* @param instructionAddress The address to compare against the address
* stored in the table model of the dialog.
* @param expectedSame True means a match is expected; false means a
* match is not expected.
* @param instructionAddress The address to compare against the address stored in the table
* model of the dialog.
* @param expectedSame True means a match is expected; false means a match is not expected.
*/
private void verifyAddressWithTableModels(Address instructionAddress, boolean fromConnected,
boolean expectedSame) {
@ -447,8 +434,7 @@ public class ShowInstructionInfoPluginTest extends AbstractGhidraHeadedIntegrati
}
/**
* A simple method to test that the tables of the "Instruction Info"
* dialog contain data.
* A simple method to test that the tables of the "Instruction Info" dialog contain data.
*
* @return True if either of the tables have data.
*/

View file

@ -112,7 +112,9 @@ public class ShowInstructionInfoPluginScreenShots extends GhidraScreenShotGenera
performAction("Show Instruction Info", plugin.getName(), true);
captureProviderWindow("Instruction Info", 1200, 500);
ComponentProvider provider =
tool.getWindowManager().getComponentProvider("Instruction Info");
captureIsolatedProvider(provider, 1200, 510);
// finished("ShowInstructionInfoPlugin", "ShowInstructionInfo.png");
}