diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointMarkerPlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointMarkerPlugin.java index 1c097ad119..87dc1d831e 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointMarkerPlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointMarkerPlugin.java @@ -85,7 +85,8 @@ import ghidra.util.Msg; servicesRequired = { DebuggerLogicalBreakpointService.class, MarkerService.class, - }) + } +) public class DebuggerBreakpointMarkerPlugin extends Plugin { private static final Color COLOR_BREAKPOINT_ENABLED_MARKER = @@ -496,7 +497,7 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin { new ProgramLocationActionContext(null, location.getProgram(), new ProgramLocation(location.getProgram(), location.getAddr()), null, null); if (contextCanManipulateBreakpoints(context)) { - doToggleBreakpointsAt(ToggleBreakpointAction.NAME, context); + doToggleBreakpointsAt(AbstractToggleBreakpointAction.NAME, context); } } } @@ -753,28 +754,32 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin { @AutoOptionDefined( name = DebuggerResources.OPTION_NAME_COLORS_ENABLED_BREAKPOINT_COLORING_BACKGROUND, description = "Whether or not to color background for memory at an enabled breakpoint", - help = @HelpInfo(anchor = "colors")) + help = @HelpInfo(anchor = "colors") + ) private boolean breakpointEnabledColoringBackground = DebuggerResources.DEFAULT_COLOR_ENABLED_BREAKPOINT_COLORING_BACKGROUND; @AutoOptionDefined( name = DebuggerResources.OPTION_NAME_COLORS_DISABLED_BREAKPOINT_COLORING_BACKGROUND, description = "Whether or not to color background for memory at a disabled breakpoint", - help = @HelpInfo(anchor = "colors")) + help = @HelpInfo(anchor = "colors") + ) private boolean breakpointDisabledColoringBackground = DebuggerResources.DEFAULT_COLOR_DISABLED_BREAKPOINT_COLORING_BACKGROUND; @AutoOptionDefined( name = DebuggerResources.OPTION_NAME_COLORS_INEFF_EN_BREAKPOINT_COLORING_BACKGROUND, description = "Whether or not to color background for memory at an enabled, but ineffective, breakpoint", - help = @HelpInfo(anchor = "colors")) + help = @HelpInfo(anchor = "colors") + ) private boolean breakpointIneffEnColoringBackground = DebuggerResources.DEFAULT_COLOR_INEFF_EN_BREAKPOINT_COLORING_BACKGROUND; @AutoOptionDefined( name = DebuggerResources.OPTION_NAME_COLORS_INEFF_DIS_BREAKPOINT_COLORING_BACKGROUND, description = "Whether or not to color background for memory at an disabled, but ineffective, breakpoint", - help = @HelpInfo(anchor = "colors")) + help = @HelpInfo(anchor = "colors") + ) private boolean breakpointIneffDisColoringBackground = DebuggerResources.DEFAULT_COLOR_INEFF_DIS_BREAKPOINT_COLORING_BACKGROUND; @@ -832,7 +837,8 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin { } @AutoOptionConsumed( - name = DebuggerResources.OPTION_NAME_COLORS_ENABLED_BREAKPOINT_COLORING_BACKGROUND) + name = DebuggerResources.OPTION_NAME_COLORS_ENABLED_BREAKPOINT_COLORING_BACKGROUND + ) private void setEnabledBreakpointMarkerBackground(boolean breakpointColoringBackground) { for (BreakpointMarkerSets markers : markersByProgram.values()) { markers.setEnabledColoringBackground(breakpointColoringBackground); @@ -840,7 +846,8 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin { } @AutoOptionConsumed( - name = DebuggerResources.OPTION_NAME_COLORS_DISABLED_BREAKPOINT_COLORING_BACKGROUND) + name = DebuggerResources.OPTION_NAME_COLORS_DISABLED_BREAKPOINT_COLORING_BACKGROUND + ) private void setDisabledBreakpointMarkerBackground(boolean breakpointColoringBackground) { for (BreakpointMarkerSets markers : markersByProgram.values()) { markers.setDisabledColoringBackground(breakpointColoringBackground); @@ -848,7 +855,8 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin { } @AutoOptionConsumed( - name = DebuggerResources.OPTION_NAME_COLORS_INEFF_EN_BREAKPOINT_COLORING_BACKGROUND) + name = DebuggerResources.OPTION_NAME_COLORS_INEFF_EN_BREAKPOINT_COLORING_BACKGROUND + ) private void setIneffectiveEBreakpointMarkerBackground(boolean breakpointColoringBackground) { for (BreakpointMarkerSets markers : markersByProgram.values()) { markers.setIneffectiveEnabledColoringBackground(breakpointColoringBackground); @@ -856,7 +864,8 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin { } @AutoOptionConsumed( - name = DebuggerResources.OPTION_NAME_COLORS_INEFF_DIS_BREAKPOINT_COLORING_BACKGROUND) + name = DebuggerResources.OPTION_NAME_COLORS_INEFF_DIS_BREAKPOINT_COLORING_BACKGROUND + ) private void setIneffectiveDBreakpointMarkerBackground(boolean breakpointColoringBackground) { for (BreakpointMarkerSets markers : markersByProgram.values()) { markers.setIneffectiveDisabledColoringBackground(breakpointColoringBackground); @@ -1089,7 +1098,8 @@ public class DebuggerBreakpointMarkerPlugin extends Plugin { actionDisableBreakpoint = new DisableBreakpointAction(); actionClearBreakpoint = new ClearBreakpointAction(); - tool.setMenuGroup(new String[] { SetBreakpointAction.NAME }, SetBreakpointAction.GROUP); + tool.setMenuGroup(new String[] { AbstractSetBreakpointAction.NAME }, + SetBreakpointAction.GROUP); } @Override diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingPlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingPlugin.java index bd4e9be4fe..d62817797e 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingPlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingPlugin.java @@ -198,7 +198,7 @@ public class DebuggerListingPlugin extends AbstractCodeBrowserPlugin ex private static final GColor CURRENT_LINE_HIGHLIGHT_COLOR = new GColor("color.bg.currentline.listing"); //@formatter:on - // - Icon - - private static final Icon CURSOR_LOC_ICON = - new GIcon("icon.plugin.codebrowser.cursor.location"); protected final P connectedProvider; protected List

disconnectedProviders = new ArrayList<>(); protected FormatManager formatMgr; protected ViewManagerService viewManager; - private MarkerService markerService; + protected AddressSetView currentView = ImmutableAddressSet.EMPTY_SET; protected Program currentProgram; private boolean selectionChanging; - private MarkerSet currentSelectionMarkers; - private MarkerSet currentHighlightMarkers; - private MarkerSet currentCursorMarkers; - private ChangeListener markerChangeListener; - private Color cursorHighlightColor; - private boolean isHighlightCursorLine; private ProgramDropProvider dndProvider; public AbstractCodeBrowserPlugin(PluginTool tool) { @@ -113,7 +101,6 @@ public abstract class AbstractCodeBrowserPlugin

ex initMiscellaneousOptions(); displayOptions.addOptionsChangeListener(this); fieldOptions.addOptionsChangeListener(this); - markerChangeListener = new MarkerChangeListener(connectedProvider); } protected abstract P createProvider(FormatManager formatManager, boolean isConnected); @@ -138,16 +125,14 @@ public abstract class AbstractCodeBrowserPlugin

ex private void viewUpdated() { updateBackgroundColorModel(); - setHighlight(connectedProvider.getHighlight()); - setSelection(connectedProvider.getSelection()); + connectedProvider.setHighlight(connectedProvider.getHighlight()); + setConnectedProviderSelection(connectedProvider.getSelection()); } @Override protected void init() { - markerService = tool.getService(MarkerService.class); - if (markerService != null) { - markerService.addChangeListener(markerChangeListener); - } + MarkerService markerService = tool.getService(MarkerService.class); + setMarkerService(markerService); updateBackgroundColorModel(); if (viewManager == null) { @@ -161,20 +146,31 @@ public abstract class AbstractCodeBrowserPlugin

ex provider.setClipboardService(clipboardService); } } + + ListingMarginProviderService[] marginServices = + tool.getServices(ListingMarginProviderService.class); + for (ListingMarginProviderService marginService : marginServices) { + connectedProvider.addMarginService(marginService); + } + + ListingOverviewProviderService[] overviewServices = + tool.getServices(ListingOverviewProviderService.class); + for (ListingOverviewProviderService service : overviewServices) { + connectedProvider.addOverviewService(service); + } } protected void updateBackgroundColorModel() { - ListingPanel listingPanel = connectedProvider.getListingPanel(); - if (markerService != null) { - AddressIndexMap indexMap = connectedProvider.getListingPanel().getAddressIndexMap(); - listingPanel.setBackgroundColorModel( - new MarkerServiceBackgroundColorModel(markerService, indexMap)); - } - else { - listingPanel.setBackgroundColorModel(null); - } - // Note: this should update all providers, not just the connected provider + updateBackgroundColorModel(connectedProvider); + for (CodeViewerProvider provider : disconnectedProviders) { + updateBackgroundColorModel(provider); + } + } + + protected void updateBackgroundColorModel(CodeViewerProvider provider) { + ListingPanel listingPanel = provider.getListingPanel(); + listingPanel.updateBackgroundColorModel(); } @Override @@ -182,20 +178,46 @@ public abstract class AbstractCodeBrowserPlugin

ex P newProvider = createProvider(formatMgr.createClone(), false); newProvider.setClipboardService(tool.getService(ClipboardService.class)); + ListingPanel listingPanel = newProvider.getListingPanel(); + FieldPanel fieldPanel = listingPanel.getFieldPanel(); + List listingPanels = List.of(listingPanel); + List fieldPanels = List.of(fieldPanel); + initPanelOptions(listingPanels, fieldPanels); + disconnectedProviders.add(newProvider); if (dndProvider != null) { newProvider.addProgramDropProvider(dndProvider); } - tool.showComponentProvider(newProvider, true); + ListingHoverService[] hoverServices = tool.getServices(ListingHoverService.class); for (ListingHoverService hoverService : hoverServices) { - newProvider.getListingPanel().addHoverService(hoverService); + listingPanel.addHoverService(hoverService); } + + ListingMarginProviderService[] marginServices = + tool.getServices(ListingMarginProviderService.class); + for (ListingMarginProviderService service : marginServices) { + newProvider.addMarginService(service); + } + + ListingOverviewProviderService[] overviewServices = + tool.getServices(ListingOverviewProviderService.class); + for (ListingOverviewProviderService service : overviewServices) { + newProvider.addOverviewService(service); + } + + MarkerService markerService = tool.getService(MarkerService.class); + listingPanel.setMarkerService(markerService); + + updateBackgroundColorModel(newProvider); + + tool.showComponentProvider(newProvider, true); + return newProvider; } - protected void setHighlight(FieldSelection highlight) { - MarkerSet highlightMarkers = getHighlightMarkers(currentProgram); + // this is for tool highlights coming in to the plugin + protected void setConnectedProviderHighlight(FieldSelection highlight) { if (highlight != null && !highlight.isEmpty()) { ListingPanel listingPanel = connectedProvider.getListingPanel(); @@ -204,17 +226,9 @@ public abstract class AbstractCodeBrowserPlugin

ex firePluginEvent( new ProgramHighlightPluginEvent(this.getName(), programHighlight, currentProgram)); - - if (highlightMarkers != null) { - highlightMarkers.clearAll(); - highlightMarkers.add(programHighlight); - } } else { connectedProvider.setHighlight(new ProgramSelection()); - if (highlightMarkers != null) { - highlightMarkers.clearAll(); - } } } @@ -229,10 +243,11 @@ public abstract class AbstractCodeBrowserPlugin

ex viewManager = (ViewManagerService) service; setView(viewManager.getCurrentView()); } - if (interfaceClass == MarkerService.class && markerService == null) { - markerService = tool.getService(MarkerService.class); - markerService.addChangeListener(markerChangeListener); + if (interfaceClass == MarkerService.class) { + MarkerService markerService = tool.getService(MarkerService.class); + setMarkerService(markerService); updateBackgroundColorModel(); + if (viewManager != null) { viewUpdated(); } @@ -248,6 +263,32 @@ public abstract class AbstractCodeBrowserPlugin

ex otherPanel.addHoverService(hoverService); } } + if (interfaceClass == ListingMarginProviderService.class) { + ListingMarginProviderService marginService = (ListingMarginProviderService) service; + connectedProvider.addMarginService(marginService); + + for (CodeViewerProvider provider : disconnectedProviders) { + provider.addMarginService(marginService); + } + } + if (interfaceClass == ListingOverviewProviderService.class) { + ListingOverviewProviderService overviewService = + (ListingOverviewProviderService) service; + connectedProvider.addOverviewService(overviewService); + + for (CodeViewerProvider provider : disconnectedProviders) { + provider.addOverviewService(overviewService); + } + } + } + + private void setMarkerService(MarkerService markerService) { + ListingPanel listingPanel = connectedProvider.getListingPanel(); + listingPanel.setMarkerService(markerService); + for (CodeViewerProvider provider : disconnectedProviders) { + listingPanel = provider.getListingPanel(); + listingPanel.setMarkerService(markerService); + } } @Override @@ -256,42 +297,55 @@ public abstract class AbstractCodeBrowserPlugin

ex viewManager = null; setView(currentProgram.getMemory()); } - if (service == markerService) { - markerService.removeChangeListener(markerChangeListener); - clearMarkers(currentProgram); - markerService = null; + if (interfaceClass == MarkerService.class) { + setMarkerService(null); + connectedProvider.clearMarkers(currentProgram); updateBackgroundColorModel(); } if (interfaceClass == ListingHoverService.class) { ListingHoverService hoverService = (ListingHoverService) service; - connectedProvider.getListingPanel().removeHoverService(hoverService); + connectedProvider.removeHoverService(hoverService); + for (CodeViewerProvider provider : disconnectedProviders) { - provider.getListingPanel().removeHoverService(hoverService); + provider.removeHoverService(hoverService); } - ListingPanel otherPanel = connectedProvider.getOtherPanel(); - if (otherPanel != null) { - otherPanel.removeHoverService(hoverService); + } + if (interfaceClass == ListingMarginProviderService.class) { + ListingMarginProviderService marginService = (ListingMarginProviderService) service; + connectedProvider.removeMarginService(marginService); + + for (CodeViewerProvider provider : disconnectedProviders) { + provider.removeMarginService(marginService); + } + } + if (interfaceClass == ListingOverviewProviderService.class) { + ListingOverviewProviderService overviewService = + (ListingOverviewProviderService) service; + connectedProvider.removeOverviewService(overviewService); + + for (CodeViewerProvider provider : disconnectedProviders) { + provider.removeOverviewService(overviewService); } } } @Override - public void addOverviewProvider(OverviewProvider overviewProvider) { + public void addOverviewProvider(ListingOverviewProvider overviewProvider) { connectedProvider.addOverviewProvider(overviewProvider); } @Override - public void addMarginProvider(MarginProvider marginProvider) { + public void addMarginProvider(ListingMarginProvider marginProvider) { connectedProvider.addMarginProvider(marginProvider); } @Override - public void removeOverviewProvider(OverviewProvider overviewProvider) { + public void removeOverviewProvider(ListingOverviewProvider overviewProvider) { connectedProvider.removeOverviewProvider(overviewProvider); } @Override - public void removeMarginProvider(MarginProvider marginProvider) { + public void removeMarginProvider(ListingMarginProvider marginProvider) { connectedProvider.removeMarginProvider(marginProvider); } @@ -338,10 +392,6 @@ public abstract class AbstractCodeBrowserPlugin

ex connectedProvider.setHighlightProvider(highlightProvider, highlightProgram); } - protected void updateHighlightProvider() { - connectedProvider.updateHighlightProvider(); - } - @Override public void setListingPanel(ListingPanel lp) { connectedProvider.setOtherPanel(lp); @@ -379,7 +429,7 @@ public abstract class AbstractCodeBrowserPlugin

ex if (currentProgram != null) { currentProgram.removeListener(this); } - clearMarkers(currentProgram); + connectedProvider.clearMarkers(currentProgram); formatMgr.dispose(); removeProvider(connectedProvider); for (CodeViewerProvider provider : disconnectedProviders) { @@ -391,68 +441,94 @@ public abstract class AbstractCodeBrowserPlugin

ex public void optionsChanged(ToolOptions options, String optionName, Object oldValue, Object newValue) { - ListingPanel listingPanel = connectedProvider.getListingPanel(); - if (options.getName().equals(GhidraOptions.CATEGORY_BROWSER_FIELDS)) { + if (!options.getName().equals(GhidraOptions.CATEGORY_BROWSER_FIELDS)) { + return; + } - FieldPanel fieldPanel = listingPanel.getFieldPanel(); - if (optionName.equals(GhidraOptions.OPTION_SELECTION_COLOR)) { - Color color = ((Color) newValue); - fieldPanel.setSelectionColor(color); - MarkerSet selectionMarkers = getSelectionMarkers(currentProgram); - if (selectionMarkers != null) { - selectionMarkers.setMarkerColor(color); - } - ListingPanel otherPanel = connectedProvider.getOtherPanel(); - if (otherPanel != null) { - otherPanel.getFieldPanel().setSelectionColor(color); - } - } - else if (optionName.equals(GhidraOptions.OPTION_HIGHLIGHT_COLOR)) { - Color color = ((Color) newValue); - fieldPanel.setHighlightColor(color); - MarkerSet highlightMarkers = getHighlightMarkers(currentProgram); - if (highlightMarkers != null) { - highlightMarkers.setMarkerColor(color); - } - } - else if (optionName.equals(CURSOR_COLOR_OPTIONS_NAME)) { - Color color = ((Color) newValue); - fieldPanel.setFocusedCursorColor(color); - } - else if (optionName.equals(UNFOCUSED_CURSOR_COLOR_OPTIONS_NAME)) { - Color color = ((Color) newValue); - fieldPanel.setNonFocusCursorColor(color); - } - else if (optionName.equals(GhidraOptions.HIGHLIGHT_CURSOR_LINE_COLOR)) { - cursorHighlightColor = (Color) newValue; - if (currentCursorMarkers != null) { - currentCursorMarkers.setMarkerColor(cursorHighlightColor); - } - } - else if (optionName.equals(GhidraOptions.HIGHLIGHT_CURSOR_LINE)) { - isHighlightCursorLine = (Boolean) newValue; - if (currentCursorMarkers != null) { - currentCursorMarkers.setColoringBackground(isHighlightCursorLine); - } - } - else if (optionName.equals(MOUSE_WHEEL_HORIZONTAL_SCROLLING_OPTIONS_NAME)) { - fieldPanel.setHorizontalScrollingEnabled((Boolean) newValue); - } + List listingPanels = allListingPanels(); + List fieldPanels = allFieldPanels(); + if (optionName.equals(GhidraOptions.OPTION_SELECTION_COLOR)) { + Color color = ((Color) newValue); + onListingPanels(listingPanels, lp -> lp.setSelectionColor(color)); + } + else if (optionName.equals(GhidraOptions.OPTION_HIGHLIGHT_COLOR)) { + Color color = ((Color) newValue); + onListingPanels(listingPanels, lp -> lp.setHighlightColor(color)); + } + else if (optionName.equals(CURSOR_COLOR_OPTIONS_NAME)) { + Color color = ((Color) newValue); + onFieldPanels(fieldPanels, fp -> fp.setFocusedCursorColor(color)); + } + else if (optionName.equals(UNFOCUSED_CURSOR_COLOR_OPTIONS_NAME)) { + Color color = ((Color) newValue); + onFieldPanels(fieldPanels, fp -> fp.setNonFocusCursorColor(color)); + } + else if (optionName.equals(GhidraOptions.HIGHLIGHT_CURSOR_LINE_COLOR)) { + Color color = (Color) newValue; + onListingPanels(listingPanels, lp -> lp.setCursorHighlightColor(color)); + } + else if (optionName.equals(GhidraOptions.HIGHLIGHT_CURSOR_LINE)) { + Boolean doHighlight = (Boolean) newValue; + onListingPanels(listingPanels, lp -> lp.setHighlightCursorLineEnabled(doHighlight)); + } + else if (optionName.equals(MOUSE_WHEEL_HORIZONTAL_SCROLLING_OPTIONS_NAME)) { + Boolean doScroll = (Boolean) newValue; + onFieldPanels(fieldPanels, fp -> fp.setHorizontalScrollingEnabled(doScroll)); } } + protected void onFieldPanels(List panels, Consumer c) { + for (FieldPanel fp : panels) { + c.accept(fp); + } + } + + protected void onListingPanels(List listingPanels, Consumer c) { + for (ListingPanel lp : listingPanels) { + c.accept(lp); + } + } + + private List allListingPanels() { + + List results = new ArrayList<>(); + results.add(connectedProvider.getListingPanel()); + + ListingPanel otherPanel = connectedProvider.getOtherPanel(); + if (otherPanel != null) { + results.add(otherPanel); + } + + for (CodeViewerProvider provider : disconnectedProviders) { + results.add(provider.getListingPanel()); + } + + return results; + } + + private List allFieldPanels() { + List results = new ArrayList<>(); + + FieldPanel fieldPanel = connectedProvider.getListingPanel().getFieldPanel(); + results.add(fieldPanel); + + ListingPanel otherPanel = connectedProvider.getOtherPanel(); + if (otherPanel != null) { + FieldPanel otherFieldPanel = otherPanel.getFieldPanel(); + results.add(otherFieldPanel); + } + + for (CodeViewerProvider provider : disconnectedProviders) { + fieldPanel = provider.getListingPanel().getFieldPanel(); + results.add(fieldPanel); + } + + return results; + } + @Override - public void selectionChanged(CodeViewerProvider provider, ProgramSelection selection) { + public void broadcastSelectionChanged(CodeViewerProvider provider, ProgramSelection selection) { if (provider == connectedProvider) { - MarkerSet selectionMarkers = getSelectionMarkers(currentProgram); - if (selectionMarkers != null) { - selectionMarkers.clearAll(); - } - if (selection != null) { - if (selectionMarkers != null) { - selectionMarkers.add(selection); - } - } if (!selectionChanging) { tool.firePluginEvent(new ProgramSelectionPluginEvent(getName(), selection, connectedProvider.getProgram())); @@ -460,88 +536,21 @@ public abstract class AbstractCodeBrowserPlugin

ex } } - protected void setHighlight(ProgramSelection highlight) { - connectedProvider.setHighlight(highlight); - } - - protected void setSelection(ProgramSelection sel) { + protected void setConnectedProviderSelection(ProgramSelection sel) { selectionChanging = true; connectedProvider.setSelection(sel); selectionChanging = false; } - protected void clearMarkers(Program program) { - if (markerService == null) { - return; - } - - if (program == null) { - return; // can happen during dispose after a programDeactivated() - } - - if (currentSelectionMarkers != null) { - markerService.removeMarker(currentSelectionMarkers, program); - currentSelectionMarkers = null; - } - - if (currentHighlightMarkers != null) { - markerService.removeMarker(currentHighlightMarkers, program); - currentHighlightMarkers = null; - } - - if (currentCursorMarkers != null) { - markerService.removeMarker(currentCursorMarkers, program); - currentCursorMarkers = null; - } - } - - private MarkerSet getSelectionMarkers(Program program) { - if (markerService == null || program == null) { - return null; - } - - // already created - if (currentSelectionMarkers != null) { - return currentSelectionMarkers; - } - - FieldPanel fp = connectedProvider.getListingPanel().getFieldPanel(); - currentSelectionMarkers = markerService.createAreaMarker("Selection", "Selection Display", - program, MarkerService.SELECTION_PRIORITY, false, true, false, fp.getSelectionColor()); - return currentSelectionMarkers; - } - - protected MarkerSet getHighlightMarkers(Program program) { - if (markerService == null || program == null) { - return null; - } - - // already created - if (currentHighlightMarkers != null) { - return currentHighlightMarkers; - } - - FieldPanel fp = connectedProvider.getListingPanel().getFieldPanel(); - currentHighlightMarkers = markerService.createAreaMarker("Highlight", "Highlight Display ", - program, MarkerService.HIGHLIGHT_PRIORITY, false, true, false, fp.getHighlightColor()); - return currentHighlightMarkers; - } - - protected MarkerSet getCursorMarkers(Program program) { - if (markerService == null || program == null) { - return null; - } - - // already created - if (currentCursorMarkers != null) { - return currentCursorMarkers; - } - - currentCursorMarkers = markerService.createPointMarker("Cursor", "Cursor Location", program, - MarkerService.CURSOR_PRIORITY, true, true, isHighlightCursorLine, cursorHighlightColor, - CURSOR_LOC_ICON); - - return currentCursorMarkers; + private void initMiscellaneousOptions() { + // make sure the following options are registered + HelpLocation helpLocation = + new HelpLocation("ShowInstructionInfoPlugin", "Processor_Manual_Options"); + Options options = tool.getOptions(ManualViewerCommandWrappedOption.OPTIONS_CATEGORY_NAME); + options.registerOption(ManualViewerCommandWrappedOption.MANUAL_VIEWER_OPTIONS, + OptionType.CUSTOM_TYPE, + ManualViewerCommandWrappedOption.getDefaultBrowserLoaderOptions(), helpLocation, + "Options for running manual viewer", () -> new ManualViewerCommandEditor()); } private void initOptions(Options fieldOptions) { @@ -572,50 +581,44 @@ public abstract class AbstractCodeBrowserPlugin

ex helpLocation, "Enables horizontal scrolling by holding the Shift key while " + "using the mouse scroll wheel"); - Color color = fieldOptions.getColor(GhidraOptions.OPTION_SELECTION_COLOR, - GhidraOptions.DEFAULT_SELECTION_COLOR); - - FieldPanel fieldPanel = connectedProvider.getListingPanel().getFieldPanel(); - fieldPanel.setSelectionColor(color); - MarkerSet selectionMarkers = getSelectionMarkers(currentProgram); - if (selectionMarkers != null) { - selectionMarkers.setMarkerColor(color); - } - - color = fieldOptions.getColor(GhidraOptions.OPTION_HIGHLIGHT_COLOR, - GhidraOptions.DEFAULT_HIGHLIGHT_COLOR); - MarkerSet highlightMarkers = getHighlightMarkers(currentProgram); - fieldPanel.setHighlightColor(color); - if (highlightMarkers != null) { - highlightMarkers.setMarkerColor(color); - } - - color = fieldOptions.getColor(CURSOR_COLOR_OPTIONS_NAME, FOCUSED_CURSOR_COLOR); - fieldPanel.setFocusedCursorColor(color); - - color = fieldOptions.getColor(UNFOCUSED_CURSOR_COLOR_OPTIONS_NAME, UNFOCUSED_CURSOR_COLOR); - fieldPanel.setNonFocusCursorColor(color); - - boolean horizontalScrollingEnabled = - fieldOptions.getBoolean(MOUSE_WHEEL_HORIZONTAL_SCROLLING_OPTIONS_NAME, true); - fieldPanel.setHorizontalScrollingEnabled(horizontalScrollingEnabled); - - cursorHighlightColor = fieldOptions.getColor(GhidraOptions.HIGHLIGHT_CURSOR_LINE_COLOR, - CURRENT_LINE_HIGHLIGHT_COLOR); - - isHighlightCursorLine = fieldOptions.getBoolean(GhidraOptions.HIGHLIGHT_CURSOR_LINE, true); + List listingPanels = allListingPanels(); + List fieldPanels = allFieldPanels(); + initPanelOptions(listingPanels, fieldPanels); } - private void initMiscellaneousOptions() { - // make sure the following options are registered - HelpLocation helpLocation = - new HelpLocation("ShowInstructionInfoPlugin", "Processor_Manual_Options"); - Options options = tool.getOptions(ManualViewerCommandWrappedOption.OPTIONS_CATEGORY_NAME); - options.registerOption(ManualViewerCommandWrappedOption.MANUAL_VIEWER_OPTIONS, - OptionType.CUSTOM_TYPE, - ManualViewerCommandWrappedOption.getDefaultBrowserLoaderOptions(), helpLocation, - "Options for running manual viewer", () -> new ManualViewerCommandEditor()); + private void initPanelOptions(List listingPanels, List fieldPanels) { + ToolOptions fieldOptions = tool.getOptions(GhidraOptions.CATEGORY_BROWSER_FIELDS); + + Color selectionColor = fieldOptions.getColor(GhidraOptions.OPTION_SELECTION_COLOR, + GhidraOptions.DEFAULT_SELECTION_COLOR); + onListingPanels(listingPanels, lp -> lp.setSelectionColor(selectionColor)); + + Color hlColor = fieldOptions.getColor(GhidraOptions.OPTION_HIGHLIGHT_COLOR, + GhidraOptions.DEFAULT_HIGHLIGHT_COLOR); + onListingPanels(listingPanels, lp -> lp.setHighlightColor(hlColor)); + + Color focusedCursorColor = + fieldOptions.getColor(CURSOR_COLOR_OPTIONS_NAME, FOCUSED_CURSOR_COLOR); + onFieldPanels(fieldPanels, fp -> fp.setFocusedCursorColor(focusedCursorColor)); + + Color unfocusedCursorColor = + fieldOptions.getColor(UNFOCUSED_CURSOR_COLOR_OPTIONS_NAME, UNFOCUSED_CURSOR_COLOR); + onFieldPanels(fieldPanels, fp -> fp.setNonFocusCursorColor(unfocusedCursorColor)); + + boolean scrollingEnabled = + fieldOptions.getBoolean(MOUSE_WHEEL_HORIZONTAL_SCROLLING_OPTIONS_NAME, true); + onFieldPanels(fieldPanels, fp -> fp.setHorizontalScrollingEnabled(scrollingEnabled)); + + Color cursorHighlightColor = + fieldOptions.getColor(GhidraOptions.HIGHLIGHT_CURSOR_LINE_COLOR, + CURRENT_LINE_HIGHLIGHT_COLOR); + onListingPanels(listingPanels, lp -> lp.setCursorHighlightColor(cursorHighlightColor)); + + boolean isHighlightCursorLine = + fieldOptions.getBoolean(GhidraOptions.HIGHLIGHT_CURSOR_LINE, true); + onListingPanels(listingPanels, + lp -> lp.setHighlightCursorLineEnabled(isHighlightCursorLine)); } @Override @@ -876,21 +879,4 @@ public abstract class AbstractCodeBrowserPlugin

ex disconnectedProviders.remove(codeViewerProvider); } } - -//================================================================================================== -// Inner Classes -//================================================================================================== - - static class MarkerChangeListener implements ChangeListener { - private FieldPanel fieldPanel; - - MarkerChangeListener(CodeViewerProvider provider) { - this.fieldPanel = provider.getListingPanel().getFieldPanel(); - } - - @Override - public void stateChanged(ChangeEvent e) { - fieldPanel.repaint(); - } - } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/CodeBrowserPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/CodeBrowserPlugin.java index 3d42d9a5b0..9cf075cefd 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/CodeBrowserPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/CodeBrowserPlugin.java @@ -97,22 +97,21 @@ public class CodeBrowserPlugin extends AbstractCodeBrowserPlugin marginProviders = lp.getMarginProviders(); - for (MarginProvider marginProvider : marginProviders) { + List marginProviders = lp.getMarginProviders(); + for (ListingMarginProvider marginProvider : marginProviders) { JComponent c = marginProvider.getComponent(); if (c == source) { MarkerLocation loc = marginProvider.getMarkerLocation(event.getX(), event.getY()); @@ -337,8 +342,8 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter } } } - List overviewProviders = lp.getOverviewProviders(); - for (OverviewProvider overviewProvider : overviewProviders) { + List overviewProviders = lp.getOverviewProviders(); + for (ListingOverviewProvider overviewProvider : overviewProviders) { JComponent c = overviewProvider.getComponent(); if (c == source) { return source; @@ -444,6 +449,10 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter contextChanged(); } + void clearMarkers(Program p) { + listingPanel.clearMarkers(p); + } + protected void updateTitle() { String subTitle = program == null ? "" : ' ' + program.getDomainFile().getName(); String newTitle = TITLE + subTitle; @@ -657,6 +666,7 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter } } + // events coming from the ListingPanel @Override public void programLocationChanged(ProgramLocation loc, EventTrigger trigger) { if (plugin.isDisposed()) { @@ -665,7 +675,7 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter if (!loc.equals(currentLocation)) { codeViewerClipboardProvider.setLocation(loc); currentLocation = loc; - plugin.locationChanged(this, loc); + plugin.broadcastLocationChanged(this, loc); contextChanged(); } } @@ -696,7 +706,7 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter currentSelection = selection; codeViewerClipboardProvider.setSelection(currentSelection); listingPanel.setSelection(currentSelection); - plugin.selectionChanged(this, currentSelection); + plugin.broadcastSelectionChanged(this, currentSelection); contextChanged(); updateSubTitle(); } @@ -792,7 +802,7 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter private void doSetHighlight(ProgramSelection highlight) { listingPanel.setHighlight(highlight); currentHighlight = highlight; - plugin.highlightChanged(this, highlight); + plugin.broadcastHighlightChanged(this, highlight); contextChanged(); } @@ -1161,45 +1171,51 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter listingPanel.removeDisplayListener(listener); } - private synchronized void createFocusingMouseListener() { - if (focusingMouseListener == null) { - focusingMouseListener = new FocusingMouseListener(); - } - } - - public void addOverviewProvider(OverviewProvider overviewProvider) { - createFocusingMouseListener(); - JComponent component = overviewProvider.getComponent(); - - // just in case we get repeated calls - component.removeMouseListener(focusingMouseListener); - component.addMouseListener(focusingMouseListener); + public void addOverviewProvider(ListingOverviewProvider overviewProvider) { overviewProvider.setNavigatable(this); getListingPanel().addOverviewProvider(overviewProvider); } - public void addMarginProvider(MarginProvider marginProvider) { - createFocusingMouseListener(); - JComponent component = marginProvider.getComponent(); - - // just in case we get repeated calls - component.removeMouseListener(focusingMouseListener); - component.addMouseListener(focusingMouseListener); - getListingPanel().addMarginProvider(marginProvider); - } - - public void removeOverviewProvider(OverviewProvider overviewProvider) { - JComponent component = overviewProvider.getComponent(); - component.removeMouseListener(focusingMouseListener); + public void removeOverviewProvider(ListingOverviewProvider overviewProvider) { getListingPanel().removeOverviewProvider(overviewProvider); } - public void removeMarginProvider(MarginProvider marginProvider) { - JComponent component = marginProvider.getComponent(); - component.removeMouseListener(focusingMouseListener); + public void addMarginProvider(ListingMarginProvider marginProvider) { + getListingPanel().addMarginProvider(marginProvider); + } + + public void removeMarginProvider(ListingMarginProvider marginProvider) { getListingPanel().removeMarginProvider(marginProvider); } + public void addMarginService(ListingMarginProviderService service) { + getListingPanel().addMarginService(service, isConnected()); + } + + public void addOverviewService(ListingOverviewProviderService service) { + getListingPanel().addOverviewService(service, this, isConnected()); + } + + public void removeOverviewService(ListingOverviewProviderService service) { + getListingPanel().removeOverviewService(service); + } + + public void removeMarginService(ListingMarginProviderService service) { + getListingPanel().removeMarginService(service); + } + + public void addHoverService(ListingHoverService hoverService) { + getListingPanel().addHoverService(hoverService); + } + + public void removeHoverService(ListingHoverService hoverService) { + getListingPanel().removeHoverService(hoverService); + + if (otherPanel != null) { + otherPanel.removeHoverService(hoverService); + } + } + //================================================================================================== // Inner Classes //================================================================================================== @@ -1280,11 +1296,4 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter return list.toArray(new Highlight[list.size()]); } } - - private class FocusingMouseListener extends MouseAdapter { - @Override - public void mousePressed(MouseEvent e) { - getListingPanel().getFieldPanel().requestFocus(); - } - } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/flowarrow/ConditionalFlowArrow.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/flowarrow/ConditionalFlowArrow.java index 33a34b055c..a9c611347a 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/flowarrow/ConditionalFlowArrow.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/flowarrow/ConditionalFlowArrow.java @@ -1,13 +1,12 @@ /* ### * IP: GHIDRA - * REVIEWED: YES * * 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. @@ -16,11 +15,11 @@ */ package ghidra.app.plugin.core.flowarrow; +import java.awt.*; + import ghidra.program.model.address.Address; import ghidra.program.model.symbol.RefType; -import java.awt.*; - class ConditionalFlowArrow extends FlowArrow { private static final Stroke CONDITIONAL_STROKE = new BasicStroke(1, BasicStroke.CAP_SQUARE, @@ -28,9 +27,9 @@ class ConditionalFlowArrow extends FlowArrow { private static final Stroke NORMAL_ACTIVE_STROKE = new BasicStroke(2, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER); - ConditionalFlowArrow(FlowArrowPlugin plugin, Component canvas, Address start, Address end, - RefType referenceType) { - super(plugin, canvas, start, end, referenceType); + ConditionalFlowArrow(FlowArrowMarginProvider provider, Component canvas, Address start, + Address end, RefType referenceType) { + super(provider, canvas, start, end, referenceType); } @Override diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/flowarrow/DefaultFlowArrow.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/flowarrow/DefaultFlowArrow.java index 65c0e64c31..ef67ef7800 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/flowarrow/DefaultFlowArrow.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/flowarrow/DefaultFlowArrow.java @@ -1,13 +1,12 @@ /* ### * IP: GHIDRA - * REVIEWED: YES * * 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. @@ -16,11 +15,11 @@ */ package ghidra.app.plugin.core.flowarrow; +import java.awt.*; + import ghidra.program.model.address.Address; import ghidra.program.model.symbol.RefType; -import java.awt.*; - class DefaultFlowArrow extends FlowArrow { private static final Stroke NORMAL_STROKE = new BasicStroke(1, BasicStroke.CAP_SQUARE, @@ -28,9 +27,9 @@ class DefaultFlowArrow extends FlowArrow { private static final Stroke NORMAL_ACTIVE_STROKE = new BasicStroke(2, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER); - DefaultFlowArrow(FlowArrowPlugin plugin, Component canvas, Address start, Address end, + DefaultFlowArrow(FlowArrowMarginProvider provider, Component canvas, Address start, Address end, RefType referenceType) { - super(plugin, canvas, start, end, referenceType); + super(provider, canvas, start, end, referenceType); } @Override diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/flowarrow/FallthroughFlowArrow.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/flowarrow/FallthroughFlowArrow.java index 3640d10121..0e0dcb5c3b 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/flowarrow/FallthroughFlowArrow.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/flowarrow/FallthroughFlowArrow.java @@ -1,13 +1,12 @@ /* ### * IP: GHIDRA - * REVIEWED: YES * * 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. @@ -16,11 +15,11 @@ */ package ghidra.app.plugin.core.flowarrow; +import java.awt.*; + import ghidra.program.model.address.Address; import ghidra.program.model.symbol.RefType; -import java.awt.*; - class FallthroughFlowArrow extends FlowArrow { private static final Stroke FALLTHROUGH_STROKE = new BasicStroke(1, BasicStroke.CAP_SQUARE, @@ -28,9 +27,9 @@ class FallthroughFlowArrow extends FlowArrow { private static final Stroke FALLTHROUGH_ACTIVE_STROKE = new BasicStroke(2, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER, 10, new float[] { 8, 3, 2, 3 }, 0); - FallthroughFlowArrow(FlowArrowPlugin plugin, Component canvas, Address start, Address end, - RefType referenceType) { - super(plugin, canvas, start, end, referenceType); + FallthroughFlowArrow(FlowArrowMarginProvider provider, Component canvas, Address start, + Address end, RefType referenceType) { + super(provider, canvas, start, end, referenceType); } @Override diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/flowarrow/FlowArrow.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/flowarrow/FlowArrow.java index ed72e4bdae..1d7a5ea523 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/flowarrow/FlowArrow.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/flowarrow/FlowArrow.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. @@ -15,6 +15,8 @@ */ package ghidra.app.plugin.core.flowarrow; +import static ghidra.util.HTMLUtilities.*; + import java.awt.*; import java.awt.geom.PathIterator; import java.awt.geom.Rectangle2D; @@ -23,7 +25,6 @@ import java.util.List; import ghidra.program.model.address.*; import ghidra.program.model.symbol.RefType; -import ghidra.util.HTMLUtilities; import ghidra.util.exception.AssertException; abstract class FlowArrow { @@ -36,15 +37,16 @@ abstract class FlowArrow { Address start; Address end; - AddressSet addressSet; - int depth = -1; + AddressSet addresses; + int column = -1; RefType refType; + private int maxColumn; private boolean isUp; boolean active; boolean selected; - private FlowArrowPlugin plugin; + private FlowArrowMarginProvider provider; private Component canvas; protected Shape arrowBody; protected Shape arrowHead; @@ -52,15 +54,16 @@ abstract class FlowArrow { /** The shape of the arrow body, but with added size */ private List clickableShapes = new ArrayList<>(); - FlowArrow(FlowArrowPlugin plugin, Component canvas, Address start, Address end, + FlowArrow(FlowArrowMarginProvider provider, Component canvas, Address start, Address end, RefType referenceType) { - this.plugin = plugin; + this.provider = provider; this.canvas = canvas; this.start = start; this.end = end; this.refType = referenceType; - this.addressSet = new AddressSet(new AddressRangeImpl(start, end)); - isUp = start.compareTo(end) > 0; + this.maxColumn = provider.getMaxColumn(); + this.addresses = new AddressSet(new AddressRangeImpl(start, end)); + this.isUp = start.compareTo(end) > 0; } abstract Stroke getSelectedStroke(); @@ -101,7 +104,7 @@ abstract class FlowArrow { g2.setStroke(oldStroke); } - /** True if this arrow points up instead of down */ + /** {@return true if this arrow points up instead of down} */ boolean isUp() { return isUp; } @@ -242,20 +245,20 @@ abstract class FlowArrow { int displayWidth = canvas.getWidth();// - FlowArrowPlugin.LEFT_OFFSET; int lineWidth = calculateLineWidth(displayWidth); - arrowBody = FlowArrowShapeFactory.createArrowBody(plugin, this, displayWidth, displayHeight, - lineWidth); + arrowBody = + FlowArrowShapeFactory.createArrowBody(provider, this, displayWidth, displayHeight, + lineWidth); - arrowHead = FlowArrowShapeFactory.createArrowHead(plugin, this, displayWidth, displayHeight, - lineWidth); + arrowHead = + FlowArrowShapeFactory.createArrowHead(provider, this, displayWidth, displayHeight, + lineWidth); } private int calculateLineWidth(int displayWidth) { // Crunch or stretch spacing depending upon width and maximum depth int lineWidth = DEFAULT_LINE_SPACING; - int maxDepth = plugin.getMaxDepth(); - - if (maxDepth >= 0) { - int availabeWidth = displayWidth - FlowArrowPlugin.LEFT_OFFSET; + if (maxColumn >= 0) { + int availabeWidth = displayWidth - FlowArrowMarginProvider.LEFT_OFFSET; lineWidth = (int) (availabeWidth * ARROW_SPACING_RATIO); } if (lineWidth < MIN_LINE_SPACING) { @@ -321,9 +324,17 @@ abstract class FlowArrow { } public String getDisplayString() { - return "
start" + HTMLUtilities.escapeHTML(start.toString()) + - "
end" + HTMLUtilities.escapeHTML(end.toString()) + - "
ref type" + refType + "
"; + //@formatter:off + return """ + + + + +
start%s
end%s
ref type%s
+ """.formatted(escapeHTML(start.toString()), + escapeHTML(end.toString()), + refType).trim(); + //@formatter:on } @Override diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/flowarrow/FlowArrowMarginProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/flowarrow/FlowArrowMarginProvider.java new file mode 100644 index 0000000000..ddbbaa9ab2 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/flowarrow/FlowArrowMarginProvider.java @@ -0,0 +1,737 @@ +/* ### + * 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.flowarrow; + +import java.awt.Color; +import java.awt.KeyboardFocusManager; +import java.awt.event.*; +import java.util.*; + +import javax.swing.JComponent; + +import docking.widgets.fieldpanel.FieldPanel; +import ghidra.app.util.viewer.field.ListingColors; +import ghidra.app.util.viewer.field.ListingColors.FlowArrowColors; +import ghidra.app.util.viewer.listingpanel.*; +import ghidra.app.util.viewer.util.AddressIndexMap; +import ghidra.program.model.address.*; +import ghidra.program.model.listing.*; +import ghidra.program.model.mem.Memory; +import ghidra.program.model.symbol.*; +import ghidra.program.util.MarkerLocation; +import ghidra.program.util.ProgramLocation; +import ghidra.util.UniversalID; + +class FlowArrowMarginProvider implements ListingMarginProvider { + + static final int LEFT_OFFSET = 3; + private static final int MAX_DEPTH = 16; + private static final int MAX_REFSTO_SHOW = 10; + + /** Start address to the index of the layout for that start address */ + private Map startAddressToPixel = new HashMap<>(); + + /** End address to the index of the layout for that end address */ + private Map endAddressToPixel = new HashMap<>(); + + private FlowArrowPlugin plugin; + private ListingPanel listingPanel; + private Program program; + private Address currentAddr; + private FlowArrowPanel flowArrowPanel; + + /** The column furthers away from the listing, to the left */ + private int maxColumn; + private boolean isShowing = true; + private boolean validState = false; + + /** On-screen layouts and their start/end addresses */ + private VerticalPixelAddressMap layoutToPixel; + private Address screenTop; + private Address screenBottom; + + /** + * We keep arrows in 3 sets: all arrows, selected arrows, and active arrows. + * Further, we rebuild the full set of arrows as the screen moves. However, the selected and + * active arrows do not get cleared when we move the screen. This allows us to keep painting + * selected arrows as the screen changes. The selected arrows are changed by user clicking. The + * active arrows are changed by program location updates. + */ + private List flowArrows = new ArrayList<>(); + + /** Arrows manually clicked by the user */ + private Set selectedArrows = new HashSet<>(); + + /** Those arrows that start at the current address */ + private Set activeArrows = new HashSet<>(); + + FlowArrowMarginProvider(FlowArrowPlugin plugin) { + + this.plugin = plugin; + + flowArrowPanel = new FlowArrowPanel(this); + flowArrowPanel.addComponentListener(new ComponentAdapter() { + @Override + public void componentResized(ComponentEvent e) { + boolean previousState = isShowing; + isShowing = flowArrowPanel.getWidth() > LEFT_OFFSET; + if (isShowing && !previousState) { + updateAndRepaint(); + } + } + + @Override + public void componentShown(ComponentEvent e) { + boolean previousState = isShowing; + isShowing = flowArrowPanel.getWidth() > LEFT_OFFSET; + if (isShowing && !previousState) { + updateAndRepaint(); + } + } + + @Override + public void componentHidden(ComponentEvent e) { + isShowing = false; + } + }); + + flowArrowPanel.setBackground(ListingColors.BACKGROUND); + flowArrowPanel.setForeground(FlowArrowColors.INACTIVE); + flowArrowPanel.setHighlightColor(FlowArrowColors.ACTIVE); + flowArrowPanel.setSelectedColor(FlowArrowColors.SELECTED); + + } + + @Override + public void setOwnerId(UniversalID ownerId) { + // we don't need the owner id + } + + @Override + public JComponent getComponent() { + return flowArrowPanel; + } + + @Override + public MarkerLocation getMarkerLocation(int x, int y) { + return null; + } + + @Override + public boolean isResizeable() { + return true; + } + + @Override + public void setLocation(ProgramLocation location) { + + currentAddr = location.getAddress(); + clearActiveArrows(); + assignActiveArrows(); + flowArrowPanel.repaint(); + } + + @Override + public void screenDataChanged(ListingPanel listing, AddressIndexMap addrMap, + VerticalPixelAddressMap pixMap) { + + this.listingPanel = listing; + Program currentProgram = listing.getProgram(); + if (this.program != currentProgram) { + clearAllArrows(); + } + + this.program = currentProgram; + this.layoutToPixel = pixMap; + validateState(); + updateAndRepaint(); + } + + Address getCurrentAddress() { + return currentAddr; + } + + Address getScreenBottomAddr() { + return screenBottom; + } + + int getMaxColumn() { + return maxColumn; + } + + boolean isOnScreen(Address address) { + if (screenBottom == null || screenTop == null) { + return true; // shouldn't happen + } + + if (address.compareTo(screenTop) < 0) { + // above the top of the screen + return false; + } + + if (address.compareTo(screenBottom) > 0) { + // below the bottom of the screen + return false; + } + return true; + } + + boolean isOffscreen(FlowArrow arrow) { + if (screenBottom == null || screenTop == null) { + return true; // shouldn't happen + } + + if (arrow.start.compareTo(screenTop) < 0 && arrow.end.compareTo(screenTop) < 0) { + // start and end are above the top of the screen + return true; + } + + if (arrow.start.compareTo(screenBottom) > 0 && arrow.end.compareTo(screenBottom) > 0) { + // start and end are below the bottom of the screen + return true; + } + return false; + } + + boolean isBelowScreen(Address address) { + if (screenBottom == null || screenTop == null) { + return true; // shouldn't happen + } + + return address.compareTo(screenBottom) > 0; + } + + /* The y value of the start of the layout at the given address. */ + Integer getStartPos(Address addr) { + return startAddressToPixel.get(addr); + } + + /* The y value of the end of the layout at the given address. */ + Integer getEndPos(Address addr) { + return endAddressToPixel.get(addr); + } + + void setArrowSelected(FlowArrow arrow, boolean selected) { + if (selected) { + selectedArrows.add(arrow); + } + else { + selectedArrows.remove(arrow); + } + } + + Iterator getSelectedFlowArrows() { + return selectedArrows.iterator(); + } + + Iterator getFlowArrowIterator() { + return flowArrows.iterator(); + } + + /* Those arrows starting at the current address */ + Iterator getActiveArrows() { + return activeArrows.iterator(); + } + + private void resetSelectedArrows() { + for (FlowArrow arrow : selectedArrows) { + arrow.resetShape(); + } + } + + private void clearAllArrows() { + flowArrows.clear(); + activeArrows.clear(); + selectedArrows.clear(); + } + + private void clearActiveArrows() { + for (FlowArrow f : activeArrows) { + f.active = false; + } + activeArrows.clear(); + } + + private void resetActiveArrows() { + for (FlowArrow arrow : activeArrows) { + arrow.resetShape(); + } + } + + private void assignActiveArrows() { + + if (!activeArrows.isEmpty()) { + resetActiveArrows(); + return; // don't overwrite existing values + } + + if (currentAddr == null) { + return; + } + + for (FlowArrow arrow : flowArrows) { + if (currentAddr.equals(arrow.start)) { + arrow.active = true; + activeArrows.add(arrow); + } + } + } + + private void mapArrowsByEndpoints(Map> arrowsByStart, + Map> arrowsByEnd) { + + for (FlowArrow arrow : flowArrows) { + arrowsByStart.computeIfAbsent(arrow.start, f -> new ArrayList<>()).add(arrow); + arrowsByEnd.computeIfAbsent(arrow.end, f -> new ArrayList<>()).add(arrow); + } + } + + private List groupArrowsBySharedEndpoints() { + + // not sure this is still needed; keeping for posterity + Collections.sort(flowArrows, (a1, a2) -> (a1).end.compareTo((a2).end)); + + Map> arrowsByStart = new HashMap<>(); + Map> arrowsByEnd = new HashMap<>(); + mapArrowsByEndpoints(arrowsByStart, arrowsByEnd); + + List groups = new ArrayList<>(); + + Set unprocessed = new HashSet<>(flowArrows); + for (FlowArrow arrow : flowArrows) { + + if (!unprocessed.contains(arrow)) { + continue; // already grouped + } + + // put all arrows in this group that share a start or end, as they will all occupy the + // same column + ArrowGroup group = new ArrowGroup(); + List starts = arrowsByStart.get(arrow.start); + for (FlowArrow f : starts) { + group.add(f); + unprocessed.remove(arrow); + } + List ends = arrowsByEnd.get(arrow.end); + for (FlowArrow f : ends) { + group.add(f); + unprocessed.remove(arrow); + } + + group.add(arrow); + unprocessed.remove(arrow); + + groups.add(group); + } + + // Sort the groups so that the lowest end address is first. I'm assuming that we wish to + // start at the top of the screen and paint incoming arrows first, closest to the Listing. + groups.sort((g1, g2) -> g1.getSortAddress().compareTo(g2.getSortAddress())); + + return groups; + } + + /** + * Assigns all arrow columns (horizontal positioning). All arrows that share a start and or + * end point will all share the same column. This reduce clutter by having them all share the + * same vertical segment. + *

+ * As the groups are assigned columns, each arrow in the group is updated. When this method is + * finished, all arrows will have a column assigned. + */ + private void assignArrowColumns() { + + Collections.sort(flowArrows, (a1, a2) -> (a1).end.compareTo((a2).end)); + + // assign groups and then assign columns to the groups + int count = 0; + List groups = groupArrowsBySharedEndpoints(); + for (ArrowGroup group : groups) { + // assignGroupColumn(group, groups); + int column = Math.min(MAX_DEPTH, count++); + group.setColumn(column); + maxColumn = column; + } + } + + private List getFlowArrowsForScreenInstructions(AddressSetView screenAddresses) { + + // A cache of arrows encountered going off the screen, above or below. For any given arrow + // start, we wish to only show one arrow for each of three references flow types. The cache + // will record when we have seen each of the types so that we can skip adding arrows for + // that type from that address again. + OffscreenArrowsFlow offscreenArrows = new OffscreenArrowsFlow(); + + Set results = new HashSet<>(); + Listing listing = program.getListing(); + InstructionIterator it = listing.getInstructions(screenAddresses, true); + for (Instruction inst : it) { + + // incoming + ReferenceManager refManager = program.getReferenceManager(); + int refCount = refManager.getReferenceCountTo(inst.getMinAddress()); + if (refCount < MAX_REFSTO_SHOW) { + for (Reference ref : inst.getReferenceIteratorTo()) { + createFlowArrow(results, offscreenArrows, ref); + } + } + + // clearing the cache resets the check for duplicates, keeping incoming and outgoing + // references separate + offscreenArrows.clear(); + + // outgoing + for (Reference ref : inst.getReferencesFrom()) { + createFlowArrow(results, offscreenArrows, ref); + } + } + + return new ArrayList<>(results); + } + + private void createFlowArrow(Set results, OffscreenArrowsFlow offscreenArrows, + Reference ref) { + RefType type = ref.getReferenceType(); + if (!(type.isJump() || type.isFallthrough())) { + return; + } + + FlowArrow arrow = doCreateFlowArrow(ref); + if (arrow == null) { + return; + } + + if (results.contains(arrow)) { + return; + } + + if (offscreenArrows.exists(arrow)) { + // We have already seen an offscreen arrow coming from the same start address in the + // same direction for this arrow's flow type. No need to add another one. + return; + } + + results.add(arrow); + updateArrowSets(arrow); + } + + /** + * Unusual Code: We keep arrows in 3 sets: all arrows, selected arrows, and active arrows. + * Further, we rebuild arrows as the screen moves, causing the x coordinate to change as arrows + * that are no longer on the screen are removed and as new arrows are added. We want to make + * sure that we don't end up with an arrow in the selected/active sets that are the same as the + * one in the 'all' set, but with a different width. This causes both arrows to become + * visible--basically, the selected arrows can become stale as their width changes. This code is + * meant to address this out-of-sync behavior. + * + * @param arrow the updated form of the arrow + */ + private void updateArrowSets(FlowArrow arrow) { + if (selectedArrows.remove(arrow)) { + arrow.selected = true; + selectedArrows.add(arrow); + } + + if (activeArrows.remove(arrow)) { + arrow.active = true; + activeArrows.add(arrow); + } + } + + private FlowArrow doCreateFlowArrow(Reference ref) { + Address start = toLayoutAddress(ref.getFromAddress()); + Address end = toLayoutAddress(ref.getToAddress()); + if (start == null || end == null) { + return null; + } + + if (!start.hasSameAddressSpace(end)) { + return null; // is this right?? + } + + Memory memory = program.getMemory(); + if (!memory.contains(end)) { + return null; // bad disassembly + } + + RefType refType = ref.getReferenceType(); + if (refType.isFallthrough()) { + return new FallthroughFlowArrow(this, flowArrowPanel, start, end, refType); + } + else if (refType.isConditional()) { + return new ConditionalFlowArrow(this, flowArrowPanel, start, end, refType); + } + + return new DefaultFlowArrow(this, flowArrowPanel, start, end, refType); + } + + private void validateState() { + validState = true; + if (program == null || layoutToPixel == null) { + validState = false; + return; + } + + int n = layoutToPixel.getNumLayouts(); + validState = n != 0; + } + + void updateAndRepaint() { + update(); + flowArrowPanel.repaint(); + } + + private void update() { + if (!isShowing || !validState) { + return; + } + + int n = layoutToPixel.getNumLayouts(); + if (n == 0) { + return; + } + + Address startAddress = layoutToPixel.getLayoutAddress(0); + Address endAddress = layoutToPixel.getLayoutAddress(n - 1); + + screenTop = startAddress; + screenBottom = endAddress; + flowArrows.clear(); + startAddressToPixel.clear(); + endAddressToPixel.clear(); + maxColumn = 0; + + resetSelectedArrows(); + + if (screenTop == null || screenBottom == null || n > 500) { + return; + } + + // find all addresses that are on the screen and compute y co-ordinate + for (int layout = 0; layout < n; layout++) { + Address addr = layoutToPixel.getLayoutAddress(layout); + if (addr != null) { + startAddressToPixel.put(addr, layoutToPixel.getBeginPosition(layout)); + endAddressToPixel.put(addr, layoutToPixel.getEndPosition(layout)); + } + } + + AddressSetView flowSet = layoutToPixel.getAddressSet(); + flowArrows = getFlowArrowsForScreenInstructions(flowSet); + + assignArrowColumns(); + + assignActiveArrows(); + } + + private Address toLayoutAddress(Address addr) { + Object pixel = startAddressToPixel.get(addr); + if (pixel != null || addr.compareTo(screenTop) < 0 || + addr.compareTo(screenBottom) > 0) { + return addr; + } + + int n = layoutToPixel.getNumLayouts(); + for (int i = 0; i < n; i++) { + Address layoutAddr = layoutToPixel.getLayoutAddress(i); + Address endLayoutAddr = layoutToPixel.getLayoutEndAddress(i); + if (layoutAddr == null || endLayoutAddr == null) { + continue; + } + + if (layoutAddr.compareTo(addr) >= 0) { + // have gone past the address, then there is a gap + return null; + } + + // we are between the start and end; inside the layout + if (addr.compareTo(endLayoutAddr) <= 0) { + return layoutAddr; + } + } + + return addr; // should never get here + } + + void setBackground(Color c) { + flowArrowPanel.setBackground(c); + } + + void setForeground(Color c) { + flowArrowPanel.setForeground(c); + } + + void setHighlightColor(Color c) { + flowArrowPanel.setHighlightColor(c); + } + + @Override + public void dispose() { + plugin.remove(this); + program = null; + layoutToPixel = null; + startAddressToPixel.clear(); + endAddressToPixel.clear(); + clearAllArrows(); + flowArrowPanel.dispose(); + } + + void goTo(Address address) { + ProgramLocation location = new ProgramLocation(program, address); + listingPanel.goTo(location); + } + + void scrollTo(Address address) { + ProgramLocation location = new ProgramLocation(program, address); + listingPanel.scrollTo(location); + } + + Address getLastAddressOnScreen(Address end, boolean up) { + if (up) { + return screenTop; + } + return screenBottom; + } + + public void forwardMouseEventToListing(MouseWheelEvent e) { + FieldPanel fieldPanel = listingPanel.getFieldPanel(); + KeyboardFocusManager kfm = KeyboardFocusManager.getCurrentKeyboardFocusManager(); + kfm.redispatchEvent(fieldPanel, e); + } + +//================================================================================================== +// Inner Classes +//================================================================================================== + + /** + * An arrow group is all arrows that will be in the same column. The column for an arrow + * will be based on the first group to assign a column. + */ + private class ArrowGroup { + + private Set arrows = new HashSet<>(); + private AddressSet addrs = new AddressSet(); + private Address lowestEndAddress; + + @SuppressWarnings("unused") // for debug + private int column; + + Address getSortAddress() { + return lowestEndAddress; + } + + void setColumn(int column) { + this.column = column; + + for (FlowArrow f : arrows) { + f.column = column; + } + } + + void add(FlowArrow f) { + if (lowestEndAddress == null) { + lowestEndAddress = f.end; + } + else if (lowestEndAddress.compareTo(f.end) >= 0) { + lowestEndAddress = f.end; + } + + arrows.add(f); + addrs.add(f.addresses); + } + } + + /** + * A cache of all arrows that start at a given address. This is only used while building the + * set of arrows. This tracks arrow usage from the start address to limit the number of arrows + * that go offscreen. We allow 1 offscreen arrow above and below for each of the three flow + * types: conditional, fallthrough and other. This is used to prevent too many arrows from + * cluttering the screen when there are many references starting at the same address. + */ + private class OffscreenArrowsFlow { + + private Map flowsAbove = new HashMap<>(); + private Map flowsBelow = new HashMap<>(); + + /** + * Tracks the given arrow and records whether we have seen an arrow at this start address, + * going offscreen in the same direction with the same flow category. + * + * @param arrow the arrow + * @return true if we already have a representative arrow + */ + boolean exists(FlowArrow arrow) { + + boolean isAbove = arrow.end.compareTo(screenTop) < 0; + boolean isBelow = arrow.end.compareTo(screenBottom) > 0; + if (!(isAbove || isBelow)) { + return false; // on-screen + } + + OffScreenFlow flow; + if (isAbove) { + flow = flowsAbove.get(arrow.start); + if (flow == null) { + flow = new OffScreenFlow(); + flowsAbove.put(arrow.start, flow); + } + } + else { // isBelow + flow = flowsBelow.get(arrow.start); + if (flow == null) { + flow = new OffScreenFlow(); + flowsBelow.put(arrow.start, flow); + } + } + + // sets the flow type and returns true if that type was already set, signalling that we + // have seen this an offscreen arrow with this flow type coming from this address in the + // up or down direction + boolean alreadyHasArrow = flow.setFlow(arrow.refType); + return alreadyHasArrow; + } + + void clear() { + flowsAbove.clear(); + flowsBelow.clear(); + } + + private class OffScreenFlow { + private boolean conditional; + private boolean fallthrough; + private boolean other; + + boolean setFlow(RefType type) { + boolean wasSet = false; + if (type.isConditional()) { + wasSet = conditional; + conditional = true; + } + else if (type.isFallthrough()) { + wasSet = fallthrough; + fallthrough = true; + } + else { + wasSet = other; + other = true; + } + + return wasSet; + } + } + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/flowarrow/FlowArrowPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/flowarrow/FlowArrowPanel.java index 9214a58805..bbe71a0e27 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/flowarrow/FlowArrowPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/flowarrow/FlowArrowPanel.java @@ -36,7 +36,7 @@ class FlowArrowPanel extends JPanel { private Cursor clickCursor; private Cursor defaultCursor; - private FlowArrowPlugin plugin; + private FlowArrowMarginProvider provider; private Color foregroundColor; private Color highlightColor; private Color selectedColor; @@ -44,8 +44,8 @@ class FlowArrowPanel extends JPanel { private SwingUpdateManager mouseClickUpdater; private Point pendingMouseClickPoint; - FlowArrowPanel(FlowArrowPlugin p) { - this.plugin = p; + FlowArrowPanel(FlowArrowMarginProvider provider) { + this.provider = provider; setMinimumSize(new Dimension(0, 0)); setPreferredSize(new Dimension(32, 1)); @@ -110,18 +110,18 @@ class FlowArrowPanel extends JPanel { } private FlowArrow getArrow(Point p) { - FlowArrow arrow = getArrow(p, plugin.getFlowArrowIterator()); + FlowArrow arrow = getArrow(p, provider.getFlowArrowIterator()); if (arrow != null) { return arrow; } // try the arrows that hang around a bit - arrow = getArrow(p, plugin.getSelectedFlowArrows()); + arrow = getArrow(p, provider.getSelectedFlowArrows()); if (arrow != null) { return arrow; } - return getArrow(p, plugin.getActiveArrows()); + return getArrow(p, provider.getActiveArrows()); } private FlowArrow getArrow(Point p, Iterator it) { @@ -139,26 +139,24 @@ class FlowArrowPanel extends JPanel { return; } -// TODO to do this, we should probably have another concept of 'navigated'/'current' as to not -// confuse the concept of selecting arrows // select any arrow we double-click arrow.selected = true; - plugin.setArrowSelected(arrow, true); + provider.setArrowSelected(arrow, true); Address end = arrow.end; - if (end.equals(plugin.getCurrentAddress())) { + if (end.equals(provider.getCurrentAddress())) { // go back the other direction end = arrow.start; } - if (plugin.isOnScreen(end)) { + if (provider.isOnScreen(end)) { // don't animate arrows completely on screen - plugin.goTo(end); + provider.goTo(end); return; } // Start the animation at the edge of the screen - Address start = plugin.getLastAddressOnScreen(end, arrow.isUp()); + Address start = provider.getLastAddressOnScreen(end, arrow.isUp()); ScrollingCallback callback = new ScrollingCallback(start, end); Animator animator = AnimationUtils.executeSwingAnimationCallback(callback); @@ -169,7 +167,7 @@ class FlowArrowPanel extends JPanel { FlowArrow arrow = getArrow(point); if (arrow != null) { arrow.selected = !arrow.selected; // toggle - plugin.setArrowSelected(arrow, arrow.selected); + provider.setArrowSelected(arrow, arrow.selected); repaint(); return; // only select one line at a time } @@ -179,13 +177,13 @@ class FlowArrowPanel extends JPanel { public void setBounds(int x, int y, int width, int height) { // note: this gets called as the user drags the divider pane super.setBounds(x, y, width, height); - plugin.updateAndRepaint(); + provider.updateAndRepaint(); } @Override public String getToolTipText(MouseEvent e) { Point point = e.getPoint(); - Iterator it = plugin.getFlowArrowIterator(); + Iterator it = provider.getFlowArrowIterator(); while (it.hasNext()) { FlowArrow arrow = it.next(); if (arrow.intersects(point)) { @@ -223,7 +221,7 @@ class FlowArrowPanel extends JPanel { super.paintComponent(g); - Address currentAddress = plugin.getCurrentAddress(); + Address currentAddress = provider.getCurrentAddress(); if (currentAddress == null) { return; } @@ -235,7 +233,7 @@ class FlowArrowPanel extends JPanel { // // Non-selected arrows // - Iterator it = plugin.getFlowArrowIterator(); + Iterator it = provider.getFlowArrowIterator(); while (it.hasNext()) { FlowArrow arrow = it.next(); if (arrow.active || arrow.selected) { @@ -251,7 +249,7 @@ class FlowArrowPanel extends JPanel { // // Active arrows--those at the selected address; paint on top of normal arrows // - it = plugin.getActiveArrows(); + it = provider.getActiveArrows(); while (it.hasNext()) { FlowArrow arrow = it.next(); if (arrow.selected) { @@ -266,7 +264,7 @@ class FlowArrowPanel extends JPanel { // Selected arrows // fgColor = selectedColor; - it = plugin.getSelectedFlowArrows(); + it = provider.getSelectedFlowArrows(); while (it.hasNext()) { FlowArrow arrow = it.next(); paintJump(g2, arrow, fgColor); @@ -274,7 +272,7 @@ class FlowArrowPanel extends JPanel { } private void paintJump(Graphics2D g2, FlowArrow arrow, Color fgColor) { - if (plugin.isOffscreen(arrow)) { + if (provider.isOffscreen(arrow)) { return; // don't paint linger arrows, such as selected or active arrows } @@ -329,19 +327,19 @@ class FlowArrowPanel extends JPanel { // System.err.printf("%1.3f%%\t", (percentComplete * 100)); // System.err.println("scrolling to: " + current); - plugin.scrollTo(current); + provider.scrollTo(current); lastAddress = current; // let's us avoid multiple duplicate requests } @Override public void done() { // set the final position -// TODO This happens after the animation is finished, which is jarring. If we want this centered, -// then we need an entirely different way of animating the transition so that the centering -// is part of the animation. +// Note: This happens after the animation is finished, which is jarring. If we want this centered, +// then we need an entirely different way of animating the transition so that the centering +// is part of the animation. // plugin.scrollToCenter(end); - plugin.goTo(end); + provider.goTo(end); } void setAnimator(Animator animator) { @@ -392,7 +390,7 @@ class FlowArrowPanel extends JPanel { private class FlowArrowPanelMouseWheelListener implements MouseWheelListener { @Override public void mouseWheelMoved(MouseWheelEvent e) { - plugin.forwardMouseEventToListing(e); + provider.forwardMouseEventToListing(e); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/flowarrow/FlowArrowPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/flowarrow/FlowArrowPlugin.java index 26af74e77a..f164e9ff10 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/flowarrow/FlowArrowPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/flowarrow/FlowArrowPlugin.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. @@ -15,34 +15,18 @@ */ package ghidra.app.plugin.core.flowarrow; -import java.awt.*; -import java.awt.event.*; import java.util.*; -import java.util.List; -import javax.swing.JComponent; - -import docking.widgets.fieldpanel.FieldPanel; import ghidra.GhidraOptions; import ghidra.app.CorePluginPackage; import ghidra.app.events.*; import ghidra.app.plugin.PluginCategoryNames; -import ghidra.app.services.CodeViewerService; -import ghidra.app.util.viewer.field.ListingColors; -import ghidra.app.util.viewer.field.ListingColors.FlowArrowColors; -import ghidra.app.util.viewer.listingpanel.*; +import ghidra.app.services.ListingMarginProviderService; +import ghidra.app.util.viewer.listingpanel.ListingMarginProvider; import ghidra.app.util.viewer.options.OptionsGui; -import ghidra.app.util.viewer.util.AddressIndexMap; -import ghidra.framework.options.OptionsChangeListener; import ghidra.framework.options.ToolOptions; import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.util.PluginStatus; -import ghidra.program.model.address.*; -import ghidra.program.model.listing.*; -import ghidra.program.model.mem.Memory; -import ghidra.program.model.symbol.*; -import ghidra.program.util.MarkerLocation; -import ghidra.program.util.ProgramLocation; /** * Plugin that has a margin provider to show the program flow. @@ -53,595 +37,53 @@ import ghidra.program.util.ProgramLocation; packageName = CorePluginPackage.NAME, category = PluginCategoryNames.CODE_VIEWER, shortDescription = "Show arrows for execution flow", - description = "This plugin shows arrows to graphically illustrate " - + "the flow of execution within a function. The arrows indicate " - + "source and destination for jumps; solid lines indicate " - + "unconditional jumps; dashed lines indicate conditional jumps.", - servicesRequired = { CodeViewerService.class }, + description = "This plugin shows arrows to graphically illustrate the flow of execution " + + "within a function. The arrows indicate source and destination for jumps; solid lines " + + "indicate unconditional jumps; dashed lines indicate conditional jumps.", + servicesProvided = { ListingMarginProviderService.class }, eventsConsumed = { ProgramActivatedPluginEvent.class, ProgramClosedPluginEvent.class, ProgramLocationPluginEvent.class } ) //@formatter:on -public class FlowArrowPlugin extends Plugin implements MarginProvider, OptionsChangeListener { +public class FlowArrowPlugin extends Plugin implements ListingMarginProviderService { - static final int LEFT_OFFSET = 3; - static final int MAX_DEPTH = 16; - private static final int MAX_REFSTO_SHOW = 10; // TODO this was 20--sounded like too many - - private FlowArrowPanel flowArrowPanel; - private boolean enabled = true; - private boolean validState = false; - private Address currentAddr; - - /** Start address to the index of the layout for that start address */ - private Map startAddressToPixel = new HashMap<>(); - - /** End address to the index of the layout for that end address */ - private Map endAddressToPixel = new HashMap<>(); - - /** On-screen layouts and their start/end addresses */ - private VerticalPixelAddressMap layoutToPixel; - private Address screenTop; - private Address screenBottom; - private int maxDepth; - - private Program program; - private CodeViewerService codeViewerService; - - private List flowArrows = new ArrayList<>(); - private Set selectedArrows = new HashSet<>(); - /** Those arrows that start at the current address */ - private Set activeArrows = new HashSet<>(); + private List providers = new ArrayList<>(); public FlowArrowPlugin(PluginTool tool) { super(tool); - flowArrowPanel = new FlowArrowPanel(this); - - flowArrowPanel.addComponentListener(new ComponentAdapter() { - @Override - public void componentResized(ComponentEvent e) { - boolean previousState = enabled; - enabled = flowArrowPanel.getWidth() > LEFT_OFFSET; - if (enabled && !previousState) { - updateAndRepaint(); - } - } - - @Override - public void componentShown(ComponentEvent e) { - boolean previousState = enabled; - enabled = flowArrowPanel.getWidth() > LEFT_OFFSET; - if (enabled && !previousState) { - updateAndRepaint(); - } - } - - @Override - public void componentHidden(ComponentEvent e) { - enabled = false; - } - }); - getOptions(); } @Override - public JComponent getComponent() { - return flowArrowPanel; + public ListingMarginProvider createMarginProvider() { + FlowArrowMarginProvider provider = new FlowArrowMarginProvider(this); + providers.add(provider); + return provider; } @Override - public MarkerLocation getMarkerLocation(int x, int y) { - return null; + public boolean isOwner(ListingMarginProvider provider) { + return providers.contains(provider); } - @Override - public boolean isResizeable() { - return true; + void remove(FlowArrowMarginProvider provider) { + providers.remove(provider); } - @Override - public void setProgram(Program program, AddressIndexMap addrMap, - VerticalPixelAddressMap pixmap) { - this.layoutToPixel = pixmap; - validateState(); - updateFlowArrows(); - } - - @Override - public void processEvent(PluginEvent event) { - boolean repaintReqd = false; - if (event instanceof ProgramActivatedPluginEvent) { - ProgramActivatedPluginEvent evt = (ProgramActivatedPluginEvent) event; - program = evt.getActiveProgram(); - flowArrows.clear(); - validateState(); - repaintReqd = true; - } - else if (event instanceof ProgramLocationPluginEvent) { - ProgramLocationPluginEvent evt = (ProgramLocationPluginEvent) event; - ProgramLocation location = evt.getLocation(); - currentAddr = location.getAddress(); - activeArrows.clear(); - repaintReqd = true; - } - else if (event instanceof ProgramClosedPluginEvent) { - ProgramClosedPluginEvent programClosedPluginEvent = (ProgramClosedPluginEvent) event; - Program closedProgram = programClosedPluginEvent.getProgram(); - if (program == closedProgram || program == null) { - program = null; - currentAddr = null; - activeArrows.clear(); - flowArrows.clear(); - validateState(); - repaintReqd = true; - } - } - if (repaintReqd) { - updateAndRepaint(); - } - } - - @Override - public void optionsChanged(ToolOptions options, String optionName, Object oldValue, - Object newValue) { - if (optionName.equals(OptionsGui.BACKGROUND.getColorOptionName())) { - Color c = (Color) newValue; - flowArrowPanel.setBackground(c); - } - else if (optionName.equals(OptionsGui.FLOW_ARROW_NON_ACTIVE.getColorOptionName())) { - Color c = (Color) newValue; - flowArrowPanel.setForeground(c); - } - else if (optionName.equals(OptionsGui.FLOW_ARROW_ACTIVE.getColorOptionName())) { - Color c = (Color) newValue; - flowArrowPanel.setHighlightColor(c); - } + List getProviders() { + return Collections.unmodifiableList(providers); } @Override protected void dispose() { - - startAddressToPixel.clear(); - endAddressToPixel.clear(); - layoutToPixel = null; - flowArrows.clear(); - flowArrowPanel.dispose(); - - codeViewerService.removeMarginProvider(this); - } - - @Override - protected void init() { - codeViewerService = tool.getService(CodeViewerService.class); - codeViewerService.addMarginProvider(this); - } - - Address getCurrentAddress() { - return currentAddr; - } - - Address getScreenBottomAddr() { - return screenBottom; - } - - int getMaxDepth() { - return maxDepth; - } - - boolean isOnScreen(Address address) { - if (screenBottom == null || screenTop == null) { - return true; // shouldn't happen - } - - if (address.compareTo(screenTop) < 0) { - // above the top of the screen - return false; - } - - if (address.compareTo(screenBottom) > 0) { - // below the bottom of the screen - return false; - } - return true; - } - - boolean isOffscreen(FlowArrow arrow) { - if (screenBottom == null || screenTop == null) { - return true; // shouldn't happen - } - - if (arrow.start.compareTo(screenTop) < 0 && arrow.end.compareTo(screenTop) < 0) { - // start and end are above the top of the screen - return true; - } - - if (arrow.start.compareTo(screenBottom) > 0 && arrow.end.compareTo(screenBottom) > 0) { - // start and end are below the bottom of the screen - return true; - } - return false; - } - - boolean isBelowScreen(Address address) { - if (screenBottom == null || screenTop == null) { - return true; // shouldn't happen - } - - return address.compareTo(screenBottom) > 0; - } - - /* The y value of the start of the layout at the given address. */ - Integer getStartPos(Address addr) { - return startAddressToPixel.get(addr); - } - - /* The y value of the end of the layout at the given address. */ - Integer getEndPos(Address addr) { - return endAddressToPixel.get(addr); - } - - void setArrowSelected(FlowArrow arrow, boolean selected) { - if (selected) { - selectedArrows.add(arrow); - } - else { - selectedArrows.remove(arrow); - } - } - - Iterator getSelectedFlowArrows() { - return selectedArrows.iterator(); - } - - Iterator getFlowArrowIterator() { - return flowArrows.iterator(); - } - - /* Those arrows starting at the current address */ - Iterator getActiveArrows() { - return activeArrows.iterator(); - } - - private void resetSelectedArrows() { - for (FlowArrow arrow : selectedArrows) { - arrow.resetShape(); - } - } - - private void resetActiveArrows() { - for (FlowArrow arrow : activeArrows) { - arrow.resetShape(); - } - } - - private void saveActiveArrows() { - - if (!activeArrows.isEmpty()) { - resetActiveArrows(); - return; // don't overwrite existing values - } - - if (currentAddr == null) { - return; - } - - for (FlowArrow arrow : flowArrows) { - if (currentAddr.equals(arrow.start)) { - arrow.active = true; - activeArrows.add(arrow); - } - } - - if (activeArrows.isEmpty()) { - return; - } - } - - /** - * Iterate over each other FlowArrow object to check overlaps - * - * @return FlowArrow objects that have a start/end address in common - */ - private List getArrowsAtSameDepth(FlowArrow jump, List allArrows) { - - List results = new ArrayList<>(); - for (FlowArrow otherArrows : allArrows) { - if (jump == otherArrows) { - continue; - } - - if (sharesEndpoint(jump, otherArrows)) { - results.add(otherArrows); - } - } - - return results; - } - - private boolean sharesEndpoint(FlowArrow a1, FlowArrow a2) { - return a1.start.equals(a2.start) || a1.end.equals(a2.end); - } - - private void computeAllArrowsDepth() { - - // Find overlapping arrows and compute depth - for (FlowArrow arrow : flowArrows) { - - List sameDepth = null; - - // If we have not already assigned a depth to this FlowArrow object - if (arrow.depth == -1) { - - sameDepth = getArrowsAtSameDepth(arrow, flowArrows); - - // Compute the full address set for all same-depth arrows - AddressSet sameDepthAddrs = new AddressSet(arrow.addressSet); - - for (FlowArrow otherArrow : sameDepth) { - sameDepthAddrs.add(otherArrow.addressSet); - } - - List differentDepth = new ArrayList<>(flowArrows); - differentDepth.removeAll(sameDepth); - differentDepth.remove(arrow); - assignArrowDepth(arrow, sameDepthAddrs, differentDepth); - } - else { - sameDepth = Collections.emptyList(); - } - - // If this is the deepest arrow seen, increase maxDepth - if (arrow.depth > maxDepth) { - maxDepth = arrow.depth; - } - - // Make same source/dest arrows the same depth - for (FlowArrow same : sameDepth) { - same.depth = arrow.depth; - } - } - } - - /** Calculates depth based on all other arrows that DO NOT share an endpoint */ - private void assignArrowDepth(FlowArrow arrow, AddressSet overlappingAddresses, - List allArrows) { - - //Keep track of which depths are used over current arrow range - boolean[] usedDepths = new boolean[MAX_DEPTH]; - - // Find all intersecting used depths - for (FlowArrow otherArrow : allArrows) { - if (otherArrow.depth == -1 || otherArrow.depth >= MAX_DEPTH) { - continue; - } - - if (sharesEndpoint(arrow, otherArrow)) { - continue; - } - - if (overlappingAddresses.intersects(otherArrow.addressSet)) { - usedDepths[otherArrow.depth] = true; - } - } - - arrow.depth = 0; - while (arrow.depth < usedDepths.length && usedDepths[arrow.depth]) { - arrow.depth++; - } - } - - private List getFlowArrowsForScreenInstructions(AddressSetView screenAddresses) { - - List results = new ArrayList<>(); - ArrowCache arrowCache = new ArrowCache(); - CodeUnitIterator it = program.getListing() - .getCodeUnitIterator(CodeUnit.INSTRUCTION_PROPERTY, screenAddresses, true); - - while (it.hasNext()) { - CodeUnit cu = it.next(); - Instruction instruction = (Instruction) cu; - - // incoming - int refCount = program.getReferenceManager().getReferenceCountTo(cu.getMinAddress()); - if (refCount < MAX_REFSTO_SHOW) { - ReferenceIterator instructionIt = instruction.getReferenceIteratorTo(); - while (instructionIt.hasNext()) { - Reference ref = instructionIt.next(); - createFlowArrow(results, arrowCache, ref); - } - } - - arrowCache.clear(); - - // outgoing - Reference[] refs = instruction.getReferencesFrom(); - for (Reference ref : refs) { - createFlowArrow(results, arrowCache, ref); - } - - } - - return results; - } - - private void createFlowArrow(List results, ArrowCache arrowCache, Reference ref) { - RefType type = ref.getReferenceType(); - if (!(type.isJump() || type.isFallthrough())) { - return; - } - - FlowArrow arrow = getFlowArrow(ref); - if (arrow == null) { - return; - } - - if (!arrowCache.isDuplicateOffscreen(arrow.start, arrow.end, type)) { - results.add(arrow); - updateArrowSets(arrow); - } - } - - /** - * Unusual Code: We keep arrows in 3 sets: all arrows, selected arrows, and active arrows. - * Further, we rebuild arrows as the screen moves, causing the x coordinate to change as arrows - * that are no longer on the screen are removed and as new arrows are added. We want to make - * sure that we don't end up with an arrow in the selected/active sets that are the same as the - * one in the 'all' set, but with a different width. This causes both arrows to become - * visible--basically, the selected arrows can become stale as their width changes. This code is - * meant to address this out-of-sync behavior. - * - * @param arrow the updated form of the arrow - */ - private void updateArrowSets(FlowArrow arrow) { - if (selectedArrows.remove(arrow)) { - arrow.selected = true; - selectedArrows.add(arrow); - } - - if (activeArrows.remove(arrow)) { - arrow.active = true; - activeArrows.add(arrow); - } - } - - private FlowArrow getFlowArrow(Reference ref) { - Address start = toLayoutAddress(ref.getFromAddress()); - Address end = toLayoutAddress(ref.getToAddress()); - if (start == null || end == null) { - return null; - } - - if (!start.hasSameAddressSpace(end)) { - return null; // is this right?? - } - - Memory memory = program.getMemory(); - if (!memory.contains(end)) { - return null; // bad disassembly - } - - RefType refType = ref.getReferenceType(); - if (refType.isFallthrough()) { - return new FallthroughFlowArrow(this, flowArrowPanel, start, end, refType); - } - else if (refType.isConditional()) { - return new ConditionalFlowArrow(this, flowArrowPanel, start, end, refType); - } - - return new DefaultFlowArrow(this, flowArrowPanel, start, end, refType); - } - - private void validateState() { - validState = false; - if (program == null || layoutToPixel == null) { - return; - } - - int n = layoutToPixel.getNumLayouts(); - if (n == 0) { - return; - } - - Address bottomAddr = layoutToPixel.getLayoutAddress(n - 1); - if (bottomAddr != null) { - AddressSpace testSpace = bottomAddr.getAddressSpace(); - validState = - (program.getAddressFactory().getAddressSpace(testSpace.getSpaceID()) == testSpace); - } - } - - void updateAndRepaint() { - update(); - flowArrowPanel.repaint(); - } - - private void update() { - if (!enabled || !validState) { - return; - } - - //Compute addresses in local range - int n = layoutToPixel.getNumLayouts(); - if (n == 0) { - return; - } - - Address startAddress = layoutToPixel.getLayoutAddress(0); - Address endAddress = layoutToPixel.getLayoutAddress(n - 1); - - screenTop = startAddress; - screenBottom = endAddress; - flowArrows.clear(); - startAddressToPixel.clear(); - endAddressToPixel.clear(); - maxDepth = 0; - - resetSelectedArrows(); - - if (screenTop == null || screenBottom == null || n > 500) { - return; - } - - // Find all addresses that are on the screen and compute y co-ordinate - for (int layout = 0; layout < n; layout++) { - Address addr = layoutToPixel.getLayoutAddress(layout); - if (addr != null) { - startAddressToPixel.put(addr, layoutToPixel.getBeginPosition(layout)); - endAddressToPixel.put(addr, layoutToPixel.getEndPosition(layout)); - } - } - - // Intersect the screenTop and screenBottom with the currentView - AddressSetView flowSet = layoutToPixel.getAddressSet(); - - // Find references at the instructions on the screen - flowArrows = getFlowArrowsForScreenInstructions(flowSet); - - Collections.sort(flowArrows, (a1, a2) -> (a1).end.compareTo((a2).end)); - - computeAllArrowsDepth(); - - saveActiveArrows(); - } - - private Address toLayoutAddress(Address addr) { - Object pixel = startAddressToPixel.get(addr); - if (pixel != null || addr.compareTo(screenTop) < 0 || addr.compareTo(screenBottom) > 0) { - return addr; - } - - int n = layoutToPixel.getNumLayouts(); - for (int i = 0; i < n; i++) { - Address layoutAddr = layoutToPixel.getLayoutAddress(i); - Address endLayoutAddr = layoutToPixel.getLayoutEndAddress(i); - if (layoutAddr == null || endLayoutAddr == null) { - continue; - } - - if (layoutAddr.compareTo(addr) >= 0) { - // have gone past the address, then there is a gap - return null; - } - - // we are between the start and end; inside the layout - if (addr.compareTo(endLayoutAddr) <= 0) { - return layoutAddr; - } - } - - return addr; // should never get here - } - - private void updateFlowArrows() { - if (enabled) { - updateAndRepaint(); + for (FlowArrowMarginProvider provider : new ArrayList<>(providers)) { + provider.dispose(); } } private void getOptions() { + // Note: these are here merely as a convenience so users don't have to use the theme editor. ToolOptions opt = tool.getOptions(GhidraOptions.CATEGORY_BROWSER_DISPLAY); - opt.registerThemeColorBinding(OptionsGui.FLOW_ARROW_NON_ACTIVE.getColorOptionName(), OptionsGui.FLOW_ARROW_NON_ACTIVE.getThemeColorId(), null, "The color for an arrow with no endpoint at the current address"); @@ -651,136 +93,6 @@ public class FlowArrowPlugin extends Plugin implements MarginProvider, OptionsCh opt.registerThemeColorBinding(OptionsGui.FLOW_ARROW_SELECTED.getColorOptionName(), OptionsGui.FLOW_ARROW_SELECTED.getThemeColorId(), null, "The color for an arrow that has been selected by the user"); - - flowArrowPanel.setBackground(ListingColors.BACKGROUND); - flowArrowPanel.setForeground(FlowArrowColors.INACTIVE); - flowArrowPanel.setHighlightColor(FlowArrowColors.ACTIVE); - flowArrowPanel.setSelectedColor(FlowArrowColors.SELECTED); - - opt.addOptionsChangeListener(this); - } - -//================================================================================================== -// Inner Classes -//================================================================================================== - - private class ArrowCache { - Address address; - boolean conditionalOffTop; - boolean conditionalOffBottom; - boolean fallthroughOffTop; - boolean fallthroughOffBottom; - boolean otherOffTop; - boolean otherOffBottom; - - void clear() { - address = null; - conditionalOffTop = false; - conditionalOffBottom = false; - fallthroughOffTop = false; - fallthroughOffBottom = false; - otherOffTop = false; - otherOffBottom = false; - } - - boolean isDuplicateOffscreen(Address pointOfInterest, Address otherEnd, RefType refType) { - if (!pointOfInterest.equals(address)) { - clear(); - address = pointOfInterest; - } - - // above the top of the screen - if (otherEnd.compareTo(screenTop) < 0) { - if (refType.isConditional()) { - if (conditionalOffTop) { - return true; - } - conditionalOffTop = true; - } - else if (refType.isFallthrough()) { - if (fallthroughOffTop) { - return true; - } - fallthroughOffTop = true; - } - else { - if (otherOffTop) { - return true; - } - otherOffTop = true; - } - } - - // below the bottom of the screen - else if (otherEnd.compareTo(screenBottom) > 0) { - if (refType.isConditional()) { - if (conditionalOffBottom) { - return true; - } - conditionalOffBottom = true; - } - else if (refType.isFallthrough()) { - if (fallthroughOffBottom) { - return true; - } - fallthroughOffBottom = true; - } - else { - if (otherOffBottom) { - return true; - } - otherOffBottom = true; - } - } - return false; - } - } - - void goTo(Address address) { - CodeViewerService codeViewer = tool.getService(CodeViewerService.class); - ListingPanel listingPanel = codeViewer.getListingPanel(); - ProgramLocation location = new ProgramLocation(program, address); - listingPanel.goTo(location, false); - } - - void scrollTo(Address address) { - CodeViewerService codeViewer = tool.getService(CodeViewerService.class); - ListingPanel listingPanel = codeViewer.getListingPanel(); - ProgramLocation location = new ProgramLocation(program, address); -// listingPanel.setCursorPosition(location); -// listingPanel.goTo(location, true); - listingPanel.scrollTo(location); - } - - void scrollToCenter(Address address) { - CodeViewerService codeViewer = tool.getService(CodeViewerService.class); - ListingPanel listingPanel = codeViewer.getListingPanel(); - ProgramLocation location = new ProgramLocation(program, address); - listingPanel.center(location); - } - - Address getAddressAtPoint(Point p) { - CodeViewerService codeViewer = tool.getService(CodeViewerService.class); - ListingPanel listingPanel = codeViewer.getListingPanel(); - ProgramLocation location = listingPanel.getProgramLocation(p); - if (location == null) { - return null; - } - return location.getAddress(); - } - - Address getLastAddressOnScreen(Address end, boolean up) { - if (up) { - return screenTop; - } - return screenBottom; - } - - public void forwardMouseEventToListing(MouseWheelEvent e) { - CodeViewerService codeViewer = tool.getService(CodeViewerService.class); - ListingPanel listingPanel = codeViewer.getListingPanel(); - FieldPanel fieldPanel = listingPanel.getFieldPanel(); - KeyboardFocusManager.getCurrentKeyboardFocusManager().redispatchEvent(fieldPanel, e); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/flowarrow/FlowArrowShapeFactory.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/flowarrow/FlowArrowShapeFactory.java index 597991ae40..b4f689a5aa 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/flowarrow/FlowArrowShapeFactory.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/flowarrow/FlowArrowShapeFactory.java @@ -1,13 +1,12 @@ /* ### * IP: GHIDRA - * REVIEWED: YES * * 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. @@ -28,26 +27,27 @@ class FlowArrowShapeFactory { private static final int TRIANGLE_HEIGHT = 9; private static final int TRIANGLE_WIDTH = 7; - static Shape createArrowBody(FlowArrowPlugin plugin, FlowArrow arrow, int width, int height, + static Shape createArrowBody(FlowArrowMarginProvider provider, FlowArrow arrow, int width, + int height, int lineSpacing) { GeneralPath linePath = new GeneralPath(); // Compute the start Y coordinate (place at proper offset, fix if off screen) - Integer startTop = plugin.getStartPos(arrow.start); - Integer startBottom = plugin.getEndPos(arrow.start); + Integer startTop = provider.getStartPos(arrow.start); + Integer startBottom = provider.getEndPos(arrow.start); int startY = 0; if (startTop != null && startBottom != null) { int start = startTop; int end = startBottom; startY = (start + end) / 2;// middle of line } - else if (plugin.isBelowScreen(arrow.start)) { + else if (provider.isBelowScreen(arrow.start)) { startY = height; } - Integer endTop = plugin.getStartPos(arrow.end); - Integer endBottom = plugin.getEndPos(arrow.end); + Integer endTop = provider.getStartPos(arrow.end); + Integer endBottom = provider.getEndPos(arrow.end); int endY = 0; if (endTop != null && endBottom != null) { int start = endTop; @@ -55,11 +55,11 @@ class FlowArrowShapeFactory { endY = (start + end) / 2; endY = Math.min(endY, height); // ensure on screen } - else if (plugin.isBelowScreen(arrow.end)) { + else if (provider.isBelowScreen(arrow.end)) { endY = height; } - int x = width - ((arrow.depth + 1) * lineSpacing); + int x = width - ((arrow.column + 1) * lineSpacing); if (x < 3) { x = 3; } @@ -126,12 +126,13 @@ class FlowArrowShapeFactory { return linePath; } - static Shape createArrowHead(FlowArrowPlugin plugin, FlowArrow arrow, int width, int height, + static Shape createArrowHead(FlowArrowMarginProvider provider, FlowArrow arrow, int width, + int height, int lineSpacing) { // Compute the start Y coordinate (place at proper offset, fix if off screen) - Integer addrStartInt = plugin.getStartPos(arrow.end); - Integer addrEndInt = plugin.getEndPos(arrow.end); + Integer addrStartInt = provider.getStartPos(arrow.end); + Integer addrEndInt = provider.getEndPos(arrow.end); int endY = 0; if (addrStartInt != null && addrEndInt != null) { int start = addrStartInt; @@ -139,11 +140,11 @@ class FlowArrowShapeFactory { endY = (start + end) / 2; endY = Math.min(endY, height); // ensure on screen } - else if (plugin.isBelowScreen(arrow.end)) { + else if (provider.isBelowScreen(arrow.end)) { endY = height; } - int x = width - ((arrow.depth + 1) * lineSpacing); + int x = width - ((arrow.column + 1) * lineSpacing); if (x < 0) { x = 3; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/marker/MarkerManager.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/marker/MarkerManager.java index 170cfc6635..1dadfbe13d 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/marker/MarkerManager.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/marker/MarkerManager.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. @@ -40,6 +40,7 @@ import ghidra.program.model.address.AddressSet; import ghidra.program.model.listing.Program; import ghidra.program.util.ProgramLocation; import ghidra.util.ColorUtils.ColorBlender; +import ghidra.util.UniversalID; import ghidra.util.datastruct.*; import ghidra.util.exception.AssertException; import ghidra.util.task.SwingUpdateManager; @@ -70,11 +71,9 @@ public class MarkerManager implements MarkerService { private SwingUpdateManager updater; private GoToService goToService; - private MarkerMarginProvider primaryMarginProvider; private WeakSet marginProviders = WeakDataStructureFactory.createCopyOnWriteWeakSet(); - private MarkerOverviewProvider primaryOverviewProvider; private WeakSet overviewProviders = WeakDataStructureFactory.createCopyOnWriteWeakSet(); @@ -101,9 +100,6 @@ public class MarkerManager implements MarkerService { notifyListeners(); }); - primaryMarginProvider = createMarginProvider(); - primaryOverviewProvider = createOverviewProvider(); - Gui.addThemeListener(themeListener); } @@ -220,8 +216,12 @@ public class MarkerManager implements MarkerService { } - public MarkerMarginProvider getMarginProvider() { - return primaryMarginProvider; + public boolean contains(ListingMarginProvider provider) { + return marginProviders.contains(provider); + } + + public boolean contains(ListingOverviewProvider provider) { + return overviewProviders.contains(provider); } @Override @@ -231,8 +231,8 @@ public class MarkerManager implements MarkerService { return provider; } - public OverviewProvider getOverviewProvider() { - return primaryOverviewProvider; + public void removeProvider(MarkerMarginProvider provider) { + marginProviders.remove(provider); } @Override @@ -246,7 +246,6 @@ public class MarkerManager implements MarkerService { Gui.removeThemeListener(themeListener); updater.dispose(); markerSetCache.clear(); - overviewProviders.forEach(provider -> provider.dispose()); } void navigateTo(Navigatable navigatable, Program program, int x, int y, int viewHeight, @@ -276,13 +275,13 @@ public class MarkerManager implements MarkerService { entry.paintNavigation(g, panel.getViewHeight(), panel.getWidth(), addrMap); } - void paintMarkers(Program program, Graphics g, VerticalPixelAddressMap pixmap, - AddressIndexMap addrMap) { + void paintMarkers(UniversalID ownerId, Program program, Graphics g, + VerticalPixelAddressMap pixMap, AddressIndexMap addrMap) { MarkerSetCacheEntry entry = markerSetCache.get(program); if (entry == null) { return; } - entry.paintMarkers(g, pixmap, addrMap); + entry.paintMarkers(ownerId, g, pixMap, addrMap); } void showToolTipPopup(MouseEvent event, String tip) { @@ -301,10 +300,6 @@ public class MarkerManager implements MarkerService { popupWindow.showPopup(event); } - /*testing*/ String generateToolTip(MouseEvent event) { - return primaryMarginProvider.generateToolTip(event); - } - List getMarkerTooltipLines(Program program, int y, int x, Address minAddr, Address maxAddr) { MarkerSetCacheEntry entry = markerSetCache.get(program); @@ -484,6 +479,7 @@ public class MarkerManager implements MarkerService { if (program == null || program.isClosed()) { return null; } + MarkerSetCacheEntry entry = map.computeIfAbsent(program, this::newEntry); if (program.isClosed()) { map.remove(program); @@ -571,13 +567,22 @@ public class MarkerManager implements MarkerService { } } - void paintMarkers(Graphics g, VerticalPixelAddressMap pixmap, AddressIndexMap addrMap) { + void paintMarkers(UniversalID ownerId, Graphics g, VerticalPixelAddressMap pixMap, + AddressIndexMap addrMap) { int count = 0; for (MarkerSetImpl markers : markerSets) { count++; - if (markers.active) { - markers.paintMarkers(g, count++, pixmap, addrMap); + if (!markers.active) { + continue; } + + UniversalID markerId = markers.getOwnerId(); + if (markerId != null && markerId != ownerId) { + // a non-global marker that does not match the owner being painted + continue; + } + + markers.paintMarkers(g, count++, pixMap, addrMap); } } @@ -638,4 +643,5 @@ public class MarkerManager implements MarkerService { return new ArrayList<>(markerSets); } } + } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/marker/MarkerManagerPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/marker/MarkerManagerPlugin.java index fb1b41f71a..165a13d065 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/marker/MarkerManagerPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/marker/MarkerManagerPlugin.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. @@ -20,6 +20,8 @@ import ghidra.app.CorePluginPackage; import ghidra.app.plugin.PluginCategoryNames; import ghidra.app.services.*; import ghidra.app.util.HelpTopics; +import ghidra.app.util.viewer.listingpanel.ListingMarginProvider; +import ghidra.app.util.viewer.listingpanel.ListingOverviewProvider; import ghidra.framework.options.Options; import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.util.PluginStatus; @@ -39,17 +41,17 @@ import ghidra.util.HelpLocation; "supported; point markers and area markers. Area markers are used to indicate a range " + "value such as selection. Point markers are used to represent individual addresses such " + "as bookmarks.", - servicesRequired = { CodeViewerService.class, GoToService.class }, - servicesProvided = { MarkerService.class }, + servicesRequired = { GoToService.class }, + servicesProvided = { MarkerService.class, ListingMarginProviderService.class, ListingOverviewProviderService.class }, eventsConsumed = {} ) //@formatter:on /** * Plugin to manage marker and navigation panels. */ -public class MarkerManagerPlugin extends Plugin { +public class MarkerManagerPlugin extends Plugin + implements ListingMarginProviderService, ListingOverviewProviderService { - private CodeViewerService codeViewerService; private MarkerManager markerManager; public MarkerManagerPlugin(PluginTool tool) { @@ -65,17 +67,26 @@ public class MarkerManagerPlugin extends Plugin { @Override protected void dispose() { - if (codeViewerService != null) { - codeViewerService.removeMarginProvider(markerManager.getMarginProvider()); - codeViewerService.removeOverviewProvider(markerManager.getOverviewProvider()); - } markerManager.dispose(); } @Override - protected void init() { - codeViewerService = tool.getService(CodeViewerService.class); - codeViewerService.addMarginProvider(markerManager.getMarginProvider()); - codeViewerService.addOverviewProvider(markerManager.getOverviewProvider()); + public ListingMarginProvider createMarginProvider() { + return markerManager.createMarginProvider(); + } + + @Override + public boolean isOwner(ListingMarginProvider provider) { + return markerManager.contains(provider); + } + + @Override + public ListingOverviewProvider createOverviewProvider() { + return markerManager.createOverviewProvider(); + } + + @Override + public boolean isOwner(ListingOverviewProvider provider) { + return markerManager.contains(provider); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/marker/MarkerMarginProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/marker/MarkerMarginProvider.java index bc32915f5d..e08b4a9637 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/marker/MarkerMarginProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/marker/MarkerMarginProvider.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. @@ -28,6 +28,8 @@ import ghidra.app.util.viewer.util.AddressIndexMap; import ghidra.program.model.address.Address; import ghidra.program.model.listing.Program; import ghidra.program.util.MarkerLocation; +import ghidra.program.util.ProgramLocation; +import ghidra.util.UniversalID; /** * The provider which renders the marker margin, usually placed to the left of listing @@ -37,7 +39,7 @@ import ghidra.program.util.MarkerLocation; * These are managed by a {@link MarkerManager}. Obtain one via * {@link MarkerService#createMarginProvider()}. */ -public class MarkerMarginProvider implements MarginProvider { +public class MarkerMarginProvider implements ListingMarginProvider { private final MarkerManager markerManager; private final MarkerPanel markerPanel; @@ -62,6 +64,22 @@ public class MarkerMarginProvider implements MarginProvider { }); } + @Override + public void setOwnerId(UniversalID ownerId) { + markerPanel.setOwnerId(ownerId); + } + + @Override + public void setLocation(ProgramLocation location) { + // we don't use locations + } + + @Override + public void dispose() { + markerManager.removeProvider(this); + markerPanel.dispose(); + } + void repaintPanel() { markerPanel.repaint(); } @@ -95,12 +113,11 @@ public class MarkerMarginProvider implements MarginProvider { } @Override - public void setProgram(Program program, AddressIndexMap addrMap, - VerticalPixelAddressMap pixmap) { - this.program = program; - this.pixmap = pixmap; - - this.markerPanel.setProgram(program, addrMap, pixmap); + public void screenDataChanged(ListingPanel listing, AddressIndexMap addrMap, + VerticalPixelAddressMap pixMap) { + this.program = listing.getProgram(); + this.pixmap = pixMap; + this.markerPanel.listingUpdated(listing, addrMap, pixMap); markerManager.updateMarkerSets(program, true, false, true); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/marker/MarkerOverviewProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/marker/MarkerOverviewProvider.java index dea8e92929..968d5da5e5 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/marker/MarkerOverviewProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/marker/MarkerOverviewProvider.java @@ -30,7 +30,7 @@ import ghidra.GhidraOptions; import ghidra.app.nav.Navigatable; import ghidra.app.services.MarkerService; import ghidra.app.util.HelpTopics; -import ghidra.app.util.viewer.listingpanel.OverviewProvider; +import ghidra.app.util.viewer.listingpanel.ListingOverviewProvider; import ghidra.app.util.viewer.util.AddressIndexMap; import ghidra.framework.options.*; import ghidra.framework.plugintool.PluginTool; @@ -46,7 +46,7 @@ import ghidra.util.Swing; * These are managed by a {@link MarkerManager}. Obtain one via * {@link MarkerService#createOverviewProvider()}. */ -public class MarkerOverviewProvider implements OverviewProvider { +public class MarkerOverviewProvider implements ListingOverviewProvider { private final PluginTool tool; private final String owner; @@ -74,7 +74,8 @@ public class MarkerOverviewProvider implements OverviewProvider { actionList = new MarkerActionList(); } - void dispose() { + @Override + public void dispose() { actionList.dispose(); } @@ -88,11 +89,11 @@ public class MarkerOverviewProvider implements OverviewProvider { } @Override - public void setProgram(Program program, AddressIndexMap map) { - this.program = program; + public void screenDataChanged(Program p, AddressIndexMap addressMap) { + this.program = p; - navigationPanel.setProgram(program, map); - markerManager.updateMarkerSets(program, true, true, false); + navigationPanel.setProgram(p, addressMap); + markerManager.updateMarkerSets(p, true, true, false); actionList.refresh(); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/marker/MarkerPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/marker/MarkerPanel.java index 7317d850ad..7532569c15 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/marker/MarkerPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/marker/MarkerPanel.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,10 +24,12 @@ import javax.swing.JPanel; import javax.swing.ToolTipManager; import docking.widgets.fieldpanel.FieldPanel; +import ghidra.app.util.viewer.listingpanel.ListingPanel; import ghidra.app.util.viewer.listingpanel.VerticalPixelAddressMap; import ghidra.app.util.viewer.util.AddressIndexMap; import ghidra.program.model.address.Address; import ghidra.program.model.listing.Program; +import ghidra.util.UniversalID; /** * Panel to display markers. Normally placed to the left hand side of the scrolled @@ -36,29 +38,40 @@ import ghidra.program.model.listing.Program; public class MarkerPanel extends JPanel { private MarkerManager manager; - private Program program; private AddressIndexMap addrMap; - private VerticalPixelAddressMap pixmap; + private VerticalPixelAddressMap pixMap; + private UniversalID ownerId; MarkerPanel(MarkerManager manager) { super(); this.manager = manager; - this.setPreferredSize(new Dimension(16, 1)); ToolTipManager.sharedInstance().registerComponent(this); } - void setProgram(Program program, AddressIndexMap addrMap, VerticalPixelAddressMap pixmap) { - this.program = program; - this.addrMap = addrMap; - this.pixmap = pixmap; + void setOwnerId(UniversalID ownerId) { + this.ownerId = ownerId; + } + + void dispose() { + this.program = null; + this.addrMap = null; + this.pixMap = null; + ToolTipManager.sharedInstance().unregisterComponent(this); + } + + void listingUpdated(ListingPanel listingPanel, AddressIndexMap addressMap, + VerticalPixelAddressMap pixelMap) { + this.program = listingPanel.getProgram(); + this.addrMap = addressMap; + this.pixMap = pixelMap; } @Override protected void paintComponent(Graphics g) { super.paintComponent(g); - manager.paintMarkers(program, g, pixmap, addrMap); + manager.paintMarkers(ownerId, program, g, pixMap, addrMap); } @Override @@ -81,14 +94,14 @@ public class MarkerPanel extends JPanel { } String generateToolTip(MouseEvent event) { - if (pixmap == null) { + if (pixMap == null) { return null; } int y = event.getY(); int x = event.getX(); - int layoutIndex = pixmap.findLayoutAt(y); - Address layoutAddress = pixmap.getLayoutAddress(layoutIndex); + int layoutIndex = pixMap.findLayoutAt(y); + Address layoutAddress = pixMap.getLayoutAddress(layoutIndex); if (layoutAddress == null) { return null; } @@ -99,7 +112,8 @@ public class MarkerPanel extends JPanel { private List getMarkerTooltipLines(int y, int x, int layoutIndex, Address layoutAddress) { - Address endAddr = pixmap.getLayoutEndAddress(layoutIndex); + Address endAddr = pixMap.getLayoutEndAddress(layoutIndex); return manager.getMarkerTooltipLines(program, y, layoutIndex, layoutAddress, endAddr); } + } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/marker/MarkerSetImpl.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/marker/MarkerSetImpl.java index 0c99f671b6..62769a6b4f 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/marker/MarkerSetImpl.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/marker/MarkerSetImpl.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. @@ -35,8 +35,7 @@ import ghidra.program.model.address.*; import ghidra.program.model.listing.Program; import ghidra.program.util.MarkerLocation; import ghidra.program.util.ProgramLocation; -import ghidra.util.ColorUtils; -import ghidra.util.Swing; +import ghidra.util.*; import ghidra.util.datastruct.SortedRangeList; abstract class MarkerSetImpl implements MarkerSet { @@ -52,7 +51,7 @@ abstract class MarkerSetImpl implements MarkerSet { protected AddressSetCollection markers; protected SortedRangeList overview = null; - protected VerticalPixelAddressMap activePixmap = null; + protected VerticalPixelAddressMap activePixMap = null; protected List activeLayouts = null; protected Color markerColor; @@ -72,9 +71,11 @@ abstract class MarkerSetImpl implements MarkerSet { private boolean colorBackground; private boolean isPreferred; + /** Optional owner ID. A null ID implies a global marker set. */ + private UniversalID ownerId; + MarkerSetImpl(MarkerManager mgr, Program program, String name, String desc, int priority, - boolean showMarkers, - boolean showNavigation, boolean colorBackground, Color markerColor, + boolean showMarkers, boolean showNavigation, boolean colorBackground, Color markerColor, boolean isPreferred) { this.mgr = mgr; @@ -93,6 +94,16 @@ abstract class MarkerSetImpl implements MarkerSet { markers = new ModifiableAddressSetCollection(); } + @Override + public void setOwnerId(UniversalID ownerId) { + this.ownerId = ownerId; + } + + @Override + public UniversalID getOwnerId() { + return ownerId; + } + protected abstract void doPaintMarkers(Graphics g, VerticalPixelAddressMap pixmap, int index, AddressIndexMap map, List layouts); @@ -244,11 +255,11 @@ abstract class MarkerSetImpl implements MarkerSet { } } - public final void paintMarkers(Graphics g, int index, VerticalPixelAddressMap pixmap, + public final void paintMarkers(Graphics g, int index, VerticalPixelAddressMap pixMap, AddressIndexMap map) { if (showMarkers) { - List layouts = computeActiveLayouts(pixmap, map); - doPaintMarkers(g, pixmap, index, map, layouts); + List layouts = computeActiveLayouts(pixMap, map); + doPaintMarkers(g, pixMap, index, map, layouts); } } @@ -289,32 +300,32 @@ abstract class MarkerSetImpl implements MarkerSet { return markers.contains(addr); } - private List computeActiveLayouts(VerticalPixelAddressMap pixmap, + private List computeActiveLayouts(VerticalPixelAddressMap pixMap, AddressIndexMap map) { - if (pixmap == null) { + if (pixMap == null) { return null; } - if (activeLayouts != null && activePixmap == pixmap) { + if (activeLayouts != null && activePixMap == pixMap) { return activeLayouts; // use cache } List newLayouts = new ArrayList<>(); - int n = pixmap.getNumLayouts(); + int n = pixMap.getNumLayouts(); for (int i = 0; i < n; i++) { - Address addr = pixmap.getLayoutAddress(i); + Address addr = pixMap.getLayoutAddress(i); if (addr == null) { continue; } - Address end = pixmap.getLayoutEndAddress(i); + Address end = pixMap.getLayoutEndAddress(i); if (markers.intersects(addr, end)) { newLayouts.add(i); } } - activePixmap = pixmap; + activePixMap = pixMap; activeLayouts = newLayouts; return newLayouts; } @@ -536,6 +547,6 @@ abstract class MarkerSetImpl implements MarkerSet { @Override public String toString() { - return Json.toString(this, "active", "colorBackground", "markers"); + return Json.toString(this, "name", "active", "colorBackground", "markers"); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/marker/PointMarkerSet.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/marker/PointMarkerSet.java index e055d7bb98..1897c2865c 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/marker/PointMarkerSet.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/marker/PointMarkerSet.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. @@ -95,7 +95,7 @@ class PointMarkerSet extends MarkerSetImpl { } @Override - protected void doPaintMarkers(Graphics g, VerticalPixelAddressMap pixmap, int index, + protected void doPaintMarkers(Graphics g, VerticalPixelAddressMap pixMap, int index, AddressIndexMap map, List layouts) { if (layouts == null) { @@ -105,20 +105,20 @@ class PointMarkerSet extends MarkerSetImpl { Iterator it = layouts.iterator(); while (it.hasNext()) { int i = it.next().intValue(); - int yStart = pixmap.getMarkPosition(i); + int yStart = pixMap.getMarkPosition(i); - Image curImage = getMarkerImage(pixmap, i, yStart); + Image curImage = getMarkerImage(pixMap, i, yStart); g.drawImage(curImage, 0, yStart, imageObserver); } } - private Image getMarkerImage(VerticalPixelAddressMap pixmap, int i, int yStart) { + private Image getMarkerImage(VerticalPixelAddressMap pixMap, int i, int yStart) { if (markerDescriptor == null) { return image; } - Address address = pixmap.getLayoutAddress(i); + Address address = pixMap.getLayoutAddress(i); MarkerLocation loc = new MarkerLocation(this, program, address, 0, yStart); ImageIcon icon = markerDescriptor.getIcon(loc); if (icon != null) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/overview/OverviewColorComponent.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/overview/OverviewColorComponent.java index 3f89bba1cb..7d338c1309 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/overview/OverviewColorComponent.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/overview/OverviewColorComponent.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. @@ -28,7 +28,7 @@ import docking.action.DockingActionIf; import generic.theme.GColor; import ghidra.app.nav.Navigatable; import ghidra.app.services.GoToService; -import ghidra.app.util.viewer.listingpanel.OverviewProvider; +import ghidra.app.util.viewer.listingpanel.ListingOverviewProvider; import ghidra.app.util.viewer.util.AddressIndexMap; import ghidra.framework.plugintool.PluginTool; import ghidra.program.model.address.Address; @@ -40,13 +40,13 @@ import help.Help; * Overview bar component. Uses color to indicate various address based properties for a program. * Uses an {@link OverviewColorService} to get the appropriate color for an address. */ -public class OverviewColorComponent extends JPanel implements OverviewProvider { +public class OverviewColorComponent extends JPanel implements ListingOverviewProvider { private static final Color DEFAULT_COLOR = new GColor("color.bg.plugin.overview.defalt"); private OverviewColorService service; private Color[] colors = new Color[0]; private final SwingUpdateManager refreshUpdater = new SwingUpdateManager(100, 15000, () -> doRefresh()); - private AddressIndexMap map; + private AddressIndexMap addressMap; private Navigatable navigatable; private PluginTool tool; private List actions; @@ -76,6 +76,12 @@ public class OverviewColorComponent extends JPanel implements OverviewProvider { actions = service.getActions(); } + @Override + public void dispose() { + service = null; + ToolTipManager.sharedInstance().unregisterComponent(this); + } + /** * Installs actions for this component */ @@ -154,10 +160,10 @@ public class OverviewColorComponent extends JPanel implements OverviewProvider { } private void doRefresh() { - if (map == null) { + if (addressMap == null) { return; } - BigInteger indexCount = map.getIndexCount(); + BigInteger indexCount = addressMap.getIndexCount(); if (indexCount.equals(BigInteger.ZERO)) { Arrays.fill(colors, DEFAULT_COLOR); repaint(); @@ -168,7 +174,7 @@ public class OverviewColorComponent extends JPanel implements OverviewProvider { for (int i = 0; i < colors.length; i++) { if (colors[i] == null) { BigInteger index = indexCount.multiply(BigInteger.valueOf(i)).divide(bigTotal); - Address address = map.getAddress(index); + Address address = addressMap.getAddress(index); colors[i] = service.getColor(address); } } @@ -178,17 +184,17 @@ public class OverviewColorComponent extends JPanel implements OverviewProvider { private Address getAddress(int pixelIndex) { BigInteger bigHeight = BigInteger.valueOf(getOverviewPixelCount()); BigInteger bigPixelIndex = BigInteger.valueOf(pixelIndex); - BigInteger bigIndex = map.getIndexCount().multiply(bigPixelIndex).divide(bigHeight); - return map.getAddress(bigIndex); + BigInteger bigIndex = addressMap.getIndexCount().multiply(bigPixelIndex).divide(bigHeight); + return addressMap.getAddress(bigIndex); } private int getPixelIndex(Address address) { - BigInteger addressIndex = map.getIndex(address); + BigInteger addressIndex = addressMap.getIndex(address); if (addressIndex == null) { return -1; } BigInteger bigHeight = BigInteger.valueOf(getOverviewPixelCount()); - BigInteger indexCount = map.getIndexCount(); + BigInteger indexCount = addressMap.getIndexCount(); return addressIndex.multiply(bigHeight).divide(indexCount).intValue(); } @@ -198,10 +204,10 @@ public class OverviewColorComponent extends JPanel implements OverviewProvider { } @Override - public void setProgram(Program program, AddressIndexMap map) { - this.map = map; - colors = new Color[getOverviewPixelCount()]; - refreshUpdater.updateLater(); + public void screenDataChanged(Program program, AddressIndexMap map) { + this.addressMap = map; + this.colors = new Color[getOverviewPixelCount()]; + this.refreshUpdater.updateLater(); } @Override @@ -248,5 +254,4 @@ public class OverviewColorComponent extends JPanel implements OverviewProvider { public PluginTool getTool() { return tool; } - } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/overview/OverviewColorService.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/overview/OverviewColorService.java index 759365ee63..a44f80b928 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/overview/OverviewColorService.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/overview/OverviewColorService.java @@ -92,5 +92,4 @@ public interface OverviewColorService extends ExtensionPoint { * @return the current program used by the service. */ public Program getProgram(); - } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/overview/addresstype/AddressTypeOverviewColorService.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/overview/addresstype/AddressTypeOverviewColorService.java index 2b386a9343..34f9334165 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/overview/addresstype/AddressTypeOverviewColorService.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/overview/addresstype/AddressTypeOverviewColorService.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. @@ -143,6 +143,8 @@ public class AddressTypeOverviewColorService @Override public void setOverviewComponent(OverviewColorComponent component) { this.overviewComponent = component; + this.overviewComponent.getAccessibleContext() + .setAccessibleName("Address Type Overview Component"); } /** diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/printing/PrintingPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/printing/PrintingPlugin.java index cc900ab836..f8a4d4f26a 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/printing/PrintingPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/printing/PrintingPlugin.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. @@ -33,6 +33,7 @@ import ghidra.app.CorePluginPackage; import ghidra.app.plugin.PluginCategoryNames; import ghidra.app.plugin.ProgramPlugin; import ghidra.app.services.CodeViewerService; +import ghidra.app.util.viewer.listingpanel.ListingPanel; import ghidra.app.util.viewer.util.AddressIndexMap; import ghidra.framework.plugintool.PluginInfo; import ghidra.framework.plugintool.PluginTool; @@ -147,7 +148,10 @@ public class PrintingPlugin extends ProgramPlugin { format = job.defaultPage(); } - LayoutModel lm = cvService.getFieldPanel().getLayoutModel(); + ListingPanel listingPanel = cvService.getListingPanel(); + FieldPanel fp = listingPanel.getFieldPanel(); + LayoutModel lm = fp.getLayoutModel(); + //Scale everything down if appropriate to fit on the page double scaleAmount = format.getImageableWidth() / lm.getPreferredViewSize().width; @@ -316,7 +320,8 @@ public class PrintingPlugin extends ProgramPlugin { private void printVisibleContent(TaskMonitor monitor, Date startDate, PrinterJob job, Book book, LayoutModel lm, double scaleAmount, int maxPageHeight) { - FieldPanel fp = cvService.getFieldPanel(); + ListingPanel listingPanel = cvService.getListingPanel(); + FieldPanel fp = listingPanel.getFieldPanel(); List visibleLayouts = fp.getVisibleLayouts(); BigInteger startIndex = visibleLayouts.get(0).getIndex(); BigInteger endIndex = visibleLayouts.get(visibleLayouts.size() - 1).getIndex(); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/services/CodeViewerService.java b/Ghidra/Features/Base/src/main/java/ghidra/app/services/CodeViewerService.java index bc36f05086..b586700c22 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/services/CodeViewerService.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/services/CodeViewerService.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. @@ -34,39 +34,48 @@ import ghidra.program.util.ProgramLocation; import ghidra.program.util.ProgramSelection; /** - * Service provided by a plugin that shows the listing from a Program, i.e., a - * Code Viewer. The service allows other plugins to add components and - * actions local to the Code Viewer. - * - * + * Service provided by a plugin that shows the Code Viewer listing for a Program. The service + * allows other plugins to add components and actions local to the Code Viewer. */ @ServiceInfo(defaultProvider = CodeBrowserPlugin.class) public interface CodeViewerService { /** * Add a provider that shows an overview of the program. + *

+ * Note: the provider passed here will only appear in the primary code viewer, not in any of the + * snapshot windows. to have an overview provider appear in all windows, you must create a + * {@link ListingOverviewProviderService} implementation instead of using this method. + * * @param overviewProvider provider to add */ - public void addOverviewProvider(OverviewProvider overviewProvider); + public void addOverviewProvider(ListingOverviewProvider overviewProvider); /** * Remove a provider that shows an overview of the program. * @param overviewProvider provider to remove + * @see CodeViewerService#addOverviewProvider(ListingOverviewProvider) */ - public void removeOverviewProvider(OverviewProvider overviewProvider); + public void removeOverviewProvider(ListingOverviewProvider overviewProvider); /** * Add a provider that shows markers in a program for the portion * that is visible. * @param marginProvider provider to add + * @deprecated clients that wish to be margin providers should now create a + * {@link ListingMarginProviderService}. */ - public void addMarginProvider(MarginProvider marginProvider); + @Deprecated(since = "12.1", forRemoval = true) + public void addMarginProvider(ListingMarginProvider marginProvider); /** * Remove a provider that shows markers in a program for the portion * that is visible. * @param marginProvider provider to remove + * @deprecated clients that wish to be margin providers should now create a + * {@link ListingMarginProviderService}. */ - public void removeMarginProvider(MarginProvider marginProvider); + @Deprecated(since = "12.1", forRemoval = true) + public void removeMarginProvider(ListingMarginProvider marginProvider); /** * Add an action that is local to the Code Viewer. @@ -88,13 +97,13 @@ public interface CodeViewerService { /** * Add a listener that is notified when a mouse button is pressed. - * @param listener + * @param listener the listener */ public void addButtonPressedListener(ButtonPressedListener listener); /** * Remove the button pressed listener. - * @param listener + * @param listener the listener */ public void removeButtonPressedListener(ButtonPressedListener listener); @@ -114,7 +123,8 @@ public interface CodeViewerService { public void removeHighlightProvider(ListingHighlightProvider provider, Program program); /** - * Set a listing panel on the code viewer. + * Set a listing panel on the code viewer. The given listing panel is a secondary listing panel + * to be displayed along with the primary listing panel. * @param listingPanel the panel to add. */ public void setListingPanel(ListingPanel listingPanel); @@ -127,11 +137,12 @@ public interface CodeViewerService { /** * Remove the given listing panel from the code viewer. + * @param listingPanel the listing panel */ public void removeListingPanel(ListingPanel listingPanel); /** - * Get Current view that the CodeViewer is showing. + * @return the current set of addresses that the CodeViewer is showing.} */ public AddressSetView getView(); @@ -145,15 +156,21 @@ public interface CodeViewerService { public boolean goTo(ProgramLocation loc, boolean centerOnScreen); /** - * Return the fieldPanel. + * Returns the current field panel. + * @return the current field panel. + * @deprecated use {@link #getListingPanel()} to get the field panel */ + @Deprecated(since = "12.1", forRemoval = true) public FieldPanel getFieldPanel(); /** - * Returns the current address-index-map + * {@return the current address index map.} */ public AddressIndexMap getAddressIndexMap(); + /** + * {@return the format manager.} + */ public FormatManager getFormatManager(); /** diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/services/ListingMarginProviderService.java b/Ghidra/Features/Base/src/main/java/ghidra/app/services/ListingMarginProviderService.java new file mode 100644 index 0000000000..5e89c4bdf1 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/services/ListingMarginProviderService.java @@ -0,0 +1,37 @@ +/* ### + * 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.services; + +import ghidra.app.util.viewer.listingpanel.ListingMarginProvider; + +/** + * A service to provide a widget that is designed to appear on the left-hand side of the Code Viewer + */ +public interface ListingMarginProviderService { + + /** + * Creates a new margin provider. + * @return the provider + */ + public ListingMarginProvider createMarginProvider(); + + /** + * True if this service is the owner of the given provider. + * @param provider the provider to check + * @return true if the owner + */ + public boolean isOwner(ListingMarginProvider provider); +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/services/ListingOverviewProviderService.java b/Ghidra/Features/Base/src/main/java/ghidra/app/services/ListingOverviewProviderService.java new file mode 100644 index 0000000000..5694373b10 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/services/ListingOverviewProviderService.java @@ -0,0 +1,34 @@ +/* ### + * 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.services; + +import ghidra.app.util.viewer.listingpanel.ListingOverviewProvider; + +public interface ListingOverviewProviderService { + + /** + * Creates a new overview provider. + * @return the provider + */ + public ListingOverviewProvider createOverviewProvider(); + + /** + * True if this service is the owner of the given provider. + * @param provider the provider to check + * @return true if the owner + */ + public boolean isOwner(ListingOverviewProvider provider); +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/services/MarkerSet.java b/Ghidra/Features/Base/src/main/java/ghidra/app/services/MarkerSet.java index 50c61ac9bf..cc1538f096 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/services/MarkerSet.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/services/MarkerSet.java @@ -18,6 +18,7 @@ package ghidra.app.services; import java.awt.Color; import ghidra.program.model.address.*; +import ghidra.util.UniversalID; /** * Defines methods for working with a set of addresses that correspond to markers. @@ -25,6 +26,19 @@ import ghidra.program.model.address.*; */ public interface MarkerSet extends Comparable { + /** + * Sets an optional owner ID that signals when this marker set should be painted. A null ID + * means that this marker set is global and should always be painted. Otherwise, this marker + * set will be painted when its owner ID matches the owner ID being painted. + * @param ownerId the ID + */ + public void setOwnerId(UniversalID ownerId); + + /** + * {@return the owner ID. See #setOwner(UniversalID).} + */ + public UniversalID getOwnerId(); + /** * Add a marker at the address * @param addr the address @@ -151,8 +165,9 @@ public interface MarkerSet extends Comparable { public void setMarkerColor(Color color); /** - * Set the marker manager listener to use for user interaction - * with markers owned by this manager. + * Set the marker set descriptor. This allows clients to control tooltip text and program + * location generation for individual markers on-the-fly. + * * @param markerDescriptor the descriptor */ public void setMarkerDescriptor(MarkerDescriptor markerDescriptor); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/AbstractLibrarySupportLoader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/AbstractLibrarySupportLoader.java index 95ea18face..bdbc9eda57 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/AbstractLibrarySupportLoader.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/AbstractLibrarySupportLoader.java @@ -965,7 +965,7 @@ public abstract class AbstractLibrarySupportLoader extends AbstractProgramLoader * A library that has not been processed by the loader yet * * @param name The name of the library - * @param depth The recursive load depth of the library (based on the original binary being + * @param column The recursive load depth of the library (based on the original binary being * loaded) * @param temporary True if the library is temporary and should be discarded prior to returning * from the load diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingMarginProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingMarginProvider.java new file mode 100644 index 0000000000..e077998105 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingMarginProvider.java @@ -0,0 +1,88 @@ +/* ### + * 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.util.viewer.listingpanel; + +import javax.swing.JComponent; + +import ghidra.app.util.viewer.util.AddressIndexMap; +import ghidra.program.util.MarkerLocation; +import ghidra.program.util.ProgramLocation; +import ghidra.util.UniversalID; + +/** + * Interface for objects that want to add a component to the listing's left margin. + */ +public interface ListingMarginProvider { + + /** + * Sets the optional owner ID to be used with this margin provider. Implementations may use + * this ID to determine when they should paint. + * @param ownerId the ID + */ + + /** + * Sets an optional owner ID that signals when the markers for this provider should be painted. + * A null ID means that this provider is a non-snapshot provider and should paint all markers. + * A non-null ID means this provider's markers will be painted when the marker's owner ID + * this provider's ID + * . + * @param ownerId the ID + */ + public void setOwnerId(UniversalID ownerId); + + /** + * Get the component to show the margin markers. + * @return the component + */ + public JComponent getComponent(); + + /** + * Return true if can be resized. + * @return true if can be resized. + */ + public boolean isResizeable(); + + /** + * Called to notify this margin provider that the current screen information has changed. + * + * @param listingPanel the listing panel. + * @param addressIndexMap the address index map to use. + * @param pixelMap the vertical pixel map to use. + */ + public void screenDataChanged(ListingPanel listingPanel, AddressIndexMap addressIndexMap, + VerticalPixelAddressMap pixelMap); + + /** + * Called from the client when their location changes internally. This is different from a tool + * location event, which is considered a global event. + * @param location the location + */ + public void setLocation(ProgramLocation location); + + /** + * Get the marker location for the given x, y point. + * + * @param x the horizontal coordinate. + * @param y the vertical coordinate. + * @return the location + */ + public MarkerLocation getMarkerLocation(int x, int y); + + /** + * Called when the client is done with this provider. + */ + public void dispose(); +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/OverviewProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingOverviewProvider.java similarity index 65% rename from Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/OverviewProvider.java rename to Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingOverviewProvider.java index be3abd49ff..ee64c84a9a 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/OverviewProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingOverviewProvider.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. @@ -22,28 +22,32 @@ import ghidra.app.util.viewer.util.AddressIndexMap; import ghidra.program.model.listing.Program; /** - * Interface implemented by classes that provide overview components to the right side of the - * listing. + * An overview component that will be placed to the right side of the listing. */ -public interface OverviewProvider { +public interface ListingOverviewProvider { /** * Returns the component to display in the right margin of the listing. * @return the component */ - JComponent getComponent(); + public JComponent getComponent(); /** - * Sets the current program and associated address-index map + * Called to notify this margin provider that the current screen information has changed. * - * @param program the program to use. - * @param map the address-index map to use. + * @param program the program to use + * @param map the address index map to use */ - void setProgram(Program program, AddressIndexMap map); + public void screenDataChanged(Program program, AddressIndexMap map); /** * Set the component provider that this overview navigates * * @param navigatable the navigatable provider */ - void setNavigatable(Navigatable navigatable); + public void setNavigatable(Navigatable navigatable); + + /** + * Clients call this when they are done with this provider. + */ + public void dispose(); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingPanel.java index 0797acceb9..f867d84028 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingPanel.java @@ -22,6 +22,7 @@ import java.util.ArrayList; import java.util.List; import javax.swing.*; +import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import docking.action.DockingActionIf; @@ -31,10 +32,13 @@ import docking.widgets.fieldpanel.field.Field; import docking.widgets.fieldpanel.listener.*; import docking.widgets.fieldpanel.support.*; import docking.widgets.indexedscrollpane.IndexedScrollPane; +import generic.theme.GIcon; import generic.theme.GThemeDefaults.Colors; +import ghidra.app.nav.Navigatable; import ghidra.app.plugin.core.codebrowser.LayeredColorModel; +import ghidra.app.plugin.core.codebrowser.MarkerServiceBackgroundColorModel; import ghidra.app.plugin.core.codebrowser.hover.ListingHoverService; -import ghidra.app.services.ButtonPressedListener; +import ghidra.app.services.*; import ghidra.app.util.ListingHighlightProvider; import ghidra.app.util.viewer.field.*; import ghidra.app.util.viewer.format.FieldHeader; @@ -44,14 +48,15 @@ import ghidra.program.model.address.*; import ghidra.program.model.listing.*; import ghidra.program.model.symbol.*; import ghidra.program.util.*; -import ghidra.util.Msg; -import ghidra.util.Swing; +import ghidra.util.*; import ghidra.util.layout.HorizontalLayout; public class ListingPanel extends JPanel implements FieldMouseListener, FieldLocationListener, FieldSelectionListener, LayoutListener { public static final int DEFAULT_DIVIDER_LOCATION = 70; + private static final Icon CURSOR_LOC_ICON = + new GIcon("icon.plugin.codebrowser.cursor.location"); private FormatManager formatManager; private ListingModelAdapter layoutModel; @@ -61,6 +66,7 @@ public class ListingPanel extends JPanel implements FieldMouseListener, FieldLoc private JSplitPane splitPane; private int splitPaneDividerLocation = DEFAULT_DIVIDER_LOCATION; + private FocusingMouseListener focusingMouseListener = new FocusingMouseListener(); private ProgramLocationListener programLocationListener; private ProgramSelectionListener programSelectionListener; private ProgramSelectionListener liveProgramSelectionListener; @@ -84,8 +90,21 @@ public class ListingPanel extends JPanel implements FieldMouseListener, FieldLoc private ListingHoverProvider listingHoverHandler; - private List marginProviders = new ArrayList<>(); - private List overviewProviders = new ArrayList<>(); + private List marginProviders = new ArrayList<>(); + private List overviewProviders = new ArrayList<>(); + + private String currentTextSelection; + private boolean useMarkerNameSuffix; + private UniversalID marginOwnerId = UniversalIdGenerator.nextID(); + + private ChangeListener markerChangeListener; + private MarkerService markerService; + private Color cursorLineHighlightColor; + private boolean isHighlightCursorLineEnabled; + private MarkerSet selectionMarkers; + private MarkerSet highlightMarkers; + private MarkerSet cursorMarkers; + private VerticalPixelAddressMapImpl pixmap; private PropertyBasedBackgroundColorModel propertyBasedColorModel; private LayeredColorModel layeredColorModel; @@ -103,8 +122,6 @@ public class ListingPanel extends JPanel implements FieldMouseListener, FieldLoc }; private List displayListeners = new ArrayList<>(); - private String currentTextSelection; - /** * Constructs a new ListingPanel using the given FormatManager * @@ -128,7 +145,7 @@ public class ListingPanel extends JPanel implements FieldMouseListener, FieldLoc fieldPanel.addComponentListener(new ComponentAdapter() { @Override public void componentResized(ComponentEvent e) { - for (MarginProvider provider : marginProviders) { + for (ListingMarginProvider provider : marginProviders) { provider.getComponent().invalidate(); } validate(); @@ -138,6 +155,8 @@ public class ListingPanel extends JPanel implements FieldMouseListener, FieldLoc String viewName = "Assembly Listing View"; fieldPanel.setName(viewName); fieldPanel.getAccessibleContext().setAccessibleName(viewName); + + markerChangeListener = new MarkerChangeListener(); } /** @@ -301,8 +320,11 @@ public class ListingPanel extends JPanel implements FieldMouseListener, FieldLoc private void updateProviders() { AddressIndexMap addressIndexMap = layoutModel.getAddressIndexMap(); - for (OverviewProvider element : overviewProviders) { - element.setProgram(getProgram(), addressIndexMap); + for (ListingMarginProvider provider : marginProviders) { + provider.screenDataChanged(this, addressIndexMap, pixmap); + } + for (ListingOverviewProvider provider : overviewProviders) { + provider.screenDataChanged(getProgram(), addressIndexMap); } for (ChangeListener indexMapChangeListener : indexMapChangeListeners) { indexMapChangeListener.stateChanged(null); @@ -323,20 +345,99 @@ public class ListingPanel extends JPanel implements FieldMouseListener, FieldLoc layoutModel.dataChanged(updateImmediately); } + public void removeMarginService(ListingMarginProviderService service) { + for (ListingMarginProvider provider : marginProviders) { + if (service.isOwner(provider)) { + removeMarginProvider(provider); + provider.dispose(); + return; + } + } + } + + public void addMarginService(ListingMarginProviderService service, boolean isConnected) { + if (containsMarginProviver(service)) { + return; + } + + ListingMarginProvider provider = service.createMarginProvider(); + provider.setOwnerId(marginOwnerId); + addMarginProvider(provider); + } + + private boolean containsMarginProviver(ListingMarginProviderService service) { + for (ListingMarginProvider provider : marginProviders) { + if (service.isOwner(provider)) { + return true; + } + } + return false; + } + + public void removeOverviewService(ListingOverviewProviderService service) { + for (ListingOverviewProvider provider : overviewProviders) { + if (service.isOwner(provider)) { + removeOverviewProvider(provider); + provider.dispose(); + return; + } + } + } + + public void addOverviewService(ListingOverviewProviderService service, Navigatable navigatable, + boolean connected) { + if (containsOverviewProvider(service)) { + return; + } + + ListingOverviewProvider provider = service.createOverviewProvider(); + provider.setNavigatable(navigatable); + addOverviewProvider(provider); + } + + private boolean containsOverviewProvider(ListingOverviewProviderService service) { + for (ListingOverviewProvider provider : overviewProviders) { + if (service.isOwner(provider)) { + return true; + } + } + return false; + } + /** - * Adds the MarginProvider to this panel + * Removes the given margin provider from this panel * - * @param provider the MarginProvider that will provide components to display in this panel's - * left margin area. + * @param provider the MarginProvider to remove. */ - public void addMarginProvider(MarginProvider provider) { + public void removeMarginProvider(ListingMarginProvider provider) { + JComponent component = provider.getComponent(); + component.removeMouseListener(focusingMouseListener); + + marginProviders.remove(provider); + buildPanels(); + } + + /** + * Adds the margin provider to this panel. + *

+ * This method is for clients that create and manage their own listing panels that are not the + * main listing panel. + * + * @param provider the provider that will display in this listing panel's left margin area + */ + public void addMarginProvider(ListingMarginProvider provider) { + + JComponent component = provider.getComponent(); + component.removeMouseListener(focusingMouseListener); + component.addMouseListener(focusingMouseListener); + if (provider.isResizeable()) { marginProviders.add(0, provider); } else { marginProviders.add(provider); } - provider.setProgram(getProgram(), layoutModel.getAddressIndexMap(), pixmap); + provider.screenDataChanged(this, layoutModel.getAddressIndexMap(), pixmap); buildPanels(); } @@ -363,26 +464,26 @@ public class ListingPanel extends JPanel implements FieldMouseListener, FieldLoc return null; } JPanel rightPanel = new JPanel(new HorizontalLayout(0)); - for (OverviewProvider overviewProvider : overviewProviders) { + for (ListingOverviewProvider overviewProvider : overviewProviders) { rightPanel.add(overviewProvider.getComponent()); } return rightPanel; } private JComponent buildLeftComponent() { - List marginProviderList = getNonResizeableMarginProviders(); + List marginProviderList = getNonResizeableMarginProviders(); JPanel leftPanel = new JPanel(new ScrollpaneAlignedHorizontalLayout(scroller)); - for (MarginProvider marginProvider : marginProviderList) { + for (ListingMarginProvider marginProvider : marginProviderList) { leftPanel.add(marginProvider.getComponent()); } return leftPanel; } - private List getNonResizeableMarginProviders() { + private List getNonResizeableMarginProviders() { if (marginProviders.isEmpty()) { return marginProviders; } - MarginProvider firstMarginProvider = marginProviders.get(0); + ListingMarginProvider firstMarginProvider = marginProviders.get(0); if (firstMarginProvider.isResizeable()) { return marginProviders.subList(1, marginProviders.size()); } @@ -391,7 +492,7 @@ public class ListingPanel extends JPanel implements FieldMouseListener, FieldLoc private JComponent buildCenterComponent() { JComponent centerComponent = scroller; - MarginProvider resizeableMarginProvider = getResizeableMarginProvider(); + ListingMarginProvider resizeableMarginProvider = getResizeableMarginProvider(); if (resizeableMarginProvider != null) { if (splitPane != null) { splitPaneDividerLocation = splitPane.getDividerLocation(); @@ -414,11 +515,11 @@ public class ListingPanel extends JPanel implements FieldMouseListener, FieldLoc return centerComponent; } - private MarginProvider getResizeableMarginProvider() { + private ListingMarginProvider getResizeableMarginProvider() { if (marginProviders.isEmpty()) { return null; } - MarginProvider marginProvider = marginProviders.get(0); + ListingMarginProvider marginProvider = marginProviders.get(0); return marginProvider.isResizeable() ? marginProvider : null; } @@ -440,24 +541,22 @@ public class ListingPanel extends JPanel implements FieldMouseListener, FieldLoc indexMapChangeListeners.remove(listener); } - /** - * Removes the given margin provider from this panel - * - * @param provider the MarginProvider to remove. - */ - public void removeMarginProvider(MarginProvider provider) { - marginProviders.remove(provider); - buildPanels(); - } - /** * Adds the given OverviewProvider with will be displayed in this panels right margin area. + *

+ * This method is for clients that create and manage their own listing panels that are not the + * main listing panel. * * @param provider the OverviewProvider to display. */ - public void addOverviewProvider(OverviewProvider provider) { + public void addOverviewProvider(ListingOverviewProvider provider) { + + JComponent component = provider.getComponent(); + component.removeMouseListener(focusingMouseListener); + component.addMouseListener(focusingMouseListener); + overviewProviders.add(provider); - provider.setProgram(getProgram(), layoutModel.getAddressIndexMap()); + provider.screenDataChanged(getProgram(), layoutModel.getAddressIndexMap()); buildPanels(); } @@ -466,7 +565,11 @@ public class ListingPanel extends JPanel implements FieldMouseListener, FieldLoc * * @param provider the OverviewProvider to remove. */ - public void removeOverviewProvider(OverviewProvider provider) { + public void removeOverviewProvider(ListingOverviewProvider provider) { + + JComponent component = provider.getComponent(); + component.removeMouseListener(focusingMouseListener); + overviewProviders.remove(provider); buildPanels(); } @@ -525,8 +628,8 @@ public class ListingPanel extends JPanel implements FieldMouseListener, FieldLoc public void layoutsChanged(List layouts) { AddressIndexMap addrMap = layoutModel.getAddressIndexMap(); this.pixmap = new VerticalPixelAddressMapImpl(layouts, addrMap); - for (MarginProvider element : marginProviders) { - element.setProgram(getProgram(), addrMap, pixmap); + for (ListingMarginProvider provider : marginProviders) { + provider.screenDataChanged(this, addrMap, pixmap); } for (AddressSetDisplayListener listener : displayListeners) { @@ -594,6 +697,10 @@ public class ListingPanel extends JPanel implements FieldMouseListener, FieldLoc setListingModel(null); + for (ListingMarginProvider provider : marginProviders) { + provider.dispose(); + } + removeAll(); listingHoverHandler.dispose(); layoutModel.dispose(); @@ -885,11 +992,19 @@ public class ListingPanel extends JPanel implements FieldMouseListener, FieldLoc headerPanel.setTabLock(false); } + ProgramLocation pLoc = layoutModel.getProgramLocation(location, field); + if (pLoc == null) { + return; + } + if (programLocationListener != null) { - ProgramLocation pLoc = layoutModel.getProgramLocation(location, field); - if (pLoc != null) { - programLocationListener.programLocationChanged(pLoc, trigger); - } + programLocationListener.programLocationChanged(pLoc, trigger); + } + + setCursorMarkerAddress(pLoc.getAddress()); + + for (ListingMarginProvider provider : marginProviders) { + provider.setLocation(pLoc); } } @@ -1005,7 +1120,7 @@ public class ListingPanel extends JPanel implements FieldMouseListener, FieldLoc * * @return the providers */ - public List getMarginProviders() { + public List getMarginProviders() { return marginProviders; } @@ -1014,7 +1129,7 @@ public class ListingPanel extends JPanel implements FieldMouseListener, FieldLoc * * @return the providers */ - public List getOverviewProviders() { + public List getOverviewProviders() { return overviewProviders; } @@ -1182,8 +1297,16 @@ public class ListingPanel extends JPanel implements FieldMouseListener, FieldLoc * @param trigger the cause of the change */ public void setSelection(ProgramSelection sel, EventTrigger trigger) { + + Program program = getProgram(); + MarkerSet markers = getSelectionMarkers(program); + if (sel == null) { fieldPanel.setSelection(layoutModel.getFieldSelection(null), trigger); + + if (markers != null) { + markers.clearAll(); + } return; } @@ -1216,6 +1339,11 @@ public class ListingPanel extends JPanel implements FieldMouseListener, FieldLoc } } fieldPanel.setSelection(layoutModel.getFieldSelection(sel), trigger); + + if (markers != null) { + markers.clearAll(); + markers.add(sel); + } } /** @@ -1225,6 +1353,19 @@ public class ListingPanel extends JPanel implements FieldMouseListener, FieldLoc */ public void setHighlight(ProgramSelection highlight) { fieldPanel.setHighlight(layoutModel.getFieldSelection(highlight)); + + Program program = getProgram(); + MarkerSet markers = getHighlightMarkers(program); + if (markers == null) { + return; + } + + markers.clearAll(); + + if (highlight != null && program != null) { + markers.setAddressSet(highlight); + } + } public ProgramSelection getProgramHighlight() { @@ -1320,4 +1461,192 @@ public class ListingPanel extends JPanel implements FieldMouseListener, FieldLoc // we are not focusable, defer to contained field panel fieldPanel.removeFocusListener(l); } + +//================================================================================================== +// Markers +//================================================================================================== + + public void setUseMarkerNameSuffix(boolean b) { + // Note: this happens just after construction, so no need to recreate the markers + this.useMarkerNameSuffix = b; + } + + public void setMarkerService(MarkerService markerService) { + + if (this.markerService != null) { + this.markerService.removeChangeListener(markerChangeListener); + } + + if (markerService != null) { + markerService.addChangeListener(markerChangeListener); + } + else { + doClearMarkers(getProgram()); + } + + this.markerService = markerService; + } + + public void clearMarkers(Program program) { + doClearMarkers(program); + } + + private void doClearMarkers(Program program) { + if (markerService == null) { + return; + } + + if (program == null) { + return; // can happen during dispose after a programDeactivated() + } + + if (selectionMarkers != null) { + markerService.removeMarker(selectionMarkers, program); + selectionMarkers = null; + } + + if (highlightMarkers != null) { + markerService.removeMarker(highlightMarkers, program); + highlightMarkers = null; + } + + if (cursorMarkers != null) { + markerService.removeMarker(cursorMarkers, program); + cursorMarkers = null; + } + } + + public void setSelectionColor(Color color) { + + fieldPanel.setSelectionColor(color); + if (selectionMarkers != null) { + selectionMarkers.setMarkerColor(color); + } + } + + public void setHighlightColor(Color color) { + fieldPanel.setHighlightColor(color); + if (highlightMarkers != null) { + highlightMarkers.setMarkerColor(color); + } + } + + private String getMarkerName(String baseName) { + if (useMarkerNameSuffix) { + return baseName + ' ' + marginOwnerId.toString(); + } + return baseName; + } + + private MarkerSet getSelectionMarkers(Program program) { + if (markerService == null || program == null) { + return null; + } + + // already created + if (selectionMarkers != null) { + return selectionMarkers; + } + + String markerName = getMarkerName("Selection"); + Color color = fieldPanel.getSelectionColor(); + selectionMarkers = markerService.createAreaMarker(markerName, "Selection Display", + program, MarkerService.SELECTION_PRIORITY, false, true, false, color); + selectionMarkers.setOwnerId(marginOwnerId); + + return selectionMarkers; + } + + private MarkerSet getHighlightMarkers(Program program) { + if (markerService == null || program == null) { + return null; + } + + // already created + if (highlightMarkers != null) { + return highlightMarkers; + } + + String markerName = getMarkerName("Highlight"); + Color color = fieldPanel.getHighlightColor(); + highlightMarkers = markerService.createAreaMarker(markerName, "Highlight Display ", + program, MarkerService.HIGHLIGHT_PRIORITY, false, true, false, color); + highlightMarkers.setOwnerId(marginOwnerId); + + return highlightMarkers; + } + + private MarkerSet getCursorMarkers(Program program) { + if (markerService == null || program == null) { + return null; + } + + // already created + if (cursorMarkers != null) { + return cursorMarkers; + } + + String markerName = getMarkerName("Cursor"); + cursorMarkers = markerService.createPointMarker(markerName, "Cursor Location", + program, MarkerService.CURSOR_PRIORITY, true, true, isHighlightCursorLineEnabled, + cursorLineHighlightColor, CURSOR_LOC_ICON); + cursorMarkers.setOwnerId(marginOwnerId); + + return cursorMarkers; + } + + public void setCursorHighlightColor(Color cursorHighlightColor) { + this.cursorLineHighlightColor = cursorHighlightColor; + if (cursorMarkers != null) { + cursorMarkers.setMarkerColor(cursorHighlightColor); + } + } + + public void setHighlightCursorLineEnabled(boolean b) { + this.isHighlightCursorLineEnabled = b; + if (cursorMarkers != null) { + cursorMarkers.setColoringBackground(b); + } + } + + public void setCursorMarkerAddress(Address address) { + MarkerSet markers = getCursorMarkers(getProgram()); + if (markers != null) { + markers.clearAll(); + markers.add(address); + } + } + + public void updateBackgroundColorModel() { + if (markerService == null) { + setBackgroundColorModel(null); + } + else { + AddressIndexMap indexMap = getAddressIndexMap(); + setBackgroundColorModel( + new MarkerServiceBackgroundColorModel(markerService, indexMap)); + } + } +//================================================================================================== +// End Markers +//================================================================================================== + +//================================================================================================== +// Inner Classes +//================================================================================================== + + private class MarkerChangeListener implements ChangeListener { + + @Override + public void stateChanged(ChangeEvent e) { + fieldPanel.repaint(); + } + } + + private class FocusingMouseListener extends MouseAdapter { + @Override + public void mousePressed(MouseEvent e) { + fieldPanel.requestFocus(); + } + } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/MarginProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/MarginProvider.java deleted file mode 100644 index c7c9522680..0000000000 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/MarginProvider.java +++ /dev/null @@ -1,60 +0,0 @@ -/* ### - * 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.util.viewer.listingpanel; - -import javax.swing.JComponent; - -import ghidra.app.util.viewer.util.AddressIndexMap; -import ghidra.program.model.listing.Program; -import ghidra.program.util.MarkerLocation; - -/** - * Interface for objects that want to add a component to the listing's left margin. - */ -public interface MarginProvider { - - /** - * Get the component to show the margin markers. - * @return the component - */ - JComponent getComponent(); - - /** - * Return true if can be resized. - * @return true if can be resized. - */ - boolean isResizeable(); - - /** - * Set the program and associated maps. - * - * @param program the program to use. - * @param addressIndexMap the address-index map to use. - * @param pixelMap the vertical pixel map to use. - */ - void setProgram(Program program, AddressIndexMap addressIndexMap, - VerticalPixelAddressMap pixelMap); - - /** - * Get the marker location for the given x, y point. - * - * @param x the horizontal coordinate. - * @param y the vertical coordinate. - * @return the location - */ - public MarkerLocation getMarkerLocation(int x, int y); - -} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/listing/ListingCodeComparisonView.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/listing/ListingCodeComparisonView.java index d4555b53ab..393750701b 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/listing/ListingCodeComparisonView.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/listing/ListingCodeComparisonView.java @@ -394,8 +394,8 @@ public class ListingCodeComparisonView public Object getContextObjectForMarginPanels(ListingPanel panel, MouseEvent event) { Object source = event.getSource(); // Is event source a marker margin provider on the left side of the listing? - List marginProviders = panel.getMarginProviders(); - for (MarginProvider marginProvider : marginProviders) { + List marginProviders = panel.getMarginProviders(); + for (ListingMarginProvider marginProvider : marginProviders) { JComponent c = marginProvider.getComponent(); if (c == source) { MarkerLocation loc = marginProvider.getMarkerLocation(event.getX(), event.getY()); @@ -406,8 +406,8 @@ public class ListingCodeComparisonView } } // Is event source an overview provider on the right side of the listing? - List overviewProviders = panel.getOverviewProviders(); - for (OverviewProvider overviewProvider : overviewProviders) { + List overviewProviders = panel.getOverviewProviders(); + for (ListingOverviewProvider overviewProvider : overviewProviders) { JComponent c = overviewProvider.getComponent(); if (c == source) { return source; // Return the overview provider that was clicked. @@ -623,8 +623,8 @@ public class ListingCodeComparisonView private Object getContextForMarginPanels(ListingPanel lp, MouseEvent event) { Object source = event.getSource(); - List marginProvidersForLP = lp.getMarginProviders(); - for (MarginProvider marginProvider : marginProvidersForLP) { + List marginProvidersForLP = lp.getMarginProviders(); + for (ListingMarginProvider marginProvider : marginProvidersForLP) { JComponent c = marginProvider.getComponent(); if (c == source) { MarkerLocation loc = marginProvider.getMarkerLocation(event.getX(), event.getY()); @@ -634,8 +634,8 @@ public class ListingCodeComparisonView return source; } } - List overviewProvidersForLP = lp.getOverviewProviders(); - for (OverviewProvider overviewProvider : overviewProvidersForLP) { + List overviewProvidersForLP = lp.getOverviewProviders(); + for (ListingOverviewProvider overviewProvider : overviewProvidersForLP) { JComponent c = overviewProvider.getComponent(); if (c == source) { return source; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/listing/ListingDisplay.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/listing/ListingDisplay.java index 829f437ff8..4380d3376d 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/listing/ListingDisplay.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/listing/ListingDisplay.java @@ -18,6 +18,7 @@ package ghidra.features.base.codecompare.listing; import static ghidra.GhidraOptions.*; import java.awt.Color; +import java.util.List; import javax.swing.Icon; @@ -94,9 +95,12 @@ public class ListingDisplay implements ListingDiffChangeListener { private void createMarkerManager(String owner) { markerManager = new ListingDisplayMarkerManager(tool, owner); markerManager.addChangeListener(e -> listingPanel.repaint()); - MarginProvider marginProvider = markerManager.getMarginProvider(); + + // Manually install our custom margin provider. We are not calling setMarginService(), + // which means that many of the listing markers will not work in the compare view. + ListingMarginProvider marginProvider = markerManager.createMarginProvider(); listingPanel.addMarginProvider(marginProvider); - OverviewProvider overviewProvider = markerManager.getOverviewProvider(); + ListingOverviewProvider overviewProvider = markerManager.createOverviewProvider(); listingPanel.addOverviewProvider(overviewProvider); } @@ -171,7 +175,12 @@ public class ListingDisplay implements ListingDiffChangeListener { markerManager.clearAll(); listingPanel.setView(view); AddressIndexMap indexMap = listingPanel.getAddressIndexMap(); - markerManager.getOverviewProvider().setProgram(program, indexMap); + + List providers = listingPanel.getOverviewProviders(); + for (ListingOverviewProvider provider : providers) { + provider.screenDataChanged(program, indexMap); + } + listingPanel.setBackgroundColorModel( new MarkerServiceBackgroundColorModel(markerManager, program, indexMap)); setUpAreaMarkerSets(program, name); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/program/util/MarkerLocation.java b/Ghidra/Features/Base/src/main/java/ghidra/program/util/MarkerLocation.java index 7ba9c5ce6d..f6a8df4304 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/program/util/MarkerLocation.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/program/util/MarkerLocation.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. @@ -68,11 +68,11 @@ public class MarkerLocation implements Serializable { } /** - * Returns the Marker Manager. + * Returns the marker set. * - * @return the marker manager + * @return the marker set */ - public MarkerSet getMarkerManager() { + public MarkerSet getMarkerSet() { return markers; } diff --git a/Ghidra/Features/Base/src/main/java/help/screenshot/AbstractScreenShotGenerator.java b/Ghidra/Features/Base/src/main/java/help/screenshot/AbstractScreenShotGenerator.java index b3ddf032a3..e791f29295 100644 --- a/Ghidra/Features/Base/src/main/java/help/screenshot/AbstractScreenShotGenerator.java +++ b/Ghidra/Features/Base/src/main/java/help/screenshot/AbstractScreenShotGenerator.java @@ -64,7 +64,7 @@ import ghidra.app.util.viewer.field.FieldFactory; import ghidra.app.util.viewer.format.FieldFormatModel; import ghidra.app.util.viewer.format.FormatManager; import ghidra.app.util.viewer.listingpanel.ListingPanel; -import ghidra.app.util.viewer.listingpanel.MarginProvider; +import ghidra.app.util.viewer.listingpanel.ListingMarginProvider; import ghidra.docking.settings.SettingsDefinition; import ghidra.framework.ToolUtils; import ghidra.framework.plugintool.Plugin; @@ -1247,9 +1247,9 @@ public abstract class AbstractScreenShotGenerator extends AbstractGhidraHeadedIn CodeBrowserPlugin plugin = getPlugin(tool, CodeBrowserPlugin.class); ListingPanel listingPanel = plugin.getListingPanel(); @SuppressWarnings("unchecked") - List list = - (List) getInstanceField("marginProviders", listingPanel); - for (MarginProvider marginProvider : list) { + List list = + (List) getInstanceField("marginProviders", listingPanel); + for (ListingMarginProvider marginProvider : list) { listingPanel.removeMarginProvider(marginProvider); } }); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/comments/CommentsPluginTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/comments/CommentsPluginTest.java index 00cc19a252..e97dfad850 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/comments/CommentsPluginTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/comments/CommentsPluginTest.java @@ -669,6 +669,8 @@ public class CommentsPluginTest extends AbstractGhidraHeadedIntegrationTest { addReference(0x1001020, 0x1008294, RefType.DATA); addReference(0x1001030, 0x1008394, RefType.DATA); + waitForProgram(program); + Address srcAddr = addr(0x01006990); CodeUnit cu = program.getListing().getCodeUnitAt(srcAddr); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/marker/MarkerTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/marker/MarkerTest.java index c0e25b4549..c6b4350f30 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/marker/MarkerTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/marker/MarkerTest.java @@ -44,17 +44,15 @@ import ghidra.app.plugin.core.clear.ClearPlugin; import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin; import ghidra.app.plugin.core.highlight.SetHighlightPlugin; import ghidra.app.services.*; -import ghidra.app.util.viewer.listingpanel.OverviewProvider; import ghidra.app.util.viewer.util.AddressIndexMap; import ghidra.framework.cmd.CompoundCmd; import ghidra.framework.plugintool.PluginTool; import ghidra.program.model.address.*; import ghidra.program.model.listing.BookmarkType; import ghidra.program.model.listing.Program; -import ghidra.program.util.ProgramLocation; import ghidra.program.util.ProgramSelection; import ghidra.test.*; -import ghidra.util.Msg; +import ghidra.util.datastruct.WeakSet; public class MarkerTest extends AbstractGhidraHeadedIntegrationTest { @@ -162,8 +160,13 @@ public class MarkerTest extends AbstractGhidraHeadedIntegrationTest { setSelection(fp, sel); MarkerManager mm = (MarkerManager) markerService; - OverviewProvider op = mm.getOverviewProvider(); - JPanel navPanel = (JPanel) op.getComponent(); + + @SuppressWarnings("unchecked") + WeakSet overviewProviders = + (WeakSet) getInstanceField("overviewProviders", mm); + MarkerOverviewProvider provider = overviewProviders.iterator().next(); + + JPanel navPanel = (JPanel) provider.getComponent(); waitForProgram(program); @@ -224,8 +227,13 @@ public class MarkerTest extends AbstractGhidraHeadedIntegrationTest { assertTrue(addrSet.contains(addr("0xf000131b"))); MarkerManager mm = (MarkerManager) markerService; - OverviewProvider op = mm.getOverviewProvider(); - JPanel navPanel = (JPanel) op.getComponent(); + + @SuppressWarnings("unchecked") + WeakSet overviewProviders = + (WeakSet) getInstanceField("overviewProviders", mm); + MarkerOverviewProvider provider = overviewProviders.iterator().next(); + + JPanel navPanel = (JPanel) provider.getComponent(); waitForProgram(program); @@ -278,7 +286,12 @@ public class MarkerTest extends AbstractGhidraHeadedIntegrationTest { MouseEvent dummyEvent = new MouseEvent(cb.getFieldPanel(), (int) time, time, 0, x, y, 1, false); MarkerManager mm = (MarkerManager) markerService; - String tooltip = runSwing(() -> mm.generateToolTip(dummyEvent)); + + @SuppressWarnings("unchecked") + WeakSet marginProviders = + (WeakSet) getInstanceField("marginProviders", mm); + MarkerMarginProvider provider = marginProviders.iterator().next(); + String tooltip = runSwing(() -> provider.generateToolTip(dummyEvent)); assertEquals( "Cursor
Note [TEST]: comment
Changes: Unsaved
", tooltip); @@ -316,11 +329,11 @@ public class MarkerTest extends AbstractGhidraHeadedIntegrationTest { MouseEvent dummyEvent = new MouseEvent(cb.getFieldPanel(), (int) System.currentTimeMillis(), System.currentTimeMillis(), 0, x, y, 1, false); - // debug - ProgramLocation location = cb.getListingPanel().getProgramLocation(dummyEvent.getPoint()); - Msg.debug(this, "location for point: " + location + "; at " + location.getAddress()); - - String tooltip = runSwing(() -> mm.generateToolTip(dummyEvent)); + @SuppressWarnings("unchecked") + WeakSet marginProviders = + (WeakSet) getInstanceField("marginProviders", mm); + MarkerMarginProvider provider = marginProviders.iterator().next(); + String tooltip = runSwing(() -> provider.generateToolTip(dummyEvent)); assertNotNull("No tooltip for field: " + cb.getCurrentField() + "\n\tat address: " + cb.getCurrentAddress(), tooltip); diff --git a/Ghidra/Features/FunctionID/src/main/java/ghidra/feature/fid/plugin/FidPlugin.java b/Ghidra/Features/FunctionID/src/main/java/ghidra/feature/fid/plugin/FidPlugin.java index 34c6d14bd0..5863e181cd 100644 --- a/Ghidra/Features/FunctionID/src/main/java/ghidra/feature/fid/plugin/FidPlugin.java +++ b/Ghidra/Features/FunctionID/src/main/java/ghidra/feature/fid/plugin/FidPlugin.java @@ -83,9 +83,9 @@ public class FidPlugin extends ProgramPlugin implements ChangeListener { } @Override - protected void cleanup() { + protected void dispose() { fidFileManager.removeChangeListener(this); - super.cleanup(); + super.dispose(); } /** diff --git a/Ghidra/Features/ProgramDiff/src/main/java/ghidra/app/plugin/core/diff/ProgramDiffPlugin.java b/Ghidra/Features/ProgramDiff/src/main/java/ghidra/app/plugin/core/diff/ProgramDiffPlugin.java index d3370e2d74..90f47bd454 100644 --- a/Ghidra/Features/ProgramDiff/src/main/java/ghidra/app/plugin/core/diff/ProgramDiffPlugin.java +++ b/Ghidra/Features/ProgramDiff/src/main/java/ghidra/app/plugin/core/diff/ProgramDiffPlugin.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. @@ -133,7 +133,6 @@ public class ProgramDiffPlugin extends ProgramPlugin private DiffController diffControl; private Program primaryProgram; private Program secondaryDiffProgram; - private AddressFactory p2AddressFactory; private ProgramDiffDetails diffDetails; private ProgramSelection p2DiffHighlight; @@ -286,33 +285,39 @@ public class ProgramDiffPlugin extends ProgramPlugin if (primaryProgram != null && !showingSecondProgram) { return; } + p1ViewAddrSet = p1AddressSet; + if (!showingSecondProgram) { + return; + } - if (showingSecondProgram) { - ProgramSelection previousP1Selection = currentSelection; - ProgramSelection previousP2DiffHighlight = p2DiffHighlight; - ProgramSelection previousP2Selection = p2Selection; + ProgramSelection previousP1Selection = currentSelection; + ProgramSelection previousP2DiffHighlight = p2DiffHighlight; + ProgramSelection previousP2Selection = p2Selection; - AddressSet p2ViewAddrSet = - DiffUtility.getCompatibleAddressSet(p1ViewAddrSet, secondaryDiffProgram); - diffListingPanel.setView(p2ViewAddrSet); - FieldPanel fp = diffListingPanel.getFieldPanel(); + AddressSet p2ViewAddrSet = + DiffUtility.getCompatibleAddressSet(p1ViewAddrSet, secondaryDiffProgram); + diffListingPanel.setView(p2ViewAddrSet); + FieldPanel fp = diffListingPanel.getFieldPanel(); - AddressSet p1AddressSetAsP2 = - DiffUtility.getCompatibleAddressSet(p1AddressSet, secondaryDiffProgram); - AddressIndexMap p2IndexMap = new AddressIndexMap(p1AddressSetAsP2); - markerManager.getOverviewProvider().setProgram(secondaryDiffProgram, p2IndexMap); - fp.setBackgroundColorModel(new MarkerServiceBackgroundColorModel(markerManager, - secondaryDiffProgram, p2IndexMap)); + AddressSet p1AddressSetAsP2 = + DiffUtility.getCompatibleAddressSet(p1AddressSet, secondaryDiffProgram); + AddressIndexMap p2IndexMap = new AddressIndexMap(p1AddressSetAsP2); + List providers = diffListingPanel.getOverviewProviders(); + for (ListingOverviewProvider provider : providers) { + provider.screenDataChanged(secondaryDiffProgram, p2IndexMap); + } - currentSelection = previousP1Selection; - p2DiffHighlight = previousP2DiffHighlight; + fp.setBackgroundColorModel(new MarkerServiceBackgroundColorModel(markerManager, + secondaryDiffProgram, p2IndexMap)); - p2Selection = previousP2Selection; - setProgram2Selection(p2Selection); - if (p2DiffHighlight != null) { - setDiffHighlight(p2DiffHighlight); - } + currentSelection = previousP1Selection; + p2DiffHighlight = previousP2DiffHighlight; + + p2Selection = previousP2Selection; + setProgram2Selection(p2Selection); + if (p2DiffHighlight != null) { + setDiffHighlight(p2DiffHighlight); } } @@ -556,9 +561,7 @@ public class ProgramDiffPlugin extends ProgramPlugin return; } if (currentSelection == null) { - AddressFactory p1AddressFactory = - (primaryProgram != null) ? primaryProgram.getAddressFactory() : null; - currentSelection = new ProgramSelection(p1AddressFactory); + currentSelection = new ProgramSelection(); } actionManager.setP1SelectToP2ActionEnabled( secondaryDiffProgram != null && !currentSelection.isEmpty()); @@ -605,8 +608,13 @@ public class ProgramDiffPlugin extends ProgramPlugin diffListingPanel.setProgramLocationListener(this); diffListingPanel.setProgramSelectionListener(this); diffListingPanel.getFieldPanel().addFieldMouseListener(new MyFieldMouseListener()); - diffListingPanel.addMarginProvider(markerManager.getMarginProvider()); - diffListingPanel.addOverviewProvider(markerManager.getOverviewProvider()); + + // Manually install our custom margin provider. We are not calling setMarginService(), + // which means that many of the listing markers will not work in the diff view. + MarkerService markerService = tool.getService(MarkerService.class); + diffListingPanel.addMarginProvider(markerService.createMarginProvider()); + diffListingPanel.addOverviewProvider(markerService.createOverviewProvider()); + diffNavigatable = new DiffNavigatable(this, codeViewerService.getNavigatable()); diffFieldNavigator = new FieldNavigator(diffServiceProvider, diffNavigatable); diffListingPanel.addButtonPressedListener(diffFieldNavigator); @@ -678,9 +686,7 @@ public class ProgramDiffPlugin extends ProgramPlugin ProgramSelection getCurrentSelection() { if (currentSelection == null) { - AddressFactory p1AddressFactory = - (primaryProgram != null) ? primaryProgram.getAddressFactory() : null; - currentSelection = new ProgramSelection(p1AddressFactory); + currentSelection = new ProgramSelection(); } return currentSelection; } @@ -753,10 +759,8 @@ public class ProgramDiffPlugin extends ProgramPlugin // Make sure that the Diff selection is to the code unit boundary. ProgramSelection p2CodeUnitSelection = new ProgramSelection(DiffUtility.getCodeUnitSet(newP2Selection, secondaryDiffProgram)); - AddressFactory p1AddressFactory = - (primaryProgram != null) ? primaryProgram.getAddressFactory() : null; ProgramSelection intersection = - new ProgramSelection(p2AddressFactory, p2CodeUnitSelection.intersect(p2DiffHighlight)); + new ProgramSelection(p2CodeUnitSelection.intersect(p2DiffHighlight)); p2Selection = intersection; AddressSet p2SelectionAsP1Set = @@ -767,8 +771,7 @@ public class ProgramDiffPlugin extends ProgramPlugin // of the MultiListing Layout being used. /////////////////////////////////////////// - ProgramSelection p2SelectionAsP1 = - new ProgramSelection(p1AddressFactory, p2SelectionAsP1Set); + ProgramSelection p2SelectionAsP1 = new ProgramSelection(p2SelectionAsP1Set); runSwing(() -> { MarkerSet selectionMarkers = getSelectionMarkers(); selectionMarkers.clearAll(); @@ -783,7 +786,7 @@ public class ProgramDiffPlugin extends ProgramPlugin actionManager.setP1SelectToP2ActionEnabled( (secondaryDiffProgram != null) && !currentSelection.isEmpty()); firePluginEvent(new ProgramSelectionPluginEvent(this.getName(), - new ProgramSelection(p1AddressFactory, currentSelection), primaryProgram)); + new ProgramSelection(currentSelection), primaryProgram)); } } @@ -873,7 +876,7 @@ public class ProgramDiffPlugin extends ProgramPlugin } AddressSet p2DiffSet = DiffUtility.getCompatibleAddressSet(p1DiffSet, secondaryDiffProgram); - ProgramSelection p2DiffSelection = new ProgramSelection(p2AddressFactory, p2DiffSet); + ProgramSelection p2DiffSelection = new ProgramSelection(p2DiffSet); p2DiffHighlight = p2DiffSelection; AddressSet p2DiffSetAsP1 = DiffUtility.getCompatibleAddressSet(p2DiffSet, primaryProgram); @@ -918,7 +921,7 @@ public class ProgramDiffPlugin extends ProgramPlugin if (diffControl.hasNext()) { diffControl.next(); } - setProgram2Selection(new ProgramSelection(p2AddressFactory, getDiffHighlightBlock())); + setProgram2Selection(new ProgramSelection(getDiffHighlightBlock())); } void previousDiff() { @@ -926,7 +929,7 @@ public class ProgramDiffPlugin extends ProgramPlugin if (diffControl.hasPrevious()) { diffControl.previous(); } - setProgram2Selection(new ProgramSelection(p2AddressFactory, getDiffHighlightBlock())); + setProgram2Selection(new ProgramSelection(getDiffHighlightBlock())); } private void clearDiff() { @@ -992,8 +995,7 @@ public class ProgramDiffPlugin extends ProgramPlugin AddressSet p1IgnoreSet = DiffUtility.getCompatibleAddressSet(p2IgnoreSet, primaryProgram); diffControl.ignore(p1IgnoreSet, null); - p2DiffHighlight = - new ProgramSelection(p2AddressFactory, p2DiffHighlight.subtract(p2IgnoreSet)); + p2DiffHighlight = new ProgramSelection(p2DiffHighlight.subtract(p2IgnoreSet)); adjustDiffDisplay(); @@ -1047,7 +1049,7 @@ public class ProgramDiffPlugin extends ProgramPlugin } AddressSetView p2ViewAddrSet = DiffUtility.getCompatibleAddressSet(adjustedView, secondaryDiffProgram); - setProgram2Selection(new ProgramSelection(p2AddressFactory, p2ViewAddrSet)); + setProgram2Selection(new ProgramSelection(p2ViewAddrSet)); } finally { diffListingPanel.getFieldPanel().requestFocus(); @@ -1078,7 +1080,6 @@ public class ProgramDiffPlugin extends ProgramPlugin primaryProgram.removeListener(this); primaryProgram = null; secondaryDiffProgram = null; - p2AddressFactory = null; } sameProgramContext = false; updatePgm2Enablement(); @@ -1221,7 +1222,7 @@ public class ProgramDiffPlugin extends ProgramPlugin if (!currentSelection.isEmpty()) { AddressSet p2SelectionSet = DiffUtility.getCompatibleAddressSet(currentSelection, secondaryDiffProgram); - setProgram2Selection(new ProgramSelection(p2AddressFactory, + setProgram2Selection(new ProgramSelection( DiffUtility.getCodeUnitSet(p2SelectionSet, secondaryDiffProgram))); } if (p2Selection.isEmpty()) { @@ -1630,7 +1631,6 @@ public class ProgramDiffPlugin extends ProgramPlugin primaryProgram = currentProgram; secondaryDiffProgram = newProgram; - p2AddressFactory = secondaryDiffProgram.getAddressFactory(); applyFilter = applySettingsMgr.getDefaultApplyFilter(); diffDetails = new ProgramDiffDetails(primaryProgram, secondaryDiffProgram); primaryProgram.addListener(this); @@ -1884,14 +1884,14 @@ public class ProgramDiffPlugin extends ProgramPlugin } } - if (set.equals(p2Selection)) { + AddressSetView asView = p2Selection; + if (set.equals(asView)) { return; // Selection is unchanged so do nothing. } MarkerSet selectionMarkers = getSelectionMarkers(); selectionMarkers.clearAll(); - programSelectionChanged(new ProgramSelection(p2AddressFactory, set), - EventTrigger.GUI_ACTION); + programSelectionChanged(new ProgramSelection(set), EventTrigger.GUI_ACTION); updatePgm2Enablement(); } } diff --git a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/plugin/VTSubToolManager.java b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/plugin/VTSubToolManager.java index 88abc4d79b..7fb5a61a55 100644 --- a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/plugin/VTSubToolManager.java +++ b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/plugin/VTSubToolManager.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. @@ -762,7 +762,8 @@ public class VTSubToolManager implements VTControllerListener, OptionsChangeList } private boolean isCursorOnScreen(CodeViewerService service) { - FieldPanel fieldPanel = service.getFieldPanel(); + ListingPanel listingPanel = service.getListingPanel(); + FieldPanel fieldPanel = listingPanel.getFieldPanel(); int cursorOffset = fieldPanel.getCursorOffset(); return cursorOffset >= 0; // negative offset means offscreen } @@ -779,7 +780,10 @@ public class VTSubToolManager implements VTControllerListener, OptionsChangeList if (service == null) { return null; } - FieldSelection selection = service.getFieldPanel().getSelection(); + + ListingPanel listingPanel = service.getListingPanel(); + FieldPanel fieldPanel = listingPanel.getFieldPanel(); + FieldSelection selection = fieldPanel.getSelection(); AddressIndexMap addressIndexMap = service.getListingPanel().getAddressIndexMap(); AddressSet addressSet = addressIndexMap.getAddressSet(selection); return addressSet; @@ -788,13 +792,11 @@ public class VTSubToolManager implements VTControllerListener, OptionsChangeList /** * Sets the address set to be the selection in the tool. * - * @param tool - * the tool - * @param set - * the addressSet to use for the selection + * @param tool the tool + * @param addresses the addressSet to use for the selection */ - private void setSelectionInTool(PluginTool tool, AddressSetView addressSet) { - ProgramSelection programSelection = new ProgramSelection(addressSet); + private void setSelectionInTool(PluginTool tool, AddressSetView addresses) { + ProgramSelection programSelection = new ProgramSelection(addresses); CodeViewerService service = tool.getService(CodeViewerService.class); if (service == null) { return; diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/Plugin.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/Plugin.java index 03b6e65b3b..d3a3cb329b 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/Plugin.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/Plugin.java @@ -268,6 +268,10 @@ public abstract class Plugin implements ExtensionPoint, PluginEventListener, Ser } } + /** + * Called by the framework to dispose of this plugin and unregister for events and services. + * Subclasses should not override this method, but should instead override {@link #dispose()}. + */ protected void cleanup() { if (!disposed) { Throwable thr = null; diff --git a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/control/DebuggerMethodActionsPluginTest.java b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/control/DebuggerMethodActionsPluginTest.java index 55b7bd891b..120ff586c0 100644 --- a/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/control/DebuggerMethodActionsPluginTest.java +++ b/Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/app/plugin/core/debug/gui/control/DebuggerMethodActionsPluginTest.java @@ -15,7 +15,7 @@ */ package ghidra.app.plugin.core.debug.gui.control; -import static org.junit.Assert.assertEquals; +import static org.junit.Assert.*; import java.util.*; import java.util.stream.Collectors; @@ -26,6 +26,7 @@ import org.junit.Test; import db.Transaction; import docking.action.DockingActionIf; import ghidra.app.context.ProgramLocationActionContext; +import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin; import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerIntegrationTest; import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin; import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingServicePlugin; @@ -50,6 +51,7 @@ public class DebuggerMethodActionsPluginTest extends AbstractGhidraHeadedDebugge @Before public void setUpMethodActionsTest() throws Exception { + addPlugin(tool, CodeBrowserPlugin.class); listingPlugin = addPlugin(tool, DebuggerListingPlugin.class); mappingService = addPlugin(tool, DebuggerStaticMappingServicePlugin.class); methodsPlugin = addPlugin(tool, DebuggerMethodActionsPlugin.class); diff --git a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/BlockModelScreenShots.java b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/BlockModelScreenShots.java index 4284c8a5d3..d96e8c8712 100644 --- a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/BlockModelScreenShots.java +++ b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/BlockModelScreenShots.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. @@ -31,7 +31,7 @@ import ghidra.app.util.viewer.field.*; import ghidra.app.util.viewer.format.FieldFormatModel; import ghidra.app.util.viewer.format.FormatManager; import ghidra.app.util.viewer.listingpanel.ListingPanel; -import ghidra.app.util.viewer.listingpanel.MarginProvider; +import ghidra.app.util.viewer.listingpanel.ListingMarginProvider; import ghidra.framework.options.Options; import ghidra.framework.options.ToolOptions; import ghidra.program.model.address.Address; @@ -138,11 +138,11 @@ public class BlockModelScreenShots extends GhidraScreenShotGenerator { final ListingPanel lp = cb.getListingPanel(); @SuppressWarnings("unchecked") - final List list = - new ArrayList<>((List) getInstanceField("marginProviders", lp)); + final List list = + new ArrayList<>((List) getInstanceField("marginProviders", lp)); runSwing(() -> { invokeInstanceMethod("buildPanels", lp); - for (MarginProvider marginProvider : list) { + for (ListingMarginProvider marginProvider : list) { lp.removeMarginProvider(marginProvider); } }); diff --git a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/CodeBrowserPluginScreenShots.java b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/CodeBrowserPluginScreenShots.java index 863ac1bd1f..402f2245c2 100644 --- a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/CodeBrowserPluginScreenShots.java +++ b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/CodeBrowserPluginScreenShots.java @@ -39,7 +39,7 @@ import ghidra.app.plugin.core.datamgr.DataTypesProvider; import ghidra.app.plugin.core.programtree.ViewManagerComponentProvider; import ghidra.app.util.viewer.field.*; import ghidra.app.util.viewer.listingpanel.ListingPanel; -import ghidra.app.util.viewer.listingpanel.OverviewProvider; +import ghidra.app.util.viewer.listingpanel.ListingOverviewProvider; import ghidra.program.model.data.*; import ghidra.program.model.listing.CommentType; @@ -265,10 +265,10 @@ public class CodeBrowserPluginScreenShots extends GhidraScreenShotGenerator { public void testCaptureMarkerPopup() { setToolSize(1400, 1200); ListingPanel listingPanel = plugin.getListingPanel(); - List overviewProviders = listingPanel.getOverviewProviders(); + List overviewProviders = listingPanel.getOverviewProviders(); assertEquals(1, overviewProviders.size()); - OverviewProvider provider = overviewProviders.get(0); + ListingOverviewProvider provider = overviewProviders.get(0); rightClick(provider.getComponent(), 1, 1); captureMenu();