diff --git a/Ghidra/Features/Decompiler/data/decompiler.theme.properties b/Ghidra/Features/Decompiler/data/decompiler.theme.properties
index b94edc13b5..f1a2f31b59 100644
--- a/Ghidra/Features/Decompiler/data/decompiler.theme.properties
+++ b/Ghidra/Features/Decompiler/data/decompiler.theme.properties
@@ -41,6 +41,7 @@ icon.decompiler.action.provider.clone = icon.provider.clone
icon.decompiler.action.provider.unreachable = eliminateUnreachable.png
icon.decompiler.action.provider.readonly = readOnly.png
icon.decompiler.action.export = page_edit.png
+icon.decompiler.action.display.lock = lock.gif
font.decompiler = font.monospaced
font.decompiler.pcode.dfg = font.graphdisplay.default
diff --git a/Ghidra/Features/Decompiler/src/main/doc/decompileplugin.xml b/Ghidra/Features/Decompiler/src/main/doc/decompileplugin.xml
index a3a301b715..25289be0fe 100644
--- a/Ghidra/Features/Decompiler/src/main/doc/decompileplugin.xml
+++ b/Ghidra/Features/Decompiler/src/main/doc/decompileplugin.xml
@@ -4009,7 +4009,7 @@
tokens (see ).
-
+
Cross-Highlighting
The main window maintains a map between the individual variable and operator tokens displayed in
@@ -4045,6 +4045,25 @@
+
+ Disabling Auto Refresh
+
+ For very large functions, the decompile window can take a significant amount of time to re-decompile
+ and update the display as changes are made to the program. This makes some actions frustrating,
+ such as renaming or retyping variables.
+
+
+ As a work around, you may use the lock toggle action
+
+
+
+ .
+ When selected, this action will prevent the decompiler from auto refreshing for each change.
+ This may make the display appear to be broken since any changes will not appear to
+ take effect until the display is manually refreshed.
+
+
+
Snapshot Windows
@@ -4085,6 +4104,18 @@
Double-clicking on specific tokens within the Snapshot window may also cause it to navigate
to a new location (see ).
+
+
+ Normally, snapshot windows are completely disconnected from the location and selection
+ events that synchronize the main components in the tool. To allow the snapshot window
+ to export its location and selection, select the
+
+
+
+
+ toolbar action.
+
+
diff --git a/Ghidra/Features/Decompiler/src/main/help/help/topics/DecompilePlugin/DecompilerWindow.html b/Ghidra/Features/Decompiler/src/main/help/help/topics/DecompilePlugin/DecompilerWindow.html
index 29a80d5a11..e34880eaac 100644
--- a/Ghidra/Features/Decompiler/src/main/help/help/topics/DecompilePlugin/DecompilerWindow.html
+++ b/Ghidra/Features/Decompiler/src/main/help/help/topics/DecompilePlugin/DecompilerWindow.html
@@ -101,7 +101,7 @@
tokens (see Mouse Actions).
-
+
+
+
+
+ For very large functions, the decompile window can take a significant amount of time to re-decompile
+ and update the display as changes are made to the program. This makes using some actions
+ frustrating, such as renaming or retyping variables.
+
+
+ As a work around, you may use the lock toggle action
+
. When selected, this action will prevent
+ the decompiler from auto refreshing for each change. This may make the display appear to be
+ broken since any changes will not appear to take effect until the display is manually refreshed.
+
+
+
@@ -187,6 +204,14 @@
Double-clicking on specific tokens within the Snapshot window may also cause it to navigate
to a new location (see
Double-Click).
+
+
+ Normally, snapshot windows are completely disconnected from the location and selection
+ events that synchronize the main components in the tool. To allow the snapshot window
+ to export its location and selection, select the
+
toolbar action.
+
+
diff --git a/Ghidra/Features/Decompiler/src/main/help/help/topics/DecompilePlugin/images/DecompWindow.png b/Ghidra/Features/Decompiler/src/main/help/help/topics/DecompilePlugin/images/DecompWindow.png
index 273b4ebe9c..5f6198d621 100644
Binary files a/Ghidra/Features/Decompiler/src/main/help/help/topics/DecompilePlugin/images/DecompWindow.png and b/Ghidra/Features/Decompiler/src/main/help/help/topics/DecompilePlugin/images/DecompWindow.png differ
diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerPanel.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerPanel.java
index 6aee312c03..2e626c8115 100644
--- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerPanel.java
+++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerPanel.java
@@ -1388,6 +1388,23 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
fieldPanel.removeFocusListener(l);
}
+ /**
+ * {@return the bounds of the content area of this decompiler panel. This includes the main
+ * decompiler content panel and the line numbers panel}
+ */
+ public Rectangle getViewContentBounds() {
+ // The bounds we want includes both the extent size of the main decompiler view + the
+ // area that displays the line numbers which is not inside the IndexedScrollPane. The width
+ // of the line numbers panel can be found by looking at the x position of the scroller as
+ // it is offset by the line number panel's width. We are also assuming there are no borders
+ // internal to the DecompilerPanel. If that changes, we would also need to factor in the
+ // insets.
+ Rectangle bounds = scroller.getBounds();
+ Dimension scrollerSize = scroller.getViewExtentSize();
+ int lineNumberWidth = bounds.x;
+ return new Rectangle(0, 0, scrollerSize.width + lineNumberWidth, scrollerSize.height);
+ }
+
private void buildPanels() {
removeAll();
add(buildLeftComponent(), BorderLayout.WEST);
diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilePlugin.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilePlugin.java
index a97ddb8dd1..0188a69b5e 100644
--- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilePlugin.java
+++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilePlugin.java
@@ -207,13 +207,13 @@ public class DecompilePlugin extends Plugin {
}
void locationChanged(DecompilerProvider provider, ProgramLocation location) {
- if (provider == connectedProvider) {
+ if (provider.shouldSendEvents()) {
firePluginEvent(new ProgramLocationPluginEvent(name, location, location.getProgram()));
}
}
void selectionChanged(DecompilerProvider provider, ProgramSelection selection) {
- if (provider == connectedProvider) {
+ if (provider.shouldSendEvents()) {
firePluginEvent(new ProgramSelectionPluginEvent(name, selection, currentProgram));
}
}
diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerProvider.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerProvider.java
index 7a229ffe26..dd74b391f0 100644
--- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerProvider.java
+++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerProvider.java
@@ -15,17 +15,20 @@
*/
package ghidra.app.plugin.core.decompile;
+import java.awt.Graphics;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.math.BigInteger;
import java.util.*;
import java.util.concurrent.ConcurrentLinkedQueue;
-import javax.swing.Icon;
-import javax.swing.JComponent;
+import javax.swing.*;
import docking.*;
import docking.action.*;
+import docking.action.builder.ActionBuilder;
+import docking.action.builder.ToggleActionBuilder;
+import docking.actions.KeyBindingUtils;
import docking.widgets.fieldpanel.support.FieldLocation;
import docking.widgets.fieldpanel.support.ViewerPosition;
import generic.theme.GIcon;
@@ -76,6 +79,7 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
private static final Icon TOGGLE_READ_ONLY_DISABLED_ICON =
new MultiIconBuilder(TOGGLE_READ_ONLY_ICON).addCenteredIcon(SLASH_ICON).build();
+ private static final Icon LOCK_DISPLAY_ICON = new GIcon("icon.decompiler.action.display.lock");
private DockingAction pcodeGraphAction;
private DockingAction astGraphAction;
@@ -100,11 +104,16 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
private SwingUpdateManager redecompileUpdater;
private DecompilerProgramListener programListener;
-
+ private boolean lockDisplay;
// Follow-up work can be items that need to happen after a pending decompile is finished, such
// as updating highlights after a variable rename
private SwingUpdateManager followUpWorkUpdater;
private Queue followUpWork = new ConcurrentLinkedQueue<>();
+ private OverlayMessagePainter overlayPainter = new OverlayMessagePainter();
+ private DockingAction refreshAction;
+
+ // only used by disconnected providers
+ private boolean allowOutgoingEvents = false;
private ServiceListener serviceListener = new ServiceListener() {
@@ -140,7 +149,14 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
// TODO move the hl controller into the panel
highlightController = new LocationClangHighlightController();
decompilerPanel.setHighlightController(highlightController);
- decorationPanel = new DecoratorPanel(decompilerPanel, isConnected);
+ decorationPanel = new DecoratorPanel(decompilerPanel, isConnected) {
+ @Override
+ public void paint(Graphics g) {
+ super.paint(g);
+ overlayPainter.paintOverlay(g, decompilerPanel.getViewContentBounds());
+ }
+
+ };
if (!isConnected) {
setTransient();
@@ -333,7 +349,28 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
controller.setOptions(decompilerOptions);
if (currentLocation != null) {
- controller.refreshDisplay(program, currentLocation, null);
+ if (lockDisplay) {
+ overlayPainter.setMessage(getOverlayRefreshMessage());
+ }
+ else {
+ controller.refreshDisplay(program, currentLocation, null);
+ overlayPainter.setMessage("");
+ }
+ }
+ }
+
+ private String getOverlayRefreshMessage() {
+ KeyStroke keyStroke = refreshAction.getKeyBinding();
+ if (keyStroke != null) {
+ String name = KeyBindingUtils.parseKeyStroke(keyStroke);
+ return name + " to refresh";
+ }
+ return "Refresh needed";
+ }
+
+ private void updateOverlayMessage() {
+ if (overlayPainter.isActive()) {
+ overlayPainter.setMessage(getOverlayRefreshMessage());
}
}
@@ -371,6 +408,8 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
options.getName().equals(GhidraOptions.CATEGORY_BROWSER_FIELDS)) {
doRefresh(true);
}
+ updateOverlayMessage();
+
}
//==================================================================================================
@@ -489,6 +528,7 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
*/
void refresh() {
controller.refreshDisplay(program, currentLocation, null);
+ overlayPainter.setMessage("");
}
/**
@@ -790,28 +830,35 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
private void createActions(boolean isConnected) {
String owner = plugin.getName();
+ new ToggleActionBuilder("Lock Display", owner)
+ .toolBarIcon(LOCK_DISPLAY_ICON)
+ .description("Lock display for auto-updates, only update on manual refresh")
+ .helpLocation(new HelpLocation(HelpTopics.DECOMPILER, "LockDisplay"))
+ .selected(false)
+ .onAction(c -> toggleDisplayLock())
+ .buildAndInstallLocal(this);
+
+ if (!isConnected) {
+ new ToggleActionBuilder("Decompiler Outgoing Events", owner)
+ .toolBarIcon(Icons.NAVIGATE_ON_OUTGOING_EVENT_ICON)
+ .description("Send location and selection events")
+ .helpLocation(new HelpLocation(HelpTopics.DECOMPILER, "EventsOut"))
+ .selected(false)
+ .onAction(c -> toggleOutgoingEvents())
+ .buildAndInstallLocal(this);
+ }
+
SelectAllAction selectAllAction =
new SelectAllAction(owner, controller.getDecompilerPanel());
- DockingAction refreshAction = new DockingAction("Refresh", owner) {
- @Override
- public void actionPerformed(ActionContext context) {
- refresh();
- }
-
- @Override
- public boolean isEnabledForContext(ActionContext context) {
- DecompileData decompileData = controller.getDecompileData();
- if (decompileData == null) {
- return false;
- }
- return decompileData.hasDecompileResults();
- }
- };
- refreshAction.setToolBarData(new ToolBarData(REFRESH_ICON, "A" /* first on toolbar */));
- refreshAction.setDescription("Push at any time to trigger a re-decompile");
- refreshAction
- .setHelpLocation(new HelpLocation(HelpTopics.DECOMPILER, "ToolBarRedecompile")); // just use the default
+ refreshAction = new ActionBuilder("Refresh", owner)
+ .popupMenuPath("Refresh")
+ .popupMenuIcon(REFRESH_ICON)
+ .keyBinding("F5")
+ .helpLocation(new HelpLocation(HelpTopics.DECOMPILER, "ToolBarRedecompile"))
+ .description("Re-decompile and update the display")
+ .onAction(c -> refresh())
+ .buildAndInstallLocal(this);
displayUnreachableCodeToggle = new ToggleDockingAction("Toggle Unreachable Code", owner) {
@Override
@@ -1120,6 +1167,8 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
findReferencesToAddressAction.getPopupMenuData().setParentMenuGroup(referencesParentGroup);
addLocalAction(findReferencesToAddressAction);
+ setGroupInfo(refreshAction, "comment6", subGroupPosition++);
+
//
// Options
//
@@ -1200,6 +1249,24 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
graphServiceAdded();
}
+ private void toggleOutgoingEvents() {
+ allowOutgoingEvents = !allowOutgoingEvents;
+ }
+
+ boolean shouldSendEvents() {
+ if (isConnected()) {
+ return true;
+ }
+ return allowOutgoingEvents;
+ }
+
+ private void toggleDisplayLock() {
+ lockDisplay = !lockDisplay;
+ if (!lockDisplay) {
+ refresh();
+ }
+ }
+
/**
* Sets the group and subgroup information for the given action.
*/
diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/OverlayMessagePainter.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/OverlayMessagePainter.java
new file mode 100644
index 0000000000..ca393e7654
--- /dev/null
+++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/OverlayMessagePainter.java
@@ -0,0 +1,82 @@
+/* ###
+ * 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.decompile;
+
+import java.awt.*;
+
+import org.apache.commons.lang3.StringUtils;
+
+import generic.theme.GColor;
+import generic.theme.GThemeDefaults.Colors.Palette;
+import generic.theme.Gui;
+
+/**
+ * Class to overlay a message on the decompiler panel to indicate the display is stale and
+ * needs to be refreshed manually.
+ */
+class OverlayMessagePainter {
+ private static final int MARGIN = 10;
+ private static final String FONT_ID = "font.graph.component.message";
+ private final Color gradientColor = new GColor("color.bg.visualgraph.message");
+ private String message;
+
+ void setMessage(String message) {
+ this.message = message;
+ }
+
+ boolean isActive() {
+ return !StringUtils.isBlank(message);
+ }
+
+ void paintOverlay(Graphics g, Rectangle bounds) {
+ if (!isActive()) {
+ return;
+ }
+
+ Graphics2D g2 = (Graphics2D) g;
+
+ // this composite softens the text and color of the message
+ Composite originalComposite = g2.getComposite();
+ g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SrcOver.getRule(), .60f));
+
+ // set up font
+ Font font = Gui.getFont(FONT_ID);
+ g.setFont(font);
+ Rectangle textBounds = font.getStringBounds(message, g2.getFontRenderContext()).getBounds();
+
+ int gh = textBounds.height * 3;
+ int gy = bounds.height - gh;
+ paintGradient(g2, 0, gy, bounds.width, gh);
+
+ // paint message
+ g2.setPaint(Palette.BLACK);
+ int textX = bounds.width - textBounds.width - MARGIN;
+ int textY = bounds.height - textBounds.height / 2; //text at bottom; account for baseline
+ g2.drawString(message, textX, textY);
+
+ g2.setComposite(originalComposite);
+ }
+
+ private void paintGradient(Graphics2D g2, int x, int y, int w, int h) {
+ Color[] colors = new Color[] { Color.WHITE, gradientColor };
+ float[] fractions = new float[] { 0.0f, .95f };
+ LinearGradientPaint gradiantPaint =
+ new LinearGradientPaint(new Point(x, y), new Point(x, y + h), fractions, colors);
+ g2.setPaint(gradiantPaint);
+ g2.fillRect(x, y, w, h);
+ }
+
+}
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/test/AbstractDockingTest.java b/Ghidra/Framework/Docking/src/main/java/docking/test/AbstractDockingTest.java
index 5581efdc68..33594248df 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/test/AbstractDockingTest.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/test/AbstractDockingTest.java
@@ -1300,6 +1300,9 @@ public abstract class AbstractDockingTest extends AbstractGuiTest {
assertNotNull("Action cannot be null", action);
assertNotNull("Action context cannot be null", context);
+ boolean isValid = runSwing(() -> action.isValidContext(context));
+ assertTrue("Attempted to invoke action with invalid context", isValid);
+
runSwing(() -> {
action.isAddToPopup(context);
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/indexedscrollpane/IndexedScrollPane.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/indexedscrollpane/IndexedScrollPane.java
index aa38110e79..a9aab9ba42 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/indexedscrollpane/IndexedScrollPane.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/indexedscrollpane/IndexedScrollPane.java
@@ -119,6 +119,10 @@ public class IndexedScrollPane extends JPanel implements IndexScrollListener {
return new Dimension(comp.getPreferredSize().width, indexMapper.getViewHeight());
}
+ public Dimension getViewExtentSize() {
+ return viewport.getExtentSize();
+ }
+
public void viewportStateChanged() {
Dimension extentSize = viewport.getExtentSize();
if (!extentSize.equals(visibleSize)) {
@@ -241,7 +245,24 @@ public class IndexedScrollPane extends JPanel implements IndexScrollListener {
@Override
public boolean getScrollableTracksViewportWidth() {
- return false;
+ int prefWidth = comp.getPreferredSize().width;
+ int scrollPaneWidth = getScrollPaneWidth();
+ return scrollPaneWidth > prefWidth;
+ }
+
+ private int getScrollPaneWidth() {
+ Container myParent = getParent();
+ if (myParent == null) {
+ return 0;
+ }
+ if (myParent instanceof JViewport vp) {
+ return vp.getExtentSize().width;
+ }
+ Container grandParent = myParent.getParent();
+ if (grandParent == null) {
+ return 0;
+ }
+ return grandParent.getSize().width;
}
@Override
diff --git a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/DecompilePluginScreenShots.java b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/DecompilePluginScreenShots.java
index aef20a0f55..d21430ae31 100644
--- a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/DecompilePluginScreenShots.java
+++ b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/DecompilePluginScreenShots.java
@@ -4,9 +4,9 @@
* 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.
@@ -24,9 +24,13 @@ import org.junit.Test;
import docking.ComponentProvider;
import docking.DockableComponent;
+import docking.widgets.fieldpanel.FieldPanel;
+import docking.widgets.fieldpanel.support.FieldLocation;
import generic.theme.GThemeDefaults.Colors.Palette;
+import ghidra.app.decompiler.component.DecompilerPanel;
import ghidra.app.plugin.core.codebrowser.CodeViewerProvider;
import ghidra.app.plugin.core.datamgr.DataTypesProvider;
+import ghidra.app.plugin.core.decompile.DecompilerProvider;
import ghidra.app.plugin.core.programtree.ViewManagerComponentProvider;
public class DecompilePluginScreenShots extends GhidraScreenShotGenerator {
@@ -115,52 +119,6 @@ public class DecompilePluginScreenShots extends GhidraScreenShotGenerator {
image = tf.getImage();
}
- @Test
- public void testBackwardSlice() {
- TextFormatter tf = new TextFormatter(16, 500, 4, 5, 0);
- TextFormatterContext hl = new TextFormatterContext(Palette.BLACK, Palette.YELLOW);
- TextFormatterContext red = new TextFormatterContext(Palette.RED, Palette.WHITE);
- TextFormatterContext blue = new TextFormatterContext(Palette.BLUE, Palette.WHITE);
- TextFormatterContext green = new TextFormatterContext(Palette.GREEN, Palette.WHITE);
- TextFormatterContext greenhl = new TextFormatterContext(Palette.GREEN, Palette.YELLOW);
- TextFormatterContext cursorhl =
- new TextFormatterContext(Palette.BLACK, Palette.YELLOW, Palette.RED);
-
- tf.writeln(" |a| = |psParm2|->id;", hl, hl);
- tf.writeln(" b = |max_alpha|(|psParm1|->next,|psParm1|->id);", red, hl, hl);
- tf.writeln(" c = |max_beta|(psParm1->prev, |a|);", red, hl);
- tf.writeln(" c = c + b;");
- tf.writeln(" dStack8 = |0|;", green);
- tf.writeln(" |while| (psParm1->count != dStack8 && (sdword)dStack8) {", blue);
- tf.writeln(" |if| (c < (sdword)(dStack8 + b)) {", blue);
- tf.writeln(" c = c + |a|;", hl);
- tf.writeln(" }");
- tf.writeln(" |else| {", blue);
- tf.writeln(" |a| = |a| + |10|;", hl, hl, greenhl);
- tf.writeln(" }");
- tf.writeln(" dStack8 = dStack8 + |1|;", green);
- tf.writeln(" }");
- tf.writeln(" psParm1->count = |a| + c;", cursorhl);
- tf.writeln(" |return|;", blue);
-
- image = tf.getImage();
- }
-
- @Test
- public void testStructnotapplied() {
- Image listingImage = getListingImage();
- Image decompImage = getDecompilerNoStructImage();
- int listingWidth = listingImage.getWidth(null);
- int decompWidth = decompImage.getWidth(null);
- int height = Math.max(listingImage.getHeight(null), decompImage.getHeight(null));
- BufferedImage combined = createEmptyImage(listingWidth + decompWidth, height);
- Graphics2D g = combined.createGraphics();
- g.drawImage(listingImage, 0, 0, null);
- g.drawImage(decompImage, listingWidth, 0, null);
- g.dispose();
- image = combined;
- }
-
public void testStructApplied() {
Image listingImage = getListingImage();
Image decompImage = getDecompilerStructAppliedImage();
@@ -177,14 +135,37 @@ public class DecompilePluginScreenShots extends GhidraScreenShotGenerator {
@Test
public void testEditFunctionSignature() {
+ DecompilerProvider provider = (DecompilerProvider) getProvider("Decompiler");
+ showProvider(provider.getClass());
goToListing(0x401040);
- ComponentProvider provider = getProvider("Decompiler");
+ int line = 2; // function signature line
+ int charPos = 15; // function name
+ setDecompilerLocation(provider, line, charPos);
showProvider(provider.getClass());
waitForSwing();
performAction("Edit Function Signature", "DecompilePlugin", provider, false);
captureDialog();
}
+ private void setDecompilerLocation(DecompilerProvider provider, int line, int charPosition) {
+
+ DecompilerPanel panel = provider.getDecompilerPanel();
+ FieldPanel fp = panel.getFieldPanel();
+ FieldLocation loc = loc(line, charPosition);
+
+ // scroll to the field to make sure it has been built so that we can get its point
+ fp.scrollTo(loc);
+ Point p = fp.getPointForLocation(loc);
+
+ click(fp, p, 1, true);
+ waitForSwing();
+ }
+
+ private FieldLocation loc(int lineNumber, int col) {
+ FieldLocation loc = new FieldLocation(lineNumber - 1, 0, 0, col);
+ return loc;
+ }
+
private Image getListingImage() {
Font font = new Font("Monospaced", Font.PLAIN, 12);