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

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -15,12 +15,14 @@
*/ */
package ghidra.app.plugin.core.processors; package ghidra.app.plugin.core.processors;
import java.awt.*; import java.awt.BorderLayout;
import java.awt.Dimension;
import javax.swing.*; import javax.swing.*;
import javax.swing.table.DefaultTableModel; import javax.swing.table.DefaultTableModel;
import docking.widgets.checkbox.GCheckBox; import docking.action.ToggleDockingAction;
import docking.action.builder.ToggleActionBuilder;
import generic.theme.Gui; import generic.theme.Gui;
import ghidra.app.plugin.processors.sleigh.SleighDebugLogger; import ghidra.app.plugin.processors.sleigh.SleighDebugLogger;
import ghidra.app.plugin.processors.sleigh.SleighDebugLogger.SleighDebugMode; import ghidra.app.plugin.processors.sleigh.SleighDebugLogger.SleighDebugMode;
@ -33,6 +35,7 @@ import ghidra.program.model.listing.*;
import ghidra.program.util.InstructionUtils; import ghidra.program.util.InstructionUtils;
import ghidra.util.HelpLocation; import ghidra.util.HelpLocation;
import ghidra.util.table.GhidraTable; import ghidra.util.table.GhidraTable;
import resources.Icons;
/** /**
* Component provider to show the instruction info. * Component provider to show the instruction info.
@ -48,7 +51,7 @@ class InstructionInfoProvider extends ComponentProviderAdapter implements Domain
private JTextArea instructionText; private JTextArea instructionText;
private JTable opTable; private JTable opTable;
private JCheckBox dynamicUpdateCB; private ToggleDockingAction dynamicUpdateAction;
private OperandModel operandModel; private OperandModel operandModel;
private Address myAddr; private Address myAddr;
@ -57,10 +60,11 @@ class InstructionInfoProvider extends ComponentProviderAdapter implements Domain
super(plugin.getTool(), "Instruction Info", plugin.getName()); super(plugin.getTool(), "Instruction Info", plugin.getName());
this.plugin = plugin; this.plugin = plugin;
buildMainPanel(isDynamic); buildMainPanel();
setTransient(); setTransient();
setWindowMenuGroup("Instruction Info"); setWindowMenuGroup("Instruction Info");
addToTool(); addToTool();
createActions(isDynamic);
} }
@Override @Override
@ -73,13 +77,23 @@ class InstructionInfoProvider extends ComponentProviderAdapter implements Domain
return new HelpLocation(plugin.getName(), "Show_Instruction_Info_Window"); 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() { boolean dynamicUpdateSelected() {
return dynamicUpdateCB.isSelected(); return dynamicUpdateAction.isSelected();
} }
/** /**
* Set the status text on this dialog. * Set the status text on this dialog.
*/ */
void setStatusText(String msg) { void setStatusText(String msg) {
tool.setStatusInfo(msg); tool.setStatusInfo(msg);
} }
@ -101,7 +115,7 @@ class InstructionInfoProvider extends ComponentProviderAdapter implements Domain
* *
* @return JPanel the completed <CODE>Main Panel</CODE> * @return JPanel the completed <CODE>Main Panel</CODE>
*/ */
protected JPanel buildMainPanel(boolean isDynamic) { protected JPanel buildMainPanel() {
mainPanel = new JPanel(new BorderLayout()); mainPanel = new JPanel(new BorderLayout());
@ -120,11 +134,6 @@ class InstructionInfoProvider extends ComponentProviderAdapter implements Domain
pane.setResizeWeight(.25); pane.setResizeWeight(.25);
mainPanel.add(pane, BorderLayout.CENTER); 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(); mainPanel.validate();
return mainPanel; return mainPanel;
@ -194,8 +203,9 @@ class InstructionInfoProvider extends ComponentProviderAdapter implements Domain
setAddress(myAddr); setAddress(myAddr);
} }
public void setNonDynamic() { public void setDynamic(boolean dynamic) {
dynamicUpdateCB.setSelected(false); dynamicUpdateAction.setSelected(dynamic);
dynamicStateChanged();
} }
public Program getProgram() { public Program getProgram() {
@ -225,6 +235,7 @@ class InstructionInfoProvider extends ComponentProviderAdapter implements Domain
/** /**
* Returns the number of columns in this data table. * Returns the number of columns in this data table.
*
* @return the number of columns in the model * @return the number of columns in the model
*/ */
@Override @Override
@ -234,10 +245,10 @@ class InstructionInfoProvider extends ComponentProviderAdapter implements Domain
/** /**
* Returns the column name. * 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> * @return a name for this column using the string value of the appropriate member in
* is null or does not have and entry for this index return the default * <I>columnIdentfiers</I>. If <I>columnIdentfiers</I> is null or does not have and
* name provided by the superclass. * entry for this index return the default name provided by the superclass.
*/ */
@Override @Override
public String getColumnName(int column) { public String getColumnName(int column) {
@ -249,6 +260,7 @@ class InstructionInfoProvider extends ComponentProviderAdapter implements Domain
/** /**
* Returns the number of rows in this data table. * Returns the number of rows in this data table.
*
* @return the number of rows in the model * @return the number of rows in the model
*/ */
@Override @Override
@ -257,14 +269,12 @@ class InstructionInfoProvider extends ComponentProviderAdapter implements Domain
} }
/** /**
* Returns an attribute value for the cell at <I>row</I> * Returns an attribute value for the cell at <I>row</I> and <I>column</I>.
* and <I>column</I>.
* *
* @param row the row whose value is to be looked up * @param row the row whose value is to be looked up
* @param column the column 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 * @return the value Object at the specified cell
* @exception ArrayIndexOutOfBoundsException if an invalid row or * @exception ArrayIndexOutOfBoundsException if an invalid row or column was given.
* column was given.
*/ */
@Override @Override
public Object getValueAt(int row, int column) { public Object getValueAt(int row, int column) {
@ -272,56 +282,39 @@ class InstructionInfoProvider extends ComponentProviderAdapter implements Domain
return null; return null;
} }
if (column == 0) { if (column == 0) {
switch (row) { return switch (row) {
case 0: case 0 -> "Operand";
return "Operand"; case 1 -> "Labeled";
case 1: case 2 -> "Type";
return "Labeled"; case 3 -> "Scalar";
case 2: case 4 -> "Address";
return "Type"; case 5 -> "Register";
case 3: case 6 -> "Op-Objects";
return "Scalar"; case 7 -> "Operand Mask";
case 4: case 8 -> "Masked Value";
return "Address"; default -> "";
case 5: };
return "Register";
case 6:
return "Op-Objects";
case 7:
return "Operand Mask";
case 8:
return "Masked Value";
}
} }
int opIndex = column - 1; int opIndex = column - 1;
if (opIndex >= instruction.getNumOperands()) { if (opIndex >= instruction.getNumOperands()) {
return ""; return "";
} }
switch (row) { return switch (row) {
case 0: case 0 -> instruction.getDefaultOperandRepresentation(opIndex);
return instruction.getDefaultOperandRepresentation(opIndex); case 1 -> CodeUnitFormat.DEFAULT.getOperandRepresentationList(instruction, opIndex);
case 1: case 2 -> OperandType.toString(instruction.getOperandType(opIndex));
return CodeUnitFormat.DEFAULT.getOperandRepresentationList(instruction, case 3 -> instruction.getScalar(opIndex);
opIndex); case 4 -> {
case 2:
return OperandType.toString(instruction.getOperandType(opIndex));
case 3:
return instruction.getScalar(opIndex);
case 4:
Address addr = instruction.getAddress(opIndex); Address addr = instruction.getAddress(opIndex);
return addr != null ? addr.toString(true) : ""; yield addr != null ? addr.toString(true) : "";
case 5: }
return instruction.getRegister(opIndex); case 5 -> instruction.getRegister(opIndex);
case 6: case 6 -> getString(
return getString( InstructionUtils.getFormatedOperandObjects(instruction, opIndex));
InstructionUtils.getFormatedOperandObjects(instruction, opIndex)); case 7 -> debug != null ? debug.getFormattedInstructionMask(opIndex) : null;
case 7: case 8 -> debug != null ? debug.getFormattedMaskedValue(opIndex) : null;
return debug != null ? debug.getFormattedInstructionMask(opIndex) : null; default -> "";
case 8: };
return debug != null ? debug.getFormattedMaskedValue(opIndex) : null;
}
return "";
} }
@Override @Override

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -344,8 +344,8 @@ public class ShowInstructionInfoPlugin extends ProgramPlugin {
} }
/** /**
* Subclass should override this method if it is interested in * Subclass should override this method if it is interested in program location events.
* program location events. *
* @param loc location could be null * @param loc location could be null
*/ */
@Override @Override
@ -463,7 +463,7 @@ public class ShowInstructionInfoPlugin extends ProgramPlugin {
} }
else if (provider != connectedProvider && isDynamic) { else if (provider != connectedProvider && isDynamic) {
if (connectedProvider != null) { if (connectedProvider != null) {
connectedProvider.setNonDynamic(); connectedProvider.setDynamic(false);
} }
disconnectedProviders.remove(provider); disconnectedProviders.remove(provider);
connectedProvider = provider; connectedProvider = provider;

View file

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

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -112,7 +112,9 @@ public class ShowInstructionInfoPluginScreenShots extends GhidraScreenShotGenera
performAction("Show Instruction Info", plugin.getName(), true); 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"); // finished("ShowInstructionInfoPlugin", "ShowInstructionInfo.png");
} }