From 10621008e038ac8106b08192b2da28aef061bf84 Mon Sep 17 00:00:00 2001 From: dragonmacher <48328597+dragonmacher@users.noreply.github.com> Date: Fri, 28 Jun 2019 18:03:19 -0400 Subject: [PATCH] GT-2925 - Key Bindings - Support Window Menu Provider Key Bindings - Step - Added 'snapshot' concept to base Component Provider to fix how actions are added to the toolbar and menu; fixed bugs and tests --- .../topics/CodeBrowserPlugin/CodeBrowser.htm | 2 +- .../help/topics/Navigation/Navigation.htm | 77 ++++++++++++++++++- .../help/topics/RegisterPlugin/Registers.htm | 2 +- .../core/codebrowser/CodeViewerProvider.java | 37 +++++---- .../console/ConsoleComponentProvider.java | 12 +-- .../navigation/ProviderNavigationPlugin.java | 3 + .../register/RegisterManagerProvider.java | 2 +- .../docking/ComponentProviderActionsTest.java | 11 +++ .../ProgramByteViewerComponentProvider.java | 19 +++-- .../byteviewer/ByteViewerPlugin1Test.java | 69 +++++++++++++++-- .../core/decompile/DecompilerProvider.java | 25 ++++-- .../plugin/core/functiongraph/FGProvider.java | 16 +++- .../src/main/java/docking/ComponentNode.java | 2 +- .../java/docking/ComponentPlaceholder.java | 10 +++ .../main/java/docking/ComponentProvider.java | 22 +++++- .../java/docking/DockingWindowManager.java | 55 ++++++------- .../docking/actions/KeyBindingAction.java | 3 +- .../java/docking/actions/KeyBindingUtils.java | 8 ++ 18 files changed, 291 insertions(+), 84 deletions(-) diff --git a/Ghidra/Features/Base/src/main/help/help/topics/CodeBrowserPlugin/CodeBrowser.htm b/Ghidra/Features/Base/src/main/help/help/topics/CodeBrowserPlugin/CodeBrowser.htm index b8d8989483..90f7e50942 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/CodeBrowserPlugin/CodeBrowser.htm +++ b/Ghidra/Features/Base/src/main/help/help/topics/CodeBrowserPlugin/CodeBrowser.htm @@ -13,7 +13,7 @@ -

Listing View

+

Listing View

The Listing View is the main windows for displaying and working with a program's instruction and data.

diff --git a/Ghidra/Features/Base/src/main/help/help/topics/Navigation/Navigation.htm b/Ghidra/Features/Base/src/main/help/help/topics/Navigation/Navigation.htm index d61c4dbc71..970a0c89bc 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/Navigation/Navigation.htm +++ b/Ghidra/Features/Base/src/main/help/help/topics/Navigation/Navigation.htm @@ -476,6 +476,17 @@

Provided by: Go To Next-Previous Code Unit plugin

+ + +
+
+
+
+
+

Next/Previous Function

@@ -497,8 +508,21 @@

This action navigates the cursor to the closest function entry point that is at an address less than the current address. The default keybinding is Control-Up Arrow.

- -

Provided by: CodeBrowser plugin

+ +

Provided by: CodeBrowser plugin

+ + + + +
+
+
+
+
+

Navigation History

@@ -554,9 +578,54 @@

After clearing the history, the  and buttons are disabled

- + +

Provided by: Next/Previous plugin

+ + + +
+
+
+
+
+
+ + + + +

Component Provider Navigation

+ +
+

+ This section lists actions that allow the user to navigate between component providers. +

+ +

Go To Last Active Component

+ +
+

+ Allows the user to switch focus back to the previously focused component provider. +

+
+ +

Provided by: ProviderNavigation plugin

+
+ + +
+
+
+
+
+
+ -

Provided by: Next/Previous plugin

Related Topics:

diff --git a/Ghidra/Features/Base/src/main/help/help/topics/RegisterPlugin/Registers.htm b/Ghidra/Features/Base/src/main/help/help/topics/RegisterPlugin/Registers.htm index e80f70b2cf..cd1c6cc5d5 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/RegisterPlugin/Registers.htm +++ b/Ghidra/Features/Base/src/main/help/help/topics/RegisterPlugin/Registers.htm @@ -30,7 +30,7 @@ completely different instruction sets. To disassemble properly, the mode register must be set at the address where the disassembly begins.

-

Register Manager

+

