diff --git a/Ghidra/Features/Decompiler/data/decompiler.theme.properties b/Ghidra/Features/Decompiler/data/decompiler.theme.properties index f1a2f31b59..00a05b0b02 100644 --- a/Ghidra/Features/Decompiler/data/decompiler.theme.properties +++ b/Ghidra/Features/Decompiler/data/decompiler.theme.properties @@ -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 diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerController.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerController.java index d3cb23c7ce..c4202631b0 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerController.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerController.java @@ -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(); 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 2e626c8115..56caf3c6e6 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 @@ -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() { 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 49a47b216a..0dc104e765 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 @@ -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")) 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 index ca393e7654..16ddbb543b 100644 --- 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 @@ -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); 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 a9aab9ba42..25c507e6d9 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 @@ -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.