Fixed Decompiler message area not going away when changing functions

This commit is contained in:
dragonmacher
2026-06-18 22:39:45 -04:00
parent 4a802292a1
commit 2517de24d5
6 changed files with 101 additions and 42 deletions
@@ -14,6 +14,9 @@ color.fg.decompiler.global = color.palette.darkcyan
color.fg.decompiler.special = color.palette.crimson
color.fg.decompiler.external.function = color.palette.fuchsia
color.fg.decompiler.message = color.palette.black
color.bg.decompiler.message = color.bg.visualgraph.message
color.bg.decompiler.current.variable = color.palette.highlight.transparent.yellow
color.bg.decompiler.highlights.middle.mouse = color.bg.highlight
@@ -46,8 +49,11 @@ icon.decompiler.action.display.lock = lock.gif
font.decompiler = font.monospaced
font.decompiler.pcode.dfg = font.graphdisplay.default
font.decompiler.message = font.graph.component.message
[Dark Defaults]
# the message text needs to be dark in dark mode
color.fg.decompiler.message = rgb(0, 0, 0)
color.bg.decompiler.highlights.find = color.palette.cornflowerblue
@@ -51,8 +51,7 @@ public class DecompilerController {
private int cacheSize;
public DecompilerController(ServiceProvider serviceProvider, DecompilerCallbackHandler handler,
DecompileOptions options,
DecompilerClipboardProvider clipboard) {
DecompileOptions options, DecompilerClipboardProvider clipboard) {
this.serviceProvider = serviceProvider;
this.cacheSize = options.getCacheSize();
this.callbackHandler = handler;
@@ -370,6 +369,13 @@ public class DecompilerController {
decompilerCache.invalidateAll();
}
public void clearCacheForCurrentFunction() {
Function function = getFunction();
if (function != null) {
decompilerCache.invalidate(function);
}
}
public void programClosed(Program closedProgram) {
for (Function function : decompilerCache.asMap().keySet()) {
Program functionProgram = function.getProgram();
@@ -1393,16 +1393,30 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
* decompiler content panel and the line numbers panel}
*/
public Rectangle getViewContentBounds() {
Insets viewInsets = scroller.getViewInsets();
int x = 0; // don't use insets here, since we want the full size including the line # panel
int y = viewInsets.top;
// This compensates for the optional parent border. We have guilty knowledge that our parent
// will have a non-empty border when in a snapshot
JComponent decorationPanel = (JComponent) getParent();
Insets parentInsets = decorationPanel.getInsets();
x += parentInsets.left;
y += parentInsets.top;
// 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.
// of the line numbers panel can be found by looking at the x position of the scroll view,
// as it is offset by the line number panel's width.
Rectangle bounds = scroller.getBounds();
Dimension scrollerSize = scroller.getViewExtentSize();
int lineNumberWidth = bounds.x;
return new Rectangle(0, 0, scrollerSize.width + lineNumberWidth, scrollerSize.height);
int gap = viewInsets.left;
Dimension extent = scroller.getViewExtentSize();
int width = lineNumberWidth + gap + extent.width;
int height = extent.height;
return new Rectangle(x, y, width, height);
}
private void buildPanels() {
@@ -86,6 +86,7 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
private ToggleDockingAction displayUnreachableCodeToggle;
private ToggleDockingAction respectReadOnlyFlags;
private ToggleDockingAction toggleLockAction;
private final DecompilePlugin plugin;
private ClipboardService clipboardService;
@@ -146,7 +147,7 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
new DecompilerController(getTool(), this, decompilerOptions, clipboardProvider);
DecompilerPanel decompilerPanel = controller.getDecompilerPanel();
// TODO move the hl controller into the panel
// FUTURE move the hl controller into the panel
highlightController = new LocationClangHighlightController();
decompilerPanel.setHighlightController(highlightController);
decorationPanel = new DecoratorPanel(decompilerPanel, isConnected) {
@@ -155,7 +156,6 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
super.paint(g);
overlayPainter.paintOverlay(g, decompilerPanel.getViewContentBounds());
}
};
if (!isConnected) {
@@ -326,6 +326,7 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
if (!isVisible()) {
return;
}
ToolOptions fieldOptions = tool.getOptions(GhidraOptions.CATEGORY_BROWSER_FIELDS);
ToolOptions opt = tool.getOptions(DecompilePlugin.OPTIONS_TITLE);
@@ -350,11 +351,15 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
if (currentLocation != null) {
if (lockDisplay) {
// Clear the cached results so the next time we come back to this function, it will
// get re-decompiled. If we don't do this, then we would need the ability to mark
// the cached results as needing update, so future loads of that cached function
// will trigger the refresh message.
controller.clearCacheForCurrentFunction();
setOverlayMessage(getOverlayRefreshMessage());
}
else {
controller.refreshDisplay(program, currentLocation, null);
setOverlayMessage("");
}
}
}
@@ -379,6 +384,11 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
decorationPanel.repaint();
}
private void clearOverlayMessage() {
overlayPainter.setMessage("");
decorationPanel.repaint();
}
private void refreshToggleButtons() {
displayUnreachableCodeToggle.setSelected(!decompilerOptions.isEliminateUnreachable());
respectReadOnlyFlags.setSelected(!decompilerOptions.isRespectReadOnly());
@@ -533,7 +543,6 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
*/
void refresh() {
controller.refreshDisplay(program, currentLocation, null);
setOverlayMessage("");
}
/**
@@ -616,6 +625,8 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
updateTitle();
contextChanged();
controller.setSelection(currentSelection);
clearOverlayMessage();
}
@Override
@@ -764,22 +775,34 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
// invoke later to give the window manage a chance to create the new window
// (its done in an invoke later)
Swing.runLater(() -> {
initializeClone(newProvider);
});
}
ViewerPosition myViewPosition = controller.getDecompilerPanel().getViewerPosition();
newProvider.doSetProgram(program);
private void initializeClone(DecompilerProvider newProvider) {
ViewerPosition myViewPosition = controller.getDecompilerPanel().getViewerPosition();
newProvider.doSetProgram(program);
// initialize the new provider's cache and then set the location
DecompileData myDecompileData = controller.getDecompileData();
newProvider.controller.addToCache(myDecompileData);
newProvider.setLocation(currentLocation, myViewPosition);
// initialize the new provider's cache and then set the location
DecompileData myDecompileData = controller.getDecompileData();
newProvider.controller.addToCache(myDecompileData);
newProvider.setLocation(currentLocation, myViewPosition);
// transfer any state after the new decompiler is initialized
DecompilerPanel myPanel = getDecompilerPanel();
DecompilerPanel newPanel = newProvider.getDecompilerPanel();
newProvider.doWhenNotBusy(() -> {
newPanel.setViewerPosition(myViewPosition);
newPanel.cloneHighlights(myPanel);
});
// update the lock action and overlay message for the new provider
if (lockDisplay) {
newProvider.toggleLockAction.setSelected(true);
newProvider.lockDisplay = true;
if (overlayPainter.isActive()) {
newProvider.setOverlayMessage(getOverlayRefreshMessage());
}
}
// transfer any state after the new decompiler is initialized
DecompilerPanel myPanel = getDecompilerPanel();
DecompilerPanel newPanel = newProvider.getDecompilerPanel();
newProvider.doWhenNotBusy(() -> {
newPanel.setViewerPosition(myViewPosition);
newPanel.cloneHighlights(myPanel);
});
}
@@ -835,7 +858,7 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
private void createActions(boolean isConnected) {
String owner = plugin.getName();
new ToggleActionBuilder("Lock Display", owner)
toggleLockAction = 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"))
@@ -20,7 +20,7 @@ import java.awt.*;
import org.apache.commons.lang3.StringUtils;
import generic.theme.GColor;
import generic.theme.GThemeDefaults.Colors.Palette;
import generic.theme.GThemeDefaults.Colors;
import generic.theme.Gui;
/**
@@ -28,9 +28,13 @@ import generic.theme.Gui;
* needs to be refreshed manually.
*/
class OverlayMessagePainter {
private static final String FONT_MESSAGE_ID = "font.decompiler.message";
private static final Color COLOR_BG_GRADIENT = new GColor("color.bg.decompiler.message");
private static final Color COLOR_FG_MESSAGE = new GColor("color.fg.decompiler.message");
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) {
@@ -47,32 +51,35 @@ class OverlayMessagePainter {
}
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// 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);
Font font = Gui.getFont(FONT_MESSAGE_ID);
g.setFont(font);
Rectangle textBounds = font.getStringBounds(message, g2.getFontRenderContext()).getBounds();
int gx = bounds.x;
int gh = textBounds.height * 3;
int gy = bounds.height - gh;
paintGradient(g2, 0, gy, bounds.width, gh);
int gy = (bounds.y + bounds.height) - gh;
int gw = bounds.width;
paintGradient(g2, gx, gy, gw, gh);
// paint message
g2.setPaint(Palette.BLACK);
g2.setPaint(COLOR_FG_MESSAGE);
int textX = bounds.width - textBounds.width - MARGIN;
int textY = bounds.height - textBounds.height / 2; //text at bottom; account for baseline
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 };
Color[] colors = new Color[] { Colors.BACKGROUND, COLOR_BG_GRADIENT };
float[] fractions = new float[] { 0.0f, .85f };
LinearGradientPaint gradiantPaint =
new LinearGradientPaint(new Point(x, y), new Point(x, y + h), fractions, colors);
g2.setPaint(gradiantPaint);
@@ -115,12 +115,15 @@ public class IndexedScrollPane extends JPanel implements IndexScrollListener {
}
public Dimension getViewSize() {
return new Dimension(comp.getPreferredSize().width, indexMapper.getViewHeight());
public Dimension getViewExtentSize() {
Dimension size = viewport.getExtentSize();
int w = size.width;
int h = size.height;
return new Dimension(w, h);
}
public Dimension getViewExtentSize() {
return viewport.getExtentSize();
public Insets getViewInsets() {
return scrollPane.getInsets();
}
public void viewportStateChanged() {
@@ -311,7 +314,7 @@ public class IndexedScrollPane extends JPanel implements IndexScrollListener {
public void setColumnHeaderComp(JComponent comp) {
scrollPane.setColumnHeaderView(comp);
// SWING WORK AROUND - setting the header panel on a scrollpane that is horizontally
// SWING WORK AROUND - setting the header panel on a scroll pane that is horizontally
// scrolled does not initially scroll the header to match the main view. Setting the
// horizontal position to 0 and back to where it was, resynchronizes the header with the
// view.