Register Manager

The Register Manager displays the assigned values of registers at addresses within diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/CodeViewerProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/CodeViewerProvider.java index 5d1464220e..bee0678a85 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/CodeViewerProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/CodeViewerProvider.java @@ -54,6 +54,7 @@ import ghidra.program.model.address.*; import ghidra.program.model.listing.*; import ghidra.program.util.*; import ghidra.util.HelpLocation; +import ghidra.util.Swing; import resources.ResourceManager; public class CodeViewerProvider extends NavigatableComponentProviderAdapter @@ -117,17 +118,21 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter private MultiListingLayoutModel multiModel; public CodeViewerProvider(CodeBrowserPluginInterface plugin, FormatManager formatMgr, - boolean connected) { - super(plugin.getTool(), plugin.getName(), plugin.getName(), CodeViewerActionContext.class); + boolean isConnected) { + super(plugin.getTool(), "Listing", plugin.getName(), CodeViewerActionContext.class); + this.plugin = plugin; this.formatMgr = formatMgr; - setConnected(connected); + setConnected(isConnected); + if (!isConnected) { + setTransient(); + } setHelpLocation(new HelpLocation("CodeBrowserPlugin", "Code_Browser")); setDefaultWindowPosition(WindowPosition.RIGHT); - setIcon(ResourceManager.loadImage("images/Browser.gif")); + setIcon(ResourceManager.loadImage("images/Browser.gif"), isConnected); listingPanel = new ListingPanel(formatMgr); listingPanel.enablePropertyBasedColorModel(true); - decorationPanel = new ListingPanelContainer(listingPanel, connected); + decorationPanel = new ListingPanelContainer(listingPanel, isConnected); ListingHighlightProvider listingHighlighter = createListingHighlighter(listingPanel, tool, decorationPanel); highlighterAdapter = new ProgramHighlighterProvider(listingHighlighter); @@ -136,7 +141,7 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter setWindowMenuGroup("Listing"); setIntraGroupPosition(WindowPosition.RIGHT); - setTitle(connected ? TITLE : "[" + TITLE + "]"); + setTitle(isConnected ? TITLE : "[" + TITLE + "]"); fieldNavigator = new FieldNavigator(tool, this); listingPanel.addButtonPressedListener(fieldNavigator); addToTool(); @@ -150,6 +155,12 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter tool.addPopupListener(this); } + @Override + public boolean isSnapshot() { + // we are a snapshot when we are 'disconnected' + return !isConnected(); + } + /** * @return true if this listing is backed by a dynamic data source (e.g., debugger) */ @@ -399,9 +410,9 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter } void updateTitle() { - String subTitle = program == null ? "" : program.getDomainFile().getName(); - String newTitle = isConnected() ? TITLE : "[" + TITLE + "]"; - setTitle(newTitle + subTitle); + String subTitle = program == null ? "" : ' ' + program.getDomainFile().getName(); + String newTitle = isConnected() ? TITLE : "[" + TITLE + subTitle + "]"; + setTitle(newTitle); } @Override @@ -882,7 +893,7 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter final ViewerPosition vp = listingPanel.getFieldPanel().getViewerPosition(); // invoke later to give the window manage a chance to create the new window // (its done in an invoke later) - SwingUtilities.invokeLater(() -> { + Swing.runLater(() -> { newProvider.doSetProgram(program); newProvider.listingPanel.getFieldPanel().setViewerPosition(vp.getIndex(), vp.getXOffset(), vp.getYOffset()); @@ -949,7 +960,7 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter class ToggleHeaderAction extends ToggleDockingAction { ToggleHeaderAction() { - super("Toggle Header", CodeViewerProvider.this.getName()); + super("Toggle Header", plugin.getName()); setEnabled(true); setToolBarData(new ToolBarData(LISTING_FORMAT_EXPAND_ICON, "zzz")); @@ -960,8 +971,8 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter public void actionPerformed(ActionContext context) { boolean show = !listingPanel.isHeaderShowing(); listingPanel.showHeader(show); - getToolBarData() - .setIcon(show ? LISTING_FORMAT_COLLAPSE_ICON : LISTING_FORMAT_EXPAND_ICON); + getToolBarData().setIcon( + show ? LISTING_FORMAT_COLLAPSE_ICON : LISTING_FORMAT_EXPAND_ICON); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/console/ConsoleComponentProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/console/ConsoleComponentProvider.java index ec21bf1e82..10b72f3192 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/console/ConsoleComponentProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/console/ConsoleComponentProvider.java @@ -64,11 +64,11 @@ public class ConsoleComponentProvider extends ComponentProviderAdapter private PrintWriter stdin; private Program currentProgram; - public ConsoleComponentProvider(PluginTool tool, String name) { - super(tool, "Console", name); + public ConsoleComponentProvider(PluginTool tool, String owner) { + super(tool, "Console", owner); setDefaultWindowPosition(WindowPosition.BOTTOM); - setHelpLocation(new HelpLocation(getName(), "console")); + setHelpLocation(new HelpLocation(owner, owner)); setIcon(ResourceManager.loadImage(CONSOLE_GIF)); setWindowMenuGroup("Console"); setSubTitle("Scripting"); @@ -94,7 +94,7 @@ public class ConsoleComponentProvider extends ComponentProviderAdapter private void createOptions() { ToolOptions options = tool.getOptions("Console"); - HelpLocation help = new HelpLocation(getName(), "ConsolePlugin"); + HelpLocation help = new HelpLocation(getOwner(), getOwner()); options.registerOption(FONT_OPTION_LABEL, DEFAULT_FONT, help, FONT_DESCRIPTION); options.setOptionsHelpLocation(help); font = options.getFont(FONT_OPTION_LABEL, DEFAULT_FONT); @@ -260,7 +260,7 @@ public class ConsoleComponentProvider extends ComponentProviderAdapter } private void createActions() { - clearAction = new DockingAction("Clear Console", getName()) { + clearAction = new DockingAction("Clear Console", getOwner()) { @Override public void actionPerformed(ActionContext context) { @@ -273,7 +273,7 @@ public class ConsoleComponentProvider extends ComponentProviderAdapter clearAction.setEnabled(true); - scrollAction = new ToggleDockingAction("Scroll Lock", getName()) { + scrollAction = new ToggleDockingAction("Scroll Lock", getOwner()) { @Override public void actionPerformed(ActionContext context) { textPane.setScrollLock(isSelected()); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/ProviderNavigationPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/ProviderNavigationPlugin.java index a5422e312b..4f6d98ff7e 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/ProviderNavigationPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/ProviderNavigationPlugin.java @@ -27,6 +27,7 @@ import ghidra.app.plugin.PluginCategoryNames; import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.util.PluginStatus; import ghidra.framework.plugintool.util.ToolConstants; +import ghidra.util.HelpLocation; //@formatter:off @PluginInfo( @@ -86,6 +87,8 @@ public class ProviderNavigationPlugin extends Plugin { "xLowInMenuSubGroup")); previousProviderAction.setKeyBindingData(new KeyBindingData( KeyStroke.getKeyStroke(KeyEvent.VK_F6, DockingUtils.CONTROL_KEY_MODIFIER_MASK))); + previousProviderAction.setHelpLocation( + new HelpLocation("Navigation", "Navigation_Previous_Provider")); tool.addAction(previousProviderAction); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/register/RegisterManagerProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/register/RegisterManagerProvider.java index 0ea506490b..7b2d414f05 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/register/RegisterManagerProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/register/RegisterManagerProvider.java @@ -65,7 +65,7 @@ public class RegisterManagerProvider extends ComponentProviderAdapter { super(tool, "Register Manager", owner, ProgramActionContext.class); buildComponent(); - setHelpLocation(new HelpLocation("RegisterPlugin", "RegisterManager")); + setHelpLocation(new HelpLocation("RegisterPlugin", "Register_Manager")); setIcon(REGISTER_ICON, true); setDefaultWindowPosition(WindowPosition.WINDOW); diff --git a/Ghidra/Features/Base/src/test.slow/java/docking/ComponentProviderActionsTest.java b/Ghidra/Features/Base/src/test.slow/java/docking/ComponentProviderActionsTest.java index b40407bad3..4878d33f0a 100644 --- a/Ghidra/Features/Base/src/test.slow/java/docking/ComponentProviderActionsTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/docking/ComponentProviderActionsTest.java @@ -170,11 +170,13 @@ public class ComponentProviderActionsTest extends AbstractGhidraHeadedIntegratio assertProviderKeyStroke(newKs); assertOptionsKeyStroke(newKs); assertMenuItemHasKeyStroke(newKs); + assertNoToolbarAction(); } @Test public void testSetKeyBinding_ViaOptions_WithToolbarAction() { + setToolbarIcon(ICON); showProvider(); KeyStroke newKs = CONTROL_T; @@ -183,6 +185,7 @@ public class ComponentProviderActionsTest extends AbstractGhidraHeadedIntegratio assertProviderKeyStroke(newKs); assertOptionsKeyStroke(newKs); assertMenuItemHasKeyStroke(newKs); + assertToolbarAction(); } @Test @@ -306,6 +309,14 @@ public class ComponentProviderActionsTest extends AbstractGhidraHeadedIntegratio waitForSwing(); } + private void assertNoToolbarAction() { + assertNotNull("No toolbar action found for provider", getToolbarShowProviderAction()); + } + + private void assertToolbarAction() { + assertNotNull("No toolbar action found for provider", getToolbarShowProviderAction()); + } + private void assertProviderKeyStroke(KeyStroke expectedKs) { DockingActionIf action = getShowProviderAction(); diff --git a/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ProgramByteViewerComponentProvider.java b/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ProgramByteViewerComponentProvider.java index 5bf2b527cc..e03bd215ea 100644 --- a/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ProgramByteViewerComponentProvider.java +++ b/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ProgramByteViewerComponentProvider.java @@ -70,9 +70,13 @@ public class ProgramByteViewerComponentProvider extends ByteViewerComponentProvi public ProgramByteViewerComponentProvider(PluginTool tool, ByteViewerPlugin plugin, boolean isConnected) { super(tool, plugin, "Bytes", ByteViewerActionContext.class); - this.isConnected = isConnected; - setIcon(ResourceManager.loadImage("images/binaryData.gif"), true); + this.isConnected = isConnected; + if (!isConnected) { + setTransient(); + } + + setIcon(ResourceManager.loadImage("images/binaryData.gif"), isConnected); decorationComponent = new DecoratorPanel(panel, isConnected); clipboardProvider = new ByteViewerClipboardProvider(this, tool); @@ -88,6 +92,12 @@ public class ProgramByteViewerComponentProvider extends ByteViewerComponentProvi tool.addLocalAction(this, cloneByteViewerAction); } + @Override + public boolean isSnapshot() { + // we are a snapshot when we are 'disconnected' + return !isConnected(); + } + @Override public JComponent getComponent() { return decorationComponent; @@ -132,11 +142,6 @@ public class ProgramByteViewerComponentProvider extends ByteViewerComponentProvi plugin.closeProvider(this); } - @Override - public boolean isTransient() { - return false; - } - @Override public void setSelection(ProgramSelection selection) { setSelection(selection, true); diff --git a/Ghidra/Features/ByteViewer/src/test.slow/java/ghidra/app/plugin/core/byteviewer/ByteViewerPlugin1Test.java b/Ghidra/Features/ByteViewer/src/test.slow/java/ghidra/app/plugin/core/byteviewer/ByteViewerPlugin1Test.java index d4a50b1f9f..3f12c44e50 100644 --- a/Ghidra/Features/ByteViewer/src/test.slow/java/ghidra/app/plugin/core/byteviewer/ByteViewerPlugin1Test.java +++ b/Ghidra/Features/ByteViewer/src/test.slow/java/ghidra/app/plugin/core/byteviewer/ByteViewerPlugin1Test.java @@ -28,8 +28,10 @@ import javax.swing.event.TableColumnModelEvent; import org.junit.*; -import docking.ActionContext; +import docking.*; import docking.action.DockingActionIf; +import docking.menu.ToolBarItemManager; +import docking.menu.ToolBarManager; import docking.widgets.EventTrigger; import docking.widgets.fieldpanel.FieldPanel; import docking.widgets.fieldpanel.field.Field; @@ -168,7 +170,7 @@ public class ByteViewerPlugin1Test extends AbstractGhidraHeadedIntegrationTest { DataModelInfo info = panel.getDataModelInfo(); String[] names = info.getNames(); assertEquals(3, names.length); - Set viewNames = new HashSet(Arrays.asList(names)); + Set viewNames = new HashSet<>(Arrays.asList(names)); assertTrue(viewNames.contains("Hex")); assertTrue(viewNames.contains("Octal")); assertTrue(viewNames.contains("Ascii")); @@ -668,6 +670,64 @@ public class ByteViewerPlugin1Test extends AbstractGhidraHeadedIntegrationTest { } } + @Test + public void testShowingSnapshotDoesNotAddMultipleToolbarActions() { + + DockingActionIf cloneAction = getAction(plugin, "ByteViewer Clone"); + performAction(cloneAction); + waitForSwing(); + assertOnlyOneProviderToolbarAction(); + + performAction(cloneAction); + waitForSwing(); + assertOnlyOneProviderToolbarAction(); + } + +//================================================================================================== +// Private Methods +//================================================================================================== + + @SuppressWarnings("unchecked") + private void assertOnlyOneProviderToolbarAction() { + + DockingWindowManager dwm = tool.getWindowManager(); + ActionToGuiMapper guiActions = + (ActionToGuiMapper) getInstanceField("actionToGuiMapper", dwm); + GlobalMenuAndToolBarManager menuManager = + (GlobalMenuAndToolBarManager) getInstanceField("menuAndToolBarManager", guiActions); + + Map windowToActionManagerMap = + (Map) getInstanceField("windowToActionManagerMap", + menuManager); + + ProgramByteViewerComponentProvider provider = plugin.getProvider(); + DockingActionIf showAction = + (DockingActionIf) getInstanceField("showProviderAction", provider); + String actionName = showAction.getName(); + List matches = new ArrayList<>(); + for (WindowActionManager actionManager : windowToActionManagerMap.values()) { + + ToolBarManager toolbarManager = + (ToolBarManager) getInstanceField("toolBarMgr", actionManager); + Map> groupToItems = + (Map>) getInstanceField("groupToItemsMap", + toolbarManager); + + Collection> values = groupToItems.values(); + for (List list : values) { + for (ToolBarItemManager manager : list) { + DockingActionIf action = manager.getAction(); + if (actionName.equals(action.getName())) { + matches.add(action); + } + } + } + } + + assertEquals("Should only have 1 action on toolbar to show the provider", 1, + matches.size()); + } + private void goToOperand(String addr) { goTo(addr(addr), OperandFieldFactory.FIELD_NAME); } @@ -731,11 +791,6 @@ public class ByteViewerPlugin1Test extends AbstractGhidraHeadedIntegrationTest { assertEquals(expectedColumn, location.getCol()); } - private void goTo(String addr) { - GoToService goToService = tool.getService(GoToService.class); - goToService.goTo(addr(addr)); - } - private void goToByte(String addr) { goToByte(addr(addr)); } diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerProvider.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerProvider.java index cd5ebc1a6b..4d9e29b00c 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerProvider.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerProvider.java @@ -124,9 +124,10 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter public DecompilerProvider(DecompilePlugin plugin, boolean isConnected) { super(plugin.getTool(), "Decompiler", plugin.getName(), DecompilerActionContext.class); - this.plugin = plugin; - clipboardProvider = new DecompilerClipboardProvider(plugin, this); + this.plugin = plugin; + this.clipboardProvider = new DecompilerClipboardProvider(plugin, this); + setConnected(isConnected); decompilerOptions = new DecompileOptions(); @@ -137,10 +138,17 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter decompilerPanel.setHighlightController(highlightController); decorationPanel = new DecoratorPanel(decompilerPanel, isConnected); + if (!isConnected) { + setTransient(); + } + else { + setDefaultKeyBinding( + new KeyBindingData(KeyEvent.VK_E, DockingUtils.CONTROL_KEY_MODIFIER_MASK)); + } + + setIcon(C_SOURCE_ICON, isConnected); setTitle("Decompile"); - setIcon(C_SOURCE_ICON, true); - setDefaultKeyBinding( - new KeyBindingData(KeyEvent.VK_E, DockingUtils.CONTROL_KEY_MODIFIER_MASK)); + setWindowMenuGroup("Decompile"); setDefaultWindowPosition(WindowPosition.RIGHT); createActions(isConnected); @@ -155,6 +163,13 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter //================================================================================================== // Component Provider methods //================================================================================================== + + @Override + public boolean isSnapshot() { + // we are a snapshot when we are 'disconnected' + return !isConnected(); + } + @Override public void closeComponent() { controller.clear(); diff --git a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/FGProvider.java b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/FGProvider.java index e0f3fd4d5a..d8287c49c9 100644 --- a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/FGProvider.java +++ b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/FGProvider.java @@ -48,8 +48,7 @@ import ghidra.program.model.address.*; import ghidra.program.model.listing.*; import ghidra.program.model.symbol.*; import ghidra.program.util.*; -import ghidra.util.HelpLocation; -import ghidra.util.SystemUtilities; +import ghidra.util.*; import ghidra.util.datastruct.WeakDataStructureFactory; import ghidra.util.datastruct.WeakSet; import ghidra.util.exception.AssertException; @@ -104,13 +103,16 @@ public class FGProvider extends VisualGraphComponentProvider { + Swing.runLater(() -> { newProvider.doSetProgram(currentProgram); FGData currentData = controller.getFunctionGraphData(); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/ComponentNode.java b/Ghidra/Framework/Docking/src/main/java/docking/ComponentNode.java index 9a699bcaa8..f285aae562 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/ComponentNode.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/ComponentNode.java @@ -326,7 +326,7 @@ class ComponentNode extends Node { DockingTabRenderer tabRenderer) { final ComponentProvider provider = placeholder.getProvider(); - if (!provider.isTransient()) { + if (!provider.isTransient() || provider.isSnapshot()) { return; // don't muck with the title of 'real' providers--only transients, like search } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/ComponentPlaceholder.java b/Ghidra/Framework/Docking/src/main/java/docking/ComponentPlaceholder.java index d5da0f81e0..3327586c85 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/ComponentPlaceholder.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/ComponentPlaceholder.java @@ -449,6 +449,16 @@ public class ComponentPlaceholder { } } + void removeAllActions() { + if (comp != null) { + for (DockingActionIf action : actions) { + comp.actionRemoved(action); + } + } + + actions.clear(); + } + /** * Removes an action from this component * @param action the action to be removed. diff --git a/Ghidra/Framework/Docking/src/main/java/docking/ComponentProvider.java b/Ghidra/Framework/Docking/src/main/java/docking/ComponentProvider.java index caeb6a5218..01e824eb4a 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/ComponentProvider.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/ComponentProvider.java @@ -406,6 +406,10 @@ public abstract class ComponentProvider implements HelpDescriptor, ActionContext this.helpLocation = helpLocation; HelpService helpService = DockingWindowManager.getHelpService(); helpService.registerHelp(this, helpLocation); + + if (showProviderAction != null) { + showProviderAction.setHelpLocation(helpLocation); + } } /** @@ -564,7 +568,17 @@ public abstract class ComponentProvider implements HelpDescriptor, ActionContext * @return true if transient */ public boolean isTransient() { - return isTransient; + return isTransient || isSnapshot(); + } + + /** + * A special marker that indicates this provider is a snapshot of a primary provider, + * somewhat like a picture of the primary provider. + * + * @return true if a snapshot + */ + public boolean isSnapshot() { + return false; } /** @@ -766,13 +780,17 @@ public abstract class ComponentProvider implements HelpDescriptor, ActionContext setToolBarData(new ToolBarData(icon, TOOLBAR_GROUP)); } - if (defaultKeyBindingData != null) { + if (supportsKeyBindings && defaultKeyBindingData != null) { // this action itself is not 'key binding managed', but the system *will* use // any key binding value we set when connecting 'shared' actions setKeyBindingData(defaultKeyBindingData); } setDescription("Display " + name); + HelpLocation providerHelp = ComponentProvider.this.getHelpLocation(); + if (providerHelp != null) { + setHelpLocation(providerHelp); + } } @Override diff --git a/Ghidra/Framework/Docking/src/main/java/docking/DockingWindowManager.java b/Ghidra/Framework/Docking/src/main/java/docking/DockingWindowManager.java index da74dd8293..0ec208edcc 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/DockingWindowManager.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/DockingWindowManager.java @@ -26,6 +26,7 @@ import java.util.Map.Entry; import javax.swing.*; +import org.apache.commons.collections4.map.LazyMap; import org.jdom.Element; import docking.action.DockingActionIf; @@ -996,11 +997,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder } private void disposePlaceholder(ComponentPlaceholder placeholder, boolean keepAround) { - Iterator iter = placeholder.getActions(); - while (iter.hasNext()) { - DockingActionIf action = iter.next(); - placeholder.removeAction(action); - } + placeholder.removeAllActions(); ComponentNode node = placeholder.getNode(); if (node == null) { @@ -1092,8 +1089,10 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder tool.getToolActions().removeActions(DOCKING_WINDOWS_OWNER); - Map> permanentMap = new HashMap<>(); - Map> transientMap = new HashMap<>(); + Map> permanentMap = + LazyMap.lazyMap(new HashMap<>(), menuName -> new ArrayList<>()); + Map> transientMap = + LazyMap.lazyMap(new HashMap<>(), menuName -> new ArrayList<>()); Map map = placeholderManager.getActiveProvidersToPlaceholders(); @@ -1103,18 +1102,18 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder ComponentPlaceholder placeholder = entry.getValue(); String subMenuName = provider.getWindowSubMenuName(); - if (provider.isTransient()) { - addToMap(transientMap, subMenuName, placeholder); + if (provider.isTransient() && !provider.isSnapshot()) { + transientMap.get(subMenuName).add(placeholder); } else { - addToMap(permanentMap, subMenuName, placeholder); + permanentMap.get(subMenuName).add(placeholder); } } promoteSingleMenuGroups(permanentMap); promoteSingleMenuGroups(transientMap); - createActions(transientMap, true); - createActions(permanentMap, false); + createActions(transientMap); + createActions(permanentMap); createWindowActions(); actionToGuiMapper.update(); @@ -1144,14 +1143,17 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder return null; } - private void createActions(Map> map, boolean isTransient) { + private void createActions(Map> map) { List actionList = new ArrayList<>(); for (String subMenuName : map.keySet()) { List placeholders = map.get(subMenuName); for (ComponentPlaceholder placeholder : placeholders) { + ComponentProvider provider = placeholder.getProvider(); + boolean isTransient = provider.isTransient(); actionList.add( new ShowComponentAction(this, placeholder, subMenuName, isTransient)); } + if (subMenuName != null) { // add an 'add all' action for the sub-menu actionList.add(new ShowAllComponentsAction(this, placeholders, subMenuName)); @@ -1165,28 +1167,21 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder } } - private void promoteSingleMenuGroups(Map> map) { - List lists = new ArrayList<>(map.keySet()); + private void promoteSingleMenuGroups(Map> lazyMap) { + List lists = new ArrayList<>(lazyMap.keySet()); for (String key : lists) { - List list = map.get(key); - if (key != null && list.size() == 1) { - addToMap(map, null, list.get(0)); - map.remove(key); + if (key == null) { + continue; + } + + List list = lazyMap.get(key); + if (list.size() == 1) { + lazyMap.get(null /*submenu name*/).add(list.get(0)); + lazyMap.remove(key); } } } - private void addToMap(Map> map, String menuGroup, - ComponentPlaceholder placeholder) { - - List list = map.get(menuGroup); - if (list == null) { - list = new ArrayList<>(); - map.put(menuGroup, list); - } - list.add(placeholder); - } - private void createWindowActions() { List windows = root.getDetachedWindows(); List actions = new ArrayList<>(); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/actions/KeyBindingAction.java b/Ghidra/Framework/Docking/src/main/java/docking/actions/KeyBindingAction.java index 028e2eb122..9857879a72 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/actions/KeyBindingAction.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/actions/KeyBindingAction.java @@ -52,8 +52,7 @@ public class KeyBindingAction extends DockingAction { if (!action.getKeyBindingType().supportsKeyBindings()) { Component parent = windowManager.getActiveComponent(); Msg.showInfo(getClass(), parent, "Unable to Set Keybinding", - "Action \"" + getActionName(action) + "\" is not keybinding managed and thus a " + - "keybinding cannot be set."); + "Action \"" + getActionName(action) + "\" does not support key bindings"); return; } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/actions/KeyBindingUtils.java b/Ghidra/Framework/Docking/src/main/java/docking/actions/KeyBindingUtils.java index 2c1ba332e6..3651117ef5 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/actions/KeyBindingUtils.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/actions/KeyBindingUtils.java @@ -446,9 +446,17 @@ public class KeyBindingUtils { public static void assertSameDefaultKeyBindings(DockingActionIf newAction, Collection existingActions) { + if (!newAction.getKeyBindingType().supportsKeyBindings()) { + return; + } + KeyBindingData newDefaultBinding = newAction.getDefaultKeyBindingData(); KeyStroke defaultKs = getKeyStroke(newDefaultBinding); for (DockingActionIf action : existingActions) { + if (!action.getKeyBindingType().supportsKeyBindings()) { + continue; + } + KeyBindingData existingDefaultBinding = action.getDefaultKeyBindingData(); KeyStroke existingKs = getKeyStroke(existingDefaultBinding); if (!Objects.equals(defaultKs, existingKs)) {