diff --git a/Ghidra/Extensions/sample/src/main/java/ghidra/examples/SampleProgramTreePlugin.java b/Ghidra/Extensions/sample/src/main/java/ghidra/examples/SampleProgramTreePlugin.java index 26657268a9..a8cc03eed6 100644 --- a/Ghidra/Extensions/sample/src/main/java/ghidra/examples/SampleProgramTreePlugin.java +++ b/Ghidra/Extensions/sample/src/main/java/ghidra/examples/SampleProgramTreePlugin.java @@ -73,14 +73,15 @@ public class SampleProgramTreePlugin extends ProgramPlugin { public void actionPerformed(ActionContext context) { modularize(); } + + @Override + public boolean isEnabledForContext(ActionContext context) { + return currentProgram != null; + } }; action.setMenuBarData( new MenuData(new String[] { "Misc", "Create Sample Tree" }, null, null)); - - action.setEnabled(false); - action.setDescription("Plugin to create a program tree and modularize accordingly"); - enableOnProgram(action); tool.addAction(action); }// end of createActions() diff --git a/Ghidra/Extensions/sample/src/main/java/ghidra/examples/TemplateProgramPlugin.java b/Ghidra/Extensions/sample/src/main/java/ghidra/examples/TemplateProgramPlugin.java index 4291c0e46a..7eb6e75d99 100644 --- a/Ghidra/Extensions/sample/src/main/java/ghidra/examples/TemplateProgramPlugin.java +++ b/Ghidra/Extensions/sample/src/main/java/ghidra/examples/TemplateProgramPlugin.java @@ -32,7 +32,6 @@ import ghidra.program.util.ProgramChangeRecord; import ghidra.util.HelpLocation; import ghidra.util.Msg; - /** * Sample plugin for dealing with Programs. The base class handles * the event processing and enabling/disabling of actions. This @@ -51,107 +50,98 @@ import ghidra.util.Msg; //@formatter:on public class TemplateProgramPlugin extends ProgramPlugin implements DomainObjectListener { - private DockingAction action; + private DockingAction action; - /******************************************************************* - * - * Standard Plugin Constructor - * - *******************************************************************/ - public TemplateProgramPlugin(PluginTool tool) { + /******************************************************************* + * + * Standard Plugin Constructor + * + *******************************************************************/ + public TemplateProgramPlugin(PluginTool tool) { - super(tool, - true, // true means this plugin consumes ProgramLocation events - false); // false means this plugin does not consume - // ProgramSelection events - // the base class ProgramPlugin handles all the event registering + super(tool, true, // true means this plugin consumes ProgramLocation events + false); // false means this plugin does not consume + // ProgramSelection events + // the base class ProgramPlugin handles all the event registering - // set up list of actions. - setupActions(); - } - - /** - * This is the callback method for DomainObjectChangedEvents. - */ - public void domainObjectChanged(DomainObjectChangedEvent ev) { - for (int i=0; i< ev.numRecords(); i++) { - DomainObjectChangeRecord record = ev.getChangeRecord(i); - if (record instanceof ProgramChangeRecord) { - @SuppressWarnings("unused") - ProgramChangeRecord r = (ProgramChangeRecord)record; - // code for processing the record... - // ... - } - } - } + // set up list of actions. + setupActions(); + } - /** - * Called when the program is opened. - */ - @Override - protected void programActivated(Program program) { - program.addListener(this); - } - /** - * Called when the program is closed. - */ - @Override - protected void programDeactivated(Program program) { - program.removeListener(this); - } + /** + * This is the callback method for DomainObjectChangedEvents. + */ + @Override + public void domainObjectChanged(DomainObjectChangedEvent ev) { + for (int i = 0; i < ev.numRecords(); i++) { + DomainObjectChangeRecord record = ev.getChangeRecord(i); + if (record instanceof ProgramChangeRecord) { + @SuppressWarnings("unused") + ProgramChangeRecord r = (ProgramChangeRecord) record; + // code for processing the record... + // ... + } + } + } - /******************************************************************* - ******************************************************************** - ** - ** - ** Function 1 - ** - ** - ******************************************************************** - *******************************************************************/ - private void Function_1 () { - // do something with a program location - Msg.info(this, getPluginDescription().getName() - + ": Program Location==> " + currentLocation.getAddress()); - } + /** + * Called when the program is opened. + */ + @Override + protected void programActivated(Program program) { + program.addListener(this); + } + /** + * Called when the program is closed. + */ + @Override + protected void programDeactivated(Program program) { + program.removeListener(this); + } - /** - * Set up Actions - */ - private void setupActions() { + /******************************************************************* + ******************************************************************** + ** + ** + ** Function 1 + ** + ** + ******************************************************************** + *******************************************************************/ + private void Function_1() { + // do something with a program location + Msg.info(this, getPluginDescription().getName() + ": Program Location==> " + + currentLocation.getAddress()); + } - // - // Function 1 - // - action = new DockingAction("Function 1 Code", getName() ) { - @Override - public void actionPerformed(ActionContext e) { - Function_1(); - } - - @Override - public boolean isValidContext( ActionContext context ) { - return context instanceof ListingActionContext; - } - }; - action.setEnabled( false ); + /** + * Set up Actions + */ + private void setupActions() { + // + // Function 1 + // + action = new DockingAction("Function 1 Code", getName()) { + @Override + public void actionPerformed(ActionContext e) { + Function_1(); + } - action.setMenuBarData( new MenuData( - new String[]{"Misc", "Menu","Menu item 1"}, null, null ) ); - - - // call method in base class to enable this action when a - // program location event comes in; disable it when focus is - // lost; it will be disable when the program is closed - enableOnLocation(action); - - action.setHelpLocation(new HelpLocation("SampleHelpTopic", - "TemplateProgramPlugin_Anchor_Name")); - tool.addAction(action); - } + @Override + public boolean isValidContext(ActionContext context) { + return context instanceof ListingActionContext; + } + }; + action.setEnabled(false); + action.setMenuBarData( + new MenuData(new String[] { "Misc", "Menu", "Menu item 1" }, null, null)); + action.setHelpLocation( + new HelpLocation("SampleHelpTopic", "TemplateProgramPlugin_Anchor_Name")); + tool.addAction(action); + } } diff --git a/Ghidra/Features/Base/ghidra_scripts/DeleteFunctionDefaultPlates.java b/Ghidra/Features/Base/ghidra_scripts/DeleteFunctionDefaultPlatesScript.java similarity index 86% rename from Ghidra/Features/Base/ghidra_scripts/DeleteFunctionDefaultPlates.java rename to Ghidra/Features/Base/ghidra_scripts/DeleteFunctionDefaultPlatesScript.java index 70d29dd513..bdd851f67a 100644 --- a/Ghidra/Features/Base/ghidra_scripts/DeleteFunctionDefaultPlates.java +++ b/Ghidra/Features/Base/ghidra_scripts/DeleteFunctionDefaultPlatesScript.java @@ -1,6 +1,5 @@ /* ### * 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. @@ -23,20 +22,18 @@ import ghidra.program.model.address.AddressSetView; import ghidra.program.model.listing.Function; import ghidra.program.model.listing.FunctionIterator; -public class DeleteFunctionDefaultPlates extends GhidraScript { +public class DeleteFunctionDefaultPlatesScript extends GhidraScript { private static String DEFAULT_PLATE = " FUNCTION"; - /* (non-Javadoc) - * @see ghidra.app.script.GhidraScript#run() - */ + @Override - public void run() throws Exception { + public void run() throws Exception { AddressSetView set = currentProgram.getMemory(); if (currentSelection != null && !currentSelection.isEmpty()) { set = currentSelection; } - int updateCount=0; + int updateCount = 0; FunctionIterator iter = currentProgram.getFunctionManager().getFunctions(set, true); while (iter.hasNext()) { Function function = iter.next(); @@ -47,7 +44,7 @@ public class DeleteFunctionDefaultPlates extends GhidraScript { } } if (updateCount > 0) { - String cmt = updateCount > 1? "comments" : "comment"; + String cmt = updateCount > 1 ? "comments" : "comment"; println("Removed " + updateCount + " default plate " + cmt + "."); } else { 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/GhidraRun.java b/Ghidra/Features/Base/src/main/java/ghidra/GhidraRun.java index 11977c6713..4c07a6b028 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/GhidraRun.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/GhidraRun.java @@ -22,7 +22,6 @@ import javax.swing.ToolTipManager; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import docking.DockingWindowManager; import docking.framework.SplashScreen; import ghidra.base.help.GhidraHelpService; import ghidra.framework.Application; @@ -86,7 +85,6 @@ public class GhidraRun implements GhidraLaunchable { updateSplashScreenStatusMessage("Populating Ghidra help..."); GhidraHelpService.install(); - DockingWindowManager.enableDiagnosticActions(SystemUtilities.isInDevelopmentMode()); ExtensionUtils.cleanupUninstalledExtensions(); // Allows handling of old content which did not have a content type property diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/actions/AbstractFindReferencesDataTypeAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/actions/AbstractFindReferencesDataTypeAction.java index f77905f39c..9c3a550ca7 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/actions/AbstractFindReferencesDataTypeAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/actions/AbstractFindReferencesDataTypeAction.java @@ -22,8 +22,7 @@ import javax.swing.KeyStroke; import docking.ActionContext; import docking.DockingUtils; -import docking.action.DockingAction; -import docking.action.KeyBindingData; +import docking.action.*; import ghidra.app.plugin.core.navigation.FindAppliedDataTypesService; import ghidra.app.plugin.core.navigation.locationreferences.ReferenceUtils; import ghidra.framework.plugintool.PluginTool; @@ -44,7 +43,7 @@ public abstract class AbstractFindReferencesDataTypeAction extends DockingAction protected AbstractFindReferencesDataTypeAction(PluginTool tool, String name, String owner, KeyStroke defaultKeyStroke) { - super(name, owner); + super(name, owner, KeyBindingType.SHARED); this.tool = tool; setHelpLocation(new HelpLocation("LocationReferencesPlugin", "Data_Types")); @@ -69,11 +68,6 @@ public abstract class AbstractFindReferencesDataTypeAction extends DockingAction setKeyBindingData(new KeyBindingData(keyStroke)); } - @Override - public boolean usesSharedKeyBinding() { - return true; - } - @Override public boolean isEnabledForContext(ActionContext context) { DataType dataType = getDataType(context); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/context/ListingContextAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/context/ListingContextAction.java index 7fb0dfb408..cb1ab0b817 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/context/ListingContextAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/context/ListingContextAction.java @@ -1,6 +1,5 @@ /* ### * 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. @@ -16,28 +15,28 @@ */ package ghidra.app.context; - import java.util.Set; import docking.ActionContext; import docking.action.DockingAction; +import docking.action.KeyBindingType; public abstract class ListingContextAction extends DockingAction { - - public ListingContextAction(String name, String owner) { - this(name, owner, true); - } - - public ListingContextAction(String name, String owner, boolean isKeyBindingManaged) { - super(name, owner, isKeyBindingManaged); + + public ListingContextAction(String name, String owner) { + super(name, owner); } - + + public ListingContextAction(String name, String owner, KeyBindingType kbType) { + super(name, owner, kbType); + } + @Override public boolean isEnabledForContext(ActionContext context) { if (!(context instanceof ListingActionContext)) { return false; } - return isEnabledForContext((ListingActionContext)context); + return isEnabledForContext((ListingActionContext) context); } @Override @@ -45,38 +44,38 @@ public abstract class ListingContextAction extends DockingAction { if (!(context instanceof ListingActionContext)) { return false; } - return isValidContext((ListingActionContext)context); + return isValidContext((ListingActionContext) context); } - + @Override public boolean isAddToPopup(ActionContext context) { if (!(context instanceof ListingActionContext)) { return false; } - return isAddToPopup((ListingActionContext)context); + return isAddToPopup((ListingActionContext) context); } @Override public void actionPerformed(ActionContext context) { - actionPerformed((ListingActionContext)context); + actionPerformed((ListingActionContext) context); } protected boolean isAddToPopup(ListingActionContext context) { - return isEnabledForContext( context ); + return isEnabledForContext(context); } - + protected boolean isValidContext(ListingActionContext context) { return true; } - + protected boolean isEnabledForContext(ListingActionContext context) { return true; } protected void actionPerformed(ListingActionContext context) { - + // clients need to override this method } - + @Override public boolean shouldAddToWindow(boolean isMainWindow, Set> contextTypes) { for (Class class1 : contextTypes) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/ProgramPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/ProgramPlugin.java index aba3f1b5c3..6ac696bde7 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/ProgramPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/ProgramPlugin.java @@ -1,6 +1,5 @@ /* ### * 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. @@ -16,6 +15,10 @@ */ package ghidra.app.plugin; +import java.util.ArrayList; + +import docking.ActionContext; +import docking.action.DockingAction; import ghidra.app.events.*; import ghidra.app.services.GoToService; import ghidra.framework.plugintool.*; @@ -25,10 +28,6 @@ import ghidra.program.model.listing.*; import ghidra.program.util.ProgramLocation; import ghidra.program.util.ProgramSelection; -import java.util.ArrayList; - -import docking.action.DockingAction; - /** * Base class to handle common program events: Program Open/Close, * Program Location, Program Selection, and Program Highlight. @@ -87,10 +86,10 @@ public abstract class ProgramPlugin extends Plugin { } registerEventConsumed(ProgramOpenedPluginEvent.class); registerEventConsumed(ProgramClosedPluginEvent.class); - programActionList = new ArrayList(3); - locationActionList = new ArrayList(3); - selectionActionList = new ArrayList(3); - highlightActionList = new ArrayList(3); + programActionList = new ArrayList<>(3); + locationActionList = new ArrayList<>(3); + selectionActionList = new ArrayList<>(3); + highlightActionList = new ArrayList<>(3); } public ProgramPlugin(PluginTool tool, boolean consumeLocationChange, @@ -201,7 +200,10 @@ public abstract class ProgramPlugin extends Plugin { * the program is closed. * @throws IllegalArgumentException if this action was called for * another enableOnXXX(PlugAction) method. + * @deprecated {@link ActionContext} is now used for action enablement. Deprecated in 9.1; to + * be removed no sooner than two versions after that. */ + @Deprecated protected void enableOnProgram(DockingAction action) { if (locationActionList.contains(action)) { throw new IllegalArgumentException("Action already added to location action list"); @@ -222,7 +224,10 @@ public abstract class ProgramPlugin extends Plugin { * is null. * @throws IllegalArgumentException if this action was called for * another enableOnXXX(PlugAction) method. + * @deprecated {@link ActionContext} is now used for action enablement. Deprecated in 9.1; to + * be removed no sooner than two versions after that. */ + @Deprecated protected void enableOnLocation(DockingAction action) { if (programActionList.contains(action)) { throw new IllegalArgumentException("Action already added to program action list"); @@ -243,7 +248,10 @@ public abstract class ProgramPlugin extends Plugin { * the selection is null or empty. * @throws IllegalArgumentException if this action was called for * another enableOnXXX(PlugAction) method. + * @deprecated {@link ActionContext} is now used for action enablement. Deprecated in 9.1; to + * be removed no sooner than two versions after that. */ + @Deprecated protected void enableOnSelection(DockingAction action) { if (programActionList.contains(action)) { throw new IllegalArgumentException("Action already added to program action list"); @@ -263,7 +271,10 @@ public abstract class ProgramPlugin extends Plugin { * the highlight is null or empty. * @throws IllegalArgumentException if this action was called for * another enableOnXXX(PlugAction) method. + * @deprecated {@link ActionContext} is now used for action enablement. Deprecated in 9.1; to + * be removed no sooner than two versions after that. */ + @Deprecated protected void enableOnHighlight(DockingAction action) { if (programActionList.contains(action)) { throw new IllegalArgumentException("Action already added to program action list"); @@ -378,8 +389,8 @@ public abstract class ProgramPlugin extends Plugin { if (currentProgram == null) { return; } - firePluginEvent(new ProgramSelectionPluginEvent(getName(), new ProgramSelection(set), - currentProgram)); + firePluginEvent( + new ProgramSelectionPluginEvent(getName(), new ProgramSelection(set), currentProgram)); } /** diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/bookmark/BookmarkPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/bookmark/BookmarkPlugin.java index 63e6f8fb2e..cf8ae6c1db 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/bookmark/BookmarkPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/bookmark/BookmarkPlugin.java @@ -22,7 +22,6 @@ import javax.swing.Icon; import javax.swing.SwingUtilities; import docking.ActionContext; -import docking.DockingUtils; import docking.action.*; import docking.widgets.table.GTable; import ghidra.app.CorePluginPackage; @@ -72,7 +71,6 @@ public class BookmarkPlugin extends ProgramPlugin private BookmarkProvider provider; private DockingAction addAction; - private DockingAction showAction; private DockingAction deleteAction; private CreateBookmarkDialog createDialog; private GoToService goToService; @@ -113,19 +111,6 @@ public class BookmarkPlugin extends ProgramPlugin addAction.setEnabled(true); tool.addAction(addAction); - showAction = new DockingAction("Show Bookmarks", getName()) { - @Override - public void actionPerformed(ActionContext context) { - tool.showComponentProvider(provider, true); - } - }; - - showAction.setKeyBindingData( - new KeyBindingData(KeyEvent.VK_B, DockingUtils.CONTROL_KEY_MODIFIER_MASK)); - showAction.setToolBarData(new ToolBarData(BookmarkNavigator.NOTE_ICON, "View")); - showAction.setDescription("Display All Bookmarks"); - tool.addAction(showAction); - MultiIconBuilder builder = new MultiIconBuilder(Icons.CONFIGURE_FILTER_ICON); builder.addLowerRightIcon(ResourceManager.loadImage("images/check.png")); Icon filterTypesChanged = builder.build(); @@ -213,10 +198,6 @@ public class BookmarkPlugin extends ProgramPlugin addAction.dispose(); addAction = null; } - if (showAction != null) { - showAction.dispose(); - showAction = null; - } if (provider != null) { provider.dispose(); provider = null; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/bookmark/BookmarkProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/bookmark/BookmarkProvider.java index 05dc2b0159..9ed8ec667d 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/bookmark/BookmarkProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/bookmark/BookmarkProvider.java @@ -17,6 +17,7 @@ package ghidra.app.plugin.core.bookmark; import java.awt.BorderLayout; import java.awt.Component; +import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; import java.util.*; @@ -24,8 +25,8 @@ import javax.swing.*; import javax.swing.event.TableModelListener; import javax.swing.table.TableColumn; -import docking.ActionContext; -import docking.WindowPosition; +import docking.*; +import docking.action.KeyBindingData; import docking.widgets.combobox.GhidraComboBox; import docking.widgets.table.GTable; import ghidra.app.context.ProgramActionContext; @@ -58,6 +59,9 @@ public class BookmarkProvider extends ComponentProviderAdapter { super(tool, "Bookmarks", plugin.getName(), ProgramActionContext.class); setIcon(BookmarkNavigator.NOTE_ICON); + addToToolbar(); + setKeyBinding(new KeyBindingData(KeyEvent.VK_B, DockingUtils.CONTROL_KEY_MODIFIER_MASK)); + model = new BookmarkTableModel(tool, null); threadedTablePanel = new GhidraThreadedTablePanel<>(model); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/calltree/CallTreePlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/calltree/CallTreePlugin.java index 4f4050153d..fd3574bc5a 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/calltree/CallTreePlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/calltree/CallTreePlugin.java @@ -62,12 +62,14 @@ public class CallTreePlugin extends ProgramPlugin { ResourceManager.loadImage("images/arrow_rotate_clockwise.png"); private List providers = new ArrayList<>(); - private DockingAction showProviderAction; + private DockingAction showCallTreeFromMenuAction; + private CallTreeProvider primaryProvider; public CallTreePlugin(PluginTool tool) { super(tool, true, false, false); createActions(); + primaryProvider = new CallTreeProvider(this, true); } @Override @@ -116,32 +118,44 @@ public class CallTreePlugin extends ProgramPlugin { } private void createActions() { - showProviderAction = new DockingAction("Show Function Call Trees", getName()) { - @Override - public void actionPerformed(ActionContext context) { - showOrCreateNewCallTree(currentLocation); - } + // use the name of the provider so that the shared key binding data will get used + String actionName = CallTreeProvider.TITLE; + showCallTreeFromMenuAction = + new DockingAction(actionName, getName(), KeyBindingType.SHARED) { + @Override + public void actionPerformed(ActionContext context) { + showOrCreateNewCallTree(currentLocation); + } - @Override - public boolean isAddToPopup(ActionContext context) { - return (context instanceof ListingActionContext); - } - }; - showProviderAction.setPopupMenuData(new MenuData( + @Override + public boolean isAddToPopup(ActionContext context) { + return (context instanceof ListingActionContext); + } + }; + + showCallTreeFromMenuAction.setPopupMenuData(new MenuData( new String[] { "References", "Show Call Trees" }, PROVIDER_ICON, "ShowReferencesTo")); - showProviderAction.setToolBarData(new ToolBarData(PROVIDER_ICON, "View")); - showProviderAction.setHelpLocation(new HelpLocation("CallTreePlugin", "Call_Tree_Plugin")); - tool.addAction(showProviderAction); + showCallTreeFromMenuAction.setHelpLocation( + new HelpLocation("CallTreePlugin", "Call_Tree_Plugin")); + tool.addAction(showCallTreeFromMenuAction); } private void creatAndShowProvider() { - CallTreeProvider provider = new CallTreeProvider(this); + CallTreeProvider provider = new CallTreeProvider(this, false); providers.add(provider); provider.initialize(currentProgram, currentLocation); tool.showComponentProvider(provider, true); } + CallTreeProvider getPrimaryProvider() { + return primaryProvider; + } + + DockingAction getShowCallTreeFromMenuAction() { + return showCallTreeFromMenuAction; + } + ProgramLocation getCurrentLocation() { return currentLocation; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/calltree/CallTreeProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/calltree/CallTreeProvider.java index 39fd756f18..8fa58d5836 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/calltree/CallTreeProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/calltree/CallTreeProvider.java @@ -57,7 +57,7 @@ import resources.ResourceManager; public class CallTreeProvider extends ComponentProviderAdapter implements DomainObjectListener { static final String EXPAND_ACTION_NAME = "Fully Expand Selected Nodes"; - private static final String TITLE = "Function Call Trees"; + static final String TITLE = "Function Call Trees"; private static final Icon EMPTY_ICON = ResourceManager.loadImage("images/EmptyIcon16.gif"); private static final Icon EXPAND_ICON = Icons.EXPAND_ALL_ICON; private static final Icon COLLAPSE_ICON = Icons.COLLAPSE_ALL_ICON; @@ -74,6 +74,7 @@ public class CallTreeProvider extends ComponentProviderAdapter implements Domain private JSplitPane splitPane; private GTree incomingTree; private GTree outgoingTree; + private boolean isPrimary; private SwingUpdateManager reloadUpdateManager = new SwingUpdateManager(500, () -> doUpdate()); @@ -93,20 +94,21 @@ public class CallTreeProvider extends ComponentProviderAdapter implements Domain private AtomicInteger recurseDepth = new AtomicInteger(); private NumberIcon recurseIcon; - public CallTreeProvider(CallTreePlugin plugin) { + public CallTreeProvider(CallTreePlugin plugin, boolean isPrimary) { super(plugin.getTool(), TITLE, plugin.getName()); this.plugin = plugin; + this.isPrimary = isPrimary; component = buildComponent(); // try to give the trees a suitable amount of space by default component.setPreferredSize(new Dimension(800, 400)); - setTransient(); setWindowMenuGroup(TITLE); setDefaultWindowPosition(WindowPosition.BOTTOM); setIcon(CallTreePlugin.PROVIDER_ICON); + addToToolbar(); setHelpLocation(new HelpLocation(plugin.getName(), "Call_Tree_Plugin")); addToTool(); @@ -430,7 +432,11 @@ public class CallTreeProvider extends ComponentProviderAdapter implements Domain } } }; - navigateIncomingToggleAction.setSelected(false); + + // note: the default state is to follow navigation events for the primary provider; + // non-primary providers will function like snapshots of the function with + // which they were activated. + navigateIncomingToggleAction.setSelected(isPrimary); navigateIncomingToggleAction.setToolBarData(new ToolBarData( Icons.NAVIGATE_ON_INCOMING_EVENT_ICON, navigationOptionsToolbarGroup, "2")); navigateIncomingToggleAction.setDescription(HTMLUtilities.toHTML("Incoming Navigation" + @@ -818,7 +824,9 @@ public class CallTreeProvider extends ComponentProviderAdapter implements Domain @Override public void componentHidden() { - plugin.removeProvider(this); + if (!isPrimary) { + plugin.removeProvider(this); + } } private void reload() { @@ -869,6 +877,7 @@ public class CallTreeProvider extends ComponentProviderAdapter implements Domain // changes, which means we will get here while setting the location, but our program // will have been null'ed out. currentProgram = plugin.getCurrentProgram(); + currentProgram.addListener(this); } Function function = plugin.getFunction(location); 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 084d6ec6e4..0d5259ff24 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 @@ -915,18 +915,13 @@ public class CodeBrowserPlugin extends Plugin } }; + // note: this action gets added later when the TableService is added tableFromSelectionAction.setEnabled(false); tableFromSelectionAction.setMenuBarData(new MenuData( new String[] { ToolConstants.MENU_SELECTION, "Create Table From Selection" }, null, "SelectUtils")); - tableFromSelectionAction - .setHelpLocation(new HelpLocation("CodeBrowserPlugin", "Selection_Table")); - - // don't add the actions initially if the service isn't there - TableService tableService = tool.getService(TableService.class); - if (tableService != null) { - tool.addAction(tableFromSelectionAction); - } + tableFromSelectionAction.setHelpLocation( + new HelpLocation("CodeBrowserPlugin", "Selection_Table")); } private GhidraProgramTableModel

createTableModel(CodeUnitIterator iterator, @@ -1001,8 +996,8 @@ public class CodeBrowserPlugin extends Plugin public boolean goToField(Address a, String fieldName, int occurrence, int row, int col, boolean scroll) { - boolean result = SystemUtilities - .runSwingNow(() -> doGoToField(a, fieldName, occurrence, row, col, scroll)); + boolean result = SystemUtilities.runSwingNow( + () -> doGoToField(a, fieldName, occurrence, row, col, scroll)); return result; } 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..fb777ec418 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 @@ -36,7 +36,6 @@ import docking.widgets.fieldpanel.FieldPanel; import docking.widgets.fieldpanel.HoverHandler; import docking.widgets.fieldpanel.internal.FieldPanelCoordinator; import docking.widgets.fieldpanel.support.*; -import ghidra.GhidraOptions; import ghidra.app.nav.*; import ghidra.app.plugin.core.clipboard.CodeBrowserClipboardProvider; import ghidra.app.plugin.core.codebrowser.actions.*; @@ -54,6 +53,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 @@ -62,8 +62,6 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter private static final String TITLE = "Listing: "; - private static final String OPERAND_OPTIONS_PREFIX = GhidraOptions.OPERAND_GROUP_TITLE + "."; - private static final Icon LISTING_FORMAT_EXPAND_ICON = ResourceManager.loadImage("images/field.header.down.png"); private static final Icon LISTING_FORMAT_COLLAPSE_ICON = @@ -92,9 +90,6 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter private ProgramDropProvider curDropProvider; private ToggleDockingAction toggleHoverAction; - //TODO - Need to improve CodeUnit.getRepresentation format options interface before adding this - //TODO private ToggleOperandMarkupAction[] toggleOperandMarkupActions; - private ProgramLocation currentLocation; private ListingPanel otherPanel; @@ -117,17 +112,26 @@ 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); + setIcon(ResourceManager.loadImage("images/Browser.gif")); + if (!isConnected) { + setTransient(); + } + else { + addToToolbar(); + } setHelpLocation(new HelpLocation("CodeBrowserPlugin", "Code_Browser")); setDefaultWindowPosition(WindowPosition.RIGHT); - setIcon(ResourceManager.loadImage("images/Browser.gif")); + 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 +140,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 +154,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 +409,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 +892,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 +959,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,14 +970,14 @@ 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); } } - class ToggleHoverAction extends ToggleDockingAction { + private class ToggleHoverAction extends ToggleDockingAction { ToggleHoverAction() { - super("Toggle Mouse Hover Popups", CodeViewerProvider.this.getName()); + super("Toggle Mouse Hover Popups", CodeViewerProvider.this.getOwner()); setEnabled(true); setToolBarData(new ToolBarData(HOVER_ON_ICON, "yyyz")); setSelected(true); @@ -987,31 +997,6 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter } } - class ToggleOperandMarkupAction extends ToggleDockingAction { - final String optionName; - - ToggleOperandMarkupAction(String operandOption) { - super(operandOption, CodeViewerProvider.this.getName()); - this.optionName = OPERAND_OPTIONS_PREFIX + operandOption; - boolean state = - tool.getOptions(GhidraOptions.CATEGORY_BROWSER_FIELDS).getBoolean(optionName, true); - setMenuBarData(new MenuData(new String[] { operandOption })); - setSelected(state); - setEnabled(true); - setHelpLocation(new HelpLocation("CodeBrowserPlugin", "Operands_Field")); - } - - public Object getOptionName() { - return optionName; - } - - @Override - public void actionPerformed(ActionContext context) { - boolean state = isSelected(); - tool.getOptions(GhidraOptions.CATEGORY_BROWSER_FIELDS).setBoolean(optionName, state); - } - } - /** * A class that allows clients to install transient highlighters while keeping the * middle-mouse highlighting on at the same time. diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/actions/CodeViewerContextAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/actions/CodeViewerContextAction.java index 5de9deb680..0ecf8218d8 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/actions/CodeViewerContextAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/actions/CodeViewerContextAction.java @@ -15,8 +15,6 @@ */ package ghidra.app.plugin.core.codebrowser.actions; - - import java.util.Set; import docking.ActionContext; @@ -24,21 +22,17 @@ import docking.action.DockingAction; import ghidra.app.plugin.core.codebrowser.CodeViewerActionContext; public abstract class CodeViewerContextAction extends DockingAction { - - public CodeViewerContextAction(String name, String owner) { - this(name, owner, true); - } - - public CodeViewerContextAction(String name, String owner, boolean isKeyBindingManaged) { - super(name, owner, isKeyBindingManaged); + + public CodeViewerContextAction(String name, String owner) { + super(name, owner); } - + @Override public boolean isEnabledForContext(ActionContext context) { if (!(context instanceof CodeViewerActionContext)) { return false; } - return isEnabledForContext((CodeViewerActionContext)context); + return isEnabledForContext((CodeViewerActionContext) context); } @Override @@ -46,26 +40,25 @@ public abstract class CodeViewerContextAction extends DockingAction { if (!(context instanceof CodeViewerActionContext)) { return false; } - return isValidContext((CodeViewerActionContext)context); + return isValidContext((CodeViewerActionContext) context); } - + @Override public boolean isAddToPopup(ActionContext context) { if (!(context instanceof CodeViewerActionContext)) { return false; } - return isAddToPopup((CodeViewerActionContext)context); + return isAddToPopup((CodeViewerActionContext) context); } @Override public void actionPerformed(ActionContext context) { - actionPerformed((CodeViewerActionContext)context); + actionPerformed((CodeViewerActionContext) context); } protected boolean isAddToPopup(CodeViewerActionContext context) { - return isEnabledForContext( context ); + return isEnabledForContext(context); } - protected boolean isValidContext(CodeViewerActionContext context) { return true; @@ -76,9 +69,9 @@ public abstract class CodeViewerContextAction extends DockingAction { } protected void actionPerformed(CodeViewerActionContext context) { - + } - + @Override public boolean shouldAddToWindow(boolean isMainWindow, Set> contextTypes) { for (Class class1 : contextTypes) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/actions/CollapseAllDataAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/actions/CollapseAllDataAction.java index aef5cd1983..b9e6e82b17 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/actions/CollapseAllDataAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/actions/CollapseAllDataAction.java @@ -39,7 +39,7 @@ public class CollapseAllDataAction extends ProgramLocationContextAction { private CodeViewerProvider provider; public CollapseAllDataAction(CodeViewerProvider provider) { - super("Collapse All Data", provider.getName()); + super("Collapse All Data", provider.getOwner()); this.provider = provider; setPopupMenuData(new MenuData(new String[] { "Collapse All Data" }, null, "Structure")); @@ -95,8 +95,7 @@ public class CollapseAllDataAction extends ProgramLocationContextAction { ProgramLocation location = context.getLocation(); Data data = getTopLevelComponentData(location); - TaskLauncher.launchModal("Collapse Data", - monitor -> model.closeAllData(data, monitor)); + TaskLauncher.launchModal("Collapse Data", monitor -> model.closeAllData(data, monitor)); } private ListingModel getModel() { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/actions/ExpandAllDataAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/actions/ExpandAllDataAction.java index 3983a5138a..47e02c433e 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/actions/ExpandAllDataAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/actions/ExpandAllDataAction.java @@ -36,7 +36,7 @@ public class ExpandAllDataAction extends ProgramLocationContextAction { private CodeViewerProvider provider; public ExpandAllDataAction(CodeViewerProvider provider) { - super("Expand All Data", provider.getName()); + super("Expand All Data", provider.getOwner()); this.provider = provider; setPopupMenuData(new MenuData(new String[] { "Expand All Data" }, null, "Structure")); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/actions/ToggleExpandCollapseDataAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/actions/ToggleExpandCollapseDataAction.java index 537455f59c..93ffe7558b 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/actions/ToggleExpandCollapseDataAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/actions/ToggleExpandCollapseDataAction.java @@ -39,7 +39,7 @@ public class ToggleExpandCollapseDataAction extends ProgramLocationContextAction private CodeViewerProvider provider; public ToggleExpandCollapseDataAction(CodeViewerProvider provider) { - super("Toggle Expand/Collapse Data", provider.getName()); + super("Toggle Expand/Collapse Data", provider.getOwner()); this.provider = provider; setPopupMenuData( diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/commentwindow/CommentWindowPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/commentwindow/CommentWindowPlugin.java index 78660773b9..85df4d7d90 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/commentwindow/CommentWindowPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/commentwindow/CommentWindowPlugin.java @@ -15,7 +15,6 @@ */ package ghidra.app.plugin.core.commentwindow; -import docking.ActionContext; import docking.action.DockingAction; import ghidra.app.CorePluginPackage; import ghidra.app.events.ProgramSelectionPluginEvent; @@ -186,13 +185,7 @@ public class CommentWindowPlugin extends ProgramPlugin implements DomainObjectLi private void createActions() { - selectAction = new MakeProgramSelectionAction(getName(), provider.getTable()) { - @Override - protected void makeSelection(ActionContext context) { - selectComment(provider.selectComment()); - } - }; - + selectAction = new MakeProgramSelectionAction(this, provider.getTable()); tool.addLocalAction(provider, selectAction); DockingAction selectionAction = new SelectionNavigationAction(this, provider.getTable()); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeEditorTableAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeEditorTableAction.java index ff66131da2..03cd7d54f1 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeEditorTableAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeEditorTableAction.java @@ -15,15 +15,14 @@ */ package ghidra.app.plugin.core.compositeeditor; -import ghidra.framework.plugintool.Plugin; -import ghidra.framework.plugintool.PluginTool; -import ghidra.util.HelpLocation; - import java.awt.event.ActionListener; import javax.swing.*; import docking.action.*; +import ghidra.framework.plugintool.Plugin; +import ghidra.framework.plugintool.PluginTool; +import ghidra.util.HelpLocation; /** * CompositeEditorAction is an abstract class that should be extended for any @@ -45,13 +44,14 @@ abstract public class CompositeEditorTableAction extends DockingAction implement public static final String EDIT_ACTION_PREFIX = "Editor: "; - /** - * Defines an Action object with the specified - * description string and a the specified icon. - */ public CompositeEditorTableAction(CompositeEditorProvider provider, String name, String group, String[] popupPath, String[] menuPath, ImageIcon icon) { - super(name, provider.plugin.getName()); + this(provider, name, group, popupPath, menuPath, icon, KeyBindingType.INDIVIDUAL); + } + + public CompositeEditorTableAction(CompositeEditorProvider provider, String name, String group, + String[] popupPath, String[] menuPath, ImageIcon icon, KeyBindingType kbType) { + super(name, provider.plugin.getName(), kbType); this.provider = provider; model = provider.getModel(); if (menuPath != null) { @@ -70,9 +70,6 @@ abstract public class CompositeEditorTableAction extends DockingAction implement setHelpLocation(new HelpLocation(provider.getHelpTopic(), helpAnchor)); } - /* (non-Javadoc) - * @see ghidra.framework.plugintool.PluginAction#dispose() - */ @Override public void dispose() { model.removeCompositeEditorModelListener(this); @@ -93,64 +90,53 @@ abstract public class CompositeEditorTableAction extends DockingAction implement } } + @Override abstract public void adjustEnablement(); public String getHelpName() { String actionName = getName(); if (actionName.startsWith(CompositeEditorTableAction.EDIT_ACTION_PREFIX)) { - actionName = actionName.substring(CompositeEditorTableAction.EDIT_ACTION_PREFIX.length()); + actionName = + actionName.substring(CompositeEditorTableAction.EDIT_ACTION_PREFIX.length()); } return actionName; } - /* (non-Javadoc) - * @see ghidra.app.plugin.stackeditor.EditorModelListener#selectionChanged() - */ + @Override public void selectionChanged() { adjustEnablement(); } - /* (non-Javadoc) - * @see ghidra.app.plugin.stackeditor.EditorModelListener#editStateChanged(int) - */ public void editStateChanged(int i) { adjustEnablement(); } - /* (non-Javadoc) - * @see ghidra.app.plugin.compositeeditor.CompositeEditorModelListener#compositeEditStateChanged(int) - */ + @Override public void compositeEditStateChanged(int type) { adjustEnablement(); } - /* (non-Javadoc) - * @see ghidra.app.plugin.compositeeditor.CompositeEditorModelListener#endFieldEditing() - */ + @Override public void endFieldEditing() { adjustEnablement(); } - /* (non-Javadoc) - * @see ghidra.app.plugin.compositeeditor.CompositeEditorModelListener#componentDataChanged() - */ + @Override public void componentDataChanged() { adjustEnablement(); } - /* (non-Javadoc) - * @see ghidra.app.plugin.compositeeditor.CompositeEditorModelListener#compositeInfoChanged() - */ + @Override public void compositeInfoChanged() { adjustEnablement(); } - /* (non-Javadoc) - * @see ghidra.app.plugin.compositeeditor.CompositeEditorModelListener#statusChanged(java.lang.String, boolean) - */ + @Override public void statusChanged(String message, boolean beep) { + // we are an action; don't care about status messages } + @Override public void showUndefinedStateChanged(boolean showUndefinedBytes) { adjustEnablement(); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CycleGroupAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CycleGroupAction.java index 5b43585514..3e66294914 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CycleGroupAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CycleGroupAction.java @@ -19,6 +19,7 @@ import javax.swing.KeyStroke; import docking.ActionContext; import docking.action.KeyBindingData; +import docking.action.KeyBindingType; import ghidra.program.model.data.CycleGroup; /** @@ -32,7 +33,7 @@ public class CycleGroupAction extends CompositeEditorTableAction { public CycleGroupAction(CompositeEditorProvider provider, CycleGroup cycleGroup) { super(provider, cycleGroup.getName(), GROUP_NAME, new String[] { "Cycle", cycleGroup.getName() }, - new String[] { "Cycle", cycleGroup.getName() }, null); + new String[] { "Cycle", cycleGroup.getName() }, null, KeyBindingType.SHARED); this.cycleGroup = cycleGroup; initKeyStroke(cycleGroup.getDefaultKeyStroke()); @@ -46,11 +47,6 @@ public class CycleGroupAction extends CompositeEditorTableAction { setKeyBindingData(new KeyBindingData(keyStroke)); } - @Override - public boolean usesSharedKeyBinding() { - return true; - } - public CycleGroup getCycleGroup() { return cycleGroup; } 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 d7e74e29b9..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, name, 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/data/ChooseDataTypeAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/ChooseDataTypeAction.java index 5e04f76b7b..df32e16246 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/ChooseDataTypeAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/ChooseDataTypeAction.java @@ -20,8 +20,7 @@ import java.awt.event.KeyEvent; import javax.swing.KeyStroke; import docking.ActionContext; -import docking.action.DockingAction; -import docking.action.KeyBindingData; +import docking.action.*; import ghidra.app.context.ListingActionContext; import ghidra.app.util.datatype.DataTypeSelectionDialog; import ghidra.framework.plugintool.PluginTool; @@ -43,7 +42,7 @@ public class ChooseDataTypeAction extends DockingAction { private final static String ACTION_NAME = "Choose Data Type"; public ChooseDataTypeAction(DataPlugin plugin) { - super(ACTION_NAME, plugin.getName(), false); + super(ACTION_NAME, plugin.getName(), KeyBindingType.SHARED); this.plugin = plugin; initKeyStroke(KEY_BINDING); @@ -57,11 +56,6 @@ public class ChooseDataTypeAction extends DockingAction { setKeyBindingData(new KeyBindingData(keyStroke)); } - @Override - public boolean usesSharedKeyBinding() { - return true; - } - @Override public void actionPerformed(ActionContext context) { ListingActionContext programActionContext = diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/CreateArrayAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/CreateArrayAction.java index 552f51ee53..8298e2ad82 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/CreateArrayAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/CreateArrayAction.java @@ -44,7 +44,7 @@ class CreateArrayAction extends DockingAction { private DataPlugin plugin; public CreateArrayAction(DataPlugin plugin) { - super("Define Array", plugin.getName(), false); + super("Define Array", plugin.getName(), KeyBindingType.SHARED); this.plugin = plugin; setPopupMenuData(new MenuData(CREATE_ARRAY_POPUP_MENU, "BasicData")); @@ -61,11 +61,6 @@ class CreateArrayAction extends DockingAction { setKeyBindingData(new KeyBindingData(keyStroke)); } - @Override - public boolean usesSharedKeyBinding() { - return true; - } - @Override public void actionPerformed(ActionContext context) { ListingActionContext programActionContext = diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/CycleGroupAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/CycleGroupAction.java index 2d6f4fe020..dde841404a 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/CycleGroupAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/CycleGroupAction.java @@ -18,8 +18,7 @@ package ghidra.app.plugin.core.data; import javax.swing.KeyStroke; import docking.ActionContext; -import docking.action.DockingAction; -import docking.action.KeyBindingData; +import docking.action.*; import ghidra.app.cmd.data.*; import ghidra.app.context.ListingActionContext; import ghidra.framework.cmd.BackgroundCommand; @@ -41,7 +40,7 @@ public class CycleGroupAction extends DockingAction { private CycleGroup cycleGroup; CycleGroupAction(CycleGroup group, DataPlugin plugin) { - super(group.getName(), plugin.getName(), false); + super(group.getName(), plugin.getName(), KeyBindingType.SHARED); this.plugin = plugin; this.cycleGroup = group; @@ -56,11 +55,6 @@ public class CycleGroupAction extends DockingAction { setKeyBindingData(new KeyBindingData(keyStroke)); } - @Override - public boolean usesSharedKeyBinding() { - return true; - } - @Override public void dispose() { cycleGroup = null; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/DataAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/DataAction.java index baaf43939a..d8e57f696a 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/DataAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/DataAction.java @@ -17,8 +17,7 @@ package ghidra.app.plugin.core.data; import javax.swing.KeyStroke; -import docking.action.KeyBindingData; -import docking.action.MenuData; +import docking.action.*; import ghidra.app.context.ListingActionContext; import ghidra.app.context.ListingContextAction; import ghidra.program.model.data.*; @@ -45,7 +44,7 @@ class DataAction extends ListingContextAction { * @param plugin the plugin that owns this action */ public DataAction(String name, String group, DataType dataType, DataPlugin plugin) { - super(name, plugin.getName(), false); + super(name, plugin.getName(), KeyBindingType.SHARED); this.plugin = plugin; this.dataType = dataType; @@ -54,11 +53,6 @@ class DataAction extends ListingContextAction { initKeyStroke(getDefaultKeyStroke()); } - @Override - public boolean usesSharedKeyBinding() { - return true; - } - protected KeyStroke getDefaultKeyStroke() { return null; // we have no default, but our subclasses may } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/DataTypeManagerPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/DataTypeManagerPlugin.java index 7c3478d4bb..2932a3e30b 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/DataTypeManagerPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/DataTypeManagerPlugin.java @@ -23,7 +23,6 @@ import java.io.IOException; import java.util.*; import java.util.Map.Entry; -import javax.swing.ImageIcon; import javax.swing.SwingUtilities; import javax.swing.tree.TreePath; @@ -60,7 +59,6 @@ import ghidra.util.HelpLocation; import ghidra.util.Msg; import ghidra.util.datastruct.LRUMap; import ghidra.util.task.TaskLauncher; -import resources.ResourceManager; /** * Plugin to pop up the dialog to manage data types in the program @@ -82,14 +80,12 @@ import resources.ResourceManager; public class DataTypeManagerPlugin extends ProgramPlugin implements DomainObjectListener, DataTypeManagerService, PopupListener { - final static String DATA_TYPES_ICON = "images/dataTypes.png"; private static final String SEACH_PROVIDER_NAME = "Search DataTypes Provider"; private static final int RECENTLY_USED_CACHE_SIZE = 10; private static final String STANDARD_ARCHIVE_MENU = "Standard Archive"; private static final String RECENTLY_OPENED_MENU = "Recently Opened Archive"; - private DockingAction manageDataAction; private DataTypeManagerHandler dataTypeManagerHandler; private DataTypesProvider provider; private OpenVersionedFileDialog openDialog; @@ -126,12 +122,10 @@ public class DataTypeManagerPlugin extends ProgramPlugin @Override public void archiveClosed(Archive archive) { if (archive instanceof ProjectArchive) { + // Program is handled by deactivation event ((ProjectArchive) archive).getDomainObject().removeListener( DataTypeManagerPlugin.this); } - // Program is handled by deactivation. - - // Otherwise, don't care. } @Override @@ -467,21 +461,6 @@ public class DataTypeManagerPlugin extends ProgramPlugin * Create the actions for the menu on the tool. */ private void createActions() { - // create action - manageDataAction = new DockingAction("Data Type Manager", getName()) { - @Override - public void actionPerformed(ActionContext context) { - tool.showComponentProvider(provider, true); - } - }; - ImageIcon dtIcon = ResourceManager.loadImage(DATA_TYPES_ICON); - manageDataAction.setToolBarData(new ToolBarData(dtIcon, "View")); - - manageDataAction.setDescription("Display Data Types"); - manageDataAction.setHelpLocation(provider.getHelpLocation()); - - tool.addAction(manageDataAction); - createStandardArchivesMenu(); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/DataTypesProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/DataTypesProvider.java index 225ef050f0..0d03a6e098 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/DataTypesProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/DataTypesProvider.java @@ -57,6 +57,8 @@ import resources.ResourceManager; import util.HistoryList; public class DataTypesProvider extends ComponentProviderAdapter { + + private static final String DATA_TYPES_ICON = "images/dataTypes.png"; private static final String TITLE = "Data Type Manager"; private static final String POINTER_FILTER_STATE = "PointerFilterState"; private static final String ARRAY_FILTER_STATE = "ArrayFilterState"; @@ -95,9 +97,12 @@ public class DataTypesProvider extends ComponentProviderAdapter { public DataTypesProvider(DataTypeManagerPlugin plugin, String providerName) { super(plugin.getTool(), providerName, plugin.getName(), DataTypesActionContext.class); - setTitle(TITLE); this.plugin = plugin; + setTitle(TITLE); + setIcon(ResourceManager.loadImage(DATA_TYPES_ICON)); + addToToolbar(); + navigationHistory.setAllowDuplicates(true); buildComponent(); @@ -106,11 +111,6 @@ public class DataTypesProvider extends ComponentProviderAdapter { createLocalActions(); } - @Override - public ImageIcon getIcon() { - return ResourceManager.loadImage(DataTypeManagerPlugin.DATA_TYPES_ICON); - } - /** * This creates all the actions for opening/creating data type archives. * It also creates the action for refreshing the built-in data types diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/NextPreviousDataTypeAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/NextPreviousDataTypeAction.java index da85e9fe1b..0a5670d34f 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/NextPreviousDataTypeAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/NextPreviousDataTypeAction.java @@ -123,7 +123,7 @@ class NextPreviousDataTypeAction extends MultiActionDockingAction { private class NavigationAction extends DockingAction { private NavigationAction(DataType dt) { - super("DataTypeNavigationAction_" + ++navigationActionIdCount, owner, false); + super("DataTypeNavigationAction_" + ++navigationActionIdCount, owner); setMenuBarData(new MenuData(new String[] { dt.getDisplayName() })); setEnabled(true); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datawindow/DataWindowPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datawindow/DataWindowPlugin.java index e51e9a8082..e483081b6e 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datawindow/DataWindowPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datawindow/DataWindowPlugin.java @@ -18,7 +18,6 @@ package ghidra.app.plugin.core.datawindow; import java.util.ArrayList; import java.util.Iterator; -import docking.ActionContext; import docking.action.DockingAction; import ghidra.app.CorePluginPackage; import ghidra.app.events.ProgramSelectionPluginEvent; @@ -180,13 +179,7 @@ public class DataWindowPlugin extends ProgramPlugin implements DomainObjectListe */ private void createActions() { - selectAction = new MakeProgramSelectionAction(getName(), provider.getTable()) { - @Override - protected void makeSelection(ActionContext context) { - selectData(provider.selectData()); - } - }; - + selectAction = new MakeProgramSelectionAction(this, provider.getTable()); tool.addLocalAction(provider, selectAction); filterAction = new FilterAction(this); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datawindow/FilterAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datawindow/FilterAction.java index f98cfec86a..bbe236ccdb 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datawindow/FilterAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datawindow/FilterAction.java @@ -23,6 +23,8 @@ import java.util.Map.Entry; import javax.swing.*; +import org.apache.commons.lang3.StringUtils; + import docking.*; import docking.action.ToggleDockingAction; import docking.action.ToolBarData; @@ -34,7 +36,6 @@ import docking.widgets.filter.FilterTextField; import docking.widgets.label.GLabel; import ghidra.program.model.listing.Program; import ghidra.util.HelpLocation; -import ghidra.util.StringUtilities; import ghidra.util.task.SwingUpdateManager; import resources.Icons; @@ -351,7 +352,7 @@ class FilterAction extends ToggleDockingAction { String curType = itr.next(); Boolean lEnabled = typeEnabledMap.get(curType); StringBuffer buildMetaCurTypeBuff = new StringBuffer(curType); - int firstIndex = StringUtilities.indexOfIgnoreCase(curType, filteredText, 0); + int firstIndex = StringUtils.indexOfIgnoreCase(curType, filteredText, 0); int lastIndex = firstIndex + filteredText.length(); buildMetaCurTypeBuff.insert(lastIndex, "");//THIS MUST ALWAYS COME BEFORE FIRST INDEX (FOR NO MATH on INDEX) buildMetaCurTypeBuff.insert(firstIndex, ""); @@ -409,7 +410,7 @@ class FilterAction extends ToggleDockingAction { while (iteratorIndex.hasNext()) { Entry entry = iteratorIndex.next(); String checkboxName = entry.getKey(); - if (StringUtilities.containsIgnoreCase(checkboxName, filteredText)) { + if (StringUtils.containsIgnoreCase(checkboxName, filteredText)) { checkboxNameList.add(checkboxName); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/disassembler/AddressTableDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/disassembler/AddressTableDialog.java index 192f958ce8..a6bfb6ef9e 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/disassembler/AddressTableDialog.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/disassembler/AddressTableDialog.java @@ -25,7 +25,7 @@ import javax.swing.event.DocumentListener; import docking.ActionContext; import docking.DialogComponentProvider; -import docking.action.*; +import docking.action.DockingAction; import docking.widgets.checkbox.GCheckBox; import docking.widgets.label.GDLabel; import docking.widgets.label.GLabel; @@ -40,11 +40,13 @@ import ghidra.program.model.mem.MemoryBlock; import ghidra.program.util.ProgramSelection; import ghidra.util.HelpLocation; import ghidra.util.table.*; +import ghidra.util.table.actions.MakeProgramSelectionAction; import ghidra.util.task.Task; -import resources.ResourceManager; public class AddressTableDialog extends DialogComponentProvider { - static final int DEFAULT_MINIMUM_TABLE_SIZE = 3; + private static final int DEFAULT_MINIMUM_TABLE_SIZE = 3; + private static final String DIALOG_NAME = "Search For Address Tables"; + private JPanel mainPanel; private String[] blockData; private AutoTableDisassemblerPlugin plugin; @@ -67,7 +69,7 @@ public class AddressTableDialog extends DialogComponentProvider { private GhidraThreadedTablePanel resultsTablePanel; public AddressTableDialog(AutoTableDisassemblerPlugin plugin) { - super("Search For Address Tables", false, true, true, true); + super(DIALOG_NAME, false, true, true, true); setHelpLocation( new HelpLocation(HelpTopics.SEARCH, AutoTableDisassemblerPlugin.SEARCH_ACTION_NAME)); this.plugin = plugin; @@ -125,8 +127,7 @@ public class AddressTableDialog extends DialogComponentProvider { JPanel makeTablePanel = new JPanel(new FlowLayout()); makeTableButton = new JButton("Make Table"); - makeTableButton.setToolTipText( - "Make a table of addresses at the selected location(s)."); + makeTableButton.setToolTipText("Make a table of addresses at the selected location(s)."); makeTablePanel.add(makeTableButton); makeTableButton.setEnabled(false); makeTableButton.addActionListener(e -> plugin.makeTable(resultsTable.getSelectedRows())); @@ -175,8 +176,7 @@ public class AddressTableDialog extends DialogComponentProvider { skipLabel = new GDLabel("Skip Length: "); skipField = new JTextField(5); skipField.setName("Skip"); - skipLabel.setToolTipText( - "Number of bytes to skip between found addresses in a table."); + skipLabel.setToolTipText("Number of bytes to skip between found addresses in a table."); skipField.setText("0"); JPanel alignPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); @@ -194,8 +194,7 @@ public class AddressTableDialog extends DialogComponentProvider { selectionButton = new GCheckBox("Search Selection"); selectionButton.setSelected(false); - selectionButton.setToolTipText( - "If checked, search only the current selection."); + selectionButton.setToolTipText("If checked, search only the current selection."); JPanel searchOptionsWestPanel = new JPanel(new GridLayout(2, 1)); searchOptionsWestPanel.add(selectionButton); @@ -233,8 +232,7 @@ public class AddressTableDialog extends DialogComponentProvider { "Label the top of the address table and all members of the table."); offsetLabel = new GDLabel("Offset: "); - offsetLabel.setToolTipText( - "Offset from the beginning of the selected table(s)"); + offsetLabel.setToolTipText("Offset from the beginning of the selected table(s)"); offsetLabel.setEnabled(false); JLabel viewOffsetLabel = new GDLabel(" "); @@ -242,8 +240,7 @@ public class AddressTableDialog extends DialogComponentProvider { viewOffset = new HintTextField(20); viewOffset.setName("viewOffset"); - viewOffset.setToolTipText( - "Address of the selected table starting at the given offset"); + viewOffset.setToolTipText("Address of the selected table starting at the given offset"); viewOffset.setHintText("table start address"); viewOffset.showHint(); @@ -526,20 +523,31 @@ public class AddressTableDialog extends DialogComponentProvider { } private void createAction() { - DockingAction selectAction = - new DockingAction("Make Selection", "AsciiFinderDialog", false) { - @Override - public void actionPerformed(ActionContext context) { - makeSelection(); + + DockingAction selectAction = new MakeProgramSelectionAction(plugin, resultsTable) { + @Override + protected ProgramSelection makeSelection(ActionContext context) { + Program program = plugin.getProgram(); + AddressSet set = new AddressSet(); + AutoTableDisassemblerModel model = plugin.getModel(); + int[] selectedRows = resultsTable.getSelectedRows(); + for (int selectedRow : selectedRows) { + Address selectedAddress = model.getAddress(selectedRow); + AddressTable addrTab = model.get(selectedAddress); + if (addrTab != null) { + set.addRange(selectedAddress, + selectedAddress.add(addrTab.getByteLength() - 1)); + } } - }; - selectAction.setDescription("Make a selection using selected rows"); - selectAction.setEnabled(true); - Icon icon = ResourceManager.loadImage("images/text_align_justify.png"); - selectAction.setPopupMenuData(new MenuData(new String[] { "Make Selection" }, icon)); - selectAction.setToolBarData(new ToolBarData(icon)); - selectAction.setHelpLocation( - new HelpLocation(HelpTopics.SEARCH, "Search_Make_Selection_Address_Tables")); + ProgramSelection selection = new ProgramSelection(set); + if (!set.isEmpty()) { + plugin.firePluginEvent( + new ProgramSelectionPluginEvent(plugin.getName(), selection, program)); + } + + return selection; + } + }; selectionNavigationAction = new SelectionNavigationAction(plugin, resultsTable); selectionNavigationAction.setHelpLocation( @@ -548,7 +556,7 @@ public class AddressTableDialog extends DialogComponentProvider { addAction(selectAction); } - private void makeSelection() { + private void doMakeSelection() { Program program = plugin.getProgram(); AddressSet set = new AddressSet(); AutoTableDisassemblerModel model = plugin.getModel(); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/disassembler/AutoTableDisassemblerPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/disassembler/AutoTableDisassemblerPlugin.java index 5529704d10..bc9a610df5 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/disassembler/AutoTableDisassemblerPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/disassembler/AutoTableDisassemblerPlugin.java @@ -140,15 +140,19 @@ public class AutoTableDisassemblerPlugin extends ProgramPlugin implements Domain public void actionPerformed(ActionContext context) { startDialog(); } + + @Override + public boolean isEnabledForContext(ActionContext context) { + return currentProgram != null; + } }; findTableAction.setHelpLocation( new HelpLocation(HelpTopics.SEARCH, findTableAction.getName())); findTableAction.setMenuBarData( new MenuData(new String[] { ToolConstants.MENU_SEARCH, "For Address Tables..." }, null, "search for")); - findTableAction.setDescription(getPluginDescription().getDescription()); - enableOnLocation(findTableAction); + tool.addAction(findTableAction); } // end of createActions() diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/equate/EquateTablePlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/equate/EquateTablePlugin.java index bf74f06bce..5449bcc647 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/equate/EquateTablePlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/equate/EquateTablePlugin.java @@ -54,12 +54,7 @@ public class EquateTablePlugin extends ProgramPlugin implements DomainObjectList public EquateTablePlugin(PluginTool tool) { super(tool, true, false); - updateMgr = new SwingUpdateManager(1000, 3000, new Runnable() { - @Override - public void run() { - provider.updateEquates(); - } - }); + updateMgr = new SwingUpdateManager(1000, 3000, () -> provider.updateEquates()); provider = new EquateTableProvider(this); } @@ -131,10 +126,10 @@ public class EquateTablePlugin extends ProgramPlugin implements DomainObjectList ev.containsEvent(ChangeManager.DOCR_CODE_ADDED) || ev.containsEvent(ChangeManager.DOCR_CODE_MOVED) || - ev.containsEvent(ChangeManager.DOCR_CODE_REMOVED) || - + ev.containsEvent(ChangeManager.DOCR_CODE_REMOVED) || + ev.containsEvent(ChangeManager.DOCR_DATA_TYPE_CHANGED)) { - + updateMgr.update(); } @@ -154,9 +149,6 @@ public class EquateTablePlugin extends ProgramPlugin implements DomainObjectList provider.programClosed(); } - /** - * Delete the list of equates. - */ void deleteEquates(List equates) { if (equates.isEmpty()) { return; @@ -173,13 +165,11 @@ public class EquateTablePlugin extends ProgramPlugin implements DomainObjectList } } String title = "Delete Equate" + (equates.size() > 1 ? "s" : "") + "?"; - String msg = - "Do you really want to delete the equate" + (equates.size() > 1 ? "s" : "") + ": " + - equates + " ?" + "\n\n NOTE: All references will be removed."; + String msg = "Do you really want to delete the equate" + (equates.size() > 1 ? "s" : "") + + ": " + equates + " ?" + "\n\n NOTE: All references will be removed."; - int option = - OptionDialog.showOptionDialog(provider.getComponent(), title, msg, "Delete", - OptionDialog.QUESTION_MESSAGE); + int option = OptionDialog.showOptionDialog(provider.getComponent(), title, msg, "Delete", + OptionDialog.QUESTION_MESSAGE); if (option != OptionDialog.CANCEL_OPTION) { tool.execute(new RemoveEquateCmd(equateNames, getTool()), currentProgram); @@ -236,26 +226,24 @@ public class EquateTablePlugin extends ProgramPlugin implements DomainObjectList } /** - * If the equate exists, checks to make sure the value matches the - * current scalar value. + * If the equate exists, checks to make sure the value matches the current scalar value * - * @param dialog the dialog whose status area should be set with any error messages. - * @param equateStr the candidate equate name for the set or rename operation. + * @param equate the equate to check + * @param equateStr the candidate equate name for the set or rename operation + * @return true if valid */ boolean isValid(Equate equate, String equateStr) { - // these are valid in the sense that they represent a clear or remove operation. + // these are valid in the sense that they represent a clear or remove operation if (equateStr == null || equateStr.length() <= 0) { return false; } - // look up the new equate string EquateTable equateTable = currentProgram.getEquateTable(); Equate newEquate = equateTable.getEquate(equateStr); - if (newEquate != null && !newEquate.equals(equate)) { - Msg.showInfo(getClass(), provider.getComponent(), "Rename Equate Failed!", "Equate " + - equateStr + " exists with value 0x" + Long.toHexString(newEquate.getValue()) + - " (" + newEquate.getValue() + ")"); + Msg.showInfo(getClass(), provider.getComponent(), "Rename Equate Failed!", + "Equate " + equateStr + " exists with value 0x" + + Long.toHexString(newEquate.getValue()) + " (" + newEquate.getValue() + ")"); return false; } return true; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/ChooseDataTypeAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/ChooseDataTypeAction.java index a4f692853d..8293e9e1eb 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/ChooseDataTypeAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/ChooseDataTypeAction.java @@ -41,7 +41,7 @@ public class ChooseDataTypeAction extends DockingAction { private FunctionPlugin plugin; public ChooseDataTypeAction(FunctionPlugin plugin) { - super(ACTION_NAME, plugin.getName(), false); + super(ACTION_NAME, plugin.getName(), KeyBindingType.SHARED); this.plugin = plugin; setHelpLocation(new HelpLocation("DataTypeEditors", "DataTypeSelectionDialog")); @@ -57,11 +57,6 @@ public class ChooseDataTypeAction extends DockingAction { setKeyBindingData(new KeyBindingData(keyStroke)); } - @Override - public boolean usesSharedKeyBinding() { - return true; - } - @Override public void actionPerformed(ActionContext actionContext) { ListingActionContext context = (ListingActionContext) actionContext.getContextObject(); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/CreateArrayAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/CreateArrayAction.java index d15c0f3a94..b79737d31d 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/CreateArrayAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/CreateArrayAction.java @@ -19,8 +19,7 @@ import java.awt.event.KeyEvent; import javax.swing.KeyStroke; -import docking.action.KeyBindingData; -import docking.action.MenuData; +import docking.action.*; import docking.widgets.dialogs.NumberInputDialog; import ghidra.app.context.ListingActionContext; import ghidra.app.context.ListingContextAction; @@ -37,7 +36,7 @@ class CreateArrayAction extends ListingContextAction { private FunctionPlugin plugin; public CreateArrayAction(FunctionPlugin plugin) { - super("Define Array", plugin.getName(), false); + super("Define Array", plugin.getName(), KeyBindingType.SHARED); this.plugin = plugin; setPopupMenu(plugin.getDataActionMenuName(null)); @@ -54,11 +53,6 @@ class CreateArrayAction extends ListingContextAction { setKeyBindingData(new KeyBindingData(keyStroke)); } - @Override - public boolean usesSharedKeyBinding() { - return true; - } - private void setPopupMenu(String name) { setPopupMenuData(new MenuData( new String[] { FunctionPlugin.SET_DATA_TYPE_PULLRIGHT, "Array..." }, null, "Array")); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/CycleGroupAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/CycleGroupAction.java index 6e43b14c09..a322dedb36 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/CycleGroupAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/CycleGroupAction.java @@ -17,8 +17,7 @@ package ghidra.app.plugin.core.function; import javax.swing.KeyStroke; -import docking.action.KeyBindingData; -import docking.action.MenuData; +import docking.action.*; import ghidra.app.context.ListingActionContext; import ghidra.app.context.ListingContextAction; import ghidra.app.util.HelpTopics; @@ -38,7 +37,7 @@ public class CycleGroupAction extends ListingContextAction { private CycleGroup cycleGroup; CycleGroupAction(CycleGroup group, FunctionPlugin plugin) { - super(group.getName(), plugin.getName(), false); + super(group.getName(), plugin.getName(), KeyBindingType.SHARED); this.plugin = plugin; this.cycleGroup = group; @@ -56,11 +55,6 @@ public class CycleGroupAction extends ListingContextAction { setKeyBindingData(new KeyBindingData(keyStroke)); } - @Override - public boolean usesSharedKeyBinding() { - return true; - } - private void setPopupMenu(String name, boolean isSignatureAction) { setPopupMenuData(new MenuData( new String[] { FunctionPlugin.SET_DATA_TYPE_PULLRIGHT, "Cycle", cycleGroup.getName() }, diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/DataAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/DataAction.java index d712d91756..4bab7848b5 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/DataAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/DataAction.java @@ -17,8 +17,7 @@ package ghidra.app.plugin.core.function; import javax.swing.KeyStroke; -import docking.action.KeyBindingData; -import docking.action.MenuData; +import docking.action.*; import ghidra.app.context.ListingActionContext; import ghidra.app.context.ListingContextAction; import ghidra.program.model.data.DataType; @@ -41,7 +40,7 @@ class DataAction extends ListingContextAction { } public DataAction(String name, String group, DataType dataType, FunctionPlugin plugin) { - super(name, plugin.getName(), false); + super(name, plugin.getName(), KeyBindingType.SHARED); this.group = group; this.plugin = plugin; this.dataType = dataType; @@ -52,11 +51,6 @@ class DataAction extends ListingContextAction { initKeyStroke(getDefaultKeyStroke()); } - @Override - public boolean usesSharedKeyBinding() { - return true; - } - protected KeyStroke getDefaultKeyStroke() { return null; // we have no default, but our subclasses may } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/VariableCommentDeleteAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/VariableCommentDeleteAction.java index 4b5f60d1dd..0d554828df 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/VariableCommentDeleteAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/VariableCommentDeleteAction.java @@ -15,6 +15,11 @@ */ package ghidra.app.plugin.core.function; +import java.awt.event.KeyEvent; + +import docking.ActionContext; +import docking.action.KeyBindingData; +import docking.action.KeyBindingType; import ghidra.app.cmd.function.SetVariableCommentCmd; import ghidra.app.context.ListingActionContext; import ghidra.app.context.ListingContextAction; @@ -22,26 +27,19 @@ import ghidra.program.model.listing.Function; import ghidra.program.model.listing.Variable; import ghidra.program.util.*; -import java.awt.event.KeyEvent; - -import docking.ActionContext; -import docking.action.KeyBindingData; - /** * VariableCommentDeleteAction allows the user to delete a function variable comment. */ class VariableCommentDeleteAction extends ListingContextAction { - /** the plugin associated with this action. */ FunctionPlugin funcPlugin; /** - * Creates a new action with the given name and associated to the given plugin. - * @param plugin - * the plugin this action is associated with. - */ + * Creates a new action with the given name and associated to the given plugin. + * @param plugin the plugin this action is associated with. + */ VariableCommentDeleteAction(FunctionPlugin plugin) { - super("Delete Function Variable Comment", plugin.getName(), false); + super("Delete Function Variable Comment", plugin.getName(), KeyBindingType.SHARED); this.funcPlugin = plugin; setKeyBindingData(new KeyBindingData(KeyEvent.VK_DELETE, 0)); } @@ -65,7 +63,6 @@ class VariableCommentDeleteAction extends ListingContextAction { } } - // /////////////////////////////////////////////////////////// /** * Get a variable using the current location. * diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/VariableDeleteAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/VariableDeleteAction.java index 7da0781ae4..71d90f9396 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/VariableDeleteAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/VariableDeleteAction.java @@ -43,19 +43,19 @@ class VariableDeleteAction extends ListingContextAction { * @param plugin the plugin this action is associated with. */ VariableDeleteAction(FunctionPlugin plugin) { - super("Delete Function Variable", plugin.getName(), true); + super("Delete Function Variable", plugin.getName()); this.funcPlugin = plugin; setPopupMenuPath(false); setKeyBindingData(new KeyBindingData(KeyEvent.VK_DELETE, 0)); - } private void setPopupMenuPath(boolean isParameter) { - setPopupMenuData(new MenuData(new String[] { FunctionPlugin.VARIABLE_MENU_PULLRIGHT, - "Delete " + (isParameter ? "Parameter" : "Local Variable") }, null, - FunctionPlugin.VARIABLE_MENU_SUBGROUP)); + setPopupMenuData(new MenuData( + new String[] { FunctionPlugin.VARIABLE_MENU_PULLRIGHT, + "Delete " + (isParameter ? "Parameter" : "Local Variable") }, + null, FunctionPlugin.VARIABLE_MENU_SUBGROUP)); } @Override diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionWindowPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionWindowPlugin.java index 40c338757c..8ed11b2128 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionWindowPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionWindowPlugin.java @@ -190,18 +190,12 @@ public class FunctionWindowPlugin extends ProgramPlugin implements DomainObjectL private void addSelectAction() { - selectAction = new MakeProgramSelectionAction(getName(), provider.getTable()) { - @Override - protected void makeSelection(ActionContext context) { - selectFunctions(provider.selectFunctions()); - } - }; - + selectAction = new MakeProgramSelectionAction(this, provider.getTable()); tool.addLocalAction(provider, selectAction); } private void addCompareAction() { - compareAction = new DockingAction("Compare Selected Functions", getName(), false) { + compareAction = new DockingAction("Compare Selected Functions", getName()) { @Override public void actionPerformed(ActionContext context) { compareSelectedFunctions(); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/ui/AbstractInstructionTable.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/ui/AbstractInstructionTable.java index ea7ea50154..f9afdb717b 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/ui/AbstractInstructionTable.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/ui/AbstractInstructionTable.java @@ -16,13 +16,10 @@ package ghidra.app.plugin.core.instructionsearch.ui; import java.awt.Font; -import java.util.List; import javax.swing.JToolBar; import javax.swing.table.TableCellRenderer; -import docking.ActionContext; -import docking.action.DockingActionIf; import docking.widgets.table.GTable; import ghidra.app.plugin.core.instructionsearch.model.*; import ghidra.util.table.GhidraTable; @@ -109,17 +106,6 @@ public abstract class AbstractInstructionTable extends GhidraTable { return (InstructionTableDataObject) getModel().getValueAt(row, col); } - /** - * Must invoke the parent implementation of this to have the context menu - * created. - * - */ - @Override - public List getDockingActions(ActionContext context) { - List list = super.getDockingActions(context); - return list; - } - /** * Must override so it doesn't return an instance of the base * {@link TableCellRenderer}, which will override our changes in the diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/ui/InstructionTable.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/ui/InstructionTable.java index 63702261d8..17adcbe1d3 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/ui/InstructionTable.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/ui/InstructionTable.java @@ -22,7 +22,6 @@ import java.util.List; import javax.swing.*; -import docking.ActionContext; import docking.DockingWindowManager; import docking.action.DockingActionIf; import docking.widgets.EmptyBorderButton; @@ -102,7 +101,7 @@ public class InstructionTable extends AbstractInstructionTable { * (which is all of them). */ @Override - public List getDockingActions(ActionContext context) { + public List getDockingActions() { return new ArrayList<>(); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/ui/PreviewTable.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/ui/PreviewTable.java index 142a24002c..85226e77ed 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/ui/PreviewTable.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/instructionsearch/ui/PreviewTable.java @@ -134,14 +134,12 @@ public class PreviewTable extends AbstractInstructionTable { /** * Adds custom context-sensitive menus to the table. This does NOT modify * any existing menus; it simply adds to them. - * - * @param context the action context */ @Override - public List getDockingActions(ActionContext context) { + public List getDockingActions() { // Invoke the base class method to add default menu options. - List list = super.getDockingActions(context); + List list = super.getDockingActions(); // And now add our own. addCustomMenuItems(list); @@ -489,7 +487,7 @@ public class PreviewTable extends AbstractInstructionTable { */ private void createCopyInstructionWithCommentsAction(String owner) { copyInstructionWithCommentsAction = - new DockingAction("Selected Instructions (with comments)", owner, false) { + new DockingAction("Selected Instructions (with comments)", owner) { @Override public void actionPerformed(ActionContext context) { int[] selectedRows = PreviewTable.this.getSelectedRows(); @@ -520,7 +518,7 @@ public class PreviewTable extends AbstractInstructionTable { * as shown in the table. */ private void createCopyInstructionAction(String owner) { - copyInstructionAction = new DockingAction("Selected Instructions", owner, false) { + copyInstructionAction = new DockingAction("Selected Instructions", owner) { @Override public void actionPerformed(ActionContext context) { int[] selectedRows = PreviewTable.this.getSelectedRows(); @@ -541,7 +539,7 @@ public class PreviewTable extends AbstractInstructionTable { * rows, as shown in the table, with no spaces. */ private void createCopyNoSpacesAction(String owner) { - copyNoSpacesAction = new DockingAction("Selected instructions (no spaces)", owner, false) { + copyNoSpacesAction = new DockingAction("Selected instructions (no spaces)", owner) { @Override public void actionPerformed(ActionContext context) { int[] selectedRows = PreviewTable.this.getSelectedRows(); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/interpreter/InterpreterComponentProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/interpreter/InterpreterComponentProvider.java index c41690114c..a51652fc3a 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/interpreter/InterpreterComponentProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/interpreter/InterpreterComponentProvider.java @@ -19,7 +19,8 @@ import java.io.*; import java.util.ArrayList; import java.util.List; -import javax.swing.*; +import javax.swing.Icon; +import javax.swing.JComponent; import docking.ActionContext; import docking.action.DockingAction; @@ -33,12 +34,12 @@ import utility.function.Callback; public class InterpreterComponentProvider extends ComponentProviderAdapter implements InterpreterConsole { + private static final String CONSOLE_GIF = "images/monitor.png"; private static final String CLEAR_GIF = "images/erase16.png"; private InterpreterPanel panel; private InterpreterConnection interpreter; - private ImageIcon icon; private List firstActivationCallbacks; public InterpreterComponentProvider(InterpreterPanelPlugin plugin, @@ -54,9 +55,9 @@ public class InterpreterComponentProvider extends ComponentProviderAdapter addToTool(); createActions(); - icon = interpreter.getIcon(); + Icon icon = interpreter.getIcon(); if (icon == null) { - ResourceManager.loadImage(CONSOLE_GIF); + icon = ResourceManager.loadImage(CONSOLE_GIF); } setIcon(icon); @@ -107,11 +108,6 @@ public class InterpreterComponentProvider extends ComponentProviderAdapter addLocalAction(disposeAction); } - @Override - public Icon getIcon() { - return icon; - } - @Override public String getWindowSubMenuName() { return interpreter.getTitle(); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/memory/MemoryMapPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/memory/MemoryMapPlugin.java index 65fa8f6a8a..784bdc3bdc 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/memory/MemoryMapPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/memory/MemoryMapPlugin.java @@ -15,6 +15,8 @@ */ package ghidra.app.plugin.core.memory; +import java.awt.Cursor; + import ghidra.app.CorePluginPackage; import ghidra.app.events.ProgramLocationPluginEvent; import ghidra.app.plugin.PluginCategoryNames; @@ -31,15 +33,6 @@ import ghidra.program.model.mem.MemoryBlock; import ghidra.program.util.ChangeManager; import ghidra.program.util.ProgramLocation; -import java.awt.Cursor; - -import javax.swing.ImageIcon; - -import resources.ResourceManager; -import docking.ActionContext; -import docking.action.DockingAction; -import docking.action.ToolBarData; - /** * MemoryMapPlugin displays a memory map of all blocks in * the current program's memory. Options for Adding, Editing, and Deleting @@ -61,20 +54,15 @@ public class MemoryMapPlugin extends ProgramPlugin implements DomainObjectListen final static Cursor WAIT_CURSOR = new Cursor(Cursor.WAIT_CURSOR); final static Cursor NORM_CURSOR = new Cursor(Cursor.DEFAULT_CURSOR); - private DockingAction memViewAction; private MemoryMapProvider provider; private GoToService goToService; private MemoryMapManager memManager; - /** - * Constructor - */ public MemoryMapPlugin(PluginTool tool) { super(tool, true, false); memManager = new MemoryMapManager(this); provider = new MemoryMapProvider(this); - createActions(); } /** @@ -83,9 +71,6 @@ public class MemoryMapPlugin extends ProgramPlugin implements DomainObjectListen */ @Override public void dispose() { - if (memViewAction != null) { - memViewAction.dispose(); - } if (provider != null) { provider.dispose(); provider = null; @@ -122,8 +107,9 @@ public class MemoryMapPlugin extends ProgramPlugin implements DomainObjectListen @Override protected void init() { goToService = tool.getService(GoToService.class); - if (currentProgram != null) + if (currentProgram != null) { programActivated(currentProgram); + } } /** @@ -168,29 +154,4 @@ public class MemoryMapPlugin extends ProgramPlugin implements DomainObjectListen ProgramLocation loc = new ProgramLocation(currentProgram, addr); goToService.goTo(loc); } - - /** - * Create the action for toolbar. - */ - private void createActions() { - - memViewAction = new DockingAction("View Memory Map", getName()) { - @Override - public void actionPerformed(ActionContext context) { - showMemory(); - } - }; - ImageIcon tableImage = ResourceManager.loadImage(MemoryMapProvider.MEMORY_IMAGE); - memViewAction.setToolBarData(new ToolBarData(tableImage, "View")); - memViewAction.setDescription("Display Memory Map"); - tool.addAction(memViewAction); - } - - /** - * Callback for the View memory Action - */ - private void showMemory() { - tool.showComponentProvider(provider, true); - } - } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/memory/MemoryMapProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/memory/MemoryMapProvider.java index 8edc51298d..058f64a356 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/memory/MemoryMapProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/memory/MemoryMapProvider.java @@ -81,9 +81,11 @@ class MemoryMapProvider extends ComponentProviderAdapter { MemoryMapProvider(MemoryMapPlugin plugin) { super(plugin.getTool(), "Memory Map", plugin.getName(), ProgramActionContext.class); this.plugin = plugin; + setHelpLocation(new HelpLocation(plugin.getName(), getName())); memManager = plugin.getMemoryMapManager(); setIcon(ResourceManager.loadImage(MEMORY_IMAGE)); + addToToolbar(); mainPanel = buildMainPanel(); addToTool(); addLocalActions(); @@ -107,9 +109,6 @@ class MemoryMapProvider extends ComponentProviderAdapter { return new ProgramActionContext(this, program); } - /** - * Set the status text on this dialog. - */ void setStatusText(String msg) { tool.setStatusInfo(msg); } @@ -367,9 +366,6 @@ class MemoryMapProvider extends ComponentProviderAdapter { } } - /** - * @return - */ JTable getTable() { return memTable; } @@ -462,7 +458,8 @@ class MemoryMapProvider extends ComponentProviderAdapter { public void mousePressed(MouseEvent e) { setStatusText(""); if (!e.isPopupTrigger()) { - if ((e.getModifiers() & (InputEvent.CTRL_MASK | InputEvent.SHIFT_MASK)) == 0) { + if ((e.getModifiersEx() & + (InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK)) == 0) { selectAddress(); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/NextPrevAddressPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/NextPrevAddressPlugin.java index 1ef665d81c..7f28126c1d 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/NextPrevAddressPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/NextPrevAddressPlugin.java @@ -368,7 +368,7 @@ public class NextPrevAddressPlugin extends Plugin { private NavigationAction(Navigatable navigatable, LocationMemento location, boolean isNext, NavigationHistoryService service, CodeUnitFormat formatter) { - super("NavigationAction: " + ++idCount, NextPrevAddressPlugin.this.getName(), false); + super("NavigationAction: " + ++idCount, NextPrevAddressPlugin.this.getName()); this.location = location; this.isNext = isNext; this.service = service; 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 new file mode 100644 index 0000000000..4f6d98ff7e --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/ProviderNavigationPlugin.java @@ -0,0 +1,106 @@ +/* ### + * 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.navigation; + +import java.awt.event.KeyEvent; +import java.util.function.Consumer; + +import javax.swing.KeyStroke; + +import docking.*; +import docking.action.*; +import ghidra.app.CorePluginPackage; +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( + status = PluginStatus.RELEASED, + packageName = CorePluginPackage.NAME, + category = PluginCategoryNames.NAVIGATION, + shortDescription = "Component Provider Navigation", + description = "The plugin provides actions to manage switching between Component Providers." +) +//@formatter:on +public class ProviderNavigationPlugin extends Plugin { + + static final String GO_TO_LAST_ACTIVE_COMPONENT_ACTION_NAME = "Go To Last Active Component"; + + private ComponentProvider previousActiveProvider; + private ComponentProvider currentActiveProvider; + private Consumer providerActivator = + provider -> tool.showComponentProvider(provider, true); + + private DockingContextListener contextListener = context -> { + + ComponentProvider componentProvider = context.getComponentProvider(); + if (componentProvider != null) { + if (componentProvider != currentActiveProvider) { + previousActiveProvider = currentActiveProvider; + currentActiveProvider = componentProvider; + } + } + }; + + public ProviderNavigationPlugin(PluginTool tool) { + super(tool); + + createActions(); + + tool.addContextListener(contextListener); + } + + private void createActions() { + + DockingAction previousProviderAction = + new DockingAction(GO_TO_LAST_ACTIVE_COMPONENT_ACTION_NAME, getName()) { + + @Override + public void actionPerformed(ActionContext context) { + providerActivator.accept(previousActiveProvider); + } + + @Override + public boolean isEnabledForContext(ActionContext context) { + return previousActiveProvider != null; + } + }; + previousProviderAction.setMenuBarData(new MenuData( + new String[] { ToolConstants.MENU_NAVIGATION, GO_TO_LAST_ACTIVE_COMPONENT_ACTION_NAME }, + null, ToolConstants.MENU_NAVIGATION_GROUP_WINDOWS, MenuData.NO_MNEMONIC, + "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); + } + + // for testing + void resetTrackingState() { + previousActiveProvider = null; + currentActiveProvider = null; + } + + // for testing + void setProviderActivator(Consumer newActivator) { + this.providerActivator = newActivator; + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/locationreferences/FindReferencesToAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/locationreferences/FindReferencesToAction.java index 3e637bb5c5..909b0b5e0b 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/locationreferences/FindReferencesToAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/locationreferences/FindReferencesToAction.java @@ -17,8 +17,7 @@ package ghidra.app.plugin.core.navigation.locationreferences; import javax.swing.KeyStroke; -import docking.action.KeyBindingData; -import docking.action.MenuData; +import docking.action.*; import ghidra.app.actions.AbstractFindReferencesDataTypeAction; import ghidra.app.context.ListingActionContext; import ghidra.app.context.ListingContextAction; @@ -34,7 +33,7 @@ public class FindReferencesToAction extends ListingContextAction { private int subGroupPosition; public FindReferencesToAction(LocationReferencesPlugin plugin, int subGroupPosition) { - super(AbstractFindReferencesDataTypeAction.NAME, plugin.getName(), false); + super(AbstractFindReferencesDataTypeAction.NAME, plugin.getName(), KeyBindingType.SHARED); this.plugin = plugin; this.subGroupPosition = subGroupPosition; @@ -54,11 +53,6 @@ public class FindReferencesToAction extends ListingContextAction { setKeyBindingData(new KeyBindingData(keyStroke)); } - @Override - public boolean usesSharedKeyBinding() { - return true; - } - @Override public void actionPerformed(ListingActionContext context) { plugin.displayProvider(context); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/locationreferences/FindReferencesToAddressAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/locationreferences/FindReferencesToAddressAction.java index 4f85a2e482..dfc7655991 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/locationreferences/FindReferencesToAddressAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/locationreferences/FindReferencesToAddressAction.java @@ -15,6 +15,7 @@ */ package ghidra.app.plugin.core.navigation.locationreferences; +import docking.action.KeyBindingType; import docking.action.MenuData; import ghidra.app.context.ListingActionContext; import ghidra.app.context.ListingContextAction; @@ -35,7 +36,7 @@ public class FindReferencesToAddressAction extends ListingContextAction { private LocationReferencesPlugin plugin; public FindReferencesToAddressAction(LocationReferencesPlugin plugin, int subGroupPosition) { - super("Show References to Address", plugin.getName(), false); + super("Show References to Address", plugin.getName(), KeyBindingType.SHARED); this.plugin = plugin; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/locationreferences/GenericCompositeDataTypeLocationDescriptor.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/locationreferences/GenericCompositeDataTypeLocationDescriptor.java index 58c3a5d563..f480c3e5fe 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/locationreferences/GenericCompositeDataTypeLocationDescriptor.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/locationreferences/GenericCompositeDataTypeLocationDescriptor.java @@ -17,13 +17,14 @@ package ghidra.app.plugin.core.navigation.locationreferences; import java.awt.Color; +import org.apache.commons.lang3.StringUtils; + import docking.widgets.fieldpanel.support.Highlight; import ghidra.app.util.viewer.field.*; import ghidra.program.model.address.Address; import ghidra.program.model.data.Composite; import ghidra.program.model.listing.Data; import ghidra.program.model.listing.Program; -import ghidra.util.StringUtilities; import ghidra.util.datastruct.Accumulator; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; @@ -115,7 +116,7 @@ public class GenericCompositeDataTypeLocationDescriptor extends GenericDataTypeL else if (OperandFieldFactory.class.isAssignableFrom(fieldFactoryClass)) { // Not sure how to get the correct part of the text. This is a hack for now. - int offset = StringUtilities.indexOfIgnoreCase(text, typeAndFieldName, 0); + int offset = StringUtils.indexOfIgnoreCase(text, typeAndFieldName, 0); if (offset != -1) { return new Highlight[] { new Highlight(offset, offset + typeAndFieldName.length() - 1, highlightColor) }; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/locationreferences/LocationReferencesProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/locationreferences/LocationReferencesProvider.java index c6fb3e222e..d2a13de27b 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/locationreferences/LocationReferencesProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/locationreferences/LocationReferencesProvider.java @@ -41,6 +41,7 @@ import ghidra.util.HelpLocation; import ghidra.util.table.GhidraTable; import ghidra.util.table.SelectionNavigationAction; import ghidra.util.table.actions.DeleteTableRowAction; +import ghidra.util.table.actions.MakeProgramSelectionAction; import ghidra.util.task.SwingUpdateManager; import resources.Icons; import resources.ResourceManager; @@ -51,7 +52,6 @@ import resources.ResourceManager; public class LocationReferencesProvider extends ComponentProviderAdapter implements DomainObjectListener, NavigatableRemovalListener { - private static Icon SELECT_ICON = ResourceManager.loadImage("images/text_align_justify.png"); private static Icon HIGHLIGHT_ICON = ResourceManager.loadImage("images/tag_yellow.png"); private static Icon HOME_ICON = ResourceManager.loadImage("images/go-home.png"); private static Icon REFRESH_ICON = Icons.REFRESH_ICON; @@ -153,7 +153,7 @@ public class LocationReferencesProvider extends ComponentProviderAdapter referencesPanel.reloadModel(); } - private void makeSelection() { + private void doMakeSelection() { locationReferencesPlugin.firePluginEvent(new ProgramSelectionPluginEvent( locationReferencesPlugin.getName(), referencesPanel.getSelection(), program)); } @@ -176,7 +176,10 @@ public class LocationReferencesProvider extends ComponentProviderAdapter setTitle(generateTitle()); } - /** Sets the new LocationDescriptor and updates the providers table contents. */ + /** + * Sets the new LocationDescriptor and updates the providers table contents. + * @param locationDescriptor the new descriptor + */ void update(LocationDescriptor locationDescriptor) { setLocationDescriptor(locationDescriptor, navigatable); updateManager.updateNow(); @@ -246,30 +249,8 @@ public class LocationReferencesProvider extends ComponentProviderAdapter homeAction.setToolBarData(new ToolBarData(HOME_ICON)); updateHomeActionState(); - selectionAction = new DockingAction("Make Selection", locationReferencesPlugin.getName()) { - @Override - public void actionPerformed(ActionContext context) { - makeSelection(); - } - - @Override - public boolean isEnabledForContext(ActionContext context) { - return referencesPanel.getTable().getSelectedRowCount() > 0; - } - - @Override - public boolean isAddToPopup(ActionContext context) { - if (referencesPanel.getTable().getClass().isInstance(context.getContextObject())) { - return super.isEnabledForContext(context); - } - return false; - } - }; - selectionAction.setPopupMenuData( - new MenuData(new String[] { "Make Selection" }, SELECT_ICON)); - selectionAction.setToolBarData(new ToolBarData(SELECT_ICON)); - selectionAction.setDescription("Make a program selection from selected rows in table"); - selectionAction.setEnabled(false); // off by default; updated when the user clicks the table + selectionAction = + new MakeProgramSelectionAction(locationReferencesPlugin, referencesPanel.getTable()); highlightAction = new ToggleDockingAction("Highlight Matches", getName()) { @Override diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/MultiTabPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/MultiTabPlugin.java index c33898cd52..9602ece236 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/MultiTabPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/progmgr/MultiTabPlugin.java @@ -15,6 +15,15 @@ */ package ghidra.app.plugin.core.progmgr; +import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; + +import javax.swing.KeyStroke; +import javax.swing.Timer; + +import docking.ActionContext; +import docking.DockingUtils; +import docking.action.*; import ghidra.app.CorePluginPackage; import ghidra.app.events.*; import ghidra.app.plugin.PluginCategoryNames; @@ -23,18 +32,10 @@ import ghidra.app.services.ProgramManager; import ghidra.framework.model.*; import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.util.PluginStatus; +import ghidra.framework.plugintool.util.ToolConstants; import ghidra.program.model.listing.Program; import ghidra.util.HelpLocation; -import java.awt.event.*; - -import javax.swing.KeyStroke; -import javax.swing.Timer; - -import docking.ActionContext; -import docking.DockingUtils; -import docking.action.*; - /** * Plugin to show a "tab" for each open program; the selected tab is the activated program. */ @@ -58,10 +59,10 @@ public class MultiTabPlugin extends Plugin implements DomainObjectListener { // DockingUtils calls into Swing code. Further, we don't want Swing code being accessed // when the Plugin classes are loaded, as they get loaded in the headless environment. // - private final KeyStroke NEXT_TAB_KEYSTROKE = KeyStroke.getKeyStroke(KeyEvent.VK_F9, - DockingUtils.CONTROL_KEY_MODIFIER_MASK); - private final KeyStroke PREVIOUS_TAB_KEYSTROKE = KeyStroke.getKeyStroke(KeyEvent.VK_F8, - DockingUtils.CONTROL_KEY_MODIFIER_MASK); + private final KeyStroke NEXT_TAB_KEYSTROKE = + KeyStroke.getKeyStroke(KeyEvent.VK_F9, DockingUtils.CONTROL_KEY_MODIFIER_MASK); + private final KeyStroke PREVIOUS_TAB_KEYSTROKE = + KeyStroke.getKeyStroke(KeyEvent.VK_F8, DockingUtils.CONTROL_KEY_MODIFIER_MASK); private MultiTabPanel tabPanel; private ProgramManager progService; @@ -92,14 +93,17 @@ public class MultiTabPlugin extends Plugin implements DomainObjectListener { showProgramList(); } }; - goToProgramAction.setMenuBarData(new MenuData(new String[] { "Navigation", - "Go To Program..." }, null, "GoToProgram", MenuData.NO_MNEMONIC, firstGroup)); - goToProgramAction.setKeyBindingData(new KeyBindingData(KeyEvent.VK_F7, - InputEvent.CTRL_DOWN_MASK)); + goToProgramAction.setMenuBarData( + new MenuData(new String[] { ToolConstants.MENU_NAVIGATION, "Go To Program..." }, null, + ToolConstants.MENU_NAVIGATION_GROUP_WINDOWS, MenuData.NO_MNEMONIC, firstGroup)); + goToProgramAction.setKeyBindingData( + new KeyBindingData(KeyEvent.VK_F7, InputEvent.CTRL_DOWN_MASK)); goToProgramAction.setEnabled(false); - goToProgramAction.setDescription("Shows the program selection dialog with the current program selected"); - goToProgramAction.setHelpLocation(new HelpLocation("ProgramManagerPlugin", "Go_To_Program")); + goToProgramAction.setDescription( + "Shows the program selection dialog with the current program selected"); + goToProgramAction.setHelpLocation( + new HelpLocation("ProgramManagerPlugin", "Go_To_Program")); goToNextProgramAction = new DockingAction("Go To Next Program", getName()) { @Override @@ -109,10 +113,11 @@ public class MultiTabPlugin extends Plugin implements DomainObjectListener { } }; goToNextProgramAction.setEnabled(false); - goToNextProgramAction.setDescription("Highlights the next program tab and then switches to that program"); + goToNextProgramAction.setDescription( + "Highlights the next program tab and then switches to that program"); goToNextProgramAction.setKeyBindingData(new KeyBindingData(NEXT_TAB_KEYSTROKE)); - goToNextProgramAction.setHelpLocation(new HelpLocation("ProgramManagerPlugin", - "Go_To_Next_And_Previous_Program")); + goToNextProgramAction.setHelpLocation( + new HelpLocation("ProgramManagerPlugin", "Go_To_Next_And_Previous_Program")); goToPreviousProgramAction = new DockingAction("Go To Previous Program", getName()) { @Override @@ -122,20 +127,14 @@ public class MultiTabPlugin extends Plugin implements DomainObjectListener { } }; goToPreviousProgramAction.setEnabled(false); - goToPreviousProgramAction.setMenuBarData(new MenuData(new String[] { "Navigation" }, null, - null)); goToPreviousProgramAction.setKeyBindingData(new KeyBindingData(PREVIOUS_TAB_KEYSTROKE)); - goToPreviousProgramAction.setDescription("Highlights the previous program tab and then switches to that program"); - goToPreviousProgramAction.setHelpLocation(new HelpLocation("ProgramManagerPlugin", - "Go_To_Next_And_Previous_Program")); + goToPreviousProgramAction.setDescription( + "Highlights the previous program tab and then switches to that program"); + goToPreviousProgramAction.setHelpLocation( + new HelpLocation("ProgramManagerPlugin", "Go_To_Next_And_Previous_Program")); // this timer is to give the user time to select successive programs before activating one - selectHighlightedProgramTimer = new Timer(750, new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - selectHighlightedProgram(); - } - }); + selectHighlightedProgramTimer = new Timer(750, e -> selectHighlightedProgram()); selectHighlightedProgramTimer.setRepeats(false); goToLastActiveProgramAction = new DockingAction("Go To Last Active Program", getName()) { @@ -144,14 +143,16 @@ public class MultiTabPlugin extends Plugin implements DomainObjectListener { switchToProgram(lastActiveProgram); } }; - goToLastActiveProgramAction.setMenuBarData(new MenuData(new String[] { "Navigation", - "Go To Last Active Program" }, null, "GoToProgram", MenuData.NO_MNEMONIC, secondGroup)); - goToLastActiveProgramAction.setKeyBindingData(new KeyBindingData(KeyEvent.VK_F6, - InputEvent.CTRL_DOWN_MASK)); + goToLastActiveProgramAction.setMenuBarData(new MenuData( + new String[] { ToolConstants.MENU_NAVIGATION, "Go To Last Active Program" }, null, + ToolConstants.MENU_NAVIGATION_GROUP_WINDOWS, MenuData.NO_MNEMONIC, secondGroup)); + goToLastActiveProgramAction.setKeyBindingData( + new KeyBindingData(KeyEvent.VK_F6, InputEvent.CTRL_DOWN_MASK)); goToLastActiveProgramAction.setEnabled(false); - goToLastActiveProgramAction.setDescription("Activates the last program used before the current program"); - goToLastActiveProgramAction.setHelpLocation(new HelpLocation("ProgramManagerPlugin", - "Go_To_Last_Active_Program")); + goToLastActiveProgramAction.setDescription( + "Activates the last program used before the current program"); + goToLastActiveProgramAction.setHelpLocation( + new HelpLocation("ProgramManagerPlugin", "Go_To_Last_Active_Program")); tool.addAction(goToProgramAction); tool.addAction(goToLastActiveProgramAction); 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 25435f96f2..ef19bb0c9d 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 @@ -64,8 +64,10 @@ public class RegisterManagerProvider extends ComponentProviderAdapter { RegisterManagerProvider(PluginTool tool, String owner) { super(tool, "Register Manager", owner, ProgramActionContext.class); buildComponent(); - setHelpLocation(new HelpLocation("RegisterPlugin", "RegisterManager")); + + setHelpLocation(new HelpLocation("RegisterPlugin", "Register_Manager")); setIcon(REGISTER_ICON); + addToToolbar(); setDefaultWindowPosition(WindowPosition.WINDOW); updateMgr = new SwingUpdateManager(500, () -> update()); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/register/RegisterPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/register/RegisterPlugin.java index c9bbe6f1b0..495d71ccaf 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/register/RegisterPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/register/RegisterPlugin.java @@ -43,7 +43,6 @@ import ghidra.program.model.listing.*; import ghidra.program.util.*; import ghidra.util.HelpLocation; import ghidra.util.Msg; -import resources.ResourceManager; /** * Shows the registers available in a program along with any values that are set. @@ -63,7 +62,6 @@ public class RegisterPlugin extends ProgramPlugin { private RegisterManagerProvider registerMgrProvider; private Register[] registers = new Register[0]; - private DockingAction showRegistersAction; private DockingAction deleteRegisterRangeAction; private DockingAction deleteRegisterAtFunctionAction; private DockingAction clearRegisterAction; @@ -94,21 +92,6 @@ public class RegisterPlugin extends ProgramPlugin { } private void createActions() { - showRegistersAction = new DockingAction("Display Register Values", getName()) { - @Override - public void actionPerformed(ActionContext context) { - tool.showComponentProvider(registerMgrProvider, true); - } - }; - showRegistersAction.setToolBarData(new ToolBarData( - ResourceManager.loadImage("images/registerGroup.png"), "View")); - showRegistersAction.setKeyBindingData(new KeyBindingData(KeyEvent.VK_V, 0)); - - showRegistersAction.setDescription("Display Register Values"); - showRegistersAction.setHelpLocation(new HelpLocation("RegisterPlugin", "RegisterManager")); - showRegistersAction.setEnabled(true); - - tool.addAction(showRegistersAction); setRegisterAction = new DockingAction("Set Register Values", getName()) { @Override @@ -125,10 +108,10 @@ public class RegisterPlugin extends ProgramPlugin { (contextObject instanceof RegisterManagerProvider); } }; - setRegisterAction.setPopupMenuData(new MenuData(new String[] { "Set Register Values..." }, - null, "Registers")); - setRegisterAction.setKeyBindingData(new KeyBindingData(KeyEvent.VK_R, - InputEvent.CTRL_DOWN_MASK)); + setRegisterAction.setPopupMenuData( + new MenuData(new String[] { "Set Register Values..." }, null, "Registers")); + setRegisterAction.setKeyBindingData( + new KeyBindingData(KeyEvent.VK_R, InputEvent.CTRL_DOWN_MASK)); setRegisterAction.setDescription("Set register values in a program."); setRegisterAction.setHelpLocation(new HelpLocation("RegisterPlugin", "SetRegisterValues")); @@ -151,12 +134,12 @@ public class RegisterPlugin extends ProgramPlugin { (contextObject instanceof RegisterManagerProvider); } }; - clearRegisterAction.setPopupMenuData(new MenuData( - new String[] { "Clear Register Values..." }, null, "Registers")); + clearRegisterAction.setPopupMenuData( + new MenuData(new String[] { "Clear Register Values..." }, null, "Registers")); clearRegisterAction.setDescription("Clear register values in a program."); - clearRegisterAction.setHelpLocation(new HelpLocation("RegisterPlugin", - "ClearRegisterValues")); + clearRegisterAction.setHelpLocation( + new HelpLocation("RegisterPlugin", "ClearRegisterValues")); clearRegisterAction.setEnabled(true); tool.addAction(clearRegisterAction); @@ -193,8 +176,8 @@ public class RegisterPlugin extends ProgramPlugin { deleteRegisterRangeAction.setKeyBindingData(new KeyBindingData(KeyEvent.VK_DELETE, 0)); deleteRegisterRangeAction.setDescription("Delete register value at Function."); - deleteRegisterRangeAction.setHelpLocation(new HelpLocation("RegisterPlugin", - "DeleteRegisterValueRange")); + deleteRegisterRangeAction.setHelpLocation( + new HelpLocation("RegisterPlugin", "DeleteRegisterValueRange")); deleteRegisterRangeAction.setEnabled(true); tool.addAction(deleteRegisterRangeAction); @@ -222,13 +205,13 @@ public class RegisterPlugin extends ProgramPlugin { return false; } }; - deleteRegisterAtFunctionAction.setPopupMenuData(new MenuData( - new String[] { "Delete Register Value Range..." }, null, "Registers")); + deleteRegisterAtFunctionAction.setPopupMenuData( + new MenuData(new String[] { "Delete Register Value Range..." }, null, "Registers")); deleteRegisterAtFunctionAction.setKeyBindingData(new KeyBindingData(KeyEvent.VK_DELETE, 0)); deleteRegisterAtFunctionAction.setDescription("Delete register value range."); - deleteRegisterAtFunctionAction.setHelpLocation(new HelpLocation("RegisterPlugin", - "DeleteRegisterValueRange")); + deleteRegisterAtFunctionAction.setHelpLocation( + new HelpLocation("RegisterPlugin", "DeleteRegisterValueRange")); deleteRegisterAtFunctionAction.setEnabled(true); tool.addAction(deleteRegisterAtFunctionAction); @@ -254,8 +237,8 @@ public class RegisterPlugin extends ProgramPlugin { while (it.hasNext()) { AddressRange range = it.next(); if (range.contains(addr)) { - Command cmd = - new SetRegisterCmd(register, range.getMinAddress(), range.getMaxAddress(), null); + Command cmd = new SetRegisterCmd(register, range.getMinAddress(), + range.getMaxAddress(), null); if (!tool.execute(cmd, context.getProgram())) { Msg.showError(this, tool.getToolFrame(), "Register Context Error", cmd.getStatusMsg()); @@ -356,7 +339,7 @@ public class RegisterPlugin extends ProgramPlugin { @Override protected void programActivated(Program program) { registerMgrProvider.setProgram(program); - ArrayList list = new ArrayList(); + ArrayList list = new ArrayList<>(); for (Register reg : program.getProgramContext().getRegisters()) { if (!reg.isHidden()) { list.add(reg); @@ -442,7 +425,8 @@ public class RegisterPlugin extends ProgramPlugin { @Override public Class[] getSupportedProgramLocations() { - return new Class[] { RegisterTransitionFieldLocation.class, RegisterFieldLocation.class }; + return new Class[] { RegisterTransitionFieldLocation.class, + RegisterFieldLocation.class }; } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/reloc/RelocationTablePlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/reloc/RelocationTablePlugin.java index 5951c0dd16..0fbc7d89ef 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/reloc/RelocationTablePlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/reloc/RelocationTablePlugin.java @@ -15,24 +15,19 @@ */ package ghidra.app.plugin.core.reloc; -import javax.swing.ImageIcon; - -import docking.ActionContext; -import docking.action.*; +import docking.action.DockingAction; import ghidra.app.CorePluginPackage; import ghidra.app.events.*; import ghidra.app.plugin.PluginCategoryNames; import ghidra.app.services.GoToService; -import ghidra.app.util.HelpTopics; import ghidra.framework.model.*; import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.util.PluginStatus; import ghidra.program.model.listing.Program; import ghidra.program.util.ChangeManager; import ghidra.program.util.ProgramSelection; -import ghidra.util.HelpLocation; import ghidra.util.table.SelectionNavigationAction; -import resources.ResourceManager; +import ghidra.util.table.actions.MakeProgramSelectionAction; //@formatter:off @PluginInfo( @@ -66,27 +61,15 @@ public class RelocationTablePlugin extends Plugin implements DomainObjectListene } private void createActions() { - DockingAction programSelectionAction = - new DockingAction("Make Selection", getName(), false) { - @Override - public void actionPerformed(ActionContext context) { - makeSelection(); - } - }; - programSelectionAction.setDescription("Make a selection using selected rows"); - ImageIcon icon = ResourceManager.loadImage("images/text_align_justify.png"); - programSelectionAction.setToolBarData(new ToolBarData(icon)); - programSelectionAction.setPopupMenuData( - new MenuData(new String[] { "Make Selection" }, icon)); - programSelectionAction.setHelpLocation( - new HelpLocation(HelpTopics.SEARCH, "Make_Selection")); - tool.addLocalAction(provider, programSelectionAction); + + DockingAction selectAction = new MakeProgramSelectionAction(this, provider.getTable()); + tool.addLocalAction(provider, selectAction); DockingAction navigationAction = new SelectionNavigationAction(this, provider.getTable()); tool.addLocalAction(provider, navigationAction); } - private void makeSelection() { + private void doMakeSelection() { ProgramSelection selection = provider.getTable().getProgramSelection(); PluginEvent event = new ProgramSelectionPluginEvent(getName(), selection, currentProgram); firePluginEvent(event); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/scalartable/ScalarSearchProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/scalartable/ScalarSearchProvider.java index ba6809d469..2c3ce5a431 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/scalartable/ScalarSearchProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/scalartable/ScalarSearchProvider.java @@ -23,7 +23,6 @@ import javax.swing.*; import javax.swing.border.Border; import docking.*; -import docking.action.DockingAction; import docking.help.HelpService; import docking.widgets.label.GLabel; import docking.widgets.table.GTableFilterPanel; @@ -62,8 +61,6 @@ public class ScalarSearchProvider extends ComponentProviderAdapter { private GhidraTable scalarTable; private ScalarSearchModel scalarModel; - private DockingAction selectAction; - private ProgramSelection currentSelection; private Program program; private String primarySubTitle; @@ -258,21 +255,11 @@ public class ScalarSearchProvider extends ComponentProviderAdapter { private void createActions() { - selectAction = new MakeProgramSelectionAction(getName(), getTable()) { - @Override - protected void makeSelection(ActionContext context) { - selectDataInProgramFromTable(getSelection()); - } - }; - - tool.addLocalAction(this, selectAction); - - DockingAction selectionAction = new SelectionNavigationAction(plugin, getTable()); - tool.addLocalAction(this, selectionAction); + tool.addLocalAction(this, new MakeProgramSelectionAction(plugin, scalarTable)); + tool.addLocalAction(this, new SelectionNavigationAction(plugin, getTable())); GhidraTable table = threadedTablePanel.getTable(); - DockingAction removeItemsAction = new DeleteTableRowAction(table, plugin.getName()); - tool.addLocalAction(this, removeItemsAction); + tool.addLocalAction(this, new DeleteTableRowAction(table, plugin.getName())); } //================================================================================================== diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptActionManager.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptActionManager.java index feaf7615f9..de3105d085 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptActionManager.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptActionManager.java @@ -26,8 +26,10 @@ import java.util.zip.ZipFile; import javax.swing.KeyStroke; -import docking.*; +import docking.ActionContext; +import docking.DockingUtils; import docking.action.*; +import docking.actions.KeyBindingUtils; import docking.widgets.table.GTable; import generic.jar.ResourceFile; import ghidra.app.script.GhidraScriptUtil; @@ -92,7 +94,7 @@ class GhidraScriptActionManager { action.setKeyBindingData(null); } else { - KeyStroke stroke = DockingKeyBindingAction.parseKeyStroke(strokeStr); + KeyStroke stroke = KeyBindingUtils.parseKeyStroke(strokeStr); if (stroke == null) { break; } @@ -137,7 +139,7 @@ class GhidraScriptActionManager { saveState.putString(scriptFile.getName(), ""); } else { - String strokeStr = DockingKeyBindingAction.parseKeyStroke(stroke); + String strokeStr = KeyBindingUtils.parseKeyStroke(stroke); saveState.putString(scriptFile.getName(), strokeStr); } } @@ -464,7 +466,6 @@ class GhidraScriptActionManager { if (action == null) { action = new ScriptAction(plugin, script); actionMap.put(script, action); - plugin.getTool().addAction(action); } return action; } @@ -628,13 +629,11 @@ class GhidraScriptActionManager { private class RerunLastScriptAction extends DockingAction { RerunLastScriptAction(String toolbarGroup) { - super(RERUN_LAST_SHARED_ACTION_NAME, plugin.getName(), false); + super(RERUN_LAST_SHARED_ACTION_NAME, plugin.getName(), KeyBindingType.SHARED); setToolBarData( new ToolBarData(ResourceManager.loadImage("images/play_again.png"), toolbarGroup)); setDescription("Rerun the last run script"); - setEnabled(false); - setHelpLocation(new HelpLocation(plugin.getName(), "Run_Last")); initKeyStroke(RERUN_LAST_SCRIPT_KEYSTROKE); @@ -648,11 +647,6 @@ class GhidraScriptActionManager { setKeyBindingData(new KeyBindingData(keyStroke)); } - @Override - public boolean usesSharedKeyBinding() { - return true; - } - @Override public void actionPerformed(ActionContext context) { provider.runLastScript(); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptComponentProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptComponentProvider.java index 5618f802f9..ce998da852 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptComponentProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptComponentProvider.java @@ -103,6 +103,7 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter { setHelpLocation(new HelpLocation(plugin.getName(), plugin.getName())); setIcon(ResourceManager.loadImage("images/play.png")); + addToToolbar(); setWindowGroup(WINDOW_GROUP); build(); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptMgrPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptMgrPlugin.java index 97d542a34b..498772040a 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptMgrPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptMgrPlugin.java @@ -19,9 +19,6 @@ import java.io.IOException; import java.io.PrintStream; import java.net.Socket; -import docking.ActionContext; -import docking.action.DockingAction; -import docking.action.ToolBarData; import generic.jar.ResourceFile; import ghidra.app.CorePluginPackage; import ghidra.app.plugin.PluginCategoryNames; @@ -36,9 +33,7 @@ import ghidra.framework.plugintool.PluginInfo; import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.util.PluginStatus; import ghidra.program.model.listing.Program; -import ghidra.util.HelpLocation; import ghidra.util.task.TaskListener; -import resources.ResourceManager; //@formatter:off @PluginInfo( @@ -54,7 +49,6 @@ import resources.ResourceManager; public class GhidraScriptMgrPlugin extends ProgramPlugin implements GhidraScriptService { private GhidraScriptComponentProvider provider; - private DockingAction action; public GhidraScriptMgrPlugin(PluginTool tool) { super(tool, true, true, true); @@ -62,31 +56,9 @@ public class GhidraScriptMgrPlugin extends ProgramPlugin implements GhidraScript provider = new GhidraScriptComponentProvider(this); } - @Override - protected void init() { - super.init(); - - action = new DockingAction("Display Script Manager", getName()) { - - @Override - public void actionPerformed(ActionContext context) { - tool.showComponentProvider(provider, true); - } - }; - - // ACTIONS - auto generated - action.setToolBarData( - new ToolBarData(ResourceManager.loadImage("images/play.png"), "View")); - - action.setEnabled(true); - action.setHelpLocation(new HelpLocation(getName(), "Script_Manager")); - tool.addAction(action); - } - @Override protected void dispose() { super.dispose(); - action.dispose(); provider.dispose(); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/KeyBindingsInfo.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/KeyBindingsInfo.java index bf8362996d..37c44f8aa2 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/KeyBindingsInfo.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/KeyBindingsInfo.java @@ -17,7 +17,7 @@ package ghidra.app.plugin.core.script; import javax.swing.KeyStroke; -import docking.DockingKeyBindingAction; +import docking.actions.KeyBindingUtils; class KeyBindingsInfo implements Comparable { boolean hasAction; @@ -31,7 +31,7 @@ class KeyBindingsInfo implements Comparable { KeyBindingsInfo(boolean hasAction, KeyStroke stroke) { this.hasAction = hasAction; - this.keystroke = stroke == null ? "" : DockingKeyBindingAction.parseKeyStroke(stroke); + this.keystroke = stroke == null ? "" : KeyBindingUtils.parseKeyStroke(stroke); } @Override diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/string/StringTableModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/string/StringTableModel.java index 144947d550..583f7aa42d 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/string/StringTableModel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/string/StringTableModel.java @@ -21,7 +21,9 @@ import ghidra.docking.settings.Settings; import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.ServiceProvider; import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressSet; import ghidra.program.model.listing.Program; +import ghidra.program.util.ProgramSelection; import ghidra.program.util.string.FoundString; import ghidra.util.datastruct.Accumulator; import ghidra.util.exception.CancelledException; @@ -46,6 +48,20 @@ public class StringTableModel extends AddressBasedTableModel { } } + @Override + public ProgramSelection getProgramSelection(int[] rows) { + + AddressSet addressSet = new AddressSet(); + for (int row : rows) { + FoundString foundString = getRowObject(row); + Address addr = foundString.getAddress(); + if (addr != null) { + addressSet.addRange(addr, addr.add(foundString.getLength() - 1)); + } + } + return new ProgramSelection(addressSet); + } + @Override public Address getAddress(int row) { FoundString stringData = getRowObject(row); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/string/StringTablePlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/string/StringTablePlugin.java index 4c7953d000..ce087c6f34 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/string/StringTablePlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/string/StringTablePlugin.java @@ -28,7 +28,8 @@ import ghidra.app.services.GoToService; import ghidra.app.util.HelpTopics; import ghidra.framework.plugintool.PluginInfo; import ghidra.framework.plugintool.PluginTool; -import ghidra.framework.plugintool.util.*; +import ghidra.framework.plugintool.util.PluginStatus; +import ghidra.framework.plugintool.util.ToolConstants; import ghidra.program.model.address.*; import ghidra.program.model.listing.Program; import ghidra.program.util.ProgramSelection; @@ -99,8 +100,7 @@ public class StringTablePlugin extends ProgramPlugin { */ @Override public void dispose() { - ArrayList list = - new ArrayList<>(transientProviders); + ArrayList list = new ArrayList<>(transientProviders); for (StringTableProvider stringTableProvider : list) { stringTableProvider.closeComponent(); @@ -114,8 +114,7 @@ public class StringTablePlugin extends ProgramPlugin { if (transientProviders.isEmpty()) { return; } - ArrayList list = - new ArrayList<>(transientProviders); + ArrayList list = new ArrayList<>(transientProviders); for (StringTableProvider stringTableProvider : list) { stringTableProvider.programClosed(program); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/string/StringTableProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/string/StringTableProvider.java index 46b0241584..4dbda3a731 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/string/StringTableProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/string/StringTableProvider.java @@ -30,15 +30,12 @@ import docking.widgets.label.GLabel; import docking.widgets.table.*; import docking.widgets.table.threaded.ThreadedTableModel; import docking.widgets.textfield.IntegerTextField; -import ghidra.app.events.ProgramSelectionPluginEvent; import ghidra.app.services.GoToService; import ghidra.app.util.HelpTopics; import ghidra.docking.settings.SettingsImpl; import ghidra.framework.model.DomainObjectChangedEvent; import ghidra.framework.model.DomainObjectListener; import ghidra.framework.plugintool.ComponentProviderAdapter; -import ghidra.program.model.address.Address; -import ghidra.program.model.address.AddressSet; import ghidra.program.model.data.StringDataInstance; import ghidra.program.model.listing.Program; import ghidra.program.model.mem.DumbMemBufferImpl; @@ -50,6 +47,7 @@ import ghidra.util.*; import ghidra.util.exception.AssertException; import ghidra.util.layout.VerticalLayout; import ghidra.util.table.*; +import ghidra.util.table.actions.MakeProgramSelectionAction; import ghidra.util.task.TaskLauncher; import resources.ResourceManager; @@ -304,19 +302,18 @@ public class StringTableProvider extends ComponentProviderAdapter implements Dom makeCharArrayAction.setHelpLocation(makeStringHelp); addLocalAction(makeCharArrayAction); - DockingAction selectAction = - new DockingAction("Make Selection", "AsciiFinderDialog", false) { - @Override - public void actionPerformed(ActionContext context) { - makeSelection(); - } - }; - selectAction.setDescription("Make a selection using selected rows"); - selectAction.setEnabled(true); - Icon icon = ResourceManager.loadImage("images/text_align_justify.png"); - selectAction.setToolBarData(new ToolBarData(icon)); - selectAction.setPopupMenuData(new MenuData(new String[] { "Make Selection" }, icon)); - selectAction.setHelpLocation(new HelpLocation(HelpTopics.SEARCH, "Make_Selection_Strings")); + DockingAction selectAction = new MakeProgramSelectionAction(plugin, table) { + @Override + protected ProgramSelection makeSelection(ActionContext context) { + ProgramSelection selection = super.makeSelection(context); + + // Also make sure this plugin keeps track of the new selection, since it will + // not receive this new event. + // TODO this should not be necessary; old code perhaps? + plugin.setSelection(selection); + return selection; + } + }; selectionNavigationAction = new SelectionNavigationAction(plugin, table); selectionNavigationAction.setHelpLocation( @@ -327,37 +324,6 @@ public class StringTableProvider extends ComponentProviderAdapter implements Dom } - private void makeSelection() { - AddressSet set = new AddressSet(); - - addToAddressSet(set, table.getSelectedRows()); - - if (!set.isEmpty()) { - - ProgramSelection ps = new ProgramSelection(set); - - // This event is given this specific source name because AsciiFinderPlugin - // is looking for it, so it can circumvent - // some unwanted behavior. See AsciiFinderPlugin.firePluginEvent for details. - plugin.firePluginEvent(new ProgramSelectionPluginEvent( - "AsciiFinderDialogFiredSelection", ps, currentProgram)); - - // Also make sure this plugin keeps track of the new selection, since it will - // not receive this new event. - plugin.setSelection(ps); - } - } - - void addToAddressSet(AddressSet modifiableSet, int[] rows) { - for (int rowValue : rows) { - FoundString foundString = stringModel.getRowObject(rowValue); - Address addr = foundString.getAddress(); - if (addr != null) { - modifiableSet.addRange(addr, addr.add(foundString.getLength() - 1)); - } - } - } - private JPanel createMainPanel() { JPanel panel = new JPanel(new BorderLayout()); panel.add(buildTablePanel(), BorderLayout.CENTER); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/strings/ViewStringsPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/strings/ViewStringsPlugin.java index d778118e61..9ee8a34e80 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/strings/ViewStringsPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/strings/ViewStringsPlugin.java @@ -82,7 +82,7 @@ public class ViewStringsPlugin extends ProgramPlugin implements DomainObjectList } private void createActions() { - DockingAction refreshAction = new DockingAction("Refresh Strings", getName(), false) { + DockingAction refreshAction = new DockingAction("Refresh Strings", getName()) { @Override public boolean isEnabledForContext(ActionContext context) { @@ -100,20 +100,12 @@ public class ViewStringsPlugin extends ProgramPlugin implements DomainObjectList refreshAction.setHelpLocation(new HelpLocation("ViewStringsPlugin", "Refresh")); tool.addLocalAction(provider, refreshAction); - selectAction = new MakeProgramSelectionAction(getName(), provider.getTable()) { - - @Override - protected void makeSelection(ActionContext context) { - selectData(provider.selectData()); - } - }; - - tool.addLocalAction(provider, selectAction); + tool.addLocalAction(provider, new MakeProgramSelectionAction(this, provider.getTable())); linkNavigationAction = new SelectionNavigationAction(this, provider.getTable()); tool.addLocalAction(provider, linkNavigationAction); - showSettingsAction = new DockingAction("Settings...", getName(), false) { + showSettingsAction = new DockingAction("Settings", getName()) { @Override public void actionPerformed(ActionContext context) { try { @@ -133,7 +125,7 @@ public class ViewStringsPlugin extends ProgramPlugin implements DomainObjectList showSettingsAction.setPopupMenuData(new MenuData(new String[] { "Settings..." }, "R")); showSettingsAction.setDescription("Shows settings for the selected strings"); showSettingsAction.setHelpLocation(new HelpLocation("DataPlugin", "Data_Settings")); - showDefaultSettingsAction = new DockingAction("Default Settings...", getName(), false) { + showDefaultSettingsAction = new DockingAction("Default Settings", getName()) { @Override public void actionPerformed(ActionContext context) { Data data = provider.getSelectedData(); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolTreePlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolTreePlugin.java index 8202778be5..52cb02c549 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolTreePlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolTreePlugin.java @@ -15,11 +15,6 @@ */ package ghidra.app.plugin.core.symboltree; -import javax.swing.ImageIcon; - -import docking.ActionContext; -import docking.action.DockingAction; -import docking.action.ToolBarData; import ghidra.app.CorePluginPackage; import ghidra.app.events.*; import ghidra.app.plugin.PluginCategoryNames; @@ -30,8 +25,6 @@ import ghidra.framework.plugintool.util.PluginStatus; import ghidra.program.model.listing.Program; import ghidra.program.model.symbol.*; import ghidra.program.util.ProgramLocation; -import ghidra.util.HelpLocation; -import resources.ResourceManager; //@formatter:off @PluginInfo( @@ -50,17 +43,13 @@ public class SymbolTreePlugin extends Plugin { public static final String PLUGIN_NAME = "SymbolTreePlugin"; - private DockingAction symTreeAction; private SymbolTreeProvider provider; private Program program; private GoToService goToService; private boolean processingGoTo; - final static ImageIcon SYMBOL_TREE_ICON = ResourceManager.loadImage("images/sitemap_color.png"); - public SymbolTreePlugin(PluginTool tool) { super(tool); - createAction(); provider = new SymbolTreeProvider(tool, this); } @@ -151,26 +140,6 @@ public class SymbolTreePlugin extends Plugin { goToService.goToExternalLocation(extLoc, false); } - private void createAction() { - symTreeAction = new DockingAction("Display Symbol Tree", getName()) { - @Override - public void actionPerformed(ActionContext context) { - showProvider(); - } - }; - symTreeAction.setToolBarData(new ToolBarData(SYMBOL_TREE_ICON, "View")); - - symTreeAction.setDescription("Display Symbol Tree"); - - symTreeAction.setHelpLocation(new HelpLocation(getName(), "SymbolTree")); - - tool.addAction(symTreeAction); - } - - private void showProvider() { - provider.showComponent(program); - } - public Program getProgram() { return program; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolTreeProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolTreeProvider.java index 79a9d21c5d..4fde47be8b 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolTreeProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolTreeProvider.java @@ -45,9 +45,11 @@ import ghidra.util.*; import ghidra.util.exception.*; import ghidra.util.task.SwingUpdateManager; import ghidra.util.task.TaskMonitor; +import resources.ResourceManager; public class SymbolTreeProvider extends ComponentProviderAdapter { + private static final ImageIcon ICON = ResourceManager.loadImage("images/sitemap_color.png"); private final static String NAME = "Symbol Tree"; private ClipboardOwner clipboardOwner; @@ -101,6 +103,9 @@ public class SymbolTreeProvider extends ComponentProviderAdapter { super(tool, NAME, plugin.getName()); this.plugin = plugin; + setIcon(ICON); + addToToolbar(); + domainObjectListener = new SymbolTreeProviderDomainObjectListener(); localClipboard = new Clipboard(NAME); @@ -231,11 +236,6 @@ public class SymbolTreeProvider extends ComponentProviderAdapter { setProgram(program); } - @Override - public ImageIcon getIcon() { - return SymbolTreePlugin.SYMBOL_TREE_ICON; - } - //================================================================================================== // Class Methods //================================================================================================== diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/SelectionAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/SelectionAction.java index 4ba842289f..15a031df59 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/SelectionAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/SelectionAction.java @@ -39,9 +39,6 @@ public class SelectionAction extends SymbolTreeContextAction { @Override protected boolean isEnabledForContext(SymbolTreeActionContext context) { -// if (context.getSymbolCount() == 0) { -// return false; -// } for (Symbol s : context.getSymbols()) { if (!s.isExternal()) { return true; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/ShowSymbolReferencesAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/ShowSymbolReferencesAction.java index b98eed71eb..8154b12910 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/ShowSymbolReferencesAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/ShowSymbolReferencesAction.java @@ -18,8 +18,7 @@ package ghidra.app.plugin.core.symboltree.actions; import javax.swing.KeyStroke; import javax.swing.tree.TreePath; -import docking.action.KeyBindingData; -import docking.action.MenuData; +import docking.action.*; import ghidra.app.actions.AbstractFindReferencesDataTypeAction; import ghidra.app.nav.Navigatable; import ghidra.app.plugin.core.navigation.locationreferences.LocationReferencesService; @@ -57,7 +56,7 @@ public class ShowSymbolReferencesAction extends SymbolTreeContextAction { }; public ShowSymbolReferencesAction(PluginTool tool, String owner) { - super(AbstractFindReferencesDataTypeAction.NAME, owner); + super(AbstractFindReferencesDataTypeAction.NAME, owner, KeyBindingType.SHARED); this.tool = tool; setPopupMenuData(new MenuData(new String[] { "Show References to" }, "0Middle")); @@ -76,11 +75,6 @@ public class ShowSymbolReferencesAction extends SymbolTreeContextAction { setKeyBindingData(new KeyBindingData(keyStroke)); } - @Override - public boolean usesSharedKeyBinding() { - return true; - } - private void installHelpLocation() { LocationReferencesService locationReferencesService = tool.getService(LocationReferencesService.class); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/SymbolTreeContextAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/SymbolTreeContextAction.java index cb085d419c..4cd532a4c4 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/SymbolTreeContextAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/SymbolTreeContextAction.java @@ -1,6 +1,5 @@ /* ### * 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. @@ -16,12 +15,12 @@ */ package ghidra.app.plugin.core.symboltree.actions; -import ghidra.app.plugin.core.symboltree.SymbolTreeActionContext; - import javax.swing.tree.TreePath; import docking.ActionContext; import docking.action.DockingAction; +import docking.action.KeyBindingType; +import ghidra.app.plugin.core.symboltree.SymbolTreeActionContext; public abstract class SymbolTreeContextAction extends DockingAction { @@ -29,6 +28,10 @@ public abstract class SymbolTreeContextAction extends DockingAction { super(name, owner); } + public SymbolTreeContextAction(String name, String owner, KeyBindingType kbType) { + super(name, owner, kbType); + } + @Override public final boolean isEnabledForContext(ActionContext actionContext) { if (!(actionContext instanceof SymbolTreeActionContext)) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/ReferenceProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/ReferenceProvider.java index 5c215d91b6..6107fe3432 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/ReferenceProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/ReferenceProvider.java @@ -29,8 +29,12 @@ import ghidra.program.model.listing.Program; import ghidra.program.model.symbol.Symbol; import ghidra.util.HelpLocation; import ghidra.util.table.GhidraTable; +import resources.ResourceManager; class ReferenceProvider extends ComponentProviderAdapter { + + private static final ImageIcon ICON = ResourceManager.loadImage("images/table_go.png"); + private SymbolTablePlugin plugin; private SymbolReferenceModel referenceKeyModel; private ReferencePanel referencePanel; @@ -39,15 +43,21 @@ class ReferenceProvider extends ComponentProviderAdapter { ReferenceProvider(SymbolTablePlugin plugin) { super(plugin.getTool(), "Symbol References", plugin.getName(), ProgramActionContext.class); this.plugin = plugin; + + setIcon(ICON); + addToToolbar(); setHelpLocation(new HelpLocation(plugin.getName(), "Symbol_References")); setWindowGroup("symbolTable"); setIntraGroupPosition(WindowPosition.RIGHT); + renderer = new SymbolRenderer(); referenceKeyModel = new SymbolReferenceModel(plugin.getBlockModelService(), plugin.getTool()); referencePanel = new ReferencePanel(this, referenceKeyModel, renderer, plugin.getGoToService()); + + addToTool(); } void dispose() { @@ -119,11 +129,6 @@ class ReferenceProvider extends ComponentProviderAdapter { return "(" + referenceKeyModel.getDescription() + ")"; } - @Override - public ImageIcon getIcon() { - return SymbolTablePlugin.REF_GIF; - } - void open() { setVisible(true); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolProvider.java index 2db72d136a..3338669448 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolProvider.java @@ -15,6 +15,7 @@ */ package ghidra.app.plugin.core.symtable; +import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; import java.util.List; @@ -22,20 +23,23 @@ import javax.swing.ImageIcon; import javax.swing.JComponent; import docking.ActionContext; +import docking.DockingUtils; +import docking.action.KeyBindingData; import ghidra.app.context.ProgramActionContext; import ghidra.app.context.ProgramSymbolActionContext; -import ghidra.app.events.ProgramSelectionPluginEvent; import ghidra.app.util.SymbolInspector; import ghidra.framework.options.SaveState; import ghidra.framework.plugintool.ComponentProviderAdapter; -import ghidra.framework.plugintool.PluginEvent; import ghidra.program.model.listing.Program; import ghidra.program.model.symbol.Symbol; -import ghidra.program.util.ProgramSelection; import ghidra.util.HelpLocation; import ghidra.util.table.GhidraTable; +import resources.ResourceManager; class SymbolProvider extends ComponentProviderAdapter { + + private static final ImageIcon ICON = ResourceManager.loadImage("images/table.png"); + private SymbolTablePlugin plugin; private SymbolRenderer renderer; private SymbolTableModel symbolKeyModel; @@ -44,6 +48,11 @@ class SymbolProvider extends ComponentProviderAdapter { SymbolProvider(SymbolTablePlugin plugin) { super(plugin.getTool(), "Symbol Table", plugin.getName(), ProgramActionContext.class); this.plugin = plugin; + + setIcon(ICON); + addToToolbar(); + setKeyBinding(new KeyBindingData(KeyEvent.VK_T, DockingUtils.CONTROL_KEY_MODIFIER_MASK)); + setHelpLocation(new HelpLocation(plugin.getName(), "Symbol_Table")); setWindowGroup("symbolTable"); renderer = new SymbolRenderer(); @@ -51,6 +60,8 @@ class SymbolProvider extends ComponentProviderAdapter { symbolKeyModel = new SymbolTableModel(this, plugin.getTool()); symbolPanel = new SymbolPanel(this, symbolKeyModel, renderer, plugin.getTool(), plugin.getGoToService()); + + addToTool(); } void updateTitle() { @@ -77,13 +88,6 @@ class SymbolProvider extends ComponentProviderAdapter { symbolKeyModel.delete(rowObjects); } - void makeSelection() { - ProgramSelection selection = symbolPanel.getProgramSelection(); - PluginEvent event = - new ProgramSelectionPluginEvent(plugin.getName(), selection, plugin.getProgram()); - plugin.firePluginEvent(event); - } - void setFilter() { symbolPanel.setFilter(); } @@ -164,11 +168,6 @@ class SymbolProvider extends ComponentProviderAdapter { } - @Override - public ImageIcon getIcon() { - return SymbolTablePlugin.SYM_GIF; - } - void open() { if (!isVisible()) { setVisible(true); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolTablePlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolTablePlugin.java index e11de4db46..fbfa385b4b 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolTablePlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolTablePlugin.java @@ -16,7 +16,6 @@ package ghidra.app.plugin.core.symtable; import java.awt.Cursor; -import java.awt.event.InputEvent; import java.awt.event.KeyEvent; import javax.swing.ImageIcon; @@ -74,12 +73,6 @@ public class SymbolTablePlugin extends Plugin implements DomainObjectListener { final static Cursor WAIT_CURSOR = new Cursor(Cursor.WAIT_CURSOR); final static Cursor NORM_CURSOR = new Cursor(Cursor.DEFAULT_CURSOR); - final static ImageIcon SYM_GIF = ResourceManager.loadImage("images/table.png"); - final static ImageIcon REF_GIF = ResourceManager.loadImage("images/table_go.png"); - - private DockingAction viewSymTableAction; - private DockingAction viewRefTableAction; - private DockingAction openRefsAction; private DockingAction deleteAction; private DockingAction makeSelectionAction; @@ -113,10 +106,6 @@ public class SymbolTablePlugin extends Plugin implements DomainObjectListener { symProvider = new SymbolProvider(this); refProvider = new ReferenceProvider(this); - tool.addComponentProvider(symProvider, false); - tool.addComponentProvider(refProvider, false); - - createActions(); createSymActions(); createRefActions(); @@ -133,8 +122,6 @@ public class SymbolTablePlugin extends Plugin implements DomainObjectListener { super.dispose(); swingMgr.dispose(); - viewSymTableAction.dispose(); - viewRefTableAction.dispose(); deleteAction.dispose(); makeSelectionAction.dispose(); @@ -357,38 +344,10 @@ public class SymbolTablePlugin extends Plugin implements DomainObjectListener { } } - private void createActions() { - viewSymTableAction = new DockingAction("View Symbol Table", getName()) { - @Override - public void actionPerformed(ActionContext context) { - tool.showComponentProvider(symProvider, true); - } - }; - viewSymTableAction.setToolBarData( - new ToolBarData(ResourceManager.loadImage("images/table.png"), "View")); - viewSymTableAction.setKeyBindingData( - new KeyBindingData(KeyEvent.VK_T, InputEvent.CTRL_DOWN_MASK)); - - viewSymTableAction.setDescription("Display Symbol Table"); - tool.addAction(viewSymTableAction); - - viewRefTableAction = new DockingAction("View Symbol References", getName()) { - @Override - public void actionPerformed(ActionContext context) { - tool.showComponentProvider(refProvider, true); - } - }; - viewRefTableAction.setToolBarData( - new ToolBarData(ResourceManager.loadImage("images/table_go.png"), "View")); - - viewRefTableAction.setDescription("Display Symbol References"); - tool.addAction(viewRefTableAction); - } - private void createSymActions() { String popupGroup = "1"; - openRefsAction = new DockingAction("Symbol References", getName()) { + openRefsAction = new DockingAction("Symbol References", getName(), KeyBindingType.SHARED) { @Override public void actionPerformed(ActionContext context) { refProvider.open(); @@ -400,7 +359,7 @@ public class SymbolTablePlugin extends Plugin implements DomainObjectListener { new MenuData(new String[] { "Symbol References" }, icon, popupGroup)); openRefsAction.setToolBarData(new ToolBarData(icon)); - openRefsAction.setDescription("Symbol References"); + openRefsAction.setDescription("Display Symbol References"); tool.addLocalAction(symProvider, openRefsAction); deleteAction = new DockingAction("Delete Symbols", getName()) { @@ -434,13 +393,7 @@ public class SymbolTablePlugin extends Plugin implements DomainObjectListener { DockingAction editExternalLocationAction = new EditExternalLocationAction(this); tool.addLocalAction(symProvider, editExternalLocationAction); - makeSelectionAction = new MakeProgramSelectionAction(getName(), symProvider.getTable()) { - @Override - protected void makeSelection(ActionContext context) { - symProvider.makeSelection(); - } - }; - + makeSelectionAction = new MakeProgramSelectionAction(this, symProvider.getTable()); makeSelectionAction.getPopupMenuData().setMenuGroup(popupGroup); tool.addLocalAction(symProvider, makeSelectionAction); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/table/TableComponentProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/table/TableComponentProvider.java index 884cc0c930..df42a812ee 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/table/TableComponentProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/table/TableComponentProvider.java @@ -159,10 +159,15 @@ public class TableComponentProvider extends ComponentProviderAdapter private void createActions(final Plugin plugin) { GhidraTable table = threadedPanel.getTable(); - selectAction = new MakeProgramSelectionAction(tableServicePlugin.getName(), table) { + selectAction = new MakeProgramSelectionAction(tableServicePlugin, table) { @Override - protected void makeSelection(ActionContext context) { - doMakeSelection(plugin); + protected ProgramSelection makeSelection(ActionContext context) { + + ProgramSelection selection = table.getProgramSelection(); + navigatable.goTo(program, new ProgramLocation(program, selection.getMinAddress())); + navigatable.setSelection(selection); + navigatable.requestFocus(); + return selection; } }; selectAction.setHelpLocation(new HelpLocation(HelpTopics.SEARCH, "Make_Selection")); @@ -171,32 +176,31 @@ public class TableComponentProvider extends ComponentProviderAdapter selectionNavigationAction.setHelpLocation( new HelpLocation(HelpTopics.SEARCH, "Selection_Navigation")); - DockingAction externalGotoAction = - new DockingAction("Go to External Location", getName(), false) { - @Override - public void actionPerformed(ActionContext context) { - gotoExternalAddress(getSelectedExternalAddress()); - } + DockingAction externalGotoAction = new DockingAction("Go to External Location", getName()) { + @Override + public void actionPerformed(ActionContext context) { + gotoExternalAddress(getSelectedExternalAddress()); + } - @Override - public boolean isEnabledForContext(ActionContext context) { - return getSelectedExternalAddress() != null && - tool.getService(GoToService.class) != null; - } + @Override + public boolean isEnabledForContext(ActionContext context) { + return getSelectedExternalAddress() != null && + tool.getService(GoToService.class) != null; + } - private Address getSelectedExternalAddress() { - if (table.getSelectedRowCount() != 1) { - return null; - } - ProgramSelection selection = table.getProgramSelection(); - Program modelProgram = model.getProgram(); - if (modelProgram == null || selection.getNumAddresses() != 1) { - return null; - } - Address addr = selection.getMinAddress(); - return addr.isExternalAddress() ? addr : null; + private Address getSelectedExternalAddress() { + if (table.getSelectedRowCount() != 1) { + return null; } - }; + ProgramSelection selection = table.getProgramSelection(); + Program modelProgram = model.getProgram(); + if (modelProgram == null || selection.getNumAddresses() != 1) { + return null; + } + Address addr = selection.getMinAddress(); + return addr.isExternalAddress() ? addr : null; + } + }; externalGotoAction.setDescription("Go to an external location"); externalGotoAction.setEnabled(false); @@ -266,19 +270,6 @@ public class TableComponentProvider extends ComponentProviderAdapter } } - private void doMakeSelection(Plugin plugin) { - ProgramSelection selection = threadedPanel.getTable().getProgramSelection(); - Program modelProgram = model.getProgram(); - if (modelProgram == null || selection.getNumAddresses() == 0) { - return; - } - - navigatable.goTo(model.getProgram(), - new ProgramLocation(modelProgram, selection.getMinAddress())); - navigatable.setSelection(selection); - navigatable.requestFocus(); - } - @Override public void closeComponent() { if (navigatable != null) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/table/TableServicePlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/table/TableServicePlugin.java index 55bb94b665..72e41ee871 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/table/TableServicePlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/table/TableServicePlugin.java @@ -55,11 +55,6 @@ import ghidra.util.task.SwingUpdateManager; public class TableServicePlugin extends ProgramPlugin implements TableService, DomainObjectListener { - static final String MAKE_SELECTION_ACTION_NAME = "Make Selection"; - static final String REMOVE_ITEMS_ACTION_NAME = "Remove Items"; - - static final String SHARED_ACTION_OWNER_SUFFIX = " (Tool)"; - private SwingUpdateManager updateMgr; private Map>> programMap = new HashMap<>(); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/debug/DbViewerProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/debug/DbViewerProvider.java index cba3aabeec..d8dc8d9b72 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/debug/DbViewerProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/debug/DbViewerProvider.java @@ -17,7 +17,6 @@ package ghidra.app.plugin.debug; import java.awt.event.MouseEvent; -import javax.swing.ImageIcon; import javax.swing.JComponent; import db.DBHandle; @@ -34,14 +33,14 @@ public class DbViewerProvider extends ComponentProviderAdapter { private DBHandle dbh; private String dbName; - private Plugin plugin; - private ImageIcon icon; private DbViewerComponent comp; public DbViewerProvider(Plugin plugin) { super(plugin.getTool(), "Database Viewer", plugin.getName()); + + setIcon(ResourceManager.loadImage(ICON_IMAGE)); setDefaultWindowPosition(WindowPosition.BOTTOM); - this.plugin = plugin; + setHelpLocation(new HelpLocation(plugin.getName(), "DbViewer")); } @@ -93,13 +92,4 @@ public class DbViewerProvider extends ComponentProviderAdapter { } return comp; } - - @Override - public ImageIcon getIcon() { - if (icon == null) { - icon = ResourceManager.loadImage(ICON_IMAGE); - } - return icon; - } - } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/script/ScriptInfo.java b/Ghidra/Features/Base/src/main/java/ghidra/app/script/ScriptInfo.java index 3f6ad3837a..0860d95dfd 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/script/ScriptInfo.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/script/ScriptInfo.java @@ -28,7 +28,7 @@ import javax.swing.KeyStroke; import org.apache.commons.lang3.StringUtils; -import docking.DockingKeyBindingAction; +import docking.actions.KeyBindingUtils; import generic.jar.ResourceFile; import ghidra.util.HTMLUtilities; import ghidra.util.Msg; @@ -318,7 +318,7 @@ public class ScriptInfo { } } - keyBinding = DockingKeyBindingAction.parseKeyStroke(buildy.toString()); + keyBinding = KeyBindingUtils.parseKeyStroke(buildy.toString()); if (keyBinding == null) { // note: this message will be cleared by the parseHeader() method keybindingErrorMessage = "Unable to parse keybinding: " + buildy; @@ -479,7 +479,7 @@ public class ScriptInfo { } return ""; } - return DockingKeyBindingAction.parseKeyStroke(keyStroke); + return KeyBindingUtils.parseKeyStroke(keyStroke); } private String toToolTip(String string) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/tablechooser/TableChooserDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/app/tablechooser/TableChooserDialog.java index 93c43a69fd..2999d21a87 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/tablechooser/TableChooserDialog.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/tablechooser/TableChooserDialog.java @@ -25,7 +25,7 @@ import javax.swing.*; import javax.swing.table.TableCellRenderer; import docking.*; -import docking.action.*; +import docking.action.DockingAction; import docking.widgets.table.*; import docking.widgets.table.threaded.ThreadedTableModel; import ghidra.app.nav.Navigatable; @@ -41,8 +41,8 @@ import ghidra.util.SystemUtilities; import ghidra.util.datastruct.WeakDataStructureFactory; import ghidra.util.datastruct.WeakSet; import ghidra.util.table.*; +import ghidra.util.table.actions.MakeProgramSelectionAction; import ghidra.util.task.TaskMonitor; -import resources.ResourceManager; import utility.function.Callback; /** @@ -157,23 +157,20 @@ public class TableChooserDialog extends DialogComponentProvider private void createActions() { String owner = getClass().getSimpleName(); - DockingAction selectAction = new DockingAction("Make Selection", owner, false) { - @Override - public void actionPerformed(ActionContext context) { - makeSelection(); - } + DockingAction selectAction = new MakeProgramSelectionAction(owner, table) { @Override - public boolean isEnabledForContext(ActionContext context) { - return table.getSelectedRowCount() != 0; + protected ProgramSelection makeSelection(ActionContext context) { + ProgramSelection selection = table.getProgramSelection(); + if (navigatable != null) { + navigatable.goTo(program, + new ProgramLocation(program, selection.getMinAddress())); + navigatable.setSelection(selection); + navigatable.requestFocus(); + } + return selection; } }; - selectAction.setDescription("Make a selection using selected rows"); - selectAction.setEnabled(true); - Icon icon = ResourceManager.loadImage("images/text_align_justify.png"); - selectAction.setToolBarData(new ToolBarData(icon)); - selectAction.setPopupMenuData(new MenuData(new String[] { "Make Selection" }, icon)); - selectAction.setHelpLocation(new HelpLocation(HelpTopics.SEARCH, "Make_Selection")); DockingAction selectionNavigationAction = new SelectionNavigationAction(owner, table); selectionNavigationAction.setHelpLocation( @@ -183,18 +180,6 @@ public class TableChooserDialog extends DialogComponentProvider addAction(selectionNavigationAction); } - private void makeSelection() { - ProgramSelection selection = table.getProgramSelection(); - if (program == null || program.isClosed() || selection.getNumAddresses() == 0) { - return; - } - if (navigatable != null) { - navigatable.goTo(program, new ProgramLocation(program, selection.getMinAddress())); - navigatable.setSelection(selection); - navigatable.requestFocus(); - } - } - public void show() { DockingWindowManager manager = DockingWindowManager.getActiveInstance(); tool.showDialog(this, manager.getMainWindow()); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/PluginConstants.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/PluginConstants.java index fb77119627..98fbd60360 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/PluginConstants.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/PluginConstants.java @@ -62,7 +62,7 @@ public interface PluginConstants { */ char ANYSINGLECHAR_WILDCARD_CHAR = '?'; - String CODE_BROWSER = "CodeBrowserPlugin"; + String CODE_BROWSER = "Listing"; String MEMORY_MAP = "Memory Map"; String BYTE_VIEWER = "ByteViewerPlugin"; String BOOKMARKS = "Bookmarks"; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/DualListingToggleDockingAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/DualListingToggleDockingAction.java index 6783f482e6..16ab4f934c 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/DualListingToggleDockingAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/DualListingToggleDockingAction.java @@ -28,10 +28,10 @@ abstract class DualListingToggleDockingAction extends ToggleDockingAction { * Constructor that creates a toggle action for a dual listing. * @param name the name for this action * @param owner the owner of this action - * @param isKeybindingManaged true if this action's key binding should be managed + * @param supportsKeyBindings true if this action's key binding should be managed */ - public DualListingToggleDockingAction(String name, String owner, boolean isKeybindingManaged) { - super(name, owner, isKeybindingManaged); + public DualListingToggleDockingAction(String name, String owner, boolean supportsKeyBindings) { + super(name, owner, supportsKeyBindings); } /** diff --git a/Ghidra/Features/Base/src/main/java/ghidra/plugins/fsbrowser/FileSystemBrowserComponentProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/plugins/fsbrowser/FileSystemBrowserComponentProvider.java index b8c98ad4de..6d5eeba21b 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/plugins/fsbrowser/FileSystemBrowserComponentProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/plugins/fsbrowser/FileSystemBrowserComponentProvider.java @@ -23,8 +23,6 @@ import java.util.ArrayList; import java.util.List; import javax.swing.*; -import javax.swing.event.TreeSelectionEvent; -import javax.swing.event.TreeSelectionListener; import javax.swing.tree.TreePath; import javax.swing.tree.TreeSelectionModel; @@ -65,22 +63,21 @@ class FileSystemBrowserComponentProvider extends ComponentProviderAdapter { */ public FileSystemBrowserComponentProvider(FileSystemBrowserPlugin plugin, FileSystemRef fsRef) { super(plugin.getTool(), fsRef.getFilesystem().getName(), plugin.getName()); - setTransient(); this.plugin = plugin; - this.rootNode = new FSBRootNode(fsRef); + + setTransient(); + setIcon(ImageManager.PHOTO); + gTree = new GTree(rootNode); gTree.getSelectionModel().setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION); - gTree.getSelectionModel().addTreeSelectionListener(new TreeSelectionListener() { - @Override - public void valueChanged(TreeSelectionEvent e) { - tool.contextChanged(FileSystemBrowserComponentProvider.this); - TreePath[] paths = gTree.getSelectionPaths(); - if (paths.length == 1) { - GTreeNode clickedNode = (GTreeNode) paths[0].getLastPathComponent(); - handleSingleClick(clickedNode); - } + gTree.getSelectionModel().addTreeSelectionListener(e -> { + tool.contextChanged(FileSystemBrowserComponentProvider.this); + TreePath[] paths = gTree.getSelectionPaths(); + if (paths.length == 1) { + GTreeNode clickedNode = (GTreeNode) paths[0].getLastPathComponent(); + handleSingleClick(clickedNode); } }); gTree.addMouseListener(new MouseAdapter() { @@ -301,11 +298,6 @@ class FileSystemBrowserComponentProvider extends ComponentProviderAdapter { return WindowPosition.WINDOW; } - @Override - public Icon getIcon() { - return ImageManager.PHOTO; - } - void dispose() { if (actionManager != null) { actionManager.dispose(); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/test/TestTool.java b/Ghidra/Features/Base/src/main/java/ghidra/test/TestTool.java index d121f4bdbe..ceaf167763 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/test/TestTool.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/test/TestTool.java @@ -32,7 +32,7 @@ public class TestTool extends GhidraTool { @Override protected DockingWindowManager createDockingWindowManager(boolean isDockable, boolean hasStatus, boolean isModal) { - return new DockingWindowManager("EMPTY", null, this, isModal, isDockable, hasStatus, null); + return new DockingWindowManager(this, null, this, isModal, isDockable, hasStatus, null); } @Override diff --git a/Ghidra/Features/Base/src/main/java/ghidra/util/table/GhidraTable.java b/Ghidra/Features/Base/src/main/java/ghidra/util/table/GhidraTable.java index 08bc4d7047..4d16646936 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/util/table/GhidraTable.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/util/table/GhidraTable.java @@ -191,10 +191,10 @@ public class GhidraTable extends GTable { } /** - * Returns the program selection equivalent - * to the rows currently selected in the table. This method - * is only valid when the underlying table model implements - * ProgramTableModel. + * Returns the program selection equivalent to the rows currently selected in the table. + * This method is only valid when the underlying table model implements + * {@link ProgramTableModel}. + *

* Returns null if no rows are selected or * the underlying model does not implement ProgramTableModel. * @return the program selection or null. @@ -207,6 +207,20 @@ public class GhidraTable extends GTable { return programTableModel.getProgramSelection(getSelectedRows()); } + /** + * Returns the program being used by this table; null if the underlying model does not + * implement {@link ProgramTableModel} + * + * @return the table's program + */ + public Program getProgram() { + ProgramTableModel programTableModel = getProgramTableModel(dataModel); + if (programTableModel == null) { + return null; + } + return programTableModel.getProgram(); + } + private ProgramTableModel getProgramTableModel(TableModel model) { if (model instanceof ProgramTableModel) { return (ProgramTableModel) model; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/util/table/actions/DeleteTableRowAction.java b/Ghidra/Features/Base/src/main/java/ghidra/util/table/actions/DeleteTableRowAction.java index 852a840f62..f0d4fac41b 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/util/table/actions/DeleteTableRowAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/util/table/actions/DeleteTableRowAction.java @@ -75,7 +75,7 @@ public class DeleteTableRowAction extends DockingAction { } private DeleteTableRowAction(String name, String owner, KeyStroke defaultkeyStroke) { - super(name, owner); + super(name, owner, KeyBindingType.SHARED); setDescription("Remove the selected rows from the table"); setHelpLocation(new HelpLocation(HelpTopics.SEARCH, "Remove_Items")); @@ -93,11 +93,6 @@ public class DeleteTableRowAction extends DockingAction { setKeyBindingData(new KeyBindingData(keyStroke)); } - @Override - public boolean usesSharedKeyBinding() { - return true; - } - @Override public boolean isEnabledForContext(ActionContext context) { return table.getSelectedRowCount() > 0; @@ -200,6 +195,12 @@ public class DeleteTableRowAction extends DockingAction { public DummyDeleteAction(PluginTool tool) { super(NAME, "Tool", DEFAULT_KEYSTROKE); + + // prevent this action from appearing in the toolbar, menus, etc + setToolBarData(null); + setPopupMenuData(null); + setKeyBindingData(null); + tool.addAction(this); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/util/table/actions/MakeProgramSelectionAction.java b/Ghidra/Features/Base/src/main/java/ghidra/util/table/actions/MakeProgramSelectionAction.java index b37a5a84bf..da4a2e138e 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/util/table/actions/MakeProgramSelectionAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/util/table/actions/MakeProgramSelectionAction.java @@ -15,26 +15,53 @@ */ package ghidra.util.table.actions; -import javax.swing.JTable; import javax.swing.KeyStroke; import docking.ActionContext; import docking.action.*; +import ghidra.app.events.ProgramSelectionPluginEvent; +import ghidra.framework.plugintool.Plugin; +import ghidra.framework.plugintool.PluginEvent; +import ghidra.program.model.listing.Program; +import ghidra.program.util.ProgramSelection; import ghidra.util.HelpLocation; +import ghidra.util.table.GhidraTable; import resources.Icons; /** - * An action to make a program selection based on the given table's selection. The clients - * must implement the make selection code, as they know their own data. Also, for the context to + * An action to make a program selection based on the given table's selection. For the context to * work, the provider using this action must create an {@link ActionContext} that returns a - * context object that is the table passed to this action's constructor. + * context object that is the table passed to this action's constructor; otherwise, this action + * will not be enabled correctly. */ -public abstract class MakeProgramSelectionAction extends DockingAction { +public class MakeProgramSelectionAction extends DockingAction { - private JTable table; + private GhidraTable table; - public MakeProgramSelectionAction(String owner, JTable table) { - super("Make Selection", owner); + // we will have one of these fields be non-null after construction + private Plugin plugin; + + /** + * Special constructor for clients that do not have a plugin. Clients using this + * constructor must override {@link #makeSelection(ActionContext)}. + * + * @param owner the action's owner + * @param table the table needed for this action + */ + public MakeProgramSelectionAction(String owner, GhidraTable table) { + super("Make Selection", owner, KeyBindingType.SHARED); + } + + /** + * This normal constructor for this action. The given plugin will be used along with the + * given table to fire program selection events as the action is executed. + * + * @param plugin the plugin + * @param table the table + */ + public MakeProgramSelectionAction(Plugin plugin, GhidraTable table) { + super("Make Selection", plugin.getName(), KeyBindingType.SHARED); + this.plugin = plugin; this.table = table; setPopupMenuData( @@ -45,7 +72,7 @@ public abstract class MakeProgramSelectionAction extends DockingAction { // this help location provides generic help; clients can override to point to their help setHelpLocation(new HelpLocation("Search", "Make_Selection")); - // null for now, but we may want a default binding in the future + // null for now, but we may want a default binding in the future initKeyStroke(null); } @@ -57,11 +84,6 @@ public abstract class MakeProgramSelectionAction extends DockingAction { setKeyBindingData(new KeyBindingData(keyStroke)); } - @Override - public boolean usesSharedKeyBinding() { - return true; - } - @Override public boolean isAddToPopup(ActionContext context) { return true; @@ -75,6 +97,15 @@ public abstract class MakeProgramSelectionAction extends DockingAction { return false; } + Program program = table.getProgram(); + if (program == null) { + return false; + } + + if (program.isClosed()) { + return false; + } + int n = table.getSelectedRowCount(); return n > 0; } @@ -84,5 +115,17 @@ public abstract class MakeProgramSelectionAction extends DockingAction { makeSelection(context); } - protected abstract void makeSelection(ActionContext context); + protected ProgramSelection makeSelection(ActionContext context) { + ProgramSelection selection = table.getProgramSelection(); + + if (plugin == null) { + throw new IllegalStateException("The Make Program Selection action cannot be used " + + "without a plugin unless the client overrides this method"); + } + + PluginEvent event = + new ProgramSelectionPluginEvent(plugin.getName(), selection, table.getProgram()); + plugin.firePluginEvent(event); + return selection; + } } diff --git a/Ghidra/Features/Base/src/test.slow/java/docking/ComponentProviderActionsTest.java b/Ghidra/Features/Base/src/test.slow/java/docking/ComponentProviderActionsTest.java new file mode 100644 index 0000000000..eef801df44 --- /dev/null +++ b/Ghidra/Features/Base/src/test.slow/java/docking/ComponentProviderActionsTest.java @@ -0,0 +1,547 @@ +/* ### + * 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 docking; + +import static org.junit.Assert.*; + +import java.awt.event.ActionEvent; +import java.util.Set; + +import javax.swing.*; + +import org.junit.*; + +import docking.action.*; +import docking.actions.KeyEntryDialog; +import docking.actions.ToolActions; +import docking.tool.util.DockingToolConstants; +import ghidra.framework.options.ToolOptions; +import ghidra.framework.plugintool.PluginTool; +import ghidra.test.AbstractGhidraHeadedIntegrationTest; +import ghidra.test.TestEnv; +import ghidra.util.Msg; +import ghidra.util.SpyErrorLogger; +import resources.Icons; +import resources.ResourceManager; + +public class ComponentProviderActionsTest extends AbstractGhidraHeadedIntegrationTest { + + // note: this has to happen after the test framework is initialized, so it cannot be static + private final Icon ICON = ResourceManager.loadImage("images/refresh.png"); + private static final String PROVIDER_NAME = "Test Action Provider"; + private static final KeyStroke CONTROL_T = + KeyStroke.getKeyStroke(Character.valueOf('t'), DockingUtils.CONTROL_KEY_MODIFIER_MASK); + + private TestEnv env; + private PluginTool tool; + private TestActionsComponentProvider provider; + private SpyErrorLogger spyLogger = new SpyErrorLogger(); + + @Before + public void setUp() throws Exception { + env = new TestEnv(); + tool = env.showTool(); + + //tool = env.launchDefaultTool(); + provider = new TestActionsComponentProvider(tool); + + Msg.setErrorLogger(spyLogger); + } + + @After + public void tearDown() { + env.dispose(); + } + + @Test + public void testIcon_WithIcon_BeforeAddedToTool() { + + setIcon(ICON); + + showProvider(); + + assertWindowMenuActionHasIcon(ICON); + } + + @Test + public void testIcon_WithIcon_AfterAddedToTool() { + + setIcon(null); + + showProvider(); + assertWindowMenuActionHasIcon(Icons.EMPTY_ICON); + + setIcon(ICON); + assertWindowMenuActionHasIcon(ICON); + } + + @Test + public void testIcon_WithoutIcon() { + showProvider(); + assertWindowMenuActionHasIcon(Icons.EMPTY_ICON); + } + + @Test + public void testSetKeyBinding_DirectlyOnProvider() { + // + // This is how clients set key bindings on providers, as desired, when constructing them + // + + KeyStroke defaultKs = CONTROL_T; + setDefaultKeyBinding(defaultKs); + + showProvider(); + + assertProviderKeyStroke(defaultKs); + assertOptionsKeyStroke(defaultKs); + assertMenuItemHasKeyStroke(defaultKs); + } + + @Test + public void testSetKeyBinding_DirectlyOnProvider_TransientProvider() { + // + // Transient providers cannot have key bindings + // + switchToTransientProvider(); + + setErrorsExpected(true); + setDefaultKeyBinding(CONTROL_T); + setErrorsExpected(false); + + showProvider(); + + spyLogger.assertLogMessage("Transient", "cannot", "key", "binding"); + } + + @Test + public void testSetTransientAfterSettingKeyBinding() { + + setDefaultKeyBinding(CONTROL_T); + + setErrorsExpected(true); + switchToTransientProvider(); + setErrorsExpected(false); + + showProvider(); + + spyLogger.assertLogMessage("Transient", "not", "key", "binding"); + } + + @Test(expected = IllegalStateException.class) + public void testSetTransientAfterAddedProviderToTheTool() { + + showProvider(); + switchToTransientProvider(); // exception + } + + @Test + public void testSetKeyBinding_ViaDialog_FromWindowMenu() { + + showProvider(); + + KeyStroke newKs = CONTROL_T; + setKeyBindingViaF4Dialog_FromWindowsMenu(newKs); + + assertProviderKeyStroke(newKs); + assertOptionsKeyStroke(newKs); + assertMenuItemHasKeyStroke(newKs); + } + + @Test + public void testSetKeyBinding_ViaDialog_FromWindowMenu_ToAlreadyBoundAction() { + + // + // Test the conflicting key binding use case + // + + HasDefaultKeyBindingComponentProvider otherProvider = + new HasDefaultKeyBindingComponentProvider(tool); + otherProvider.setVisible(true); + + showProvider(); + + KeyStroke newKs = CONTROL_T; + applyBindingToDialog_FromWindowsMenu(newKs); + + assertCollisionsWithKeyStroke(); + } + + @Test + public void testSetKeyBinding_ViaOptions_WithoutToolbarAction() { + + showProvider(); + + KeyStroke newKs = CONTROL_T; + setOptionsKeyStroke(newKs); + + assertProviderKeyStroke(newKs); + assertOptionsKeyStroke(newKs); + assertMenuItemHasKeyStroke(newKs); + assertNoToolbarAction(); + } + + @Test + public void testSetKeyBinding_ViaOptions_WithToolbarAction() { + + setToolbarIcon(ICON); + showProvider(); + + KeyStroke newKs = CONTROL_T; + setOptionsKeyStroke(newKs); + + assertProviderKeyStroke(newKs); + assertOptionsKeyStroke(newKs); + assertMenuItemHasKeyStroke(newKs); + assertToolbarAction(); + } + + @Test + public void testSetKeyBinding_ViaDialog_FromToolBar() { + + setToolbarIcon(ICON); + showProvider(); + + KeyStroke newKs = CONTROL_T; + setKeyBindingViaF4Dialog_FromToolsToolbar(newKs); + + assertProviderKeyStroke(newKs); + assertOptionsKeyStroke(newKs); + assertMenuItemHasKeyStroke(newKs); + } + + @Test + public void testSetKeyBinding_TransientProvider_CannotBeSetFromWindowMenu() { + + switchToTransientProvider(); + showProvider(); + + assertCannotShowKeyBindingDialog_FromWindowsMenu(); + } + + @Test + public void testSetKeyBinding_TransientProvider_CannotBeSetFromToolbar() { + + switchToTransientProvider(); + + setErrorsExpected(true); + setToolbarIcon(ICON); + setErrorsExpected(false); + + spyLogger.assertLogMessage("Transient", "not", "toolbar"); + } + + @Test + public void testDefaultKeyBindingAppearsInWindowMenu() { + + setDefaultKeyBinding(CONTROL_T); + showProvider(); + assertWindowMenuActionHasKeyBinding(CONTROL_T); + } + + @Test + public void testAddToToolbar_WithoutIcon() { + + runSwing(() -> provider.addToToolbar()); + + try { + setErrorsExpected(true); + runSwingWithExceptions(this::showProvider, true); + setErrorsExpected(false); + fail(); + } + catch (Throwable t) { + // good + } + } + + @Test + public void testSetIcon_NullIconWithToolbarAction() { + + setIcon(ICON); + runSwing(() -> provider.addToToolbar()); + showProvider(); + + try { + setErrorsExpected(true); + runSwingWithExceptions(() -> provider.setIcon(null), true); + setErrorsExpected(false); + fail("Expected an exception passing a null icon when specifying a toolbar action"); + } + catch (Throwable t) { + // expected + } + } + + @Test + public void testSetIcon_WithToolbarAction() { + setToolbarIcon(ICON); + showProvider(); + + assertWindowMenuActionHasIcon(ICON); + assertToolbarActionHasIcon(ICON); + } + + @Test + public void testSetIcon_WithToolbarAction_AfterActionHasBeenAddedToToolbar() { + + // + // We currently do not prevent providers from changing their icons. Make sure we respond + // to changes correctly. + // + + setToolbarIcon(ICON); + showProvider(); + + Icon newIcon = Icons.COLLAPSE_ALL_ICON; + setIcon(newIcon); + + assertWindowMenuActionHasIcon(newIcon); + assertToolbarActionHasIcon(newIcon); + } + +//================================================================================================== +// Private Methods +//================================================================================================== + + private void switchToTransientProvider() { + provider.setTransient(); + } + + private void showProvider() { + provider.addToTool(); + tool.showComponentProvider(provider, true); + waitForSwing(); + } + + private void setDefaultKeyBinding(KeyStroke defaultKs) { + runSwing(() -> provider.setKeyBinding(new KeyBindingData(defaultKs))); + } + + private void setIcon(Icon icon) { + runSwing(() -> provider.setIcon(icon)); + } + + private void setToolbarIcon(Icon icon) { + runSwing(() -> { + provider.setIcon(icon); + provider.addToToolbar(); + }); + } + + private DockingActionIf getShowProviderAction() { + + DockingActionIf showProviderAction = + getAction(tool, provider.getOwner(), provider.getName()); + assertNotNull("Could not find action to show ", showProviderAction); + return showProviderAction; + } + + private DockingActionIf getWindowMenuShowProviderAction() { + waitForSwing(); + DockingActionIf action = waitFor(() -> { + Set actions = getActionsByName(tool, PROVIDER_NAME); + + //@formatter:off + return actions + .stream() + .filter(a -> a.getOwner().equals(DockingWindowManager.DOCKING_WINDOWS_OWNER)) + .findFirst() + .orElseGet(() -> null) + ; + //@formatter:on + }); + + assertNotNull("Window menu action not installed for provider", action); + assertTrue(action.getClass().getSimpleName().contains("ShowComponentAction")); + return action; + } + + private DockingActionIf getToolbarShowProviderAction() { + + DockingWindowManager dwm = tool.getWindowManager(); + ActionToGuiMapper guiActions = + (ActionToGuiMapper) getInstanceField("actionToGuiMapper", dwm); + GlobalMenuAndToolBarManager toolbarManager = + (GlobalMenuAndToolBarManager) getInstanceField("menuAndToolBarManager", guiActions); + DockingActionIf action = provider.getShowProviderAction(); + DockingActionIf toolbarAction = toolbarManager.getToolbarAction(action.getName()); + return toolbarAction; + } + + private void setOptionsKeyStroke(KeyStroke newKs) { + ToolOptions keyOptions = tool.getOptions(DockingToolConstants.KEY_BINDINGS); + + // shared option name/format: "Provider Name (Tool)" - the shared action's owner is the Tool + runSwing(() -> keyOptions.setKeyStroke(provider.getName() + " (Tool)", newKs)); + waitForSwing(); + } + + private void assertNoToolbarAction() { + assertNull("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(); + KeyStroke actionKs = action.getKeyBinding(); + assertEquals(expectedKs, actionKs); + } + + private void assertOptionsKeyStroke(KeyStroke expectedKs) { + + ToolOptions options = getKeyBindingOptions(); + + // Option name: the action name with the 'Tool' as the owner + String fullName = provider.getName() + " (Tool)"; + KeyStroke optionsKs = runSwing(() -> options.getKeyStroke(fullName, null)); + assertEquals("Key stroke in options does not match expected key stroke", expectedKs, + optionsKs); + } + + private void assertWindowMenuActionHasIcon(Icon expected) { + DockingActionIf action = getWindowMenuShowProviderAction(); + assertEquals("Windows menu icons for provider does not match the value set on the provider", + expected, action.getMenuBarData().getMenuIcon()); + } + + private void assertToolbarActionHasIcon(Icon expected) { + DockingActionIf action = getToolbarShowProviderAction(); + assertNotNull("No toolbar action found; it should be there", action); + ToolBarData tbData = action.getToolBarData(); + assertEquals(expected, tbData.getIcon()); + } + + private void assertWindowMenuActionHasKeyBinding(KeyStroke ks) { + DockingActionIf action = getWindowMenuShowProviderAction(); + assertEquals( + "Windows menu key bindings for provider does not match the value set on the provider", + ks, action.getKeyBinding()); + } + + private void assertCannotShowKeyBindingDialog_FromWindowsMenu() { + // simulate the user mousing over the 'Window' menu's action + DockingActionIf windowMenuAction = getWindowMenuShowProviderAction(); + DockingWindowManager.setMouseOverAction(windowMenuAction); + + performLaunchKeyStrokeDialogAction(); + DialogComponentProvider warningDialog = waitForDialogComponent("Unable to Set Keybinding"); + close(warningDialog); + } + + private void setKeyBindingViaF4Dialog_FromWindowsMenu(KeyStroke ks) { + + // simulate the user mousing over the 'Window' menu's action + DockingActionIf windowMenuAction = getWindowMenuShowProviderAction(); + DockingWindowManager.setMouseOverAction(windowMenuAction); + + performLaunchKeyStrokeDialogAction(); + KeyEntryDialog dialog = waitForDialogComponent(KeyEntryDialog.class); + + runSwing(() -> dialog.setKeyStroke(ks)); + + pressButtonByText(dialog, "OK"); + + assertFalse("Invalid key stroke: " + ks, runSwing(() -> dialog.isVisible())); + } + + private void applyBindingToDialog_FromWindowsMenu(KeyStroke ks) { + DockingActionIf windowMenuAction = getWindowMenuShowProviderAction(); + DockingWindowManager.setMouseOverAction(windowMenuAction); + + performLaunchKeyStrokeDialogAction(); + KeyEntryDialog dialog = waitForDialogComponent(KeyEntryDialog.class); + + runSwing(() -> dialog.setKeyStroke(ks)); + } + + private void assertCollisionsWithKeyStroke() { + KeyEntryDialog dialog = waitForDialogComponent(KeyEntryDialog.class); + + JTextPane collisionPane = (JTextPane) getInstanceField("collisionPane", dialog); + String collisionText = runSwing(() -> collisionPane.getText()); + assertTrue(collisionText.contains("Actions mapped to")); + } + + private void assertMenuItemHasKeyStroke(KeyStroke expected) { + + DockingActionIf action = getWindowMenuShowProviderAction(); + assertEquals( + "Windows menu key binding for provider does not match the value of the provider", + expected, action.getKeyBinding()); + } + + private void setKeyBindingViaF4Dialog_FromToolsToolbar(KeyStroke ks) { + + // simulate the user mousing over the 'Window' menu's action + DockingActionIf toolbarAction = getToolbarShowProviderAction(); + assertNotNull("Provider action not installed in toolbar", toolbarAction); + DockingWindowManager.setMouseOverAction(toolbarAction); + + performLaunchKeyStrokeDialogAction(); + KeyEntryDialog dialog = waitForDialogComponent(KeyEntryDialog.class); + + runSwing(() -> dialog.setKeyStroke(ks)); + + pressButtonByText(dialog, "OK"); + + assertFalse("Invalid key stroke: " + ks, runSwing(() -> dialog.isVisible())); + } + + private void performLaunchKeyStrokeDialogAction() { + ToolActions toolActions = ((AbstractDockingTool) tool).getToolActions(); + Action action = toolActions.getAction(KeyStroke.getKeyStroke("F4")); + assertNotNull(action); + runSwing(() -> action.actionPerformed(new ActionEvent(this, 0, "")), false); + } + + private ToolOptions getKeyBindingOptions() { + return tool.getOptions(DockingToolConstants.KEY_BINDINGS); + } + + private class TestActionsComponentProvider extends ComponentProvider { + + private JComponent component = new JTextField("Hey!"); + + TestActionsComponentProvider(DockingTool tool) { + super(tool, PROVIDER_NAME, "Fooberry Plugin"); + } + + @Override + public JComponent getComponent() { + return component; + } + } + + private class HasDefaultKeyBindingComponentProvider extends ComponentProvider { + private JComponent component = new JTextField("Hey!"); + + HasDefaultKeyBindingComponentProvider(DockingTool tool) { + super(tool, HasDefaultKeyBindingComponentProvider.class.getSimpleName(), + "Fooberry Plugin"); + + setKeyBinding(new KeyBindingData(CONTROL_T)); + } + + @Override + public JComponent getComponent() { + return component; + } + } +} diff --git a/Ghidra/Features/Base/src/test.slow/java/docking/DockingWindowManagerTest.java b/Ghidra/Features/Base/src/test.slow/java/docking/DockingWindowManagerTest.java index c90c7437f7..e4b6ec6a77 100644 --- a/Ghidra/Features/Base/src/test.slow/java/docking/DockingWindowManagerTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/docking/DockingWindowManagerTest.java @@ -32,12 +32,11 @@ import org.junit.Test; import docking.test.AbstractDockingTest; import docking.widgets.label.GDLabel; import ghidra.framework.plugintool.ComponentProviderAdapter; +import ghidra.test.DummyTool; public class DockingWindowManagerTest extends AbstractDockingTest { - public DockingWindowManagerTest() { - super(); - } + private DockingTool tool = new DummyTool(); @Test public void testDefaultGroupWindowPosition() { @@ -46,7 +45,7 @@ public class DockingWindowManagerTest extends AbstractDockingTest { // default window position. // - DockingWindowManager dwm = new DockingWindowManager("test", (List) null, null); + DockingWindowManager dwm = new DockingWindowManager(tool, (List) null, null); ComponentProvider providerA = addProvider(dwm, "A", "a", RIGHT); ComponentProvider providerB = addProvider(dwm, "B", "b", BOTTOM); @@ -66,7 +65,7 @@ public class DockingWindowManagerTest extends AbstractDockingTest { // intragroup window position. Note: 'Stacked' is the default. // - DockingWindowManager dwm = new DockingWindowManager("test", (List) null, null); + DockingWindowManager dwm = new DockingWindowManager(tool, (List) null, null); ComponentProvider providerA1 = addProvider(dwm, "A1", "a", RIGHT, STACK); ComponentProvider providerA2 = addProvider(dwm, "A2", "a", BOTTOM, STACK); @@ -84,7 +83,7 @@ public class DockingWindowManagerTest extends AbstractDockingTest { // intragroup window position. // - DockingWindowManager dwm = new DockingWindowManager("test", (List) null, null); + DockingWindowManager dwm = new DockingWindowManager(tool, (List) null, null); ComponentProvider providerA1 = addProvider(dwm, "A1", "a", RIGHT, STACK); ComponentProvider providerA2 = addProvider(dwm, "A2", "a", LEFT, BOTTOM); @@ -101,7 +100,7 @@ public class DockingWindowManagerTest extends AbstractDockingTest { // intragroup window position. // - DockingWindowManager dwm = new DockingWindowManager("test", (List) null, null); + DockingWindowManager dwm = new DockingWindowManager(tool, (List) null, null); ComponentProvider providerA1 = addProvider(dwm, "A1", "a", RIGHT, WINDOW); ComponentProvider providerA2 = addProvider(dwm, "A2", "a", LEFT, WINDOW); @@ -122,7 +121,7 @@ public class DockingWindowManagerTest extends AbstractDockingTest { // // note: the positions specified here are for default positions, not intragroup positions - DockingWindowManager dwm = new DockingWindowManager("test", (List) null, null); + DockingWindowManager dwm = new DockingWindowManager(tool, (List) null, null); ComponentProvider providerA = addProvider(dwm, "A", "a", RIGHT, STACK); ComponentProvider providerAB = addProvider(dwm, "AB", "a.b", BOTTOM, STACK); @@ -143,7 +142,7 @@ public class DockingWindowManagerTest extends AbstractDockingTest { // // note: the positions specified here are for default positions, not intragroup positions - DockingWindowManager dwm = new DockingWindowManager("test", (List) null, null); + DockingWindowManager dwm = new DockingWindowManager(tool, (List) null, null); ComponentProvider providerA = addProvider(dwm, "A", "a", RIGHT, BOTTOM); ComponentProvider providerAB = addProvider(dwm, "AB", "a.b", BOTTOM, TOP); @@ -181,8 +180,7 @@ public class DockingWindowManagerTest extends AbstractDockingTest { // Test that, for unrelated groups, the layout info stored in XML is re-used when providers // are shown after that XML is restored--even if the window positioning changes // - final DockingWindowManager dwm1 = - new DockingWindowManager("test", (List) null, null); + final DockingWindowManager dwm1 = new DockingWindowManager(tool, (List) null, null); ComponentProvider providerA = addProvider(dwm1, "A", "a", RIGHT); ComponentProvider providerB = addProvider(dwm1, "B", "b", BOTTOM); @@ -217,7 +215,7 @@ public class DockingWindowManagerTest extends AbstractDockingTest { // Test that, for related groups, the layout info stored in XML is re-used when providers // are shown after that XML is restored--even if the window positioning changes // - DockingWindowManager dwm1 = new DockingWindowManager("test", (List) null, null); + DockingWindowManager dwm1 = new DockingWindowManager(tool, (List) null, null); ComponentProvider providerA = addProvider(dwm1, "A", "a", RIGHT); ComponentProvider providerAB = addProvider(dwm1, "AB", "a.b", BOTTOM); @@ -253,7 +251,7 @@ public class DockingWindowManagerTest extends AbstractDockingTest { // and that a subgroup 'a.b' will open relative to the parent. **Make sure that this // works when the parent is the first provider open. // - DockingWindowManager dwm = new DockingWindowManager("test", (List) null, null); + DockingWindowManager dwm = new DockingWindowManager(tool, (List) null, null); ComponentProvider providerA = addProvider(dwm, "A", "a", TOP, RIGHT); ComponentProvider providerAB = addProvider(dwm, "AB", "a.b", RIGHT, BOTTOM); @@ -270,7 +268,7 @@ public class DockingWindowManagerTest extends AbstractDockingTest { // and that a subgroup 'a.b' will open relative to the parent. **Make sure that this // works when the subgroup is the first provider open. // - DockingWindowManager dwm = new DockingWindowManager("test", (List) null, null); + DockingWindowManager dwm = new DockingWindowManager(tool, (List) null, null); ComponentProvider providerAB = addProvider(dwm, "AB", "a.b", RIGHT, BOTTOM); ComponentProvider providerA = addProvider(dwm, "A", "a", TOP, RIGHT); @@ -288,7 +286,7 @@ public class DockingWindowManagerTest extends AbstractDockingTest { // Test that two providers that don't share an owner (the plugin) can share a group and // open relative to each other. // - DockingWindowManager dwm = new DockingWindowManager("test", (List) null, null); + DockingWindowManager dwm = new DockingWindowManager(tool, (List) null, null); ComponentProvider p1 = addProvider(dwm, "Owner_1", "Name_1", "group", TOP, TOP); ComponentProvider p2 = addProvider(dwm, "Owner_2", "Name_2", "group", BOTTOM, RIGHT); @@ -337,7 +335,7 @@ public class DockingWindowManagerTest extends AbstractDockingTest { */ - DockingWindowManager dwm = new DockingWindowManager("test", (List) null, null); + DockingWindowManager dwm = new DockingWindowManager(tool, (List) null, null); ComponentProvider pA = addProvider(dwm, "Owner_1", "A", "a", LEFT, LEFT); ComponentProvider pB = addProvider(dwm, "Owner_2", "B", "b", RIGHT, RIGHT); @@ -410,7 +408,7 @@ public class DockingWindowManagerTest extends AbstractDockingTest { private DockingWindowManager createNewDockingWindowManagerFromXML(final Element element) { final DockingWindowManager dwm2 = - new DockingWindowManager("text2", (List) null, null); + new DockingWindowManager(new DummyTool("Tool2"), (List) null, null); runSwing(() -> { dwm2.setVisible(true); diff --git a/Ghidra/Features/Base/src/test.slow/java/docking/action/KeyEntryDialogTest.java b/Ghidra/Features/Base/src/test.slow/java/docking/action/KeyEntryDialogTest.java index 61038d2b83..e5031119fc 100644 --- a/Ghidra/Features/Base/src/test.slow/java/docking/action/KeyEntryDialogTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/docking/action/KeyEntryDialogTest.java @@ -18,12 +18,15 @@ package docking.action; import static org.junit.Assert.*; import java.awt.event.KeyEvent; +import java.util.Map; import javax.swing.*; import org.junit.*; import docking.*; +import docking.actions.KeyEntryDialog; +import docking.actions.ToolActions; import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin; import ghidra.app.plugin.core.data.DataPlugin; import ghidra.app.plugin.core.function.FunctionPlugin; @@ -185,20 +188,26 @@ public class KeyEntryDialogTest extends AbstractGhidraHeadedIntegrationTest { } public DockingAction getKeyBindingAction() { - DockingWindowManager dwm = DockingWindowManager.getInstance(tool.getToolFrame()); - ActionToGuiMapper dockingActionManager = - (ActionToGuiMapper) getInstanceField("actionManager", dwm); - return (DockingAction) getInstanceField("keyBindingsAction", dockingActionManager); + + ToolActions toolActions = tool.getToolActions(); + KeyBindingsManager kbm = + (KeyBindingsManager) getInstanceField("keyBindingsManager", toolActions); + Map dockingKeyMap = + (Map) getInstanceField("dockingKeyMap", kbm); + KeyStroke ks = KeyStroke.getKeyStroke(KeyEvent.VK_F4, 0); + DockingKeyBindingAction dockingAction = dockingKeyMap.get(ks); + DockingAction f4Action = (DockingAction) getInstanceField("docakbleAction", dockingAction); + return f4Action; } private void showDialog(final DockingAction actionToEdit) throws Exception { - final DockingAction keyBindingAction = getKeyBindingAction(); + DockingAction keyBindingAction = getKeyBindingAction(); executeOnSwingWithoutBlocking(() -> { DockingWindowManager.setMouseOverAction(actionToEdit); performAction(keyBindingAction, false); }); - keyEntryDialog = waitForDialogComponent(tool.getToolFrame(), KeyEntryDialog.class, 2000); + keyEntryDialog = waitForDialogComponent(KeyEntryDialog.class); assertNotNull(keyEntryDialog); collisionPane = (JTextPane) getInstanceField("collisionPane", keyEntryDialog); diff --git a/Ghidra/Features/Base/src/test.slow/java/docking/widgets/dialogs/NumberInputDialogTest.java b/Ghidra/Features/Base/src/test.slow/java/docking/widgets/dialogs/NumberInputDialogTest.java index 6e23cc8274..2a4d54d64e 100644 --- a/Ghidra/Features/Base/src/test.slow/java/docking/widgets/dialogs/NumberInputDialogTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/docking/widgets/dialogs/NumberInputDialogTest.java @@ -30,10 +30,12 @@ import org.junit.Test; import docking.DockingWindowManager; import docking.test.AbstractDockingTest; import docking.widgets.textfield.IntegerTextField; +import ghidra.test.DummyTool; public class NumberInputDialogTest extends AbstractDockingTest { - private DockingWindowManager dwm = new DockingWindowManager("test", (List) null, null); + private DockingWindowManager dwm = + new DockingWindowManager(new DummyTool(), (List) null, null); private NumberInputDialog dialog; private JButton okButton; private JTextField textField; diff --git a/Ghidra/Features/Base/src/test.slow/java/docking/widgets/table/GhidraTableFilterTest.java b/Ghidra/Features/Base/src/test.slow/java/docking/widgets/table/GhidraTableFilterTest.java index 29fcd55ccc..0d5d589179 100644 --- a/Ghidra/Features/Base/src/test.slow/java/docking/widgets/table/GhidraTableFilterTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/docking/widgets/table/GhidraTableFilterTest.java @@ -28,6 +28,7 @@ import docking.widgets.filter.*; import docking.widgets.table.model.DirData; import docking.widgets.table.model.TestDataModel; import ghidra.test.AbstractGhidraHeadedIntegrationTest; +import ghidra.test.DummyTool; import ghidra.util.table.GhidraTable; public class GhidraTableFilterTest extends AbstractGhidraHeadedIntegrationTest { @@ -50,7 +51,7 @@ public class GhidraTableFilterTest extends AbstractGhidraHeadedIntegrationTest { filteredModel = filterPanel.getTableFilterModel(); table.setAutoLookupColumn(4); - winMgr = new DockingWindowManager("Tests", null, null); + winMgr = new DockingWindowManager(new DummyTool(), null, null); winMgr.addComponent(new TestTableComponentProvider()); winMgr.setVisible(true); } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/bookmark/BookmarkPluginTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/bookmark/BookmarkPluginTest.java index aef5b20654..25724c56e2 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/bookmark/BookmarkPluginTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/bookmark/BookmarkPluginTest.java @@ -718,7 +718,7 @@ public class BookmarkPluginTest extends AbstractGhidraHeadedIntegrationTest { } private void showBookmarkProvider() throws Exception { - DockingActionIf action = getAction(plugin, "Show Bookmarks"); + DockingActionIf action = getAction(plugin, "Bookmarks"); performAction(action, true); table = plugin.getBookmarkTable(); waitForTable(); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/calltree/CallTreePluginTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/calltree/CallTreePluginTest.java index a5e2a57ea3..d6858474de 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/calltree/CallTreePluginTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/calltree/CallTreePluginTest.java @@ -46,7 +46,6 @@ import ghidra.framework.plugintool.PluginTool; import ghidra.program.database.ProgramBuilder; import ghidra.program.database.ProgramDB; import ghidra.program.model.address.Address; -import ghidra.program.model.address.AddressFactory; import ghidra.program.model.data.DataType; import ghidra.program.model.data.PointerDataType; import ghidra.program.model.listing.Function; @@ -62,7 +61,6 @@ public class CallTreePluginTest extends AbstractGhidraHeadedIntegrationTest { private TestEnv env; private CodeBrowserPlugin codeBrowserPlugin; private PluginTool tool; - private ProgramDB program; private CallTreePlugin callTreePlugin; private CallTreeProvider provider; private List providers; @@ -70,14 +68,10 @@ public class CallTreePluginTest extends AbstractGhidraHeadedIntegrationTest { private GTree incomingTree; private GTree outgoingTree; - private DockingAction showProviderAction; + private ProgramBuilder builder; + private ProgramDB program; private GoToService goToService; - private AddressFactory addressFactory; - - public CallTreePluginTest() { - super(); - } @SuppressWarnings("unchecked") // cast to list @@ -91,7 +85,6 @@ public class CallTreePluginTest extends AbstractGhidraHeadedIntegrationTest { callTreePlugin = env.getPlugin(CallTreePlugin.class); providers = (List) getInstanceField("providers", callTreePlugin); - showProviderAction = (DockingAction) getInstanceField("showProviderAction", callTreePlugin); GoToServicePlugin goToPlugin = env.getPlugin(GoToServicePlugin.class); goToService = (GoToService) invokeInstanceMethod("getGotoService", goToPlugin); @@ -100,20 +93,18 @@ public class CallTreePluginTest extends AbstractGhidraHeadedIntegrationTest { env.showTool(); program = createProgram(); - addressFactory = program.getAddressFactory(); ProgramManager pm = tool.getService(ProgramManager.class); pm.openProgram(program.getDomainFile()); // setup a good start location goTo(addr("5000")); - provider = getProvider(); + provider = callTreePlugin.getPrimaryProvider(); + tool.showComponentProvider(provider, true); incomingTree = (GTree) getInstanceField("incomingTree", provider); outgoingTree = (GTree) getInstanceField("outgoingTree", provider); } - ProgramBuilder builder; - private ProgramDB createProgram() throws Exception { builder = new ProgramBuilder("Call Trees", ProgramBuilder._TOY); @@ -385,21 +376,21 @@ public class CallTreePluginTest extends AbstractGhidraHeadedIntegrationTest { assertNotNull("Provider did not update its information when made visible", providerFunction()); - final ToggleDockingAction navigateIncomingLoctionsAction = + ToggleDockingAction navigateIncomingLoctionsAction = (ToggleDockingAction) getAction("Navigation Incoming Location Changes"); - assertTrue(!navigateIncomingLoctionsAction.isSelected()); + setToggleActionSelected(navigateIncomingLoctionsAction, provider.getActionContext(null), + false); assertEquals("Provider's location does not match that of the listing.", currentFunction(), providerFunction()); goTo(addr("0x6000")); - assertTrue("Provider's location matches that of the listing when not following " + - "location changes.", !currentFunction().equals(providerFunction())); - + assertNotEquals("Provider's location matches that of the listing when not following " + + "location changes", currentFunction(), providerFunction()); performAction(navigateIncomingLoctionsAction, true); - assertEquals("Provider's location does not match that of the listing.", currentFunction(), + assertEquals("Provider's location does not match that of the listing", currentFunction(), providerFunction()); } @@ -779,21 +770,6 @@ public class CallTreePluginTest extends AbstractGhidraHeadedIntegrationTest { return functionManager.getFunctionAt(address); } - private CallTreeProvider getProvider() { - final AtomicReference ref = new AtomicReference<>(); - - // run in swing, as two threads are accessing/manipulating a variable - runSwing(() -> { - if (providers.size() == 0) { - ref.set(showProvider()); - } - else { - ref.set(providers.get(0)); - } - }); - return ref.get(); - } - private CallTreeProvider getProvider(final String address) { final CallTreeProvider[] providerBox = new CallTreeProvider[1]; @@ -939,13 +915,13 @@ public class CallTreePluginTest extends AbstractGhidraHeadedIntegrationTest { } private void fullyExpandIncomingNode(GTreeNode node) { - DockingActionIf expandAction = getAction(callTreePlugin, "Fully Expand Selected Nodes"); + DockingActionIf expandAction = getLocalAction(provider, "Fully Expand Selected Nodes"); performAction(expandAction, new ActionContext(provider, incomingTree), false); waitForTree(node.getTree()); } private void fullyExpandOutgoingNode(GTreeNode node) { - DockingActionIf expandAction = getAction(callTreePlugin, "Fully Expand Selected Nodes"); + DockingActionIf expandAction = getLocalAction(provider, "Fully Expand Selected Nodes"); performAction(expandAction, new ActionContext(provider, outgoingTree), false); waitForTree(node.getTree()); } @@ -1054,11 +1030,11 @@ public class CallTreePluginTest extends AbstractGhidraHeadedIntegrationTest { String name = child.getName(); Integer integer = map.get(name); if (integer == null) { - integer = new Integer(0); + integer = 0; } int asInt = integer; asInt++; - map.put(name, new Integer(asInt)); + map.put(name, asInt); } return map; } @@ -1088,10 +1064,7 @@ public class CallTreePluginTest extends AbstractGhidraHeadedIntegrationTest { } private DockingActionIf getAction(String actionName) { - // make sure there is a provider from which to get actions - getProvider(); - - DockingActionIf action = getAction(tool, "CallTreePlugin", actionName); + DockingActionIf action = getLocalAction(provider, actionName); return action; } @@ -1121,20 +1094,14 @@ public class CallTreePluginTest extends AbstractGhidraHeadedIntegrationTest { } } - /** - * Shows and returns a provider for the current address. - */ - private CallTreeProvider showProvider() { - performAction(showProviderAction, true); - return getProvider(); - } - /** * Shows and returns a provider for the specified address. */ private CallTreeProvider showProvider(String address) { goTo(addr(address)); - performAction(showProviderAction, true); + + DockingAction action = callTreePlugin.getShowCallTreeFromMenuAction(); + performAction(action); return getProvider(address); } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/codebrowser/CodeBrowserScreenMovementTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/codebrowser/CodeBrowserScreenMovementTest.java index 19de6dd5b0..b4b0b56ee5 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/codebrowser/CodeBrowserScreenMovementTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/codebrowser/CodeBrowserScreenMovementTest.java @@ -61,6 +61,9 @@ public class CodeBrowserScreenMovementTest extends AbstractProgramBasedTest { @Before public void setUp() throws Exception { + // warning: this test is sensitive to size and layout of the visible component providers; + // any layout changes may affect the values being tested below + initialize(); fp = codeBrowser.getFieldPanel(); @@ -115,6 +118,9 @@ public class CodeBrowserScreenMovementTest extends AbstractProgramBasedTest { @Test public void testBasicControls() throws Exception { + // warning: this test is sensitive to size and layout of the visible component providers; + // any layout changes may affect the values being tested below + fp = codeBrowser.getFieldPanel(); ListingPanel panel = codeBrowser.getListingPanel(); JScrollBar scrollbar = panel.getVerticalScrollBar(); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/codebrowser/ExpandCollapseDataActionsTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/codebrowser/ExpandCollapseDataActionsTest.java index 533cbab14f..30c892aad8 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/codebrowser/ExpandCollapseDataActionsTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/codebrowser/ExpandCollapseDataActionsTest.java @@ -68,7 +68,7 @@ public class ExpandCollapseDataActionsTest extends AbstractGhidraHeadedIntegrati pm.openProgram(program.getDomainFile()); addrFactory = program.getAddressFactory(); env.showTool(); - provider = (CodeViewerProvider) tool.getComponentProvider("CodeBrowserPlugin"); + provider = (CodeViewerProvider) tool.getComponentProvider("Listing"); listingModel = provider.getListingPanel().getListingModel(); CodeBrowserPlugin plugin = getPlugin(tool, CodeBrowserPlugin.class); toggleExpand = getAction(plugin, "Toggle Expand/Collapse Data"); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/StructureEditorArchiveTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/StructureEditorArchiveTest.java index 579053f4e1..188830ed15 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/StructureEditorArchiveTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/compositeeditor/StructureEditorArchiveTest.java @@ -62,7 +62,7 @@ public class StructureEditorArchiveTest extends AbstractStructureEditorTest { tool.addPlugin(DataTypeManagerPlugin.class.getName()); dtmService = tool.getService(DataTypeManagerService.class); plugin = (DataTypeManagerPlugin) dtmService; - manageDts = getAction(plugin, "Data Type Manager"); + manageDts = getAction(plugin, "DataTypes Provider"); DataTypesProvider dataTypesProvider = plugin.getProvider(); dtTree = dataTypesProvider.getGTree(); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/datamgr/ArchiveRemappedHeadedTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/datamgr/ArchiveRemappedHeadedTest.java index 6398057965..77e531847f 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/datamgr/ArchiveRemappedHeadedTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/datamgr/ArchiveRemappedHeadedTest.java @@ -55,14 +55,6 @@ public class ArchiveRemappedHeadedTest extends AbstractGhidraHeadedIntegrationTe private File vs12ArchiveFile; private File vs9ArchiveFile; - /** - * Constructor for ArchiveRemappedTest. - * @param testName - */ - public ArchiveRemappedHeadedTest() { - super(); - } - @Before public void setUp() throws Exception { @@ -97,10 +89,9 @@ public class ArchiveRemappedHeadedTest extends AbstractGhidraHeadedIntegrationTe env.showTool(); - tool.showComponentProvider(provider, true); - waitForPostedSwingRunnables(); - provider = plugin.getProvider(); + tool.showComponentProvider(provider, true); + tree = provider.getGTree(); waitForTree(tree); } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/memory/MemoryMapPluginTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/memory/MemoryMapPluginTest.java index e0b69ccf3f..a6f1c6bc5f 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/memory/MemoryMapPluginTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/memory/MemoryMapPluginTest.java @@ -87,7 +87,7 @@ public class MemoryMapPluginTest extends AbstractGhidraHeadedIntegrationTest { @Test public void testActionEnabled() { - DockingActionIf action = getAction(plugin, "View Memory Map"); + DockingActionIf action = getAction(plugin, "Memory Map"); assertTrue(action.isEnabled()); } @@ -99,7 +99,7 @@ public class MemoryMapPluginTest extends AbstractGhidraHeadedIntegrationTest { Set actions = getActionsByOwner(tool, plugin.getName()); for (DockingActionIf action : actions) { if (action.getName().equals("Add Block") || action.getName().equals("Set Image Base") || - action.getName().equals("View Memory Map")) { + action.getName().equals("Memory Map")) { assertTrue(action.isEnabledForContext(provider.getActionContext(null))); } else { @@ -116,7 +116,7 @@ public class MemoryMapPluginTest extends AbstractGhidraHeadedIntegrationTest { assertEquals(0, table.getModel().getRowCount()); Set actions = getActionsByOwner(tool, plugin.getName()); for (DockingActionIf action : actions) { - if (!action.getName().equals("View Memory Map")) { + if (!action.getName().equals("Memory Map")) { assertTrue(!action.isEnabledForContext(provider.getActionContext(null))); } } @@ -297,7 +297,7 @@ public class MemoryMapPluginTest extends AbstractGhidraHeadedIntegrationTest { ///////////////////////////////////////////////////////////////////////// private void showProvider() { - DockingActionIf action = getAction(plugin, "View Memory Map"); + DockingActionIf action = getAction(plugin, "Memory Map"); performAction(action, true); provider = plugin.getMemoryMapProvider(); } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/memory/MemoryMapProvider1Test.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/memory/MemoryMapProvider1Test.java index 528773a745..69f95b362f 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/memory/MemoryMapProvider1Test.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/memory/MemoryMapProvider1Test.java @@ -137,7 +137,7 @@ public class MemoryMapProvider1Test extends AbstractGhidraHeadedIntegrationTest String name = action.getName(); if (name.equals("Add Block") || name.equals("Merge Blocks") || name.equals("Delete Block") || name.equals("Set Image Base") || - name.equals("View Memory Map")) { + name.equals("Memory Map")) { assertTrue(action.isEnabled()); } else { @@ -667,7 +667,7 @@ public class MemoryMapProvider1Test extends AbstractGhidraHeadedIntegrationTest ///////////////////////////////////////////////////////////////////// private void showProvider() { - DockingActionIf action = getAction(plugin, "View Memory Map"); + DockingActionIf action = getAction(plugin, "Memory Map"); performAction(action, true); waitForPostedSwingRunnables(); provider = plugin.getMemoryMapProvider(); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/memory/MemoryMapProvider2Test.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/memory/MemoryMapProvider2Test.java index 6341a1da80..8209a275ad 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/memory/MemoryMapProvider2Test.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/memory/MemoryMapProvider2Test.java @@ -1332,7 +1332,7 @@ public class MemoryMapProvider2Test extends AbstractGhidraHeadedIntegrationTest /////////////////////////////////////////////////////////////////// private void showProvider() { - DockingActionIf action = getAction(plugin, "View Memory Map"); + DockingActionIf action = getAction(plugin, "Memory Map"); performAction(action, true); provider = plugin.getMemoryMapProvider(); table = provider.getTable(); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/memory/MemoryMapProvider3Test.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/memory/MemoryMapProvider3Test.java index fdc1c9c132..6df271bd1b 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/memory/MemoryMapProvider3Test.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/memory/MemoryMapProvider3Test.java @@ -886,7 +886,7 @@ public class MemoryMapProvider3Test extends AbstractGhidraHeadedIntegrationTest /////////////////////////////////////////////////////////////////// private void showProvider() { - DockingActionIf action = getAction(plugin, "View Memory Map"); + DockingActionIf action = getAction(plugin, "Memory Map"); performAction(action, true); waitForPostedSwingRunnables(); provider = plugin.getMemoryMapProvider(); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/memory/MemoryMapProvider4Test.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/memory/MemoryMapProvider4Test.java index 7b64ee0d31..940bf06e0e 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/memory/MemoryMapProvider4Test.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/memory/MemoryMapProvider4Test.java @@ -54,10 +54,6 @@ public class MemoryMapProvider4Test extends AbstractGhidraHeadedIntegrationTest private JTable table; private TableModel model; - public MemoryMapProvider4Test() { - super(); - } - private Program buildProgram(String programName) throws Exception { ProgramBuilder builder = new ProgramBuilder(programName, ProgramBuilder._TOY); builder.createMemory(".text", Long.toHexString(0x1001000), 0x6600); @@ -247,7 +243,7 @@ public class MemoryMapProvider4Test extends AbstractGhidraHeadedIntegrationTest } private void showProvider() { - DockingActionIf action = getAction(plugin, "View Memory Map"); + DockingActionIf action = getAction(plugin, "Memory Map"); performAction(action, true); waitForPostedSwingRunnables(); provider = plugin.getMemoryMapProvider(); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/navigation/NextPrevAddressPluginTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/navigation/NextPrevAddressPluginTest.java index 17add00a55..8794cae194 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/navigation/NextPrevAddressPluginTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/navigation/NextPrevAddressPluginTest.java @@ -71,8 +71,7 @@ public class NextPrevAddressPluginTest extends AbstractGhidraHeadedIntegrationTe NextPrevAddressPlugin plugin = env.getPlugin(NextPrevAddressPlugin.class); previousAction = (MultiActionDockingAction) TestUtils.getInstanceField("previousAction", plugin); - nextAction = - (MultiActionDockingAction) TestUtils.getInstanceField("nextAction", plugin); + nextAction = (MultiActionDockingAction) TestUtils.getInstanceField("nextAction", plugin); GoToAddressLabelPlugin goToPlugin = env.getPlugin(GoToAddressLabelPlugin.class); dialog = goToPlugin.getDialog(); @@ -355,9 +354,9 @@ public class NextPrevAddressPluginTest extends AbstractGhidraHeadedIntegrationTe @SuppressWarnings("unchecked") // let caution fly private JButton findButtonForAction(DockingWindowManager windowManager, DockingAction action) { - Object actionManager = TestUtils.getInstanceField("actionManager", windowManager); + Object actionToGuiMapper = TestUtils.getInstanceField("actionToGuiMapper", windowManager); Object menuAndToolBarManager = - TestUtils.getInstanceField("menuAndToolBarManager", actionManager); + TestUtils.getInstanceField("menuAndToolBarManager", actionToGuiMapper); Map map = (Map) TestUtils.getInstanceField( "windowToActionManagerMap", menuAndToolBarManager); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/navigation/ProviderNavigationPluginTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/navigation/ProviderNavigationPluginTest.java new file mode 100644 index 0000000000..f5a6024964 --- /dev/null +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/navigation/ProviderNavigationPluginTest.java @@ -0,0 +1,175 @@ +/* ### + * 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.navigation; + +import static org.junit.Assert.*; + +import java.util.HashSet; +import java.util.Set; +import java.util.function.Consumer; + +import org.junit.Before; +import org.junit.Test; + +import docking.*; +import docking.action.DockingActionIf; +import ghidra.program.database.ProgramBuilder; +import ghidra.program.model.listing.Program; +import ghidra.test.AbstractProgramBasedTest; +import ghidra.util.Msg; +import ghidra.util.datastruct.WeakSet; + +public class ProviderNavigationPluginTest extends AbstractProgramBasedTest { + + private ProviderNavigationPlugin plugin; + private DockingActionIf previousProviderAction; + + private SpyProviderActivator spyProviderActivator = new SpyProviderActivator(); + private Set testContextListeners; + + @Before + public void setUp() throws Exception { + initialize(); + + plugin = env.getPlugin(ProviderNavigationPlugin.class); + previousProviderAction = + getAction(tool, ProviderNavigationPlugin.GO_TO_LAST_ACTIVE_COMPONENT_ACTION_NAME); + + fakeOutContextNotification(); + } + + @SuppressWarnings("unchecked") + private void fakeOutContextNotification() { + // + // This is the mechanism the tool uses for notifying clients of Component Provider + // activation. This activation is focus-sensitive, which makes it unreliable for testing. + // Thus, replace this mechanism with one that we can control. + // + DockingWindowManager windowManager = tool.getWindowManager(); + testContextListeners = new HashSet<>(); + WeakSet contextListeners = + (WeakSet) getInstanceField("contextListeners", windowManager); + testContextListeners.addAll(contextListeners.values()); + contextListeners.clear(); + + // + // Now, install a spy that allows us to know when our action under test triggers and + // with which state it does so. + // + plugin.setProviderActivator(spyProviderActivator); + + } + + @Override + protected Program getProgram() throws Exception { + return buildProgram(); + } + + private Program buildProgram() throws Exception { + ProgramBuilder builder = new ProgramBuilder("Test", ProgramBuilder._TOY); + builder.createMemory(".text", "0x1001000", 0x6600); + return builder.getProgram(); + } + + @Test + public void testGoToLastActiveComponent() { + + clearPluginState(); + assertPreviousProviderActionNotEnabled(); + + ComponentProvider bookmarks = activateProvider("Bookmarks"); + assertPreviousProviderActionNotEnabled(); // first provider; nothing to go back to + + ComponentProvider dataTypes = activateProvider("DataTypes Provider"); + assertPreviousProviderActionEnabled(); + + // active provider : 'data types'; previous: 'bookmarks' + performPreviousProviderAction(); + assertActivated(bookmarks); + + // active provider : 'bookmarks'; previous: 'data types' + performPreviousProviderAction(); + assertActivated(dataTypes); + + activateProvider("Symbol Table"); + + // active provider : 'symbol table'; previous: 'data types' + performPreviousProviderAction(); + assertActivated(dataTypes); + } + + private void clearPluginState() { + waitForSwing(); + runSwing(() -> plugin.resetTrackingState()); + } + + private void assertActivated(ComponentProvider bookmarks) { + assertEquals("The active provider was not restored correctly", bookmarks, + spyProviderActivator.lastActivated); + } + + private void performPreviousProviderAction() { + performAction(previousProviderAction, true); + waitForSwing(); + } + + private void assertPreviousProviderActionEnabled() { + assertTrue( + "'" + ProviderNavigationPlugin.GO_TO_LAST_ACTIVE_COMPONENT_ACTION_NAME + "'" + + " should be enabled when there is a previous provider set", + previousProviderAction.isEnabledForContext(new ActionContext())); + } + + private void assertPreviousProviderActionNotEnabled() { + assertFalse( + "'" + ProviderNavigationPlugin.GO_TO_LAST_ACTIVE_COMPONENT_ACTION_NAME + "'" + + " should not be enabled when there is no previous provider set", + previousProviderAction.isEnabledForContext(new ActionContext())); + } + + private ComponentProvider activateProvider(String name) { + + waitForSwing(); + ComponentProvider provider = tool.getComponentProvider(name); + assertNotNull(provider); + + tool.showComponentProvider(provider, true); + runSwing(() -> forceActivate(provider)); + waitForSwing(); + return provider; + } + + private void forceActivate(ComponentProvider provider) { + ActionContext context = new ActionContext(provider, provider); + for (DockingContextListener l : testContextListeners) { + l.contextChanged(context); + } + } + + private class SpyProviderActivator implements Consumer { + + private ComponentProvider lastActivated; + + @Override + public void accept(ComponentProvider c) { + + Msg.out("Spy - activated: " + c); + + lastActivated = c; + forceActivate(c); + } + } +} diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/progmgr/MultiTabPluginTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/progmgr/MultiTabPluginTest.java index 21cd84c437..2d9198bd25 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/progmgr/MultiTabPluginTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/progmgr/MultiTabPluginTest.java @@ -256,35 +256,6 @@ public class MultiTabPluginTest extends AbstractGhidraHeadedIntegrationTest { assertEquals(0, panel.getHiddenCount()); } - private void printResizeDebug() { - // - // To show the '>>' label, the number of tabs must exceed the room visible to show them - // - - // frame size - - // available width - int panelWidth = panel.getWidth(); - System.out.println("available width: " + panelWidth); - - // size label - int totalWidth = 0; - JComponent listLabel = (JComponent) getInstanceField("showHiddenListLabel", panel); - System.out.println("label width: " + listLabel.getWidth()); - totalWidth = listLabel.getWidth(); - - // size of each tab's panel - Map map = (Map) getInstanceField("linkedProgramMap", panel); - Collection values = map.values(); - for (Object object : values) { - JComponent c = (JComponent) object; - totalWidth += c.getWidth(); - System.out.println("\t" + c.getWidth()); - } - - System.out.println("Total width: " + totalWidth + " out of " + panelWidth); - } - @Test public void testTabUpdatesOnProgramChange() throws Exception { ProgramBuilder builder = new ProgramBuilder("notepad", ProgramBuilder._TOY); @@ -315,11 +286,11 @@ public class MultiTabPluginTest extends AbstractGhidraHeadedIntegrationTest { setFrameSize(400, 500); programNames = new String[] { "notepad", "login", "tms", "taskman", "TestGhidraSearches" }; openPrograms(programNames); - assertTrue(panel.isHidden(programs[1])); + assertHidden(programs[1]); runSwing(() -> panel.setSelectedProgram(programs[1])); assertEquals(programs[1], panel.getSelectedProgram()); - assertTrue(!panel.isHidden(programs[1])); + assertShowing(programs[1]); } @Test @@ -384,10 +355,6 @@ public class MultiTabPluginTest extends AbstractGhidraHeadedIntegrationTest { assertEquals(BigInteger.valueOf(4), fp.getCursorLocation().getIndex()); } - private Color getFieldPanelBackgroundColor(FieldPanel fp, BigInteger index) { - return runSwing(() -> fp.getBackgroundColor(index)); - } - @Test public void testTabUpdate() throws Exception { Program p = openDummyProgram("login", true); @@ -449,8 +416,8 @@ public class MultiTabPluginTest extends AbstractGhidraHeadedIntegrationTest { // by trial-and-error, we know that 'tms' is the last visible program tab // after resizing setFrameSize(500, 500); - assertTrue(!panel.isHidden(programs[2])); - assertTrue(panel.isHidden(programs[3])); + assertShowing(programs[2]); + assertHidden(programs[3]); // select the last visible tab selectTab(programs[2]); @@ -485,8 +452,8 @@ public class MultiTabPluginTest extends AbstractGhidraHeadedIntegrationTest { // by trial-and-error, we know that 'tms' is the last visible program tab // after resizing setFrameSize(500, 500); - assertTrue(!panel.isHidden(programs[2])); - assertTrue(panel.isHidden(programs[3])); + assertShowing(programs[2]); + assertHidden(programs[3]); // select 'tms', which is the last tab before the list is shown selectTab(programs[2]); @@ -521,6 +488,43 @@ public class MultiTabPluginTest extends AbstractGhidraHeadedIntegrationTest { assertEquals(selectedProgram, p); } + private void printResizeDebug() { + // + // To show the '>>' label, the number of tabs must exceed the room visible to show them + // + + // frame size + + // available width + int panelWidth = panel.getWidth(); + System.out.println("available width: " + panelWidth); + + // size label + int totalWidth = 0; + JComponent listLabel = (JComponent) getInstanceField("showHiddenListLabel", panel); + System.out.println("label width: " + listLabel.getWidth()); + totalWidth = listLabel.getWidth(); + + // size of each tab's panel + Map map = (Map) getInstanceField("linkedProgramMap", panel); + Collection values = map.values(); + for (Object object : values) { + JComponent c = (JComponent) object; + totalWidth += c.getWidth(); + System.out.println("\t" + c.getWidth()); + } + + System.out.println("Total width: " + totalWidth + " out of " + panelWidth); + } + + private void assertShowing(Program p) { + assertFalse(runSwing(() -> panel.isHidden(p))); + } + + private void assertHidden(Program p) { + assertTrue(runSwing(() -> panel.isHidden(p))); + } + private void assertListWindowHidden() { Window listWindow = getListWindow(); assertFalse(listWindow.isShowing()); @@ -544,6 +548,10 @@ public class MultiTabPluginTest extends AbstractGhidraHeadedIntegrationTest { return windowForComponent(listPanel); } + private Color getFieldPanelBackgroundColor(FieldPanel fp, BigInteger index) { + return runSwing(() -> fp.getBackgroundColor(index)); + } + private void performPreviousAction() throws Exception { MultiTabPlugin plugin = env.getPlugin(MultiTabPlugin.class); DockingAction goToPreviousProgramAction = diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/DeleteFunctionDefaultPlatesTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/DeleteFunctionDefaultPlatesScriptTest.java similarity index 89% rename from Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/DeleteFunctionDefaultPlatesTest.java rename to Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/DeleteFunctionDefaultPlatesScriptTest.java index 81b3e5f6d0..73d2eaf02c 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/DeleteFunctionDefaultPlatesTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/DeleteFunctionDefaultPlatesScriptTest.java @@ -37,12 +37,9 @@ import ghidra.program.util.ProgramSelection; import ghidra.test.*; /** - * Test for deleting default plate comments on a function. - * - * - * + * Test for deleting default plate comments on a function */ -public class DeleteFunctionDefaultPlatesTest extends AbstractGhidraHeadedIntegrationTest { +public class DeleteFunctionDefaultPlatesScriptTest extends AbstractGhidraHeadedIntegrationTest { private TestEnv env; private PluginTool tool; @@ -50,13 +47,6 @@ public class DeleteFunctionDefaultPlatesTest extends AbstractGhidraHeadedIntegra private File script; private ToyProgramBuilder builder; - public DeleteFunctionDefaultPlatesTest() { - super(); - } - - /* - * @see TestCase#setUp() - */ @Before public void setUp() throws Exception { env = new TestEnv(); @@ -71,11 +61,8 @@ public class DeleteFunctionDefaultPlatesTest extends AbstractGhidraHeadedIntegra ProgramManager pm = tool.getService(ProgramManager.class); pm.openProgram(program.getDomainFile()); - //script = - // new File(Application.getApplicationRootDirectory().getFile(), - // "Features/Base/ghidra_scripts/DeleteFunctionDefaultPlates.java"); script = Application.getModuleFile("Base", - "ghidra_scripts/DeleteFunctionDefaultPlates.java").getFile(true); + "ghidra_scripts/DeleteFunctionDefaultPlatesScript.java").getFile(true); env.showTool(); } @@ -97,9 +84,6 @@ public class DeleteFunctionDefaultPlatesTest extends AbstractGhidraHeadedIntegra return builder.getProgram(); } - /* - * @see TestCase#tearDown() - */ @After public void tearDown() throws Exception { env.dispose(); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/symboltree/SymbolTreePlugin1Test.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/symboltree/SymbolTreePlugin1Test.java index 1baee48aea..c267d9e113 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/symboltree/SymbolTreePlugin1Test.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/symboltree/SymbolTreePlugin1Test.java @@ -90,7 +90,7 @@ public class SymbolTreePlugin1Test extends AbstractGhidraHeadedIntegrationTest { tool.addPlugin(SymbolTreePlugin.class.getName()); plugin = env.getPlugin(SymbolTreePlugin.class); - symTreeAction = getAction(plugin, "Display Symbol Tree"); + symTreeAction = getAction(plugin, "Symbol Tree"); cbPlugin = env.getPlugin(CodeBrowserPlugin.class); util = new SymbolTreeTestUtils(plugin); @@ -107,15 +107,6 @@ public class SymbolTreePlugin1Test extends AbstractGhidraHeadedIntegrationTest { env.dispose(); } - @Test - public void testActionEnabled() { - assertTrue(symTreeAction.isEnabledForContext(null)); - - util.openProgram(); - - assertTrue(symTreeAction.isEnabledForContext(null)); - } - @Test public void testShowDisplay() throws Exception { showSymbolTree(); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/symboltree/SymbolTreeTestUtils.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/symboltree/SymbolTreeTestUtils.java index 0eaa3dd2d8..3d8dd8e9d2 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/symboltree/SymbolTreeTestUtils.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/symboltree/SymbolTreeTestUtils.java @@ -90,14 +90,14 @@ class SymbolTreeTestUtils { this.plugin = plugin; this.program = buildProgram(); - symTreeAction = getAction(plugin, "Display Symbol Tree"); + symTreeAction = getAction(plugin, "Symbol Tree"); } SymbolTreeTestUtils(SymbolTreePlugin plugin, Program program) { this.plugin = plugin; this.program = program; - symTreeAction = getAction(plugin, "Display Symbol Tree"); + symTreeAction = getAction(plugin, "Symbol Tree"); } public static Program buildProgram() throws Exception { diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/symtable/SymbolTablePluginTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/symtable/SymbolTablePluginTest.java index 785139cd12..08416bc884 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/symtable/SymbolTablePluginTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/symtable/SymbolTablePluginTest.java @@ -21,6 +21,7 @@ import java.awt.*; import java.awt.event.*; import java.util.ArrayList; import java.util.List; +import java.util.Set; import java.util.function.BiConsumer; import javax.swing.*; @@ -59,6 +60,7 @@ import ghidra.util.table.GhidraTableFilterPanel; import ghidra.util.table.ProgramTableModel; import ghidra.util.table.field.AddressBasedLocation; import ghidra.util.xml.XmlUtilities; +import util.CollectionUtils; public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest { @@ -93,8 +95,14 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest { plugin = env.getPlugin(SymbolTablePlugin.class); provider = (SymbolProvider) getInstanceField("symProvider", plugin); - viewSymAction = getAction(plugin, "View Symbol Table"); - viewRefAction = getAction(plugin, "View Symbol References"); + viewSymAction = getAction(plugin, "Symbol Table"); + + // this action is actually in the tool twice: once for the provider and once as a + // local action in the Symbol Table header, so we must pick one + Set symbolReferencesActions = + getActionsByOwnerAndName(tool, plugin.getName(), "Symbol References"); + viewRefAction = CollectionUtils.any(symbolReferencesActions); + deleteAction = getAction(plugin, "Delete Symbols"); makeSelectionAction = getAction(plugin, "Make Selection"); setFilterAction = getAction(plugin, "Set Filter"); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/main/datatree/ActionManager2Test.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/main/datatree/ActionManager2Test.java deleted file mode 100644 index 2a47bd67fe..0000000000 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/main/datatree/ActionManager2Test.java +++ /dev/null @@ -1,390 +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.framework.main.datatree; - -import static org.junit.Assert.*; - -import java.awt.Container; -import java.util.ArrayList; -import java.util.List; - -import javax.swing.*; -import javax.swing.tree.DefaultTreeCellEditor; -import javax.swing.tree.TreePath; - -import org.junit.*; - -import docking.ActionContext; -import docking.action.DockingActionIf; -import docking.action.ToggleDockingAction; -import docking.test.AbstractDockingTest; -import docking.widgets.OptionDialog; -import docking.widgets.tree.GTreeNode; -import docking.widgets.tree.GTreeRootNode; -import docking.widgets.tree.support.BreadthFirstIterator; -import ghidra.framework.data.DomainObjectAdapter; -import ghidra.framework.main.FrontEndTool; -import ghidra.framework.model.DomainFile; -import ghidra.framework.model.DomainFolder; -import ghidra.program.database.ProgramBuilder; -import ghidra.program.database.ProgramDB; -import ghidra.program.model.listing.Program; -import ghidra.test.AbstractGhidraHeadedIntegrationTest; -import ghidra.test.TestEnv; -import ghidra.util.task.TaskMonitorAdapter; -import resources.MultiIcon; -import resources.ResourceManager; - -/** - * - * Mores Tests for actions in the front end (Ghidra project window). - * - */ -public class ActionManager2Test extends AbstractGhidraHeadedIntegrationTest { - - private FrontEndTool frontEndTool; - private TestEnv env; - private DataTree tree; - private DomainFolder rootFolder; - private GTreeRootNode rootNode; - - @Before - public void setUp() throws Exception { - env = new TestEnv(); - - frontEndTool = env.getFrontEndTool(); - env.showFrontEndTool(); - tree = findComponent(frontEndTool.getToolFrame(), DataTree.class); - rootFolder = env.getProject().getProjectData().getRootFolder(); - - Program p = createDefaultProgram("p1", ProgramBuilder._TOY, this); - - rootFolder.createFile("notepad", p, TaskMonitorAdapter.DUMMY_MONITOR); - p.release(this); - - p = createDefaultProgram("p2", ProgramBuilder._TOY, this); - rootFolder.createFile("X07", p, TaskMonitorAdapter.DUMMY_MONITOR); - p.release(this); - - rootNode = tree.getRootNode(); - - expandPath(rootNode.getTreePath()); - } - - @After - public void tearDown() throws Exception { - env.dispose(); - } - - @Test - public void testRenameFolder() throws Exception { - rootFolder.createFolder("myFolder"); - waitForSwing(); - - final GTreeNode myNode = rootNode.getChild("myFolder"); - setSelectionPath(myNode.getTreePath()); - - DockingActionIf renameAction = getAction("Rename"); - performAction(renameAction, getDomainFileActionContext(), true); - waitForTree(); - - // select "Rename" action - SwingUtilities.invokeAndWait(() -> { - int row = tree.getRowForPath(myNode.getTreePath()); - JTree jTree = (JTree) getInstanceField("tree", tree); - DefaultTreeCellEditor cellEditor = (DefaultTreeCellEditor) tree.getCellEditor(); - Container container = (Container) cellEditor.getTreeCellEditorComponent(jTree, myNode, - true, true, false, row); - JTextField textField = (JTextField) container.getComponent(0); - - textField.setText("MyNewFolder"); - tree.stopEditing(); - }); - waitForSwing(); - assertNotNull(rootNode.getChild("MyNewFolder")); - assertNull(rootNode.getChild("myFolder")); - } - - @Test - public void testRenameFile() throws Exception { - final GTreeNode npNode = rootNode.getChild("notepad"); - setSelectionPath(npNode.getTreePath()); - - DockingActionIf renameAction = getAction("Rename"); - performAction(renameAction, getDomainFileActionContext(), true); - waitForTree(); - - // select "Rename" action - SwingUtilities.invokeAndWait(() -> { - int row = tree.getRowForPath(npNode.getTreePath()); - DefaultTreeCellEditor cellEditor = (DefaultTreeCellEditor) tree.getCellEditor(); - JTree jTree = (JTree) getInstanceField("tree", tree); - Container container = (Container) cellEditor.getTreeCellEditorComponent(jTree, npNode, - true, true, false, row); - JTextField textField = (JTextField) container.getComponent(0); - - textField.setText("My_notepad"); - tree.stopEditing(); - }); - waitForSwing(); - assertNotNull(rootNode.getChild("My_notepad")); - assertNull(rootNode.getChild("notepad")); - - } - - @Test - public void testRenameFileInUse() throws Exception { - final GTreeNode npNode = rootNode.getChild("notepad"); - DomainFile df = ((DomainFileNode) npNode).getDomainFile(); - - setInUse(df); - - setSelectionPath(npNode.getTreePath()); - - DockingActionIf renameAction = getAction("Rename"); - executeOnSwingWithoutBlocking( - () -> performAction(renameAction, getDomainFileActionContext(), true)); - waitForSwing(); - OptionDialog dlg = waitForDialogComponent(OptionDialog.class); - assertEquals("Rename Not Allowed", dlg.getTitle()); - pressButtonByText(dlg.getComponent(), "OK"); - assertNotNull(rootNode.getChild("notepad")); - } - - private void setInUse(DomainFile df) throws Exception { - setInUse(df, "/notepad"); - } - - private void setInUse(DomainFile df, final String path) throws Exception { - ProgramDB program = createDefaultProgram("test1", ProgramBuilder._TOY, this); - - // - // Unusual Code Alert! - // We are calling an internal method to trigger the 'in use' state, as it is much - // faster to do this than it is to open a program in a tool! - // - - //@formatter:off - Object projectFileManager = getInstanceField("fileManager", df); - invokeInstanceMethod("setDomainObject", projectFileManager, - new Class[] { String.class, DomainObjectAdapter.class }, - new Object[] { path, program } - ); - //@formatter:on - } - - @Test - public void testRenameFolderInUse() throws Exception { - // folder contains a file that is in use - DomainFolder f = rootFolder.createFolder("myFolder"); - f = f.createFolder("A"); - f = f.createFolder("B"); - f = f.createFolder("C"); - - Program p = createDefaultProgram("new", ProgramBuilder._TOY, this); - - DomainFile df = f.createFile("notepad", p, TaskMonitorAdapter.DUMMY_MONITOR); - waitForSwing(); - - final GTreeNode myNode = rootNode.getChild("myFolder"); - ((DomainFolderNode) myNode).getDomainFolder().createFile("notepad", p, - TaskMonitorAdapter.DUMMY_MONITOR); - p.release(this); - - waitForSwing(); - tree.expandPath(myNode.getTreePath()); - assertNotNull(myNode.getChild("notepad")); - - setInUse(df, "/myFolder/notepad"); - - setSelectionPath(myNode.getTreePath()); - - final DockingActionIf renameAction = getAction("Rename"); - performAction(renameAction, getDomainFileActionContext(), true); - waitForTree(); - - // attempt to rename "myFolder" - SwingUtilities.invokeLater(() -> { - int row = tree.getRowForPath(myNode.getTreePath()); - DefaultTreeCellEditor cellEditor = (DefaultTreeCellEditor) tree.getCellEditor(); - JTree jTree = (JTree) getInstanceField("tree", tree); - Container container = (Container) cellEditor.getTreeCellEditorComponent(jTree, myNode, - true, true, false, row); - JTextField textField = (JTextField) container.getComponent(0); - - textField.setText("My_Newfolder"); - tree.stopEditing(); - }); - - waitForSwing(); - - OptionDialog d = - waitForDialogComponent(frontEndTool.getToolFrame(), OptionDialog.class, 2000); - assertNotNull(d); - assertEquals("Rename Failed", d.getTitle()); - pressButtonByText(d.getComponent(), "OK"); - assertNotNull(rootNode.getChild("myFolder")); - } - - @Test - public void testExpandAll() throws Exception { - DomainFolder f = rootFolder.createFolder("myFolder"); - f = f.createFolder("A"); - f = f.createFolder("B"); - f = f.createFolder("C"); - waitForSwing(); - - GTreeNode myNode = rootNode.getChild("myFolder"); - setSelectionPath(rootNode.getTreePath()); - DockingActionIf expandAction = getAction("Expand All"); - performAction(expandAction, getDomainFileActionContext(), true); - GTreeNode aNode = myNode.getChild("A"); - assertNotNull(aNode); - GTreeNode bNode = aNode.getChild("B"); - assertNotNull(bNode); - GTreeNode cNode = bNode.getChild("C"); - assertNotNull(cNode); - } - - @Test - public void testCollapseAll() throws Exception { - DomainFolder f = rootFolder.createFolder("myFolder"); - f = f.createFolder("A"); - f = f.createFolder("B"); - f = f.createFolder("C"); - waitForSwing(); - - GTreeNode myNode = rootNode.getChild("myFolder"); - setSelectionPath(myNode.getTreePath()); - DockingActionIf expandAction = getAction("Expand All"); - performAction(expandAction, getDomainFileActionContext(), true); - waitForTree(); - - DockingActionIf collapseAction = getAction("Collapse All"); - performAction(collapseAction, getDomainFileActionContext(), true); - waitForTree(); - assertTrue(!tree.isExpanded(myNode.getTreePath())); - GTreeNode aNode = myNode.getChild("A"); - assertTrue(!tree.isExpanded(aNode.getTreePath())); - GTreeNode bNode = aNode.getChild("B"); - assertTrue(!tree.isExpanded(bNode.getTreePath())); - GTreeNode cNode = bNode.getChild("C"); - assertNotNull(cNode); - assertTrue(!tree.isExpanded(cNode.getTreePath())); - } - - @Test - public void testSelectAll() throws Exception { - DomainFolder f = rootFolder.createFolder("myFolder"); - f = f.createFolder("A"); - f = f.createFolder("B"); - f = f.createFolder("C"); - waitForSwing(); - - setSelectionPath(rootNode.getTreePath()); - DockingActionIf selectAction = getAction("Select All"); - performAction(selectAction, getDomainFileActionContext(), true); - waitForTree(); - - BreadthFirstIterator it = new BreadthFirstIterator(tree, rootNode); - while (it.hasNext()) { - GTreeNode node = it.next(); - assertTrue(tree.isPathSelected(node.getTreePath())); - } - } - - @Test - public void testSetReadOnly() throws Exception { - GTreeNode npNode = rootNode.getChild("notepad"); - setSelectionPath(npNode.getTreePath()); - ToggleDockingAction readOnlyAction = (ToggleDockingAction) getAction("Read-Only"); - readOnlyAction.setSelected(true); - performAction(readOnlyAction, getDomainFileActionContext(), true); - - assertTrue(((DomainFileNode) npNode).getDomainFile().isReadOnly()); - ImageIcon icon = ResourceManager.loadImage("fileIcons/ProgramReadOnly.gif"); - icon = ResourceManager.getScaledIcon(icon, 16, 16); - - assertTrue(npNode.getIcon(false) instanceof MultiIcon); - } - - @Test - public void testSetReadOnlyInUse() throws Exception { - GTreeNode npNode = rootNode.getChild("notepad"); - DomainFile df = ((DomainFileNode) npNode).getDomainFile(); - setInUse(df); - - setSelectionPath(npNode.getTreePath()); - ToggleDockingAction readOnlyAction = (ToggleDockingAction) getAction("Read-Only"); - readOnlyAction.setSelected(true); - performAction(readOnlyAction, getDomainFileActionContext(), true); - - assertTrue(((DomainFileNode) npNode).getDomainFile().isReadOnly()); - } - -//================================================================================================== -// Private Methods -//================================================================================================== - - private ActionContext getDomainFileActionContext() { - List fileList = new ArrayList<>(); - List folderList = new ArrayList<>(); - - TreePath[] paths = tree.getSelectionPaths(); - for (TreePath path : paths) { - - GTreeNode node = (GTreeNode) path.getLastPathComponent(); - if (node instanceof DomainFileNode) { - fileList.add(((DomainFileNode) node).getDomainFile()); - } - else if (node instanceof DomainFolderNode) { - folderList.add(((DomainFolderNode) node).getDomainFolder()); - } - } - - return new ProjectDataTreeActionContext(null, null, paths, folderList, fileList, tree, - true); - } - - private DockingActionIf getAction(String actionName) { - DockingActionIf action = - AbstractDockingTest.getAction(frontEndTool, "FrontEndPlugin", actionName); - return action; - } - - private void setSelectionPath(final TreePath path) throws Exception { - tree.setSelectionPath(path); - waitForTree(); - } - - private void expandPath(TreePath treePath) { - tree.expandPath(treePath); - waitForTree(); - } - - private void waitForTree() { - waitForSwing(); - while (tree.isBusy()) { - try { - Thread.sleep(10); - } - catch (InterruptedException e) { - // try again - } - } - waitForSwing(); - } -} diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/main/datatree/ActionManager1Test.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/main/datatree/FrontEndPluginActionsTest.java similarity index 71% rename from Ghidra/Features/Base/src/test.slow/java/ghidra/framework/main/datatree/ActionManager1Test.java rename to Ghidra/Features/Base/src/test.slow/java/ghidra/framework/main/datatree/FrontEndPluginActionsTest.java index b28418ace8..b8f442cb6f 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/main/datatree/ActionManager1Test.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/main/datatree/FrontEndPluginActionsTest.java @@ -30,12 +30,12 @@ import org.junit.*; import docking.ActionContext; import docking.action.DockingActionIf; +import docking.action.ToggleDockingAction; import docking.test.AbstractDockingTest; import docking.widgets.OptionDialog; import docking.widgets.tree.GTreeNode; import docking.widgets.tree.GTreeRootNode; -import docking.widgets.tree.support.GTreeDragNDropHandler; -import docking.widgets.tree.support.GTreeNodeTransferable; +import docking.widgets.tree.support.*; import ghidra.framework.data.DomainObjectAdapter; import ghidra.framework.main.FrontEndTool; import ghidra.framework.model.DomainFile; @@ -46,11 +46,13 @@ import ghidra.program.model.listing.Program; import ghidra.test.AbstractGhidraHeadedIntegrationTest; import ghidra.test.TestEnv; import ghidra.util.task.TaskMonitor; +import resources.MultiIcon; +import resources.ResourceManager; /** - * Tests for actions in the front end (Ghidra project window). + * Tests for actions in the front end (Ghidra project window) */ -public class ActionManager1Test extends AbstractGhidraHeadedIntegrationTest { +public class FrontEndPluginActionsTest extends AbstractGhidraHeadedIntegrationTest { private FrontEndTool frontEndTool; private TestEnv env; @@ -58,10 +60,6 @@ public class ActionManager1Test extends AbstractGhidraHeadedIntegrationTest { private DomainFolder rootFolder; private GTreeRootNode rootNode; - public ActionManager1Test() { - super(); - } - @Before public void setUp() throws Exception { @@ -533,10 +531,237 @@ public class ActionManager1Test extends AbstractGhidraHeadedIntegrationTest { assertNull(getChild(rootNode, "tms")); } + @Test + public void testRenameFolder() throws Exception { + rootFolder.createFolder("myFolder"); + waitForSwing(); + + final GTreeNode myNode = rootNode.getChild("myFolder"); + setSelectionPath(myNode.getTreePath()); + + DockingActionIf renameAction = getAction("Rename"); + performAction(renameAction, getDomainFileActionContext(), true); + waitForTree(); + + // select "Rename" action + SwingUtilities.invokeAndWait(() -> { + int row = tree.getRowForPath(myNode.getTreePath()); + JTree jTree = (JTree) getInstanceField("tree", tree); + DefaultTreeCellEditor cellEditor = (DefaultTreeCellEditor) tree.getCellEditor(); + Container container = (Container) cellEditor.getTreeCellEditorComponent(jTree, myNode, + true, true, false, row); + JTextField textField = (JTextField) container.getComponent(0); + + textField.setText("MyNewFolder"); + tree.stopEditing(); + }); + waitForSwing(); + assertNotNull(rootNode.getChild("MyNewFolder")); + assertNull(rootNode.getChild("myFolder")); + } + + @Test + public void testRenameFile() throws Exception { + final GTreeNode npNode = rootNode.getChild("notepad"); + setSelectionPath(npNode.getTreePath()); + + DockingActionIf renameAction = getAction("Rename"); + performAction(renameAction, getDomainFileActionContext(), true); + waitForTree(); + + // select "Rename" action + SwingUtilities.invokeAndWait(() -> { + int row = tree.getRowForPath(npNode.getTreePath()); + DefaultTreeCellEditor cellEditor = (DefaultTreeCellEditor) tree.getCellEditor(); + JTree jTree = (JTree) getInstanceField("tree", tree); + Container container = (Container) cellEditor.getTreeCellEditorComponent(jTree, npNode, + true, true, false, row); + JTextField textField = (JTextField) container.getComponent(0); + + textField.setText("My_notepad"); + tree.stopEditing(); + }); + waitForSwing(); + assertNotNull(rootNode.getChild("My_notepad")); + assertNull(rootNode.getChild("notepad")); + + } + + @Test + public void testRenameFileInUse() throws Exception { + final GTreeNode npNode = rootNode.getChild("notepad"); + DomainFile df = ((DomainFileNode) npNode).getDomainFile(); + + setInUse(df); + + setSelectionPath(npNode.getTreePath()); + + DockingActionIf renameAction = getAction("Rename"); + executeOnSwingWithoutBlocking( + () -> performAction(renameAction, getDomainFileActionContext(), true)); + waitForSwing(); + OptionDialog dlg = waitForDialogComponent(OptionDialog.class); + assertEquals("Rename Not Allowed", dlg.getTitle()); + pressButtonByText(dlg.getComponent(), "OK"); + assertNotNull(rootNode.getChild("notepad")); + } + + @Test + public void testRenameFolderInUse() throws Exception { + // folder contains a file that is in use + DomainFolder f = rootFolder.createFolder("myFolder"); + f = f.createFolder("A"); + f = f.createFolder("B"); + f = f.createFolder("C"); + + Program p = createDefaultProgram("new", ProgramBuilder._TOY, this); + + DomainFile df = f.createFile("notepad", p, TaskMonitor.DUMMY); + waitForSwing(); + + final GTreeNode myNode = rootNode.getChild("myFolder"); + ((DomainFolderNode) myNode).getDomainFolder().createFile("notepad", p, TaskMonitor.DUMMY); + p.release(this); + + waitForSwing(); + tree.expandPath(myNode.getTreePath()); + assertNotNull(myNode.getChild("notepad")); + + setInUse(df, "/myFolder/notepad"); + + setSelectionPath(myNode.getTreePath()); + + final DockingActionIf renameAction = getAction("Rename"); + performAction(renameAction, getDomainFileActionContext(), true); + waitForTree(); + + // attempt to rename "myFolder" + SwingUtilities.invokeLater(() -> { + int row = tree.getRowForPath(myNode.getTreePath()); + DefaultTreeCellEditor cellEditor = (DefaultTreeCellEditor) tree.getCellEditor(); + JTree jTree = (JTree) getInstanceField("tree", tree); + Container container = (Container) cellEditor.getTreeCellEditorComponent(jTree, myNode, + true, true, false, row); + JTextField textField = (JTextField) container.getComponent(0); + + textField.setText("My_Newfolder"); + tree.stopEditing(); + }); + + waitForSwing(); + + OptionDialog d = waitForDialogComponent(OptionDialog.class); + assertNotNull(d); + assertEquals("Rename Failed", d.getTitle()); + pressButtonByText(d.getComponent(), "OK"); + assertNotNull(rootNode.getChild("myFolder")); + } + + @Test + public void testExpandAll() throws Exception { + DomainFolder f = rootFolder.createFolder("myFolder"); + f = f.createFolder("A"); + f = f.createFolder("B"); + f = f.createFolder("C"); + waitForSwing(); + + GTreeNode myNode = rootNode.getChild("myFolder"); + setSelectionPath(rootNode.getTreePath()); + DockingActionIf expandAction = getAction("Expand All"); + performAction(expandAction, getDomainFileActionContext(), true); + GTreeNode aNode = myNode.getChild("A"); + assertNotNull(aNode); + GTreeNode bNode = aNode.getChild("B"); + assertNotNull(bNode); + GTreeNode cNode = bNode.getChild("C"); + assertNotNull(cNode); + } + + @Test + public void testCollapseAll() throws Exception { + DomainFolder f = rootFolder.createFolder("myFolder"); + f = f.createFolder("A"); + f = f.createFolder("B"); + f = f.createFolder("C"); + waitForSwing(); + + GTreeNode myNode = rootNode.getChild("myFolder"); + setSelectionPath(myNode.getTreePath()); + DockingActionIf expandAction = getAction("Expand All"); + performAction(expandAction, getDomainFileActionContext(), true); + waitForTree(); + + DockingActionIf collapseAction = getAction("Collapse All"); + performAction(collapseAction, getDomainFileActionContext(), true); + waitForTree(); + assertTrue(!tree.isExpanded(myNode.getTreePath())); + GTreeNode aNode = myNode.getChild("A"); + assertTrue(!tree.isExpanded(aNode.getTreePath())); + GTreeNode bNode = aNode.getChild("B"); + assertTrue(!tree.isExpanded(bNode.getTreePath())); + GTreeNode cNode = bNode.getChild("C"); + assertNotNull(cNode); + assertTrue(!tree.isExpanded(cNode.getTreePath())); + } + + @Test + public void testSelectAll() throws Exception { + DomainFolder f = rootFolder.createFolder("myFolder"); + f = f.createFolder("A"); + f = f.createFolder("B"); + f = f.createFolder("C"); + waitForSwing(); + + setSelectionPath(rootNode.getTreePath()); + DockingActionIf selectAction = getAction("Select All"); + performAction(selectAction, getDomainFileActionContext(), true); + waitForTree(); + + BreadthFirstIterator it = new BreadthFirstIterator(tree, rootNode); + while (it.hasNext()) { + GTreeNode node = it.next(); + assertTrue(tree.isPathSelected(node.getTreePath())); + } + } + + @Test + public void testSetReadOnly() throws Exception { + GTreeNode npNode = rootNode.getChild("notepad"); + setSelectionPath(npNode.getTreePath()); + ToggleDockingAction readOnlyAction = (ToggleDockingAction) getAction("Read-Only"); + readOnlyAction.setSelected(true); + performAction(readOnlyAction, getDomainFileActionContext(), true); + + assertTrue(((DomainFileNode) npNode).getDomainFile().isReadOnly()); + ImageIcon icon = ResourceManager.loadImage("fileIcons/ProgramReadOnly.gif"); + icon = ResourceManager.getScaledIcon(icon, 16, 16); + + assertTrue(npNode.getIcon(false) instanceof MultiIcon); + } + + @Test + public void testSetReadOnlyInUse() throws Exception { + GTreeNode npNode = rootNode.getChild("notepad"); + DomainFile df = ((DomainFileNode) npNode).getDomainFile(); + setInUse(df); + + setSelectionPath(npNode.getTreePath()); + ToggleDockingAction readOnlyAction = (ToggleDockingAction) getAction("Read-Only"); + readOnlyAction.setSelected(true); + performAction(readOnlyAction, getDomainFileActionContext(), true); + + assertTrue(((DomainFileNode) npNode).getDomainFile().isReadOnly()); + } + //================================================================================================== // Private Methods //================================================================================================== + private void setSelectionPath(final TreePath path) throws Exception { + tree.setSelectionPath(path); + waitForTree(); + } + private void pressDelete() { DockingActionIf deleteAction = getAction("Delete"); performAction(deleteAction, getDomainFileActionContext(), false); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/plugintool/dialog/KeyBindingUtilsTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/plugintool/dialog/KeyBindingUtilsTest.java index a8e580c61d..320f3b2e26 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/plugintool/dialog/KeyBindingUtilsTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/plugintool/dialog/KeyBindingUtilsTest.java @@ -408,15 +408,13 @@ public class KeyBindingUtilsTest extends AbstractGhidraHeadedIntegrationTest { Set list = tool.getAllActions(); DockingActionIf arbitraryAction = null; for (DockingActionIf action : list) { - if (action.isKeyBindingManaged() && action.getKeyBinding() == null) { + if (action.getKeyBindingType().isManaged() && action.getKeyBinding() == null) { arbitraryAction = action; break; } } - if (arbitraryAction == null) { - Assert.fail("Unable to find an action for which to set a key binding."); - } + assertNotNull("Unable to find an action for which to set a key binding", arbitraryAction); selectRowForAction(arbitraryAction); triggerText(keyField, keyText); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/plugintool/dialog/KeyBindingsTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/plugintool/dialog/KeyBindingsTest.java index 480722bdb5..cfb9912bc8 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/plugintool/dialog/KeyBindingsTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/framework/plugintool/dialog/KeyBindingsTest.java @@ -104,7 +104,7 @@ public class KeyBindingsTest extends AbstractGhidraHeadedIntegrationTest { public void testManagedKeyBindings() { Set list = tool.getAllActions(); for (DockingActionIf action : list) { - if (action.isKeyBindingManaged()) { + if (action.getKeyBindingType().isManaged()) { assertTrue(actionInTable(action)); } } @@ -130,7 +130,7 @@ public class KeyBindingsTest extends AbstractGhidraHeadedIntegrationTest { Set list = tool.getAllActions(); for (DockingActionIf action : list) { KeyStroke ks = getKeyStroke(action); - if (isKeyBindingManaged(action) && ks != KeyStroke.getKeyStroke(KeyEvent.VK_Z, 0)) { + if (supportsKeyBindings(action) && ks != KeyStroke.getKeyStroke(KeyEvent.VK_Z, 0)) { break; } } @@ -310,15 +310,15 @@ public class KeyBindingsTest extends AbstractGhidraHeadedIntegrationTest { waitForSwing(); } - private boolean isKeyBindingManaged(DockingActionIf action) { - return action.isKeyBindingManaged(); + private boolean supportsKeyBindings(DockingActionIf action) { + return action.getKeyBindingType().isManaged(); } private DockingActionIf getKeyBindingPluginAction() { Set list = tool.getAllActions(); for (DockingActionIf action : list) { KeyStroke ks = action.getKeyBinding(); - if (action.isKeyBindingManaged() && ks != null && + if (action.getKeyBindingType().isManaged() && ks != null && ks != KeyStroke.getKeyStroke(KeyEvent.VK_Z, 0)) { return action; } @@ -388,7 +388,7 @@ public class KeyBindingsTest extends AbstractGhidraHeadedIntegrationTest { private void grabActionsWithoutKeybinding() { Set list = tool.getAllActions(); for (DockingActionIf action : list) { - if (!action.isKeyBindingManaged()) { + if (!action.getKeyBindingType().isManaged()) { continue; } if (action.getKeyBinding() != null) { diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/util/bean/opteditor/OptionsDialogTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/util/bean/opteditor/OptionsDialogTest.java index 9bc6c20a0a..b606f822da 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/util/bean/opteditor/OptionsDialogTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/util/bean/opteditor/OptionsDialogTest.java @@ -33,8 +33,8 @@ import javax.swing.tree.TreePath; import org.apache.commons.lang3.StringUtils; import org.junit.*; -import docking.DockingKeyBindingAction; import docking.action.DockingActionIf; +import docking.actions.KeyBindingUtils; import docking.options.editor.*; import docking.widgets.MultiLineLabel; import docking.widgets.filechooser.GhidraFileChooser; @@ -712,7 +712,7 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest { if (StringUtils.isBlank(keyBindingColumnValue)) { return null; } - return DockingKeyBindingAction.parseKeyStroke(keyBindingColumnValue); + return KeyBindingUtils.parseKeyStroke(keyBindingColumnValue); } private void assertOptionsKeyStroke(String actionName, String pluginName, KeyStroke value) diff --git a/Ghidra/Features/Base/src/test/java/ghidra/test/DummyTool.java b/Ghidra/Features/Base/src/test/java/ghidra/test/DummyTool.java index 20b92f2c59..3f01e155f2 100644 --- a/Ghidra/Features/Base/src/test/java/ghidra/test/DummyTool.java +++ b/Ghidra/Features/Base/src/test/java/ghidra/test/DummyTool.java @@ -28,6 +28,7 @@ import org.jdom.Element; import docking.*; import docking.action.DockingActionIf; +import docking.actions.DockingToolActions; import ghidra.framework.model.*; import ghidra.framework.options.ToolOptions; import ghidra.framework.plugintool.PluginEvent; @@ -42,6 +43,8 @@ public class DummyTool implements Tool { private ToolIconURL iconURL; private Project project; + private DockingToolActions toolActions = new DummyToolActions(); + public DummyTool() { this(DEFAULT_NAME); } @@ -55,30 +58,16 @@ public class DummyTool implements Tool { this.project = project; } - /** - * Sets the type name of the tool. - * @exception PropertyVetoException thrown if a VetoableChangeListener - * rejects the change - */ @Override public void setToolName(String typeName) throws PropertyVetoException { name = typeName; } - /** - * Tells the tool to stop functioning and release its resources. - * Called by Session when it wants to dispose of a tool. The tool - * MUST NOT call System.exit() in response to this method call. Instead - * the tool should dispose of all its windows and other resources. - */ @Override public void exit() { //do nothing } - /* (non-Javadoc) - * @see ghidra.framework.model.Tool#close() - */ @Override public void close() { if (project != null) { @@ -92,37 +81,21 @@ public class DummyTool implements Tool { return true; } - /** - * Associates a unique(within a session) name to a tool instance. - */ @Override public void putInstanceName(String newInstanceName) { this.instanceName = newInstanceName; } - /** - * Returns the name associated with the tool's type. - */ @Override public String getToolName() { return name; } - /** - * Sets the tool visible or invisible. This method is used by - * the Session to make it's tools visible or invisible depending - * on whether or not the session this tool is in is the current Session. - * - * @param visibility true specifies that the tool should be visible. - */ @Override public void setVisible(boolean visibility) { //do nothing } - /** - * @see ghidra.framework.model.Tool#isVisible() - */ @Override public boolean isVisible() { return false; @@ -133,238 +106,129 @@ public class DummyTool implements Tool { //do nothing } - /** - * returns a combination of the type name and the instance name of the - * form typename(instancename) - */ @Override public String getName() { return name + instanceName; } - /** - * Returns a list of eventNames that this Tool is interested in. - */ @Override public String[] getConsumedToolEventNames() { return new String[] { "DummyToolEvent" }; } - /** - * Returns the tool's unique name. - */ @Override public String getInstanceName() { return instanceName; } - /** - * Adds a ToolListener to be notified only for a specific ToolEvent. - * - * @param listener The ToolListener to be added. - * @param toolEvent The name of the desired event. - */ public void addToolListener(ToolListener listener, String toolEvent) { //do nothing } - /** - * Adds a ToolListener to be notified only for a specific ToolEvent. - * - * @param listener The ToolListener to be added. - * @param toolEvent The name of the desired event. - */ @Override public void addToolListener(ToolListener listener) { //do nothing } - /** - * Returns the names of all the possible ToolEvents that this - * tool might generate. Used by the ConnectionManager to connect - * tools together. - */ @Override public String[] getToolEventNames() { return new String[] { "DummyToolEvent" }; } - /** - * Removes a ToolListener from receiving the specific event. - * - * @param listener The ToolListener to be removed. - * @param toolEvent The name of the event that no longer is of interest. - */ public void removeToolListener(ToolListener listener, String toolEvent) { //do nothing } - /** - * Removes a ToolListener from receiving the specific event. - * - * @param listener The ToolListener to be removed. - * @param toolEvent The name of the event that no longer is of interest. - */ @Override public void removeToolListener(ToolListener listener) { //do nothing } - /** - * Check whether this tool has changed its configuration. - * This is called to check if a tool needs to save its state, when the - * tool exits or the session the tool is in closes. - * - * @return true if the tool's configuration has changed, false otherwise - */ @Override public boolean hasConfigChanged() { return false; } - /** - * Add a change listener that is notified when a tool changes its state. - */ public void addChangeListener(ChangeListener l) { //do nothing } - /** - * Add property change listener. - */ @Override public void addPropertyChangeListener(PropertyChangeListener l) { //do nothing } - /** - * Get the classes of the data types that this tool supports, - * i.e., what data types can be dropped onto this tool. - */ @Override public Class[] getSupportedDataTypes() { return new Class[] { Program.class }; } - /** - * When the user drags a data file onto a tool, an event will be fired - * that the tool will respond to by accepting the data. - * - * @param data the data to be used by the running tool - */ @Override public boolean acceptDomainFiles(DomainFile[] data) { return true; } - /** - * Get the domain files that this tool currently has open. - */ @Override public DomainFile[] getDomainFiles() { return null; } - /** - * Remove the change listener. - */ public void removeChangeListener(ChangeListener l) { //do nothing } - /** - * Tells tool to write its config state from the given output stream. - */ @Override public void setConfigChanged(boolean changed) { //do nothing } - /** - * Tells tool to write its config state from the given output stream. - */ @Override public Element saveToXml(boolean includeConfigState) { return null; } - /** - * Tells tool to write its config state from the given output stream. - */ @Override public Element saveDataStateToXml(boolean isTransactionState) { return null; } - /** - * Tells tool to write its config state from the given output stream. - */ public void restoreFromXml(Element root) { //do nothing } - /** - * Tells tool to write its config state from the given output stream. - */ @Override public void restoreDataStateFromXml(Element root) { //do nothing } - /** - * Remove property change listener. - */ @Override public void removePropertyChangeListener(PropertyChangeListener l) { //do nothing } - /** - * This method is invoked when the registered ToolEvent event occurs. - * - * @param toolEvent The ToolEvent. - */ @Override public void processToolEvent(PluginEvent toolEvent) { //do nothing } - /** - * Fire the plugin event by notifying the event manager which - * calls the listeners. - */ @Override public void firePluginEvent(PluginEvent event) { //do nothing } - /** - * Get the description of the tool. - */ public String getDescription() { return description; } - /** - * Set the description of the tool. - */ public void setDescription(String description) { this.description = description; } - /** - * Set the icon for this tool configuration. - */ @Override public void setIconURL(ToolIconURL iconURL) { this.iconURL = iconURL; } - /** - * Get the icon for this tool configuration. - * - * @return Icon - */ @Override public ToolIconURL getIconURL() { return iconURL; @@ -380,9 +244,6 @@ public class DummyTool implements Tool { return getToolTemplate(true); } - /** - * @see ghidra.framework.model.Tool#enableClose() - */ public void enableClose() { //do nothing } @@ -447,6 +308,11 @@ public class DummyTool implements Tool { return Collections.emptySet(); } + @Override + public ComponentProvider getActiveComponentProvider() { + return null; + } + @Override public void showComponentProvider(ComponentProvider componentProvider, boolean visible) { //do nothing @@ -487,6 +353,11 @@ public class DummyTool implements Tool { //do nothing } + @Override + public ActionContext getGlobalContext() { + return null; + } + @Override public void setStatusInfo(String text) { //do nothing @@ -521,4 +392,19 @@ public class DummyTool implements Tool { public ToolOptions getOptions(String categoryName) { return null; } + + @Override + public void addContextListener(DockingContextListener listener) { + //do nothing + } + + @Override + public void removeContextListener(DockingContextListener listener) { + //do nothing + } + + @Override + public DockingToolActions getToolActions() { + return toolActions; + } } diff --git a/Ghidra/Features/Base/src/test/java/ghidra/test/DummyToolActions.java b/Ghidra/Features/Base/src/test/java/ghidra/test/DummyToolActions.java new file mode 100644 index 0000000000..3b4b216106 --- /dev/null +++ b/Ghidra/Features/Base/src/test/java/ghidra/test/DummyToolActions.java @@ -0,0 +1,70 @@ +/* ### + * 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.test; + +import java.util.Set; + +import docking.ComponentProvider; +import docking.action.DockingActionIf; +import docking.actions.DockingToolActions; + +public class DummyToolActions implements DockingToolActions { + + @Override + public void addLocalAction(ComponentProvider provider, DockingActionIf action) { + // stub + } + + @Override + public void addGlobalAction(DockingActionIf action) { + // stub + } + + @Override + public void removeGlobalAction(DockingActionIf action) { + // stub + } + + @Override + public void removeActions(String owner) { + // stub + } + + @Override + public DockingActionIf getLocalAction(ComponentProvider provider, String actionName) { + return null; + } + + @Override + public Set getActions(String owner) { + return null; + } + + @Override + public Set getAllActions() { + return null; + } + + @Override + public void removeLocalAction(ComponentProvider provider, DockingActionIf action) { + // stub + } + + @Override + public void removeActions(ComponentProvider provider) { + // stub + } +} diff --git a/Ghidra/Features/Base/src/test/resources/defaultTools/TestCodeBrowser.tool b/Ghidra/Features/Base/src/test/resources/defaultTools/TestCodeBrowser.tool index 48608084a9..ece29faf92 100644 --- a/Ghidra/Features/Base/src/test/resources/defaultTools/TestCodeBrowser.tool +++ b/Ghidra/Features/Base/src/test/resources/defaultTools/TestCodeBrowser.tool @@ -26,7 +26,7 @@ - + diff --git a/Ghidra/Features/BytePatterns/src/main/java/ghidra/bitpatterns/gui/ByteSequenceAnalyzerProvider.java b/Ghidra/Features/BytePatterns/src/main/java/ghidra/bitpatterns/gui/ByteSequenceAnalyzerProvider.java index fa824de809..ce17de6f98 100644 --- a/Ghidra/Features/BytePatterns/src/main/java/ghidra/bitpatterns/gui/ByteSequenceAnalyzerProvider.java +++ b/Ghidra/Features/BytePatterns/src/main/java/ghidra/bitpatterns/gui/ByteSequenceAnalyzerProvider.java @@ -123,38 +123,36 @@ public abstract class ByteSequenceAnalyzerProvider extends DialogComponentProvid } private void addSendSelectedToClipboardAction() { - sendSelectedToClipboardAction = - new DockingAction("Send Selected to Clipboard", title, false) { - @Override - public void actionPerformed(ActionContext context) { - List rows = byteSequenceTable.getLastSelectedObjects(); - for (ByteSequenceRowObject row : rows) { - DittedBitSequence seq = new DittedBitSequence(row.getSequence(), true); - PatternInfoRowObject pattern = - new PatternInfoRowObject(type, seq, cRegFilter); - pattern.setNote(row.getDisassembly()); - plugin.addPattern(pattern); - } - plugin.updateClipboard(); + sendSelectedToClipboardAction = new DockingAction("Send Selected to Clipboard", title) { + @Override + public void actionPerformed(ActionContext context) { + List rows = byteSequenceTable.getLastSelectedObjects(); + for (ByteSequenceRowObject row : rows) { + DittedBitSequence seq = new DittedBitSequence(row.getSequence(), true); + PatternInfoRowObject pattern = new PatternInfoRowObject(type, seq, cRegFilter); + pattern.setNote(row.getDisassembly()); + plugin.addPattern(pattern); } + plugin.updateClipboard(); + } - @Override - public boolean isEnabledForContext(ActionContext context) { - List rows = byteSequenceTable.getLastSelectedObjects(); - if (rows == null) { - return false; - } - if (rows.isEmpty()) { - return false; - } - return true; + @Override + public boolean isEnabledForContext(ActionContext context) { + List rows = byteSequenceTable.getLastSelectedObjects(); + if (rows == null) { + return false; } + if (rows.isEmpty()) { + return false; + } + return true; + } - @Override - public boolean isAddToPopup(ActionContext context) { - return true; - } - }; + @Override + public boolean isAddToPopup(ActionContext context) { + return true; + } + }; ImageIcon icon = ResourceManager.loadImage("images/2rightarrow.png"); sendSelectedToClipboardAction.setPopupMenuData( @@ -168,7 +166,7 @@ public abstract class ByteSequenceAnalyzerProvider extends DialogComponentProvid } private void addMergeAction() { - mergeAction = new DockingAction("Merge Selected Rows", title, false) { + mergeAction = new DockingAction("Merge Selected Rows", title) { @Override public void actionPerformed(ActionContext context) { merged = byteSequenceTable.mergeSelectedRows(); @@ -203,7 +201,7 @@ public abstract class ByteSequenceAnalyzerProvider extends DialogComponentProvid } private void addSendMergedToClipboardAction() { - sendMergedToClipboardAction = new DockingAction("Send Merged to Clipboard", title, false) { + sendMergedToClipboardAction = new DockingAction("Send Merged to Clipboard", title) { @Override public void actionPerformed(ActionContext context) { if (merged != null) { diff --git a/Ghidra/Features/BytePatterns/src/main/java/ghidra/bitpatterns/gui/ClosedPatternTableDialog.java b/Ghidra/Features/BytePatterns/src/main/java/ghidra/bitpatterns/gui/ClosedPatternTableDialog.java index 963314823c..fdfe49dd68 100644 --- a/Ghidra/Features/BytePatterns/src/main/java/ghidra/bitpatterns/gui/ClosedPatternTableDialog.java +++ b/Ghidra/Features/BytePatterns/src/main/java/ghidra/bitpatterns/gui/ClosedPatternTableDialog.java @@ -78,7 +78,7 @@ public class ClosedPatternTableDialog extends DialogComponentProvider { private JPanel createMainPanel() { JPanel panel = new JPanel(new BorderLayout()); GThreadedTablePanel table = - new GThreadedTablePanel(closedPatternTableModel); + new GThreadedTablePanel<>(closedPatternTableModel); panel.add(table, BorderLayout.CENTER); return panel; } @@ -89,33 +89,31 @@ public class ClosedPatternTableDialog extends DialogComponentProvider { } private void addClipboardAction() { - sendToClipboardAction = - new DockingAction("Send Selected Sequences to Clipboard", TITLE, false) { + sendToClipboardAction = new DockingAction("Send Selected Sequences to Clipboard", TITLE) { - @Override - public void actionPerformed(ActionContext context) { - List rows = - closedPatternTableModel.getLastSelectedObjects(); - for (ClosedPatternRowObject row : rows) { - DittedBitSequence seq = new DittedBitSequence(row.getDittedString(), true); - PatternInfoRowObject pattern = - new PatternInfoRowObject(type, seq, cRegFilter); - plugin.addPattern(pattern); - } - plugin.updateClipboard(); + @Override + public void actionPerformed(ActionContext context) { + List rows = + closedPatternTableModel.getLastSelectedObjects(); + for (ClosedPatternRowObject row : rows) { + DittedBitSequence seq = new DittedBitSequence(row.getDittedString(), true); + PatternInfoRowObject pattern = new PatternInfoRowObject(type, seq, cRegFilter); + plugin.addPattern(pattern); } + plugin.updateClipboard(); + } - @Override - public boolean isAddToPopup(ActionContext context) { - return true; - } + @Override + public boolean isAddToPopup(ActionContext context) { + return true; + } - @Override - public boolean isEnabledForContext(ActionContext context) { - return (!closedPatternTableModel.getLastSelectedObjects().isEmpty()); - } + @Override + public boolean isEnabledForContext(ActionContext context) { + return (!closedPatternTableModel.getLastSelectedObjects().isEmpty()); + } - }; + }; ImageIcon icon = ResourceManager.loadImage("images/2rightarrow.png"); sendToClipboardAction.setPopupMenuData( new MenuData(new String[] { "Send Selected Sequences to Clipboard" }, icon)); diff --git a/Ghidra/Features/BytePatterns/src/main/java/ghidra/bitpatterns/gui/PatternMiningAnalyzerProvider.java b/Ghidra/Features/BytePatterns/src/main/java/ghidra/bitpatterns/gui/PatternMiningAnalyzerProvider.java index c70212df1d..a98e544721 100644 --- a/Ghidra/Features/BytePatterns/src/main/java/ghidra/bitpatterns/gui/PatternMiningAnalyzerProvider.java +++ b/Ghidra/Features/BytePatterns/src/main/java/ghidra/bitpatterns/gui/PatternMiningAnalyzerProvider.java @@ -62,7 +62,7 @@ public class PatternMiningAnalyzerProvider extends ByteSequenceAnalyzerProvider } private void addMiningAction() { - mineClosedPatternsAction = new DockingAction(MINE_PATTERNS_BUTTON_TEXT, title, false) { + mineClosedPatternsAction = new DockingAction(MINE_PATTERNS_BUTTON_TEXT, title) { @Override public void actionPerformed(ActionContext context) { List lastSelectedObjects = diff --git a/Ghidra/Features/ByteViewer/src/main/help/help/topics/ByteViewerPlugin/The_Byte_Viewer.htm b/Ghidra/Features/ByteViewer/src/main/help/help/topics/ByteViewerPlugin/The_Byte_Viewer.htm index 61866121be..c325e89c3a 100644 --- a/Ghidra/Features/ByteViewer/src/main/help/help/topics/ByteViewerPlugin/The_Byte_Viewer.htm +++ b/Ghidra/Features/ByteViewer/src/main/help/help/topics/ByteViewerPlugin/The_Byte_Viewer.htm @@ -8,7 +8,7 @@ -

The Byte Viewer

+

The Byte Viewer

The Byte Viewer displays bytes in memory in various formats, e.g., Hex, Ascii, Octal, etc. The figure below shows the Byte Viewer plugin in a separate window from the diff --git a/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteViewerPlugin.java b/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteViewerPlugin.java index b68d8d30c6..57cf9009a3 100644 --- a/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteViewerPlugin.java +++ b/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteViewerPlugin.java @@ -19,9 +19,6 @@ import java.util.*; import org.jdom.Element; -import docking.ActionContext; -import docking.action.DockingAction; -import docking.action.ToolBarData; import ghidra.app.CorePluginPackage; import ghidra.app.events.*; import ghidra.app.plugin.PluginCategoryNames; @@ -35,7 +32,6 @@ import ghidra.program.model.listing.Program; import ghidra.program.util.ProgramLocation; import ghidra.program.util.ProgramSelection; import ghidra.util.SystemUtilities; -import resources.ResourceManager; import utility.function.Callback; /** @@ -73,23 +69,6 @@ public class ByteViewerPlugin extends Plugin { super(tool); connectedProvider = new ProgramByteViewerComponentProvider(tool, this, true); - - createActions(); - } - - private void createActions() { - DockingAction action = new DockingAction("Byte Viewer", getName()) { - @Override - public void actionPerformed(ActionContext context) { - showConnectedProvider(); - } - }; - action.setToolBarData( - new ToolBarData(ResourceManager.loadImage("images/binaryData.gif"), "View")); - - action.setDescription("Display Bytes"); - action.setEnabled(true); - tool.addAction(action); } protected void showConnectedProvider() { 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 83ea31b888..d6035251ef 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,17 @@ public class ProgramByteViewerComponentProvider extends ByteViewerComponentProvi public ProgramByteViewerComponentProvider(PluginTool tool, ByteViewerPlugin plugin, boolean isConnected) { super(tool, plugin, "Bytes", ByteViewerActionContext.class); - this.isConnected = isConnected; - decorationComponent = new DecoratorPanel(panel, isConnected); + this.isConnected = isConnected; + setIcon(ResourceManager.loadImage("images/binaryData.gif")); + if (!isConnected) { + setTransient(); + } + else { + addToToolbar(); + } + + decorationComponent = new DecoratorPanel(panel, isConnected); clipboardProvider = new ByteViewerClipboardProvider(this, tool); addToTool(); @@ -86,6 +94,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; @@ -130,11 +144,6 @@ public class ProgramByteViewerComponentProvider extends ByteViewerComponentProvi plugin.closeProvider(this); } - @Override - public boolean isTransient() { - return false; - } - @Override public void setSelection(ProgramSelection selection) { setSelection(selection, true); @@ -503,7 +512,8 @@ public class ProgramByteViewerComponentProvider extends ByteViewerComponentProvi } /** - * Gets the text of the current {@link ProgramSelection}. + * Gets the text of the current {@link ProgramSelection} + * @return the text */ String getCurrentTextSelection() { return panel.getCurrentComponent().getTextForSelection(); 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/decompiler/component/ClangLayoutController.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/ClangLayoutController.java index 57bbafe391..e88a9c4570 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/ClangLayoutController.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/ClangLayoutController.java @@ -24,6 +24,8 @@ import java.util.regex.*; import javax.swing.JComponent; +import org.apache.commons.lang3.StringUtils; + import docking.widgets.SearchLocation; import docking.widgets.fieldpanel.Layout; import docking.widgets.fieldpanel.LayoutModel; @@ -37,7 +39,6 @@ import ghidra.program.model.listing.Function; import ghidra.program.model.listing.Program; import ghidra.program.model.pcode.HighFunction; import ghidra.util.Msg; -import ghidra.util.StringUtilities; /** * @@ -522,7 +523,7 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener { java.util.function.Function function = textLine -> { - int index = StringUtilities.indexOfIgnoreCase(textLine, searchString); + int index = StringUtils.indexOfIgnoreCase(textLine, searchString); if (index == -1) { return SearchMatch.NO_MATCH; } @@ -534,7 +535,7 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener { java.util.function.Function function = textLine -> { - int index = StringUtilities.lastIndexOfIgnoreCase(textLine, searchString); + int index = StringUtils.lastIndexOfIgnoreCase(textLine, searchString); if (index == -1) { return SearchMatch.NO_MATCH; } diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilePlugin.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilePlugin.java index 8939c16d59..9c063f51a3 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilePlugin.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilePlugin.java @@ -15,14 +15,10 @@ */ package ghidra.app.plugin.core.decompile; -import java.awt.event.InputEvent; -import java.awt.event.KeyEvent; import java.util.*; import org.jdom.Element; -import docking.ActionContext; -import docking.action.*; import ghidra.app.CorePluginPackage; import ghidra.app.decompiler.component.DecompilerHighlightService; import ghidra.app.decompiler.component.hover.DecompilerHoverService; @@ -37,9 +33,7 @@ import ghidra.program.model.address.Address; import ghidra.program.model.listing.*; import ghidra.program.util.ProgramLocation; import ghidra.program.util.ProgramSelection; -import ghidra.util.HelpLocation; import ghidra.util.task.SwingUpdateManager; -import resources.ResourceManager; /** * Plugin for producing a high-level C interpretation of assembly functions. @@ -72,8 +66,6 @@ public class DecompilePlugin extends Plugin { private ProgramLocation currentLocation; private ProgramSelection currentSelection; - private DockingAction decompileAction; - /** * Delay location changes to allow location events to settle down. * This happens when a readDataState occurs when a tool is restored @@ -92,8 +84,6 @@ public class DecompilePlugin extends Plugin { disconnectedProviders = new ArrayList<>(); connectedProvider = new PrimaryDecompilerProvider(this); - createActions(); - registerServices(); } @@ -112,22 +102,6 @@ public class DecompilePlugin extends Plugin { } } - private void createActions() { - decompileAction = new DockingAction("Display Decompiler", getName()) { - @Override - public void actionPerformed(ActionContext context) { - showProvider(); - } - }; - decompileAction.setToolBarData( - new ToolBarData(ResourceManager.loadImage("images/decompileFunction.gif"), "View")); - decompileAction.setKeyBindingData( - new KeyBindingData(KeyEvent.VK_E, InputEvent.CTRL_DOWN_MASK)); - - decompileAction.setHelpLocation(new HelpLocation(getName(), "Decompiler")); - tool.addAction(decompileAction); - } - /** * Tells the Plugin to write any data-dependent state to the * output stream. @@ -184,10 +158,6 @@ public class DecompilePlugin extends Plugin { } } - private void showProvider() { - connectedProvider.setVisible(true); - } - DecompilerProvider createNewDisconnectedProvider() { DecompilerProvider decompilerProvider = new DecompilerProvider(this, false); decompilerProvider.setClipboardService(tool.getService(ClipboardService.class)); 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 8f048c2fb9..81b3d15fb9 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerProvider.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerProvider.java @@ -15,6 +15,7 @@ */ package ghidra.app.plugin.core.decompile; +import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; import java.math.BigInteger; import java.util.Arrays; @@ -22,8 +23,7 @@ import java.util.List; import javax.swing.*; -import docking.ActionContext; -import docking.WindowPosition; +import docking.*; import docking.action.*; import docking.widgets.fieldpanel.LayoutModel; import docking.widgets.fieldpanel.support.FieldLocation; @@ -124,8 +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.clipboardProvider = new DecompilerClipboardProvider(plugin, this); + setConnected(isConnected); decompilerOptions = new DecompileOptions(); @@ -135,8 +137,19 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter DecompilerPanel decompilerPanel = controller.getDecompilerPanel(); decompilerPanel.setHighlightController(highlightController); decorationPanel = new DecoratorPanel(decompilerPanel, isConnected); - setTitle("Decompile"); + + if (!isConnected) { + setTransient(); + } + else { + addToToolbar(); + setKeyBinding( + new KeyBindingData(KeyEvent.VK_E, DockingUtils.CONTROL_KEY_MODIFIER_MASK)); + } + setIcon(C_SOURCE_ICON); + setTitle("Decompile"); + setWindowMenuGroup("Decompile"); setDefaultWindowPosition(WindowPosition.RIGHT); createActions(isConnected); @@ -151,6 +164,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/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/SelectAllAction.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/SelectAllAction.java index e82fa896f9..a98865d569 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/SelectAllAction.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/SelectAllAction.java @@ -1,6 +1,5 @@ /* ### * 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. @@ -16,36 +15,33 @@ */ package ghidra.app.plugin.core.decompile.actions; -import ghidra.app.decompiler.component.DecompilerPanel; -import ghidra.app.util.HelpTopics; -import ghidra.util.HelpLocation; - import java.awt.event.InputEvent; import java.awt.event.KeyEvent; import docking.ActionContext; import docking.action.DockingAction; import docking.action.KeyBindingData; +import ghidra.app.decompiler.component.DecompilerPanel; +import ghidra.app.util.HelpTopics; +import ghidra.util.HelpLocation; /** * Action for adding all fields to the current format. */ public class SelectAllAction extends DockingAction { DecompilerPanel panel; - - public SelectAllAction(String owner, DecompilerPanel panel) { - super("Select All", owner, false); - this.panel = panel; - setKeyBindingData( new KeyBindingData( - KeyEvent.VK_A, InputEvent.CTRL_DOWN_MASK) ); + + public SelectAllAction(String owner, DecompilerPanel panel) { + super("Select All", owner); + this.panel = panel; + setKeyBindingData(new KeyBindingData(KeyEvent.VK_A, InputEvent.CTRL_DOWN_MASK)); setHelpLocation(new HelpLocation(HelpTopics.SELECTION, getName())); - } + } - @Override - public void actionPerformed(ActionContext context) { - panel.selectAll(); + @Override + public void actionPerformed(ActionContext context) { + panel.selectAll(); } } - diff --git a/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/decompiler/component/DecompilerCachingTest.java b/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/decompiler/component/DecompilerCachingTest.java index 54a765a8ff..7ba195b376 100644 --- a/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/decompiler/component/DecompilerCachingTest.java +++ b/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/decompiler/component/DecompilerCachingTest.java @@ -27,7 +27,7 @@ import org.junit.*; import com.google.common.cache.*; -import docking.action.DockingAction; +import docking.ComponentProvider; import generic.test.TestUtils; import ghidra.app.decompiler.DecompileResults; import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin; @@ -201,10 +201,8 @@ public class DecompilerCachingTest extends AbstractGhidraHeadedIntegrationTest { } private void showDecompilerProvider() { - DockingAction showDecompileAction = - (DockingAction) TestUtils.getInstanceField("decompileAction", decompilePlugin); - performAction(showDecompileAction, true); - + ComponentProvider decompiler = tool.getComponentProvider("Decompiler"); + tool.showComponentProvider(decompiler, true); decompilerProvider = waitForComponentProvider(DecompilerProvider.class); } 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 f469c92895..fe477d581e 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,20 @@ public class FGProvider extends VisualGraphComponentProvider { + Swing.runLater(() -> { newProvider.doSetProgram(currentProgram); FGData currentData = controller.getFunctionGraphData(); diff --git a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/FunctionGraphPlugin.java b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/FunctionGraphPlugin.java index 1ac78d78ee..25bfe2826c 100644 --- a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/FunctionGraphPlugin.java +++ b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/FunctionGraphPlugin.java @@ -21,9 +21,6 @@ import javax.swing.ImageIcon; import org.jdom.Element; -import docking.ActionContext; -import docking.action.DockingAction; -import docking.action.ToolBarData; import ghidra.GhidraOptions; import ghidra.app.CorePluginPackage; import ghidra.app.events.*; @@ -43,7 +40,6 @@ import ghidra.graph.viewer.options.VisualGraphOptions; import ghidra.program.model.listing.Program; import ghidra.program.util.ProgramLocation; import ghidra.program.util.ProgramSelection; -import ghidra.util.HelpLocation; import resources.ResourceManager; //@formatter:off @@ -75,8 +71,6 @@ public class FunctionGraphPlugin extends ProgramPlugin implements OptionsChangeL private static final String PROGRAM_PATH_ID = "Program Path"; private static final String DISCONNECTED_COUNT_ID = "Disconnected Count"; - private DockingAction showFunctionGraphAction; - private FGProvider connectedProvider; private List disconnectedProviders = new ArrayList<>(); private FormatManager userDefinedFormatManager; @@ -88,8 +82,6 @@ public class FunctionGraphPlugin extends ProgramPlugin implements OptionsChangeL public FunctionGraphPlugin(PluginTool tool) { super(tool, true, true, true); - createActions(); - colorProvider = new IndependentColorProvider(tool); } @@ -216,21 +208,6 @@ public class FunctionGraphPlugin extends ProgramPlugin implements OptionsChangeL } } - private void createActions() { - showFunctionGraphAction = new DockingAction("Display Function Graph", getName()) { - @Override - public void actionPerformed(ActionContext context) { - showProvider(); - } - }; - showFunctionGraphAction.setToolBarData(new ToolBarData(ICON, "View")); - - showFunctionGraphAction.setHelpLocation( - new HelpLocation("FunctionGraphPlugin", "Function_Graph_Plugin")); - - tool.addAction(showFunctionGraphAction); - } - void showProvider() { connectedProvider.setVisible(true); connectedProvider.setLocation(currentLocation); diff --git a/Ghidra/Features/FunctionGraph/src/test/java/ghidra/app/plugin/core/functiongraph/AbstractFunctionGraphTest.java b/Ghidra/Features/FunctionGraph/src/test/java/ghidra/app/plugin/core/functiongraph/AbstractFunctionGraphTest.java index 6fdf0efd3f..1aa088fb71 100644 --- a/Ghidra/Features/FunctionGraph/src/test/java/ghidra/app/plugin/core/functiongraph/AbstractFunctionGraphTest.java +++ b/Ghidra/Features/FunctionGraph/src/test/java/ghidra/app/plugin/core/functiongraph/AbstractFunctionGraphTest.java @@ -15,7 +15,7 @@ */ package ghidra.app.plugin.core.functiongraph; -import static ghidra.graph.viewer.GraphViewerUtils.*; +import static ghidra.graph.viewer.GraphViewerUtils.getGraphScale; import static org.junit.Assert.*; import java.awt.*; @@ -552,9 +552,9 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte } protected void showFunctionGraphProvider() { - DockingAction showGraphAction = - (DockingAction) TestUtils.getInstanceField("showFunctionGraphAction", graphPlugin); - performAction(showGraphAction, true); + + ComponentProvider provider = tool.getComponentProvider("Function Graph"); + tool.showComponentProvider(provider, true); graphProvider = waitForComponentProvider(FGProvider.class); assertNotNull("Graph not shown", graphProvider); @@ -2011,7 +2011,7 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte } - protected FGData triggerPersistence(String functionAddress) { + protected FGData triggerPersistenceAndReload(String functionAddress) { // // Ideally, we would like to save, close and re-open the program so that we can get // a round-trip saving and reloading. However, in the test environment, we cannot save @@ -2019,8 +2019,9 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte // the cache (to make sure that we read the settings again), and then verify that the // data saved in the program has been used to re-group. // - graphFunction("0100415a"); - clearCache(); + String otherAddress = "0100415a"; + assertNotEquals(functionAddress, otherAddress); + graphFunction(otherAddress); // // Graph the original function and make sure that the previously grouped nodes is again diff --git a/Ghidra/Features/FunctionGraph/src/test/java/ghidra/app/plugin/core/functiongraph/FunctionGraphGroupVertices1Test.java b/Ghidra/Features/FunctionGraph/src/test/java/ghidra/app/plugin/core/functiongraph/FunctionGraphGroupVertices1Test.java index 56ac72f7cd..c9c6dfa189 100644 --- a/Ghidra/Features/FunctionGraph/src/test/java/ghidra/app/plugin/core/functiongraph/FunctionGraphGroupVertices1Test.java +++ b/Ghidra/Features/FunctionGraph/src/test/java/ghidra/app/plugin/core/functiongraph/FunctionGraphGroupVertices1Test.java @@ -90,7 +90,7 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest { Address minAddress = addresses.getMinAddress(); Address maxAddress = addresses.getMaxAddress(); - // Recored the edges for later validate. Note: we have to keep the string form, as the + // Record the edges for later validation. Note: we have to keep the string form, as the // toString() on the edges will call back to its vertices, which will later have been // disposed. Collection oringalGroupedEdges = new HashSet<>(graph.getEdges());// copy so they don't get cleared @@ -99,7 +99,9 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest { originalEdgeStrings.add(edge.toString()); } - graphData = triggerPersistence("01002cf5"); + // debug + capture(getPrimaryGraphViewer(), "graph.grouping.before.reload"); + graphData = triggerPersistenceAndReload("01002cf5"); waitForAnimation();// the re-grouping may be using animation, which runs after the graph is loaded functionGraph = graphData.getFunctionGraph(); @@ -113,6 +115,7 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest { // TODO debug - this has failed; suspected timing issue waitForCondition(() -> pointsAreSimilar(location, newLocation)); + capture(getPrimaryGraphViewer(), "graph.grouping.after.reload"); assertTrue( "Vertex location not restored to default after performing a relayout " + "original point: " + location + " - reloaded point: " + newLocation, @@ -292,7 +295,7 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest { Address secondMinAddress = outerAddresses.getMinAddress(); Address secondMaxAddress = outerAddresses.getMaxAddress(); - graphData = triggerPersistence("01002cf5"); + graphData = triggerPersistenceAndReload("01002cf5"); waitForAnimation();// the re-grouping may be using animation, which runs after the graph is loaded functionGraph = graphData.getFunctionGraph(); @@ -410,7 +413,7 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest { Address minAddress = addresses.getMinAddress(); Address maxAddress = addresses.getMaxAddress(); - graphData = triggerPersistence("01002cf5"); + graphData = triggerPersistenceAndReload("01002cf5"); waitForAnimation();// the re-grouping may be using animation, which runs after the graph is loaded functionGraph = graphData.getFunctionGraph(); @@ -665,7 +668,7 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest { // Trigger persistence // Address groupAddress = group.getVertexAddress(); - FGData graphData = triggerPersistence("01002cf5"); + FGData graphData = triggerPersistenceAndReload("01002cf5"); // // Retrieve the group and make sure its color is restored @@ -702,7 +705,7 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest { // Trigger persistence // Address groupAddress = group.getVertexAddress(); - FGData graphData = triggerPersistence("01002cf5"); + FGData graphData = triggerPersistenceAndReload("01002cf5"); // // Retrieve the group and make sure its color is restored @@ -745,7 +748,7 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest { // Trigger persistence // Address groupAddress = group.getVertexAddress(); - FGData graphData = triggerPersistence("01002cf5"); + FGData graphData = triggerPersistenceAndReload("01002cf5"); // // Retrieve the group and make sure its color is restored diff --git a/Ghidra/Features/FunctionGraph/src/test/java/ghidra/app/plugin/core/functiongraph/FunctionGraphGroupVertices3Test.java b/Ghidra/Features/FunctionGraph/src/test/java/ghidra/app/plugin/core/functiongraph/FunctionGraphGroupVertices3Test.java index 2b37d1693e..c2c6ee8cd2 100644 --- a/Ghidra/Features/FunctionGraph/src/test/java/ghidra/app/plugin/core/functiongraph/FunctionGraphGroupVertices3Test.java +++ b/Ghidra/Features/FunctionGraph/src/test/java/ghidra/app/plugin/core/functiongraph/FunctionGraphGroupVertices3Test.java @@ -122,7 +122,7 @@ public class FunctionGraphGroupVertices3Test extends AbstractFunctionGraphTest { uncollapse(groupA); assertUncollapsed(v1, v2); - triggerPersistence(functionAddress); + triggerPersistenceAndReload(functionAddress); waitForBusyGraph();// the re-grouping may be using animation, which runs after the graph is loaded v1 = vertex(a1); @@ -161,7 +161,7 @@ public class FunctionGraphGroupVertices3Test extends AbstractFunctionGraphTest { assertUncollapsed(v1, v2);// sanity check--still uncollapsed - triggerPersistence(functionAddress); + triggerPersistenceAndReload(functionAddress); waitForBusyGraph();// the re-grouping may be using animation, which runs after the graph is loaded v1 = vertex(a1); @@ -195,7 +195,7 @@ public class FunctionGraphGroupVertices3Test extends AbstractFunctionGraphTest { uncollapse(outerGroup); assertUncollapsed(innerGroup, v3, v4); - triggerPersistence(functionAddress); + triggerPersistenceAndReload(functionAddress); waitForBusyGraph();// the re-grouping may be using animation, which runs after the graph is loaded v1 = vertex(a1); diff --git a/Ghidra/Features/ProgramDiff/src/main/java/ghidra/app/plugin/core/diff/DiffActionManager.java b/Ghidra/Features/ProgramDiff/src/main/java/ghidra/app/plugin/core/diff/DiffActionManager.java index a0c5edbec4..0168a7ea50 100644 --- a/Ghidra/Features/ProgramDiff/src/main/java/ghidra/app/plugin/core/diff/DiffActionManager.java +++ b/Ghidra/Features/ProgramDiff/src/main/java/ghidra/app/plugin/core/diff/DiffActionManager.java @@ -51,7 +51,7 @@ class DiffActionManager { static final String IGNORE_DIFFS_NEXT_ACTION = "Ignore Selection and Goto Next Difference"; static final String NEXT_DIFF_ACTION = "Next Difference"; static final String PREVIOUS_DIFF_ACTION = "Previous Difference"; - static final String DIFF_DETAILS_ACTION = "Diff Location Details"; + static final String DIFF_DETAILS_ACTION = "Show Diff Location Details"; static final String SHOW_DIFF_SETTINGS_ACTION = "Show Diff Apply Settings"; static final String GET_DIFFS_ACTION = "Get Differences"; static final String SELECT_ALL_DIFFS_ACTION = "Select All Differences"; diff --git a/Ghidra/Features/ProgramDiff/src/test.slow/java/ghidra/app/plugin/core/diff/DiffTest.java b/Ghidra/Features/ProgramDiff/src/test.slow/java/ghidra/app/plugin/core/diff/DiffTest.java index 0b49fafef5..b4ed548e7e 100644 --- a/Ghidra/Features/ProgramDiff/src/test.slow/java/ghidra/app/plugin/core/diff/DiffTest.java +++ b/Ghidra/Features/ProgramDiff/src/test.slow/java/ghidra/app/plugin/core/diff/DiffTest.java @@ -665,7 +665,7 @@ public class DiffTest extends DiffTestAdapter { checkDiffAction("Ignore Selection and Goto Next Difference", true, false); checkDiffAction("Next Difference", true, true); checkDiffAction("Previous Difference", true, false); - checkDiffAction("Diff Location Details", true, true); + checkDiffAction("Show Diff Location Details", true, true); checkDiffAction("Show Diff Apply Settings", true, true); checkDiffAction("Get Differences", true, true); checkDiffAction("Select All Differences", true, true); @@ -699,7 +699,7 @@ public class DiffTest extends DiffTestAdapter { checkDiffAction("Ignore Selection and Goto Next Difference", false, false); checkDiffAction("Next Difference", false, false); checkDiffAction("Previous Difference", false, false); - checkDiffAction("Diff Location Details", false, false); + checkDiffAction("Show Diff Location Details", false, false); checkDiffAction("Show Diff Apply Settings", false, false); checkDiffAction("Get Differences", false, false); checkDiffAction("Select All Differences", false, false); @@ -715,7 +715,7 @@ public class DiffTest extends DiffTestAdapter { checkDiffAction("Ignore Selection and Goto Next Difference", false, false); checkDiffAction("Next Difference", false, false); checkDiffAction("Previous Difference", false, false); - checkDiffAction("Diff Location Details", false, false); + checkDiffAction("Show Diff Location Details", false, false); checkDiffAction("Show Diff Apply Settings", false, false); checkDiffAction("Get Differences", false, false); checkDiffAction("Select All Differences", false, false); @@ -732,7 +732,7 @@ public class DiffTest extends DiffTestAdapter { checkDiffAction("Ignore Selection and Goto Next Difference", true, false); checkDiffAction("Next Difference", true, true); checkDiffAction("Previous Difference", true, true); - checkDiffAction("Diff Location Details", true, true); + checkDiffAction("Show Diff Location Details", true, true); checkDiffAction("Show Diff Apply Settings", true, true); checkDiffAction("Get Differences", true, true); checkDiffAction("Select All Differences", true, true); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/AbstractDockingTool.java b/Ghidra/Framework/Docking/src/main/java/docking/AbstractDockingTool.java index 5902ab81b9..893ad0be06 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/AbstractDockingTool.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/AbstractDockingTool.java @@ -23,7 +23,7 @@ import javax.swing.JFrame; import docking.action.DockingActionIf; import docking.actions.ToolActions; import ghidra.framework.options.ToolOptions; -import ghidra.util.SystemUtilities; +import ghidra.util.Swing; /** * A partial implementation of {@link DockingTool} that serves as a place to share common @@ -32,7 +32,7 @@ import ghidra.util.SystemUtilities; public abstract class AbstractDockingTool implements DockingTool { protected DockingWindowManager winMgr; - protected ToolActions actionMgr; + protected ToolActions toolActions; protected Map optionsMap = new HashMap<>(); protected boolean configChangedFlag; @@ -57,17 +57,21 @@ public abstract class AbstractDockingTool implements DockingTool { @Override public void addComponentProvider(ComponentProvider provider, boolean show) { - Runnable r = () -> winMgr.addComponent(provider, show); - SystemUtilities.runSwingNow(r); + Runnable r = () -> { + winMgr.addComponent(provider, show); + toolActions.addGlobalAction(provider.getShowProviderAction()); + }; + Swing.runNow(r); } @Override public void removeComponentProvider(ComponentProvider provider) { Runnable r = () -> { - actionMgr.removeComponentActions(provider); + toolActions.removeGlobalAction(provider.getShowProviderAction()); + toolActions.removeActions(provider); winMgr.removeComponent(provider); }; - SystemUtilities.runSwingNow(r); + Swing.runNow(r); } @Override @@ -96,41 +100,43 @@ public abstract class AbstractDockingTool implements DockingTool { @Override public void addAction(DockingActionIf action) { - actionMgr.addToolAction(action); + toolActions.addGlobalAction(action); } @Override public void removeAction(DockingActionIf action) { - actionMgr.removeToolAction(action); + toolActions.removeGlobalAction(action); } @Override public void addLocalAction(ComponentProvider provider, DockingActionIf action) { - actionMgr.addLocalAction(provider, action); + toolActions.addLocalAction(provider, action); } @Override public void removeLocalAction(ComponentProvider provider, DockingActionIf action) { - actionMgr.removeProviderAction(provider, action); + toolActions.removeLocalAction(provider, action); } @Override public Set getAllActions() { - Set actions = actionMgr.getAllActions(); - ActionToGuiMapper am = winMgr.getActionManager(); - actions.addAll(am.getAllActions()); - return actions; + return toolActions.getAllActions(); } @Override public Set getDockingActionsByOwnerName(String owner) { - return actionMgr.getActions(owner); + return toolActions.getActions(owner); + } + + @Override + public ComponentProvider getActiveComponentProvider() { + return winMgr.getActiveComponentProvider(); } @Override public void showComponentProvider(ComponentProvider provider, boolean visible) { Runnable r = () -> winMgr.showComponent(provider, visible); - SystemUtilities.runSwingNow(r); + Swing.runNow(r); } @Override @@ -150,7 +156,7 @@ public abstract class AbstractDockingTool implements DockingTool { @Override public void toFront(ComponentProvider provider) { Runnable r = () -> winMgr.toFront(provider); - SystemUtilities.runSwingNow(r); + Swing.runNow(r); } @Override @@ -173,6 +179,21 @@ public abstract class AbstractDockingTool implements DockingTool { winMgr.contextChanged(provider); } + @Override + public ActionContext getGlobalContext() { + return winMgr.getGlobalContext(); + } + + @Override + public void addContextListener(DockingContextListener listener) { + winMgr.addContextListener(listener); + } + + @Override + public void removeContextListener(DockingContextListener listener) { + winMgr.removeContextListener(listener); + } + @Override public DockingWindowManager getWindowManager() { return winMgr; @@ -187,4 +208,9 @@ public abstract class AbstractDockingTool implements DockingTool { public boolean hasConfigChanged() { return configChangedFlag; } + + @Override + public ToolActions getToolActions() { + return toolActions; + } } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/ActionToGuiHelper.java b/Ghidra/Framework/Docking/src/main/java/docking/ActionToGuiHelper.java index 1400d776da..41aa235be4 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/ActionToGuiHelper.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/ActionToGuiHelper.java @@ -78,4 +78,11 @@ public class ActionToGuiHelper { public void removeProviderAction(ComponentProvider provider, DockingActionIf action) { windowManager.removeProviderAction(provider, action); } + + /** + * Call this method to signal that key bindings for one or more actions have changed + */ + public void keyBindingsChanged() { + windowManager.scheduleUpdate(); + } } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/ActionToGuiMapper.java b/Ghidra/Framework/Docking/src/main/java/docking/ActionToGuiMapper.java index d70ac94189..3f84748118 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/ActionToGuiMapper.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/ActionToGuiMapper.java @@ -16,70 +16,38 @@ package docking; import java.awt.event.MouseEvent; -import java.util.*; +import java.util.LinkedHashSet; +import java.util.Set; -import javax.swing.*; +import javax.swing.JComponent; +import javax.swing.MenuSelectionManager; -import docking.action.*; +import docking.action.DockingActionIf; import docking.menu.MenuGroupMap; import docking.menu.MenuHandler; -import ghidra.util.*; +import ghidra.util.HelpLocation; /** * Manages the global actions for the menu and toolbar. */ public class ActionToGuiMapper { - private HashSet globalActions = new LinkedHashSet<>(); + private Set globalActions = new LinkedHashSet<>(); private MenuHandler menuBarMenuHandler; private MenuGroupMap menuGroupMap; - private static boolean enableDiagnosticActions; - - private KeyBindingsManager keyBindingsManager; - private GlobalMenuAndToolBarManager menuAndToolBarManager; - private PopupActionManager popupActionManager; - private DockingAction keyBindingsAction; ActionToGuiMapper(DockingWindowManager winMgr) { menuGroupMap = new MenuGroupMap(); - menuBarMenuHandler = new MenuBarMenuHandler(winMgr); - - keyBindingsManager = new KeyBindingsManager(winMgr); menuAndToolBarManager = new GlobalMenuAndToolBarManager(winMgr, menuBarMenuHandler, menuGroupMap); popupActionManager = new PopupActionManager(winMgr, menuGroupMap); - initializeHelpActions(); - } - - private void initializeHelpActions() { DockingWindowsContextSensitiveHelpListener.install(); - - keyBindingsAction = new KeyBindingAction(this); - keyBindingsManager.addReservedAction(new HelpAction(false, ReservedKeyBindings.HELP_KEY1)); - keyBindingsManager.addReservedAction(new HelpAction(false, ReservedKeyBindings.HELP_KEY2)); - keyBindingsManager.addReservedAction( - new HelpAction(true, ReservedKeyBindings.HELP_INFO_KEY)); - keyBindingsManager.addReservedAction(keyBindingsAction); - - if (enableDiagnosticActions) { - keyBindingsManager.addReservedAction(new ShowFocusInfoAction()); - keyBindingsManager.addReservedAction(new ShowFocusCycleAction()); - } - } - - /** - * A static initializer allowing additional diagnostic actions - * to be added to all frame and dialog windows. - * @param enable - */ - static void enableDiagnosticActions(boolean enable) { - enableDiagnosticActions = enable; } /** @@ -94,41 +62,12 @@ public class ActionToGuiMapper { DockingWindowManager.getHelpService().registerHelp(c, helpLocation); } - /** - * Removes all actions associated with the given owner - * @param owner the owner of all actions to be removed. - */ - void removeAll(String owner) { - Iterator iter = new ArrayList<>(globalActions).iterator(); - List removedList = new ArrayList<>(); - while (iter.hasNext()) { - DockingActionIf action = iter.next(); - if (owner.equals(action.getOwner())) { - keyBindingsManager.removeAction(action); - menuAndToolBarManager.removeAction(action); - popupActionManager.removeAction(action); - removedList.add(action); - } - } - - globalActions.removeAll(removedList); - } - - void addLocalAction(DockingActionIf action, ComponentProvider provider) { - keyBindingsManager.addAction(action, provider); - } - - void removeLocalAction(DockingActionIf action) { - keyBindingsManager.removeAction(action); - } - /** * Adds the given Global action to the menu and/or toolbar. * @param action the action to be added. */ void addToolAction(DockingActionIf action) { if (globalActions.add(action)) { - keyBindingsManager.addAction(action, null); popupActionManager.addAction(action); menuAndToolBarManager.addAction(action); } @@ -139,28 +78,11 @@ public class ActionToGuiMapper { * @param action the action to be removed. */ void removeToolAction(DockingActionIf action) { - keyBindingsManager.removeAction(action); popupActionManager.removeAction(action); menuAndToolBarManager.removeAction(action); globalActions.remove(action); } - public Set getAllActions() { - - // Note: this method is called by non-Swing test code. Synchronize access to the - // data structures in this class in order to prevent concurrent mod exceptions. - Set actions = new HashSet<>(); - SystemUtilities.runSwingNow(() -> { - actions.addAll(globalActions); - actions.addAll(keyBindingsManager.getLocalActions()); - }); - return actions; - } - - public Action getDockingKeyAction(KeyStroke keyStroke) { - return keyBindingsManager.getDockingKeyAction(keyStroke); - } - Set getGlobalActions() { return globalActions; } @@ -173,28 +95,16 @@ public class ActionToGuiMapper { } } - /** - * Close all menus (includes popup menus) - */ - static void dismissMenus() { + private void dismissMenus() { MenuSelectionManager.defaultManager().clearSelectedPath(); } - /** - * Updates the menu and toolbar to reflect any changes in the set of actions. - * - */ void update() { menuAndToolBarManager.update(); contextChangedAll(); } - /** - * Releases all resources and makes this object unavailable for future use. - * - */ void dispose() { - keyBindingsManager.dispose(); popupActionManager.dispose(); menuAndToolBarManager.dispose(); globalActions.clear(); 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 6427e1ac17..e9d98fa0da 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/ComponentProvider.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/ComponentProvider.java @@ -25,9 +25,9 @@ import javax.swing.*; import docking.action.*; import docking.help.HelpDescriptor; import docking.help.HelpService; -import ghidra.util.HelpLocation; -import ghidra.util.UniversalIdGenerator; +import ghidra.util.*; import ghidra.util.exception.AssertException; +import utilities.util.reflection.ReflectionUtilities; /** * Abstract base class for creating dockable GUI components within a tool. @@ -61,13 +61,28 @@ import ghidra.util.exception.AssertException; *

  • {@link #componentActivated()} and {@link #componentDeactived()} *
  • {@link #componentHidden()} and {@link #componentShown()} * + * *

    - * Note: This class was created so that implementors could add local actions within the constructor + * Show Provider Action - Each provider has an action to show the provider. For + * typical, non-transient providers (see {@link #setTransient()}) the action will appear in + * the tool's Window menu. You can have your provider also appear in the tool's toolbar + * by calling {@link #setIcon(Icon, boolean)}, passing true for + * isToolbarAction. + *

    + * Historical Note: This class was created so that implementors could add local actions within the constructor * without having to understand that they must first add themselves to the WindowManager. */ - public abstract class ComponentProvider implements HelpDescriptor, ActionContextProvider { + private static final String TRANSIENT_PROVIDER_TOOLBAR_WARNING_MESSAGE = + "Transient providers are not added to the toolbar"; + + private static final String TRANSIENT_PROVIDER_KEY_BINDING_WARNING_MESSAGE = + "Transient providers cannot have key bindings"; + public static final String DEFAULT_WINDOW_GROUP = "Default"; + + private static final String TOOLBAR_GROUP = "View"; + // maps for mapping old provider names and owner to new names and/or owner private static Map oldOwnerMap = new HashMap<>(); private static Map oldNameMap = new HashMap<>(); @@ -77,19 +92,29 @@ public abstract class ComponentProvider implements HelpDescriptor, ActionContext private String title; private String subTitle; private String tabText; - private Icon icon; + private Set actionSet = new LinkedHashSet<>(); - private String windowMenuGroup; + + /** True if this provider's action should appear in the toolbar */ + private boolean addToolbarAction; private boolean isTransient; - private HelpLocation helpLocation; + private KeyBindingData defaultKeyBindingData; + private Icon icon; + + private String windowMenuGroup; private String group = DEFAULT_WINDOW_GROUP; private WindowPosition defaultWindowPosition = WindowPosition.WINDOW; private WindowPosition defaultIntraGroupPosition = WindowPosition.STACK; + private DockingAction showProviderAction; + + private HelpLocation helpLocation; private final Class contextType; private long instanceID = UniversalIdGenerator.nextID().getValue(); private boolean instanceIDHasBeenInitialized; + private String inceptionInformation; + /** * Creates a new component provider with a default location of {@link WindowPosition#WINDOW}. * @param tool The tool will manage and show this provider @@ -116,6 +141,32 @@ public abstract class ComponentProvider implements HelpDescriptor, ActionContext this.owner = owner; this.title = name; this.contextType = contextType; + + recordInception(); + } + + /** + * Returns the action used to show this provider + * @return the action + */ + DockingActionIf getShowProviderAction() { + createShowProviderAction(); + return showProviderAction; + } + + private void createShowProviderAction() { + if (showProviderAction != null) { + return; + } + + if (addToolbarAction) { + Objects.requireNonNull(icon, + "The provider's icon cannot be null when requesting the provider's action " + + "appear in the toolbar"); + } + + boolean supportsKeyBindings = !isTransient; + showProviderAction = new ShowProviderAction(supportsKeyBindings); } /** @@ -361,6 +412,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); + } } /** @@ -449,13 +504,56 @@ public abstract class ComponentProvider implements HelpDescriptor, ActionContext } /** - * Convenience method for setting the provider's icon. - * @param icon the icon to use for this provider. + * Sets the default key binding that will show this provider when pressed. This value can + * be changed by the user and saved as part of the Tool options. + * + * @param kbData the key binding */ - public void setIcon(Icon icon) { - this.icon = icon; + protected void setKeyBinding(KeyBindingData kbData) { + if (isInTool()) { - dockingTool.getWindowManager().setIcon(this, icon); + throw new IllegalStateException( + "Cannot set the default key binding after the provider is added to the tool"); + } + + this.defaultKeyBindingData = kbData; + + if (isTransient && kbData != null) { + Msg.error(this, TRANSIENT_PROVIDER_KEY_BINDING_WARNING_MESSAGE, + ReflectionUtilities.createJavaFilteredThrowable()); + this.defaultKeyBindingData = null; + } + } + + /** + * Convenience method for setting the provider's icon + * @param icon the icon to use for this provider + */ + protected void setIcon(Icon icon) { + this.icon = icon; + if (!isInTool()) { + return; + } + + if (addToolbarAction && showProviderAction != null) { + Objects.requireNonNull(icon, "Icon cannot be set to null when using a toolbar action"); + showProviderAction.setToolBarData(new ToolBarData(icon)); + } + + dockingTool.getWindowManager().setIcon(this, icon); + } + + /** + * Signals that this provider's action for showing the provider should appear in the main + * toolbar + */ + protected void addToToolbar() { + this.addToolbarAction = true; + + if (isTransient) { + Msg.error(this, TRANSIENT_PROVIDER_TOOLBAR_WARNING_MESSAGE, + ReflectionUtilities.createJavaFilteredThrowable()); + addToolbarAction = false; } } @@ -465,7 +563,6 @@ public abstract class ComponentProvider implements HelpDescriptor, ActionContext * @return the menu group for this provider or null if this provider should appear in the * top-level menu. */ - public String getWindowSubMenuName() { return windowMenuGroup; } @@ -473,10 +570,20 @@ public abstract class ComponentProvider implements HelpDescriptor, ActionContext /** * Returns true if this component goes away during a user session (most providers remain in * the tool all session long, visible or not) - * @return true if transitent + * @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; } /** @@ -485,6 +592,25 @@ public abstract class ComponentProvider implements HelpDescriptor, ActionContext */ protected void setTransient() { isTransient = true; + + if (isInTool()) { + throw new IllegalStateException( + "A component provider cannot be marked as 'transient' " + + "after it is added to the tool"); + } + + // avoid visually disturbing the user by adding/removing toolbar actions for temp providers + if (addToolbarAction) { + addToolbarAction = false; + Msg.error(this, TRANSIENT_PROVIDER_TOOLBAR_WARNING_MESSAGE, + ReflectionUtilities.createJavaFilteredThrowable()); + } + + if (defaultKeyBindingData != null) { + defaultKeyBindingData = null; + Msg.error(this, TRANSIENT_PROVIDER_KEY_BINDING_WARNING_MESSAGE, + ReflectionUtilities.createJavaFilteredThrowable()); + } } /** @@ -591,6 +717,22 @@ public abstract class ComponentProvider implements HelpDescriptor, ActionContext return name + " - " + getTitle() + " - " + getSubTitle(); } + private void recordInception() { + if (!SystemUtilities.isInDevelopmentMode()) { + inceptionInformation = ""; + return; + } + + inceptionInformation = getInceptionFromTheFirstClassThatIsNotUs(); + } + + private String getInceptionFromTheFirstClassThatIsNotUs() { + Throwable t = ReflectionUtilities.createThrowableWithStackOlderThan(getClass()); + StackTraceElement[] trace = t.getStackTrace(); + String classInfo = trace[0].toString(); + return classInfo; + } + /** * Returns any registered new provider name for the oldName/oldOwner pair. * @param oldOwner the old owner name @@ -633,4 +775,38 @@ public abstract class ComponentProvider implements HelpDescriptor, ActionContext return "owner=" + oldOwner + "name=" + oldName; } + private class ShowProviderAction extends DockingAction { + + ShowProviderAction(boolean supportsKeyBindings) { + super(name, owner, + supportsKeyBindings ? KeyBindingType.SHARED : KeyBindingType.UNSUPPORTED); + + if (addToolbarAction) { + setToolBarData(new ToolBarData(icon, TOOLBAR_GROUP)); + } + + 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 + public void actionPerformed(ActionContext context) { + dockingTool.showComponentProvider(ComponentProvider.this, true); + } + + @Override + protected String getInceptionFromTheFirstClassThatIsNotUs() { + // overridden to show who created the provider, as that is what this action represents + return inceptionInformation; + } + } } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/DialogComponentProviderPopupActionManager.java b/Ghidra/Framework/Docking/src/main/java/docking/DialogComponentProviderPopupActionManager.java index cc7270ca87..060f05c814 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/DialogComponentProviderPopupActionManager.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/DialogComponentProviderPopupActionManager.java @@ -69,7 +69,7 @@ public class DialogComponentProviderPopupActionManager { return; } - ActionToGuiMapper actionManager = dwm.getActionManager(); + ActionToGuiMapper actionManager = dwm.getActionToGuiMapper(); MenuGroupMap menuGroupMap = actionManager.getMenuGroupMap(); MenuManager menuMgr = new MenuManager("Popup", '\0', null, true, popupMenuHandler, menuGroupMap); @@ -102,7 +102,7 @@ public class DialogComponentProviderPopupActionManager { Object source = actionContext.getSourceObject(); if (source instanceof DockingActionProviderIf) { DockingActionProviderIf actionProvider = (DockingActionProviderIf) source; - List dockingActions = actionProvider.getDockingActions(actionContext); + List dockingActions = actionProvider.getDockingActions(); for (DockingActionIf action : dockingActions) { MenuData popupMenuData = action.getPopupMenuData(); if (popupMenuData != null && action.isValidContext(actionContext) && diff --git a/Ghidra/Framework/Docking/src/main/java/docking/DockableComponent.java b/Ghidra/Framework/Docking/src/main/java/docking/DockableComponent.java index ad7a2b5feb..4083aa4458 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/DockableComponent.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/DockableComponent.java @@ -65,7 +65,7 @@ public class DockableComponent extends JPanel implements ContainerListener { this.componentInfo = placeholder; winMgr = placeholder.getNode().winMgr; - actionMgr = winMgr.getActionManager(); + actionMgr = winMgr.getActionToGuiMapper(); popupListener = new MouseAdapter() { @Override diff --git a/Ghidra/Framework/Docking/src/main/java/docking/DockableHeader.java b/Ghidra/Framework/Docking/src/main/java/docking/DockableHeader.java index e84839447e..5ad8af4a72 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/DockableHeader.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/DockableHeader.java @@ -87,13 +87,12 @@ public class DockableHeader extends GenericHeader private boolean isDocking; private Animator focusAnimator; - private int focusToggle = -1; /** * Constructs a new DockableHeader for the given dockableComponent. * - * @param dockableComp - * the dockableComponent that this header is for. + * @param dockableComp the dockableComponent that this header is for. + * @param isDocking true means this widget can be dragged and docked by the user */ DockableHeader(DockableComponent dockableComp, boolean isDocking) { this.dockComp = dockableComp; @@ -173,8 +172,11 @@ public class DockableHeader extends GenericHeader } protected Animator createEmphasizingAnimator(JFrame parentFrame) { - focusToggle += 1; - switch (focusToggle) { + + double random = Math.random(); + int choices = 4; + int value = (int) (choices * random); + switch (value) { case 0: return AnimationUtils.shakeComponent(component); case 1: @@ -182,7 +184,6 @@ public class DockableHeader extends GenericHeader case 2: return raiseComponent(parentFrame); default: - focusToggle = -1; return AnimationUtils.pulseComponent(component); } } @@ -226,12 +227,12 @@ public class DockableHeader extends GenericHeader if (!isDocking) { return; } - // check input event: if any button other than MB1 is pressed, - // don't attempt to process the drag and drop event. + + // if any button other than MB1 is pressed, don't attempt to process the drag and drop event InputEvent ie = event.getTriggerEvent(); - int modifiers = ie.getModifiers(); - if ((modifiers & InputEvent.BUTTON2_MASK) != 0 || - (modifiers & InputEvent.BUTTON3_MASK) != 0) { + int modifiers = ie.getModifiersEx(); + if ((modifiers & InputEvent.BUTTON2_DOWN_MASK) != 0 || + (modifiers & InputEvent.BUTTON3_DOWN_MASK) != 0) { return; } DockableComponent.DROP_CODE = DockableComponent.DropCode.WINDOW; diff --git a/Ghidra/Framework/Docking/src/main/java/docking/DockableToolBarManager.java b/Ghidra/Framework/Docking/src/main/java/docking/DockableToolBarManager.java index b74d9deff5..365b26435f 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/DockableToolBarManager.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/DockableToolBarManager.java @@ -60,7 +60,7 @@ class DockableToolBarManager { ComponentPlaceholder placeholder = dockableComp.getComponentWindowingPlaceholder(); DockingWindowManager winMgr = dockableComp.getComponentWindowingPlaceholder().getNode().winMgr; - ActionToGuiMapper actionManager = winMgr.getActionManager(); + ActionToGuiMapper actionManager = winMgr.getActionToGuiMapper(); menuGroupMap = actionManager.getMenuGroupMap(); MenuHandler menuHandler = actionManager.getMenuHandler(); @@ -168,7 +168,8 @@ class DockableToolBarManager { private DockableComponent dockableComponent; ToolBarCloseAction(DockableComponent dockableComponent) { - super("Close Window", DockingWindowManager.DOCKING_WINDOWS_OWNER); + super("Close Window", DockingWindowManager.DOCKING_WINDOWS_OWNER, + KeyBindingType.UNSUPPORTED); this.dockableComponent = dockableComponent; setDescription("Close Window"); setToolBarData(new ToolBarData(closeIcon, null)); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/DockingActionProxy.java b/Ghidra/Framework/Docking/src/main/java/docking/DockingActionProxy.java index fc7b8aa8bc..f7492b697e 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/DockingActionProxy.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/DockingActionProxy.java @@ -200,8 +200,8 @@ public class DockingActionProxy } @Override - public boolean isKeyBindingManaged() { - return dockingAction.isKeyBindingManaged(); + public KeyBindingType getKeyBindingType() { + return dockingAction.getKeyBindingType(); } @Override diff --git a/Ghidra/Framework/Docking/src/main/java/docking/DockingContextListener.java b/Ghidra/Framework/Docking/src/main/java/docking/DockingContextListener.java index eaeecf6344..256ba3b400 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/DockingContextListener.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/DockingContextListener.java @@ -1,6 +1,5 @@ /* ### * 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. @@ -16,6 +15,18 @@ */ package docking; +import docking.action.DockingActionIf; + +/** + * A listener to be notified when the tool's context changes. Normally context is used to + * manage {@link DockingActionIf} enablement directly by the system. This class allows + * clients to listen to context change as well. + */ public interface DockingContextListener { - void contextChanged(ActionContext context); + + /** + * Called when the context changes + * @param context the context + */ + public void contextChanged(ActionContext context); } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/DockingKeyBindingAction.java b/Ghidra/Framework/Docking/src/main/java/docking/DockingKeyBindingAction.java index 195b37085e..15512a423d 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/DockingKeyBindingAction.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/DockingKeyBindingAction.java @@ -15,42 +15,28 @@ */ package docking; -import java.awt.event.*; -import java.util.*; +import java.awt.event.ActionEvent; import javax.swing.AbstractAction; import javax.swing.KeyStroke; import docking.action.DockingActionIf; -import ghidra.util.Msg; -import ghidra.util.StringUtilities; +import docking.actions.KeyBindingUtils; /** * A class that can be used as an interface for using actions associated with keybindings. This * class is meant to only by used by internal Ghidra key event processing. */ -public class DockingKeyBindingAction extends AbstractAction { - - private static final String RELEASED = "released"; - private static final String TYPED = "typed"; - private static final String PRESSED = "pressed"; - - private static final String SHIFT = "Shift"; - private static final String CTRL = "Ctrl"; - private static final String CONTROL = "Control"; - private static final String ALT = "Alt"; - private static final String META = "Meta"; - private static final String MODIFIER_SEPARATOR = "-"; +public abstract class DockingKeyBindingAction extends AbstractAction { private DockingActionIf docakbleAction; - protected KeyStroke keyStroke; - protected final DockingWindowManager winMgr; + protected final KeyStroke keyStroke; + protected final DockingTool tool; - public DockingKeyBindingAction(DockingWindowManager winMgr, DockingActionIf action, - KeyStroke keyStroke) { - super(parseKeyStroke(keyStroke)); - this.winMgr = winMgr; + public DockingKeyBindingAction(DockingTool tool, DockingActionIf action, KeyStroke keyStroke) { + super(KeyBindingUtils.parseKeyStroke(keyStroke)); + this.tool = tool; this.docakbleAction = action; this.keyStroke = keyStroke; } @@ -65,18 +51,16 @@ public class DockingKeyBindingAction extends AbstractAction { return true; } - public boolean isReservedKeybindingPrecedence() { - return getKeyBindingPrecedence() == KeyBindingPrecedence.ReservedActionsLevel; - } + public abstract KeyBindingPrecedence getKeyBindingPrecedence(); - public KeyBindingPrecedence getKeyBindingPrecedence() { - return KeyBindingPrecedence.ReservedActionsLevel; + public boolean isReservedKeybindingPrecedence() { + return false; } @Override public void actionPerformed(final ActionEvent e) { - winMgr.setStatusText(""); - ComponentProvider provider = winMgr.getActiveComponentProvider(); + tool.setStatusInfo(""); + ComponentProvider provider = tool.getActiveComponentProvider(); ActionContext context = getLocalContext(provider); context.setSource(e.getSource()); docakbleAction.actionPerformed(context); @@ -94,147 +78,4 @@ public class DockingKeyBindingAction extends AbstractAction { return new ActionContext(localProvider, null); } - - /** - * Convert the toString() form of the keyStroke. - *
    In Java 1.4.2 & earlier, Ctrl-M is returned as "keyCode CtrlM-P" - * and we want it to look like: "Ctrl-M". - *
    In Java 1.5.0, Ctrl-M is returned as "ctrl pressed M" - * and we want it to look like: "Ctrl-M". - */ - public static String parseKeyStroke(KeyStroke keyStroke) { - final String keyPressSuffix = "-P"; - - String keyString = keyStroke.toString(); - int type = keyStroke.getKeyEventType(); - if (type == KeyEvent.KEY_TYPED) { - return String.valueOf(keyStroke.getKeyChar()); - } - - // get the character used in the key stroke - int firstIndex = keyString.lastIndexOf(' ') + 1; - int ctrlIndex = keyString.indexOf(CTRL, firstIndex); - if (ctrlIndex >= 0) { - firstIndex = ctrlIndex + CTRL.length(); - } - int altIndex = keyString.indexOf(ALT, firstIndex); - if (altIndex >= 0) { - firstIndex = altIndex + ALT.length(); - } - int shiftIndex = keyString.indexOf(SHIFT, firstIndex); - if (shiftIndex >= 0) { - firstIndex = shiftIndex + SHIFT.length(); - } - int metaIndex = keyString.indexOf(META, firstIndex); - if (metaIndex >= 0) { - firstIndex = metaIndex + META.length(); - } - - int lastIndex = keyString.length(); - if (keyString.endsWith(keyPressSuffix)) { - lastIndex -= keyPressSuffix.length(); - } - if (lastIndex >= 0) { - keyString = keyString.substring(firstIndex, lastIndex); - } - - int modifiers = keyStroke.getModifiers(); - - StringBuffer buffer = new StringBuffer(); - if ((modifiers & InputEvent.SHIFT_MASK) != 0) { - buffer.insert(0, SHIFT + MODIFIER_SEPARATOR); - } - if ((modifiers & InputEvent.ALT_MASK) != 0) { - buffer.insert(0, ALT + MODIFIER_SEPARATOR); - } - if ((modifiers & InputEvent.CTRL_MASK) != 0) { - buffer.insert(0, CTRL + MODIFIER_SEPARATOR); - } - if ((modifiers & InputEvent.META_MASK) != 0) { - buffer.insert(0, META + MODIFIER_SEPARATOR); - } - buffer.append(keyString); - return buffer.toString(); - } - - /** - * Parses the given text into a KeyStroke. This method relies upon - * {@link KeyStroke#getKeyStroke(String)} for parsing. Before making that call, this method - * will perform fixup on the given text for added flexibility. For example, the given - * text may contain spaces or dashes as the separators between parts in the string. Also, - * the text is converted such that it is not case-sensitive. So, the following example - * formats are allowed: - *

    -	 *    Alt-F
    -	 *    alt p
    -	 *    Ctrl-Alt-Z
    -	 *    ctrl Z
    -	 * 
    - * - * @param keyStroke - * @return - */ - public static KeyStroke parseKeyStroke(String keyStroke) { - List pieces = new ArrayList<>(); - StringTokenizer tokenizer = new StringTokenizer(keyStroke, "- "); - while (tokenizer.hasMoreTokens()) { - String token = tokenizer.nextToken(); - if (!pieces.contains(token)) { - pieces.add(token); - } - } - - StringBuffer keyStrokeBuff = new StringBuffer(); - for (Iterator iterator = pieces.iterator(); iterator.hasNext();) { - String piece = iterator.next(); - if (StringUtilities.indexOfIgnoreCase(piece, SHIFT) != -1) { - keyStrokeBuff.append("shift "); - iterator.remove(); - } - else if (StringUtilities.indexOfIgnoreCase(piece, CTRL) != -1) { - keyStrokeBuff.append("ctrl "); - iterator.remove(); - } - else if (StringUtilities.indexOfIgnoreCase(piece, CONTROL) != -1) { - keyStrokeBuff.append("ctrl "); - iterator.remove(); - } - else if (StringUtilities.indexOfIgnoreCase(piece, ALT) != -1) { - keyStrokeBuff.append("alt "); - iterator.remove(); - } - else if (StringUtilities.indexOfIgnoreCase(piece, META) != -1) { - keyStrokeBuff.append("meta "); - iterator.remove(); - } - else if (StringUtilities.indexOfIgnoreCase(piece, PRESSED) != -1) { - iterator.remove(); - } - else if (StringUtilities.indexOfIgnoreCase(piece, TYPED) != -1) { - iterator.remove(); - } - else if (StringUtilities.indexOfIgnoreCase(piece, RELEASED) != -1) { - iterator.remove(); - } - - } - - keyStrokeBuff.append(PRESSED).append(' '); - - // at this point we should only have left one piece--the key ID - int leftover = pieces.size(); - if (leftover > 1 || leftover == 0) { - Msg.warn(DockingKeyBindingAction.class, "Invalid keystroke string found. Expected " + - "format of '[modifier] ... key'. Found: '" + keyStroke + "'"); - - if (leftover == 0) { - return null; // nothing to do - } - } - - String key = pieces.get(0); - keyStrokeBuff.append(key.toUpperCase()); - - return KeyStroke.getKeyStroke(keyStrokeBuff.toString()); - } } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/DockingTool.java b/Ghidra/Framework/Docking/src/main/java/docking/DockingTool.java index 23c25fbc2c..2116bd64b7 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/DockingTool.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/DockingTool.java @@ -21,6 +21,7 @@ import java.util.Set; import javax.swing.ImageIcon; import docking.action.DockingActionIf; +import docking.actions.DockingToolActions; import ghidra.framework.options.ToolOptions; /** @@ -151,6 +152,12 @@ public interface DockingTool { */ public Set getDockingActionsByOwnerName(String owner); + /** + * Returns the active component provider, that which has focus + * @return the active provider + */ + public ComponentProvider getActiveComponentProvider(); + /** * Shows or hides the component provider in the tool * @param componentProvider the provider to either show or hide. @@ -209,6 +216,27 @@ public interface DockingTool { */ public void contextChanged(ComponentProvider provider); + /** + * Returns this tool's notion of the current action context, which is based upon the active + * {@link ComponentProvider}. If there is not active provider, then a generic context will + * be returned. + * + * @return the context + */ + public ActionContext getGlobalContext(); + + /** + * Adds the given context listener to this tool + * @param listener the listener to add + */ + public void addContextListener(DockingContextListener listener); + + /** + * Removes the given context listener to this tool + * @param listener the listener to add + */ + public void removeContextListener(DockingContextListener listener); + /** * Returns the DockingWindowManger for this tool. * @return the DockingWindowManger for this tool. @@ -235,4 +263,14 @@ public interface DockingTool { */ public boolean hasConfigChanged(); + /** + * Returns the class that manages actions for the tool. + * + *

    Most clients will not need to use this methods. Instead, actions should be added to + * the tool via {@link #addAction(DockingActionIf)} and + * {@link #addLocalAction(ComponentProvider, DockingActionIf)}. + * + * @return the action manager + */ + public DockingToolActions getToolActions(); } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/DockingWindowManager.java b/Ghidra/Framework/Docking/src/main/java/docking/DockingWindowManager.java index 7723085e4b..7799c80bf1 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/DockingWindowManager.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/DockingWindowManager.java @@ -26,16 +26,18 @@ import java.util.Map.Entry; import javax.swing.*; +import org.apache.commons.collections4.map.LazyMap; import org.jdom.Element; import docking.action.DockingActionIf; +import docking.actions.DockingToolActions; +import docking.actions.ToolActions; import docking.help.HelpService; import generic.util.WindowUtilities; import ghidra.framework.OperatingSystem; import ghidra.framework.Platform; import ghidra.framework.options.PreferenceState; -import ghidra.util.HelpLocation; -import ghidra.util.SystemUtilities; +import ghidra.util.*; import ghidra.util.datastruct.*; import ghidra.util.exception.AssertException; import ghidra.util.task.SwingUpdateManager; @@ -45,16 +47,14 @@ import util.CollectionUtils; * Manages the "Docking" arrangement of a set of components and actions. The components can be "docked" * together or exist in their own window. Actions can be associated with components so they * "move" with the component as it moved from one location to another. - * + *

    * Components are added via ComponentProviders. A ComponentProvider is an interface for getting * a component and its related information. The docking window manager will get the component * from the provider as needed. It is up to the provider if it wants to reuse the component or - * recreate a new one when the component is requested. When the user "hides" (by using - * the x button on the component area) a component, the docking window manager removes all + * recreate a new one when the component is requested. When the user hides a component (by using + * the x button on the component header), the docking window manager removes all * knowledge of the component and will request it again from the provider if the component - * becomes "unhidden". The provider is also notified whenever a component is hidden. Some - * providers will use the notification to remove the provider from the docking window manager so - * that the can not "unhide" the component using the built-in window menu. + * is again shown. The provider is also notified whenever a component is hidden and shown. */ public class DockingWindowManager implements PropertyChangeListener, PlaceholderInstaller { @@ -73,6 +73,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder private static List instanceList = new ArrayList<>(); + private DockingTool tool; private RootNode root; private PlaceholderManager placeholderManager; @@ -87,7 +88,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder private Map providerNameCache = new HashMap<>(); private Map preferenceStateMap = new HashMap<>(); private DockWinListener docListener; - private ActionToGuiMapper actionManager; + private ActionToGuiMapper actionToGuiMapper; private WeakSet contextListeners = WeakDataStructureFactory.createSingleThreadAccessWeakSet(); @@ -105,18 +106,18 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder /** * Constructs a new DockingWindowManager - * @param toolName the name of the tool. + * @param tool the tool * @param images the images to use for windows in this window manager - * @param docListener the listener to be notified when the user closes the manager. + * @param docListener the listener to be notified when the user closes the manager */ - public DockingWindowManager(String toolName, List images, DockWinListener docListener) { - this(toolName, images, docListener, false, true, true, null); + public DockingWindowManager(DockingTool tool, List images, DockWinListener docListener) { + this(tool, images, docListener, false, true, true, null); } /** * Constructs a new DockingWindowManager * - * @param toolName the name of the tool + * @param tool the tool * @param images the list of icons to set on the window * @param docListener the listener to be notified when the user closes the manager * @param modal if true then the root window will be a modal dialog instead of a frame @@ -125,11 +126,12 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder * @param hasStatusBar if true a status bar will be created for the main window * @param factory the drop target factory */ - public DockingWindowManager(String toolName, List images, DockWinListener docListener, + public DockingWindowManager(DockingTool tool, List images, DockWinListener docListener, boolean modal, boolean isDocking, boolean hasStatusBar, DropTargetFactory factory) { KeyBindingOverrideKeyEventDispatcher.install(); + this.tool = tool; this.docListener = docListener; this.isDocking = isDocking; this.hasStatusBar = hasStatusBar; @@ -137,8 +139,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder images = new ArrayList<>(); } - root = new RootNode(this, toolName, images, modal, factory); - actionManager = new ActionToGuiMapper(this); + root = new RootNode(this, tool.getName(), images, modal, factory); KeyboardFocusManager km = KeyboardFocusManager.getCurrentKeyboardFocusManager(); km.addPropertyChangeListener("permanentFocusOwner", this); @@ -146,6 +147,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder addInstance(this); placeholderManager = new PlaceholderManager(this); + actionToGuiMapper = new ActionToGuiMapper(this); } @Override @@ -153,15 +155,6 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder return "DockingWindowManager: " + root.getTitle(); } - /** - * A static initializer allowing additional diagnostic actions - * to be enabled added to all frame and dialog windows. - * @param enable - */ - public static void enableDiagnosticActions(boolean enable) { - ActionToGuiMapper.enableDiagnosticActions(enable); - } - /** * Sets the help service for the all docking window managers. * @param helpSvc the help service to use. @@ -311,17 +304,6 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder root.setIcon(icon); } - /** - * Returns any action that is bound to the given keystroke for the tool associated with this - * DockingWindowManager instance. - * @param keyStroke The keystroke to check for key bindings. - * @return The action that is bound to the keystroke, or null of there is no binding for the - * given keystroke. - */ - Action getActionForKeyStroke(KeyStroke keyStroke) { - return actionManager.getDockingKeyAction(keyStroke); - } - /** * Returns true if this manager contains the given provider. * @@ -336,8 +318,8 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder return placeholderManager; } - ActionToGuiMapper getActionManager() { - return actionManager; + ActionToGuiMapper getActionToGuiMapper() { + return actionToGuiMapper; } RootNode getRootNode() { @@ -363,6 +345,13 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder defaultProvider = provider; } + /** + * Returns this tool's notion of the current action context, which is based upon the active + * {@link ComponentProvider}. If there is not active provider, then a generic context will + * be returned. + * + * @return the context + */ public ActionContext getGlobalContext() { if (defaultProvider != null) { ActionContext actionContext = defaultProvider.getActionContext(null); @@ -641,16 +630,6 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder placeholderManager.removeComponent(provider); } - /** - * Removes all components and actions associated with the given owner. - * @param owner the name of the owner whose associated component and actions should be removed. - */ - public void removeAll(String owner) { - actionManager.removeAll(owner); - placeholderManager.removeAll(owner); - scheduleUpdate(); - } - //================================================================================================== // Package-level Action Methods //================================================================================================== @@ -668,7 +647,6 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder void removeProviderAction(ComponentProvider provider, DockingActionIf action) { ComponentPlaceholder placeholder = getActivePlaceholder(provider); if (placeholder != null) { - actionManager.removeLocalAction(action); placeholder.removeAction(action); } } @@ -679,22 +657,43 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder throw new IllegalArgumentException("Unknown component provider: " + provider); } placeholder.addAction(action); - actionManager.addLocalAction(action, provider); } void addToolAction(DockingActionIf action) { - actionManager.addToolAction(action); + actionToGuiMapper.addToolAction(action); scheduleUpdate(); } void removeToolAction(DockingActionIf action) { - actionManager.removeToolAction(action); + actionToGuiMapper.removeToolAction(action); scheduleUpdate(); } + /** + * Returns any action that is bound to the given keystroke for the tool associated with this + * DockingWindowManager instance. + * @param keyStroke The keystroke to check for key bindings. + * @return The action that is bound to the keystroke, or null of there is no binding for the + * given keystroke. + */ + Action getActionForKeyStroke(KeyStroke keyStroke) { + DockingToolActions toolActions = tool.getToolActions(); + if (toolActions instanceof ToolActions) { + // Using a cast here; it didn't make sense to include this 'getAction' on the + // DockingToolActions + return ((ToolActions) toolActions).getAction(keyStroke); + } + return null; + } + //================================================================================================== // End Package-level Methods -//================================================================================================== +//================================================================================================== + + public void ownerRemoved(String owner) { + placeholderManager.removeAll(owner); + scheduleUpdate(); + } /** * Hides or shows the component associated with the given provider. @@ -711,6 +710,15 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder ComponentPlaceholder placeholder = getActivePlaceholder(provider); if (placeholder != null) { showComponent(placeholder, visibleState, true); + return; + } + + if (visibleState) { + + // a null placeholder implies the client is trying to show a provider that has not + // been added to the tool + Msg.warn(this, "Attempting to show an unknown Component Provider '" + + provider.getName() + "' - " + "check that the provider has been added to the tool"); } } @@ -791,7 +799,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder KeyboardFocusManager mgr = KeyboardFocusManager.getCurrentKeyboardFocusManager(); mgr.removePropertyChangeListener("permanentFocusOwner", this); - actionManager.dispose(); + actionToGuiMapper.dispose(); root.dispose(); placeholderManager.disposePlaceholders(); @@ -990,11 +998,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(); - actionManager.removeLocalAction(action); - } + placeholder.removeAllActions(); ComponentNode node = placeholder.getNode(); if (node == null) { @@ -1084,10 +1088,12 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder return; } - actionManager.removeAll(DOCKING_WINDOWS_OWNER); + 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(); @@ -1097,21 +1103,21 @@ 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(); - actionManager.update(); + actionToGuiMapper.update(); } private boolean isWindowMenuShowing() { @@ -1138,47 +1144,45 @@ 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)); } } + + DockingToolActions toolActions = tool.getToolActions(); Collections.sort(actionList); for (ShowComponentAction action : actionList) { - actionManager.addToolAction(action); + toolActions.addGlobalAction(action); } } - 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<>(); @@ -1189,9 +1193,10 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder } } + DockingToolActions toolActions = tool.getToolActions(); Collections.sort(actions); for (ShowWindowAction action : actions) { - actionManager.addToolAction(action); + toolActions.addGlobalAction(action); } } @@ -1199,6 +1204,9 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder * Notifies the window manager that an update is needed */ void scheduleUpdate() { + if (rebuildUpdater.isBusy()) { + return; + } rebuildUpdater.updateLater(); } @@ -1216,7 +1224,6 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder } root.update(); // do this before rebuilding the menu, as new windows may be opened - buildComponentMenu(); SystemUtilities.runSwingLater(() -> updateFocus()); } @@ -1387,7 +1394,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder if (root == null) { return; } - actionManager.setActive(active); + actionToGuiMapper.setActive(active); if (active) { setActiveManager(this); if (focusedPlaceholder != null && root.getWindow(focusedPlaceholder) == window) { @@ -1938,7 +1945,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder * when we know that an update is not need, as we are in the middle of an update. */ void doSetMenuGroup(String[] menuPath, String group) { - actionManager.setMenuGroup(menuPath, group); + actionToGuiMapper.setMenuGroup(menuPath, group); } /** @@ -1954,7 +1961,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder * its level */ public void setMenuGroup(String[] menuPath, String group, String menuSubGroup) { - actionManager.setMenuGroup(menuPath, group, menuSubGroup); + actionToGuiMapper.setMenuGroup(menuPath, group, menuSubGroup); scheduleUpdate(); } @@ -2060,7 +2067,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder public void contextChanged(ComponentProvider provider) { if (provider == null) { - actionManager.contextChangedAll(); // update all windows; + actionToGuiMapper.contextChangedAll(); // update all windows; return; } @@ -2069,7 +2076,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder return; } placeHolder.contextChanged(); - actionManager.contextChanged(placeHolder); + actionToGuiMapper.contextChanged(placeHolder); } public void addContextListener(DockingContextListener listener) { @@ -2080,8 +2087,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder contextListeners.remove(listener); } - public void notifyContextListeners(ComponentPlaceholder placeHolder, - ActionContext actionContext) { + void notifyContextListeners(ComponentPlaceholder placeHolder, ActionContext actionContext) { if (placeHolder == focusedPlaceholder) { for (DockingContextListener listener : contextListeners) { diff --git a/Ghidra/Framework/Docking/src/main/java/docking/GlobalMenuAndToolBarManager.java b/Ghidra/Framework/Docking/src/main/java/docking/GlobalMenuAndToolBarManager.java index 9be430b4dc..4ae713f1ed 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/GlobalMenuAndToolBarManager.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/GlobalMenuAndToolBarManager.java @@ -17,11 +17,10 @@ package docking; import java.util.*; -import javax.swing.SwingUtilities; - import docking.action.DockingActionIf; import docking.menu.MenuGroupMap; import docking.menu.MenuHandler; +import ghidra.util.Swing; public class GlobalMenuAndToolBarManager implements DockingWindowListener { @@ -66,6 +65,17 @@ public class GlobalMenuAndToolBarManager implements DockingWindowListener { } } + public DockingActionIf getToolbarAction(String actionName) { + + for (WindowActionManager actionManager : windowToActionManagerMap.values()) { + DockingActionIf action = actionManager.getToolbarAction(actionName); + if (action != null) { + return action; + } + } + return null; + } + public void dispose() { for (WindowActionManager actionManager : windowToActionManagerMap.values()) { actionManager.dispose(); @@ -130,7 +140,7 @@ public class GlobalMenuAndToolBarManager implements DockingWindowListener { } private List getActionsForWindow(WindowNode windowNode) { - ActionToGuiMapper actionManager = windowManager.getActionManager(); + ActionToGuiMapper actionManager = windowManager.getActionToGuiMapper(); Collection globalActions = actionManager.getGlobalActions(); List actionsForWindow = new ArrayList<>(globalActions.size()); Set> contextTypes = windowNode.getContextTypes(); @@ -143,12 +153,7 @@ public class GlobalMenuAndToolBarManager implements DockingWindowListener { } public void contextChangedAll() { - if (SwingUtilities.isEventDispatchThread()) { - updateAllWindowActions(); - } - else { - SwingUtilities.invokeLater(() -> updateAllWindowActions()); - } + Swing.runIfSwingOrRunLater(this::updateAllWindowActions); } private void updateAllWindowActions() { diff --git a/Ghidra/Framework/Docking/src/main/java/docking/KeyBindingOverrideKeyEventDispatcher.java b/Ghidra/Framework/Docking/src/main/java/docking/KeyBindingOverrideKeyEventDispatcher.java index d1f6bffeb3..f14da83cc4 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/KeyBindingOverrideKeyEventDispatcher.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/KeyBindingOverrideKeyEventDispatcher.java @@ -425,7 +425,9 @@ class KeyBindingOverrideKeyEventDispatcher implements KeyEventDispatcher { } KeyStroke keyStroke = KeyStroke.getKeyStrokeForEvent(event); - return (DockingKeyBindingAction) activeManager.getActionForKeyStroke(keyStroke); + DockingKeyBindingAction bindingAction = + (DockingKeyBindingAction) activeManager.getActionForKeyStroke(keyStroke); + return bindingAction; } private DockingWindowManager getActiveDockingWindowManager() { diff --git a/Ghidra/Framework/Docking/src/main/java/docking/KeyEntryTextField.java b/Ghidra/Framework/Docking/src/main/java/docking/KeyEntryTextField.java index ac66d7c496..b7fd0b239e 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/KeyEntryTextField.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/KeyEntryTextField.java @@ -1,6 +1,5 @@ /* ### * 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. @@ -22,6 +21,8 @@ import java.awt.event.KeyListener; import javax.swing.JTextField; import javax.swing.KeyStroke; +import docking.actions.KeyBindingUtils; + /** * Text field captures key strokes and notifies a listener to process the key entry. */ @@ -43,21 +44,31 @@ public class KeyEntryTextField extends JTextField { } /** - * Get the current key stroke. + * Get the current key stroke + * @return the key stroke */ - public KeyStroke getCurrentKeyStroke() { + public KeyStroke getKeyStroke() { return currentKeyStroke; } /** - * Converts the toString() form of the keyStroke, e.g., Ctrl-M - * is returned as "keyCode CtrlM-P" and we want it to look like: - * "Ctrl-M" - * @param ks the keystroke to parse. - * @return the parse string for the keystroke. + * Sets the current key stroke + * @param ks the new key stroke + */ + public void setKeyStroke(KeyStroke ks) { + processEntry(ks); + setText(parseKeyStroke(ks)); + } + + /** + * Converts the toString() form of the keyStroke, e.g., Ctrl-M is returned as + * "keyCode CtrlM-P" and we want it to look like: "Ctrl-M" + * + * @param ks the keystroke to parse + * @return the parse string for the keystroke */ public static String parseKeyStroke(KeyStroke ks) { - return DockingKeyBindingAction.parseKeyStroke(ks); + return KeyBindingUtils.parseKeyStroke(ks); } public void clearField() { @@ -66,8 +77,6 @@ public class KeyEntryTextField extends JTextField { currentKeyStroke = null; } - ////////////////////////////////////////////////////////////////////// - private void processEntry(KeyStroke ks) { ksName = null; @@ -78,7 +87,7 @@ public class KeyEntryTextField extends JTextField { char keyChar = ks.getKeyChar(); if (!Character.isWhitespace(keyChar) && Character.getType(keyChar) != Character.DIRECTIONALITY_LEFT_TO_RIGHT_OVERRIDE) { - ksName = DockingKeyBindingAction.parseKeyStroke(ks); + ksName = KeyBindingUtils.parseKeyStroke(ks); } } listener.processEntry(ks); @@ -111,7 +120,7 @@ public class KeyEntryTextField extends JTextField { KeyStroke keyStroke = null; if (!isClearKey(keyCode) && !isModifiersOnly(e)) { - keyStroke = KeyStroke.getKeyStroke(keyCode, e.getModifiersEx() | e.getModifiers()); + keyStroke = KeyStroke.getKeyStroke(keyCode, e.getModifiersEx()); } processEntry(keyStroke); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/PopupActionManager.java b/Ghidra/Framework/Docking/src/main/java/docking/PopupActionManager.java index e2ab0d0060..12fd797548 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/PopupActionManager.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/PopupActionManager.java @@ -101,7 +101,7 @@ public class PopupActionManager implements PropertyChangeListener { Object source = actionContext.getSourceObject(); if (source instanceof DockingActionProviderIf) { DockingActionProviderIf actionProvider = (DockingActionProviderIf) source; - List dockingActions = actionProvider.getDockingActions(actionContext); + List dockingActions = actionProvider.getDockingActions(); for (DockingActionIf action : dockingActions) { MenuData popupMenuData = action.getPopupMenuData(); if (popupMenuData != null && action.isValidContext(actionContext) && diff --git a/Ghidra/Framework/Docking/src/main/java/docking/RootNode.java b/Ghidra/Framework/Docking/src/main/java/docking/RootNode.java index 0876dcf3ea..94456dd833 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/RootNode.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/RootNode.java @@ -319,7 +319,7 @@ class RootNode extends WindowNode { invalid = false; } - winMgr.getActionManager().update(); + winMgr.getActionToGuiMapper().update(); windowWrapper.validate(); } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/ShowComponentAction.java b/Ghidra/Framework/Docking/src/main/java/docking/ShowComponentAction.java index ea07e1b565..569a19691a 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/ShowComponentAction.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/ShowComponentAction.java @@ -18,8 +18,7 @@ package docking; import javax.swing.Icon; import javax.swing.ImageIcon; -import docking.action.DockingAction; -import docking.action.MenuData; +import docking.action.*; import ghidra.util.HelpLocation; import resources.ResourceManager; @@ -33,8 +32,10 @@ class ShowComponentAction extends DockingAction implements Comparable clazz = info.getProvider().getClass(); + ComponentProvider provider = info.getProvider(); + Class clazz = provider.getClass(); String className = clazz.getName(); String filename = className.substring(className.lastIndexOf('.') + 1); - String javaName = filename + ".java"; - buffy.append(" ").append("PROVIDER: ").append(info.getName()).append(' '); - buffy.append('(').append(javaName).append(":1)"); + DockingActionIf showAction = provider.getShowProviderAction(); + String realInception = showAction.getInceptionInformation(); + buffy.append(" ").append(realInception).append("\n "); + + buffy.append(" ").append("PROVIDER: ").append(filename).append(' '); + buffy.append('(').append(provider.getOwner()).append(")"); buffy.append("\n "); return buffy.toString(); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/WindowActionManager.java b/Ghidra/Framework/Docking/src/main/java/docking/WindowActionManager.java index 565047c6d0..e2232c1d94 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/WindowActionManager.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/WindowActionManager.java @@ -72,6 +72,10 @@ public class WindowActionManager { } } + public DockingActionIf getToolbarAction(String actionName) { + return toolBarMgr.getAction(actionName); + } + public void update() { JMenuBar menuBar = menuBarMgr.getMenuBar(); if (menuBar.getMenuCount() > 0) { @@ -92,9 +96,6 @@ public class WindowActionManager { toolBarMgr.dispose(); } - /** - * Notifies the window manager that an action context update is needed. - */ synchronized void contextChanged(ComponentPlaceholder placeHolder) { placeHolderForScheduledActionUpdate = placeHolder; diff --git a/Ghidra/Framework/Docking/src/main/java/docking/action/DockingAction.java b/Ghidra/Framework/Docking/src/main/java/docking/action/DockingAction.java index 63fecf7ea9..c3e3e76e9b 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/action/DockingAction.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/action/DockingAction.java @@ -17,6 +17,7 @@ package docking.action; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; +import java.util.Objects; import java.util.Set; import javax.swing.*; @@ -63,8 +64,8 @@ public abstract class DockingAction implements DockingActionIf { private String inceptionInformation; private boolean isEnabled = true; - private boolean isKeyBindingManaged = true; + private KeyBindingType keyBindingType = KeyBindingType.INDIVIDUAL; private KeyBindingData defaultKeyBindingData; private KeyBindingData keyBindingData; private MenuBarData menuBarData; @@ -72,19 +73,29 @@ public abstract class DockingAction implements DockingActionIf { private ToolBarData toolBarData; public DockingAction(String name, String owner) { - this(name, owner, true); - } - - public DockingAction(String name, String owner, boolean isKeyBindingManaged) { this.name = name; this.owner = owner; - this.isKeyBindingManaged = isKeyBindingManaged; recordInception(); HelpLocation location = new HelpLocation(owner, name, inceptionInformation); setHelpLocation(location); } + public DockingAction(String name, String owner, KeyBindingType kbType) { + this(name, owner); + this.keyBindingType = Objects.requireNonNull(kbType); + } + + public DockingAction(String name, String owner, boolean supportsKeyBindings) { + this(name, owner); + this.keyBindingType = + supportsKeyBindings ? KeyBindingType.INDIVIDUAL : KeyBindingType.UNSUPPORTED; + } + + protected KeyBindingType getPreferredKeyBindingType() { + return KeyBindingType.INDIVIDUAL; + } + @Override public abstract void actionPerformed(ActionContext context); @@ -98,11 +109,6 @@ public abstract class DockingAction implements DockingActionIf { propertyListeners.remove(listener); } - @Override - public boolean isKeyBindingManaged() { - return isKeyBindingManaged; - } - @Override public String getDescription() { return description; @@ -258,6 +264,11 @@ public abstract class DockingAction implements DockingActionIf { return menuItem; } + @Override + public KeyBindingType getKeyBindingType() { + return keyBindingType; + } + @Override public KeyStroke getKeyBinding() { return keyBindingData == null ? null : keyBindingData.getKeyBinding(); @@ -489,7 +500,7 @@ public abstract class DockingAction implements DockingActionIf { inceptionInformation = getInceptionFromTheFirstClassThatIsNotUs(); } - private String getInceptionFromTheFirstClassThatIsNotUs() { + protected String getInceptionFromTheFirstClassThatIsNotUs() { Throwable t = ReflectionUtilities.createThrowableWithStackOlderThan(getClass()); StackTraceElement[] trace = t.getStackTrace(); String classInfo = trace[0].toString(); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/action/DockingActionIf.java b/Ghidra/Framework/Docking/src/main/java/docking/action/DockingActionIf.java index b763806f84..dc35f51912 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/action/DockingActionIf.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/action/DockingActionIf.java @@ -23,6 +23,17 @@ import javax.swing.*; import docking.ActionContext; import docking.help.HelpDescriptor; +/** + * The base interface for clients that wish to create commands to be registered with a tool. + * + *

    An action may appear in a primary menu, a popup menu or a toolbar. Further, an action + * may have a key binding assigned. + * + *

    The particular support for key bindings is defined by {@link KeyBindingType}. Almost all + * client actions will use the default setting of {@link KeyBindingType#INDIVIDUAL}. To control + * the level of key binding support, you can pass the desired {@link KeyBindingType} to the + * base implementation of this interface. + */ public interface DockingActionIf extends HelpDescriptor { public static final String ENABLEMENT_PROPERTY = "enabled"; public static final String GLOBALCONTEXT_PROPERTY = "globalContext"; @@ -44,6 +55,16 @@ public interface DockingActionIf extends HelpDescriptor { */ public String getOwner(); + /** + * Returns a description of this actions owner. For most actions this will return the + * same value as {@link #getOwner()}. + * + * @return the description + */ + public default String getOwnerDescription() { + return getOwner(); + } + /** * Returns a short description of this action. Generally used for a tooltip * @return the description @@ -248,10 +269,17 @@ public interface DockingActionIf extends HelpDescriptor { public boolean shouldAddToWindow(boolean isMainWindow, Set> contextTypes); /** - * Returns true if this action can have its keybinding information changed by the user. - * @return true if this action can have its keybinding information changed by the user. + * Returns this actions level of support for key binding accelerator keys + * + *

    Actions support key bindings by default. Some reserved actions do not support + * key bindings, while others wish to share the same key bindings with multiple, equivalent + * actions (this allows the user to set one binding that works in many different contexts). + * + * @return the key binding support */ - public boolean isKeyBindingManaged(); + public default KeyBindingType getKeyBindingType() { + return KeyBindingType.INDIVIDUAL; + } /** * Sets the {@link KeyBindingData} on an action to either assign a keybinding or remove it @@ -272,22 +300,4 @@ public interface DockingActionIf extends HelpDescriptor { * @param newKeyBindingData the KeyBindingData to be used to assign this action to a keybinding */ public void setUnvalidatedKeyBindingData(KeyBindingData newKeyBindingData); - - /** - * Returns true if this action shares a keybinding with other actions. If this returns true, - * then this action, and any action that shares a name with this action, will be updated - * to the same key binding value whenever the key binding options change. - * - *

    This will be false for the vast majority of actions. If you are unsure if your action - * should use a shared keybinding, then do not set this value to true. - * - *

    This value is not meant to change over the life of the action. Thus, there is no - * set method to change this value. Rather, you should override this method - * to return true as desired. - * - * @return true to share a shared keybinding - */ - public default boolean usesSharedKeyBinding() { - return false; - } } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/action/DockingActionProviderIf.java b/Ghidra/Framework/Docking/src/main/java/docking/action/DockingActionProviderIf.java index 3f2cffd4b3..176987d272 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/action/DockingActionProviderIf.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/action/DockingActionProviderIf.java @@ -1,6 +1,5 @@ /* ### * 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. @@ -18,8 +17,6 @@ package docking.action; import java.util.List; -import docking.ActionContext; - /** * An interface for objects (really Components) to implement that signals they provide actions * for the Docking environment. This interface will be called when the implementor is the source @@ -32,9 +29,9 @@ import docking.ActionContext; */ public interface DockingActionProviderIf { - /** - * Returns actions that are compatible with the given context. - * @param context the current context of the Docking system - */ - public List getDockingActions( ActionContext context ); + /** + * Returns actions that are compatible with the given context. + * @return the actions + */ + public List getDockingActions(); } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/action/KeyBindingData.java b/Ghidra/Framework/Docking/src/main/java/docking/action/KeyBindingData.java index 6191a2de75..dee9dc6a60 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/action/KeyBindingData.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/action/KeyBindingData.java @@ -15,12 +15,10 @@ */ package docking.action; -import java.awt.event.*; - import javax.swing.KeyStroke; -import docking.DockingUtils; import docking.KeyBindingPrecedence; +import docking.actions.KeyBindingUtils; public class KeyBindingData { private KeyStroke keyStroke; @@ -49,6 +47,7 @@ public class KeyBindingData { /** * Returns an accelerator keystroke to be associated with this action. + * @return the binding */ public KeyStroke getKeyBinding() { return keyStroke; @@ -56,6 +55,7 @@ public class KeyBindingData { /** * Returns the keyBindingPrecedence for this action + * @return the precedence */ public KeyBindingPrecedence getKeyBindingPrecedence() { return keyBindingPrecedence; @@ -80,6 +80,10 @@ public class KeyBindingData { * @return the potentially changed data */ public static KeyBindingData validateKeyBindingData(KeyBindingData newKeyBindingData) { + if (newKeyBindingData == null) { + return null; + } + KeyStroke keyBinding = newKeyBindingData.getKeyBinding(); if (keyBinding == null) { // not sure when this can happen @@ -88,63 +92,8 @@ public class KeyBindingData { KeyBindingPrecedence precedence = newKeyBindingData.getKeyBindingPrecedence(); if (precedence == KeyBindingPrecedence.ReservedActionsLevel) { - return createReservedKeyBindingData(validateKeyStroke(keyBinding)); + return createReservedKeyBindingData(KeyBindingUtils.validateKeyStroke(keyBinding)); } - return new KeyBindingData(validateKeyStroke(keyBinding), precedence); - } - - /** - * Updates the given data with system-independent versions of key modifiers. For example, - * the control key will be converted to the command key on the Mac. - * - * @param keyStroke the keystroke to validate - * @return the potentially changed keystroke - */ - public static KeyStroke validateKeyStroke(KeyStroke keyStroke) { - if (keyStroke == null) { - return null; - } - - // remove system-dependent control key mask and transform deprecated modifiers - int modifiers = keyStroke.getModifiers(); - if ((modifiers & InputEvent.CTRL_DOWN_MASK) == InputEvent.CTRL_DOWN_MASK) { - modifiers = modifiers ^ InputEvent.CTRL_DOWN_MASK; - modifiers = modifiers | DockingUtils.CONTROL_KEY_MODIFIER_MASK; - } - - if ((modifiers & InputEvent.CTRL_MASK) == InputEvent.CTRL_MASK) { - modifiers = modifiers ^ InputEvent.CTRL_MASK; - modifiers = modifiers | DockingUtils.CONTROL_KEY_MODIFIER_MASK; - } - - if ((modifiers & ActionEvent.CTRL_MASK) == ActionEvent.CTRL_MASK) { - modifiers = modifiers ^ ActionEvent.CTRL_MASK; - modifiers = modifiers | DockingUtils.CONTROL_KEY_MODIFIER_MASK; - } - - if ((modifiers & InputEvent.SHIFT_MASK) == InputEvent.SHIFT_MASK) { - modifiers = modifiers ^ InputEvent.SHIFT_MASK; - modifiers = modifiers | InputEvent.SHIFT_DOWN_MASK; - } - - if ((modifiers & InputEvent.ALT_MASK) == InputEvent.ALT_MASK) { - modifiers = modifiers ^ InputEvent.ALT_MASK; - modifiers = modifiers | InputEvent.ALT_DOWN_MASK; - } - - if ((modifiers & InputEvent.META_MASK) == InputEvent.META_MASK) { - modifiers = modifiers ^ InputEvent.META_MASK; - modifiers = modifiers | InputEvent.META_DOWN_MASK; - } - - int eventType = keyStroke.getKeyEventType(); - if (eventType == KeyEvent.KEY_TYPED) { - // we know that typed events have a key code of VK_UNDEFINED - return KeyStroke.getKeyStroke(keyStroke.getKeyChar(), modifiers); - } - - // key pressed or released - boolean isOnKeyRelease = keyStroke.isOnKeyRelease(); - return KeyStroke.getKeyStroke(keyStroke.getKeyCode(), modifiers, isOnKeyRelease); + return new KeyBindingData(KeyBindingUtils.validateKeyStroke(keyBinding), precedence); } } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/action/KeyBindingType.java b/Ghidra/Framework/Docking/src/main/java/docking/action/KeyBindingType.java new file mode 100644 index 0000000000..b026d3a6c1 --- /dev/null +++ b/Ghidra/Framework/Docking/src/main/java/docking/action/KeyBindingType.java @@ -0,0 +1,82 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package docking.action; + +/** + * Allows clients to signal their support for the assigning of key binding shortcut keys. Most + * action clients need not be concerned with this class. The default settings of + * {@link DockingAction} work correctly for almost all cases, which is to have the action + * support individual key bindings, which are managed by the system via the UI. + * + * @see DockingActionIf + */ +public enum KeyBindingType { + + //@formatter:off + /** + * Indicates the setting of key bindings through the UI is not supported + */ + UNSUPPORTED, + + /** + * Supports the assignment of key bindings via the UI. Setting a key binding on an action + * with this type will not affect any other action. + */ + INDIVIDUAL, + + /** + * When the key binding is set via the UI, this action, and any action that shares a + * name with this action, will be updated to the same key binding value whenever the key + * binding options change. + * + *

    Most actions will not be shared. If you are unsure if your action + * should use a shared keybinding, then do not do so. + */ + + SHARED; + //@formatter:on + + /** + * Returns true if this type supports key bindings. This is a convenience method for + * checking that this type is not {@link #UNSUPPORTED}. + * @return true if key bindings are supported + */ + public boolean supportsKeyBindings() { + return this != UNSUPPORTED; + } + + /** + * Convenience method for checking if this type is the {@link #SHARED} type + * @return true if shared + */ + public boolean isShared() { + return this == SHARED; + } + + /** + * A convenience method for clients to check whether this key binding type should be + * managed directly by the system. + * + *

    Shared actions are not managed directly by the system, but are instead managed through + * a proxy action. + * + * @return true if managed directly by the system; false if key binding are not supported + * or are managed through a proxy + */ + public boolean isManaged() { + return this == INDIVIDUAL; + } +} diff --git a/Ghidra/Framework/Docking/src/main/java/docking/KeyBindingsManager.java b/Ghidra/Framework/Docking/src/main/java/docking/action/KeyBindingsManager.java similarity index 80% rename from Ghidra/Framework/Docking/src/main/java/docking/KeyBindingsManager.java rename to Ghidra/Framework/Docking/src/main/java/docking/action/KeyBindingsManager.java index 7814a2b2a3..17e59d9247 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/KeyBindingsManager.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/action/KeyBindingsManager.java @@ -1,6 +1,5 @@ /* ### * 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. @@ -14,34 +13,33 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package docking; - -import ghidra.util.ReservedKeyBindings; -import ghidra.util.exception.AssertException; +package docking.action; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; -import java.util.*; +import java.util.HashMap; +import java.util.Map; import javax.swing.Action; import javax.swing.KeyStroke; -import docking.action.*; +import docking.*; +import ghidra.util.ReservedKeyBindings; +import ghidra.util.exception.AssertException; public class KeyBindingsManager implements PropertyChangeListener { protected Map dockingKeyMap; protected Map actionToProviderMap; + private DockingTool tool; - private DockingWindowManager winMgr; - - public KeyBindingsManager(DockingWindowManager winMgr) { - this.winMgr = winMgr; - dockingKeyMap = new HashMap(); - actionToProviderMap = new HashMap(); + public KeyBindingsManager(DockingTool tool) { + this.tool = tool; + dockingKeyMap = new HashMap<>(); + actionToProviderMap = new HashMap<>(); } - public void addAction(DockingActionIf action, ComponentProvider optionalProvider) { + public void addAction(ComponentProvider optionalProvider, DockingActionIf action) { action.addPropertyChangeListener(this); if (optionalProvider != null) { actionToProviderMap.put(action, optionalProvider); @@ -50,7 +48,7 @@ public class KeyBindingsManager implements PropertyChangeListener { KeyStroke keyBinding = action.getKeyBinding(); if (keyBinding != null) { - addKeyBinding(action, optionalProvider, keyBinding); + addKeyBinding(optionalProvider, action, keyBinding); } } @@ -59,13 +57,17 @@ public class KeyBindingsManager implements PropertyChangeListener { addReservedKeyBinding(action, keyBinding); } + public void addReservedAction(DockingActionIf action, KeyStroke ks) { + addReservedKeyBinding(action, ks); + } + public void removeAction(DockingActionIf action) { action.removePropertyChangeListener(this); actionToProviderMap.remove(action); removeKeyBinding(action.getKeyBinding(), action); } - private void addKeyBinding(DockingActionIf action, ComponentProvider provider, + private void addKeyBinding(ComponentProvider provider, DockingActionIf action, KeyStroke keyStroke) { if (ReservedKeyBindings.isReservedKeystroke(keyStroke)) { throw new AssertException("Cannot assign action to a reserved keystroke. " + @@ -74,7 +76,7 @@ public class KeyBindingsManager implements PropertyChangeListener { DockingKeyBindingAction existingAction = dockingKeyMap.get(keyStroke); if (existingAction == null) { - dockingKeyMap.put(keyStroke, new MultipleKeyAction(winMgr, provider, action, keyStroke)); + dockingKeyMap.put(keyStroke, new MultipleKeyAction(tool, provider, action, keyStroke)); return; } @@ -93,7 +95,9 @@ public class KeyBindingsManager implements PropertyChangeListener { "action to a given keystroke: " + keyStroke); } - dockingKeyMap.put(keyStroke, new ReservedKeyBindingAction(winMgr, action, keyStroke)); + KeyBindingData binding = KeyBindingData.createReservedKeyBindingData(keyStroke); + action.setKeyBindingData(binding); + dockingKeyMap.put(keyStroke, new ReservedKeyBindingAction(tool, action, keyStroke)); } /** @@ -141,23 +145,17 @@ public class KeyBindingsManager implements PropertyChangeListener { if (newKeyData != null) { KeyStroke ks = newKeyData.getKeyBinding(); if (ks != null) { - addKeyBinding(action, actionToProviderMap.get(action), ks); + addKeyBinding(actionToProviderMap.get(action), action, ks); } } } - public List getLocalActions() { - return new ArrayList(actionToProviderMap.keySet()); - } - public Action getDockingKeyAction(KeyStroke keyStroke) { return dockingKeyMap.get(keyStroke); } public void dispose() { - winMgr = null; dockingKeyMap.clear(); actionToProviderMap.clear(); } - } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/action/MultipleKeyAction.java b/Ghidra/Framework/Docking/src/main/java/docking/action/MultipleKeyAction.java index a57fc703df..d7bc3a5395 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/action/MultipleKeyAction.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/action/MultipleKeyAction.java @@ -21,6 +21,7 @@ import java.util.*; import javax.swing.*; import docking.*; +import docking.actions.KeyBindingUtils; import ghidra.util.Swing; /** @@ -31,35 +32,17 @@ public class MultipleKeyAction extends DockingKeyBindingAction { private ActionDialog dialog; - class ActionData { - DockingActionIf action; - ComponentProvider provider; - - ActionData(DockingActionIf action, ComponentProvider provider) { - this.action = action; - this.provider = provider; - } - - boolean isGlobalAction() { - return provider == null; - } - - boolean isMyProvider(ComponentProvider otherProvider) { - return provider == otherProvider; - } - } - /** * Creates new MultipleKeyAction * - * @param winMgr window manager used to determine context. + * @param tool used to determine context * @param provider the provider, if any, associated with the action * @param action action that will be added to the list of actions bound to a keystroke * @param keyStroke the keystroke, if any, associated with the action */ - public MultipleKeyAction(DockingWindowManager winMgr, ComponentProvider provider, - DockingActionIf action, KeyStroke keyStroke) { - super(winMgr, action, keyStroke); + public MultipleKeyAction(DockingTool tool, ComponentProvider provider, DockingActionIf action, + KeyStroke keyStroke) { + super(tool, action, keyStroke); addAction(provider, action); } @@ -80,6 +63,7 @@ public class MultipleKeyAction extends DockingKeyBindingAction { throw new IllegalArgumentException( "KeyStrokes don't match - was: " + keyStroke + " new: " + keyBinding); } + actions.add(new ActionData(action, provider)); } @@ -133,11 +117,11 @@ public class MultipleKeyAction extends DockingKeyBindingAction { @Override public void actionPerformed(final ActionEvent event) { // Build list of actions which are valid in current context - ComponentProvider localProvider = winMgr.getActiveComponentProvider(); + ComponentProvider localProvider = tool.getActiveComponentProvider(); ActionContext localContext = getLocalContext(localProvider); localContext.setSource(event.getSource()); - ActionContext globalContext = winMgr.getGlobalContext(); + ActionContext globalContext = tool.getGlobalContext(); List list = getValidContextActions(localContext, globalContext); // If menu active, disable all key bindings @@ -149,7 +133,7 @@ public class MultipleKeyAction extends DockingKeyBindingAction { if (list.size() > 1) { // popup dialog to show multiple actions if (dialog == null) { - dialog = new ActionDialog(parseKeyStroke(keyStroke), list); + dialog = new ActionDialog(KeyBindingUtils.parseKeyStroke(keyStroke), list); } else { dialog.setActionList(list); @@ -162,12 +146,12 @@ public class MultipleKeyAction extends DockingKeyBindingAction { } else if (list.size() == 1) { final ExecutableKeyActionAdapter actionProxy = list.get(0); - winMgr.setStatusText(""); + tool.setStatusInfo(""); actionProxy.execute(); } else { String name = (String) getValue(Action.NAME); - winMgr.setStatusText("Action (" + name + ") not valid in this context!", true); + tool.setStatusInfo("Action (" + name + ") not valid in this context!", true); } } @@ -229,9 +213,9 @@ public class MultipleKeyAction extends DockingKeyBindingAction { @Override public KeyBindingPrecedence getKeyBindingPrecedence() { - ComponentProvider localProvider = winMgr.getActiveComponentProvider(); + ComponentProvider localProvider = tool.getActiveComponentProvider(); ActionContext localContext = getLocalContext(localProvider); - ActionContext globalContext = winMgr.getGlobalContext(); + ActionContext globalContext = tool.getGlobalContext(); List validActions = getValidContextActions(localContext, globalContext); @@ -273,4 +257,27 @@ public class MultipleKeyAction extends DockingKeyBindingAction { return buildy.toString(); } + + private class ActionData { + DockingActionIf action; + ComponentProvider provider; + + ActionData(DockingActionIf action, ComponentProvider provider) { + this.action = action; + this.provider = provider; + } + + boolean isGlobalAction() { + return provider == null; + } + + boolean isMyProvider(ComponentProvider otherProvider) { + return provider == otherProvider; + } + + @Override + public String toString() { + return provider.toString() + " - " + action; + } + } } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/ReservedKeyBindingAction.java b/Ghidra/Framework/Docking/src/main/java/docking/action/ReservedKeyBindingAction.java similarity index 72% rename from Ghidra/Framework/Docking/src/main/java/docking/ReservedKeyBindingAction.java rename to Ghidra/Framework/Docking/src/main/java/docking/action/ReservedKeyBindingAction.java index ded83c401f..2d69b23cd0 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/ReservedKeyBindingAction.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/action/ReservedKeyBindingAction.java @@ -1,6 +1,5 @@ /* ### * 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. @@ -14,21 +13,25 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package docking; +package docking.action; import javax.swing.KeyStroke; -import docking.action.DockingActionIf; +import docking.*; class ReservedKeyBindingAction extends DockingKeyBindingAction { - ReservedKeyBindingAction(DockingWindowManager winMgr, DockingActionIf action, - KeyStroke keyStroke) { - super(winMgr, action, keyStroke); + ReservedKeyBindingAction(DockingTool tool, DockingActionIf action, KeyStroke keyStroke) { + super(tool, action, keyStroke); } @Override public boolean isReservedKeybindingPrecedence() { return true; } + + @Override + public KeyBindingPrecedence getKeyBindingPrecedence() { + return KeyBindingPrecedence.ReservedActionsLevel; + } } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/action/ToggleDockingAction.java b/Ghidra/Framework/Docking/src/main/java/docking/action/ToggleDockingAction.java index a7f0180220..fd1361a5d2 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/action/ToggleDockingAction.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/action/ToggleDockingAction.java @@ -1,6 +1,5 @@ /* ### * 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. @@ -29,14 +28,16 @@ public abstract class ToggleDockingAction extends DockingAction implements Toggl super(name, owner); } - public ToggleDockingAction(String name, String owner, boolean isKeybindingManaged) { - super(name, owner, isKeybindingManaged); + public ToggleDockingAction(String name, String owner, boolean supportsKeyBindings) { + super(name, owner, supportsKeyBindings); } + @Override public boolean isSelected() { return isSelected; } + @Override public void setSelected(boolean newValue) { isSelected = newValue; firePropertyChanged(SELECTED_STATE_PROPERTY, !isSelected, isSelected); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/actions/DockingToolActions.java b/Ghidra/Framework/Docking/src/main/java/docking/actions/DockingToolActions.java new file mode 100644 index 0000000000..2de77f96e0 --- /dev/null +++ b/Ghidra/Framework/Docking/src/main/java/docking/actions/DockingToolActions.java @@ -0,0 +1,95 @@ +/* ### + * 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 docking.actions; + +import java.util.Set; + +import docking.ComponentProvider; +import docking.action.DockingActionIf; + +/** + * Represents the collection of actions registered with the tool, along with method for adding + * and removing actions. + */ +public interface DockingToolActions { + + /** + * Adds the given action that enabled when the given provider is active + * + * @param provider the provider + * @param action the action + */ + public void addLocalAction(ComponentProvider provider, DockingActionIf action); + + /** + * Gets the provider action by the given name + * + * @param provider the provider + * @param actionName the action name + * @return the action + */ + public DockingActionIf getLocalAction(ComponentProvider provider, String actionName); + + /** + * Removes the given provider's local action + * + * @param provider the provider + * @param action the action + */ + public void removeLocalAction(ComponentProvider provider, DockingActionIf action); + + /** + * Adds the given action that is enabled, regardless of the active provider + * + * @param action the action + */ + public void addGlobalAction(DockingActionIf action); + + /** + * Removes the given global action + * @param action the action + */ + public void removeGlobalAction(DockingActionIf action); + + /** + * Removes all global actions for the given owner + * + * @param owner the owner + */ + public void removeActions(String owner); + + /** + * Removes all local actions for the given provider + * + * @param provider the provider + */ + public void removeActions(ComponentProvider provider); + + /** + * Returns all actions with the given owner + * + * @param owner the owner + * @return the actions + */ + public Set getActions(String owner); + + /** + * Returns all actions known to the tool + * @return the actions + */ + public Set getAllActions(); + +} diff --git a/Ghidra/Framework/Docking/src/main/java/docking/action/KeyBindingAction.java b/Ghidra/Framework/Docking/src/main/java/docking/actions/KeyBindingAction.java similarity index 65% rename from Ghidra/Framework/Docking/src/main/java/docking/action/KeyBindingAction.java rename to Ghidra/Framework/Docking/src/main/java/docking/actions/KeyBindingAction.java index fb8c979f7b..9857879a72 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/action/KeyBindingAction.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/actions/KeyBindingAction.java @@ -13,24 +13,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package docking.action; +package docking.actions; import java.awt.Component; -import java.util.Set; -import docking.*; -import docking.actions.KeyBindingUtils; +import docking.ActionContext; +import docking.DockingWindowManager; +import docking.action.*; import ghidra.util.Msg; -import ghidra.util.ReservedKeyBindings; public class KeyBindingAction extends DockingAction { - private final ActionToGuiMapper dockingActionManager; - public KeyBindingAction(ActionToGuiMapper dockingActionManager) { - super("Set KeyBinding", DockingWindowManager.DOCKING_WINDOWS_OWNER); - this.dockingActionManager = dockingActionManager; - createReservedKeyBinding(ReservedKeyBindings.UPDATE_KEY_BINDINGS_KEY); - setEnabled(true); + public static String NAME = "Set KeyBinding"; + private ToolActions toolActions; + + public KeyBindingAction(ToolActions toolActions) { + super(NAME, DockingWindowManager.DOCKING_WINDOWS_OWNER); + this.toolActions = toolActions; // Help actions don't have help DockingWindowManager.getHelpService().excludeFromHelp(this); @@ -50,15 +49,14 @@ public class KeyBindingAction extends DockingAction { action = maybeGetToolLevelAction(action); - if (!action.isKeyBindingManaged()) { + 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; } - KeyEntryDialog d = new KeyEntryDialog(action, dockingActionManager); + KeyEntryDialog d = new KeyEntryDialog(action, toolActions); DockingWindowManager.showDialog(d); } @@ -69,17 +67,15 @@ public class KeyBindingAction extends DockingAction { * @return A tool-level action if one is found; otherwise, the original action */ private DockingActionIf maybeGetToolLevelAction(DockingActionIf dockingAction) { - if (dockingAction.isKeyBindingManaged()) { - return dockingAction; - } - // It is not key binding managed, which means that it may be a shared key binding - String actionName = dockingAction.getName(); - Set allActions = dockingActionManager.getAllActions(); - DockingActionIf sharedAction = - KeyBindingUtils.getSharedKeyBindingAction(allActions, actionName); - if (sharedAction != null) { - return sharedAction; + if (dockingAction.getKeyBindingType().isShared()) { + + // It is not key binding managed, which means that it may be a shared key binding + String actionName = dockingAction.getName(); + DockingActionIf sharedAction = toolActions.getSharedStubKeyBindingAction(actionName); + if (sharedAction != null) { + return sharedAction; + } } return dockingAction; 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 026782d5bf..3651117ef5 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/actions/KeyBindingUtils.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/actions/KeyBindingUtils.java @@ -15,13 +15,17 @@ */ package docking.actions; +import static org.apache.commons.lang3.StringUtils.indexOfIgnoreCase; + import java.awt.Component; import java.awt.KeyboardFocusManager; +import java.awt.event.*; import java.io.*; import java.util.*; import javax.swing.*; +import org.apache.commons.collections4.map.LazyMap; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.jdom.*; @@ -31,6 +35,7 @@ import org.jdom.output.XMLOutputter; import com.google.common.collect.Sets; import docking.DockingTool; +import docking.DockingUtils; import docking.action.*; import docking.widgets.filechooser.GhidraFileChooser; import ghidra.framework.options.ToolOptions; @@ -53,10 +58,20 @@ import utilities.util.reflection.ReflectionUtilities; public class KeyBindingUtils { private static final String LAST_KEY_BINDING_EXPORT_DIRECTORY = "LastKeyBindingExportDirectory"; + private static final String RELEASED = "released"; + private static final String TYPED = "typed"; + private static final String PRESSED = "pressed"; + + private static final String SHIFT = "Shift"; + private static final String CTRL = "Ctrl"; + private static final String CONTROL = "Control"; + private static final String ALT = "Alt"; + private static final String META = "Meta"; + private static final String MODIFIER_SEPARATOR = "-"; + private static final Logger log = LogManager.getLogger(KeyBindingUtils.class); public static final String PREFERENCES_FILE_EXTENSION = ".kbxml"; - private static final GhidraFileFilter FILE_FILTER = new GhidraFileFilter() { @Override public boolean accept(File pathname, GhidraFileChooserModel model) { @@ -330,15 +345,21 @@ public class KeyBindingUtils { } /** - * A utility method to get all key binding actions. This method will remove duplicate - * actions and will only return actions that are {@link DockingActionIf#isKeyBindingManaged()} + * A utility method to get all key binding actions. This method will + * only return actions that support {@link KeyBindingType key bindings}. + * + *

    The mapping returned provides a list of items because it is possible for there to + * exists multiple actions with the same name and owner. (This can happen when multiple copies + * of a component provider are shown, each with their own set of actions that share the + * same name.) * * @param tool the tool containing the actions * @return the actions mapped by their full name (e.g., 'Name (OwnerName)') */ - public static Map getAllActionsByFullName(DockingTool tool) { + public static Map> getAllActionsByFullName(DockingTool tool) { - Map deduper = new HashMap<>(); + Map> result = + LazyMap.lazyMap(new HashMap<>(), s -> new LinkedList<>()); Set actions = tool.getAllActions(); for (DockingActionIf action : actions) { if (isIgnored(action)) { @@ -348,16 +369,16 @@ public class KeyBindingUtils { continue; } - deduper.put(action.getFullName(), action); + result.get(action.getFullName()).add(action); } - return deduper; + return result; } /** * A utility method to get all key binding actions that have the given owner. * This method will remove duplicate actions and will only return actions - * that are {@link DockingActionIf#isKeyBindingManaged()} + * that support {@link KeyBindingType key bindings}. * * @param tool the tool containing the actions * @param owner the action owner name @@ -398,36 +419,6 @@ public class KeyBindingUtils { return Sets.filter(ownerMatch, action -> action.getName().equals(name)); } - /** - * A method to locate the {@link SharedStubKeyBindingAction} representative for the given - * action name. This method is not useful to general clients. - * - * @param allActions all actions in the system - * @param sharedName the name of the shared action - * @return the shared action representative - */ - public static DockingActionIf getSharedKeyBindingAction(Set allActions, - String sharedName) { - - String owner = "Tool"; - for (DockingActionIf action : allActions) { - if (!(action instanceof SharedStubKeyBindingAction)) { - continue; - } - - if (action.getOwner().equals(owner) && action.getName().equals(sharedName)) { - return action; - } - } - return null; - } - - private static boolean isIgnored(DockingActionIf action) { - // not keybinding managed; a shared keybinding implies that this action should not be in - // the UI, as there will be a single proxy in place of all actions sharing that binding - return !action.isKeyBindingManaged() || action.usesSharedKeyBinding(); - } - /** * Takes the existing docking action and allows it to be registered with * Swing components @@ -455,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)) { @@ -493,6 +492,252 @@ public class KeyBindingUtils { Msg.warn(KeyBindingUtils.class, s, ReflectionUtilities.createJavaFilteredThrowable()); } + /** + * Updates the given data with system-independent versions of key modifiers. For example, + * the control key will be converted to the command key on the Mac. + * + * @param keyStroke the keystroke to validate + * @return the potentially changed keystroke + */ + // TODO ignore the deprecation, as this method is responsible for fixing deprecated usage. + // When all actions no longer user the deprecated modifiers, the deprecated elements + // of this method can be removed + @SuppressWarnings("deprecation") + public static KeyStroke validateKeyStroke(KeyStroke keyStroke) { + if (keyStroke == null) { + return null; + } + + // remove system-dependent control key mask and transform deprecated modifiers + int modifiers = keyStroke.getModifiers(); + if ((modifiers & InputEvent.CTRL_DOWN_MASK) == InputEvent.CTRL_DOWN_MASK) { + modifiers = modifiers ^ InputEvent.CTRL_DOWN_MASK; + modifiers = modifiers | DockingUtils.CONTROL_KEY_MODIFIER_MASK; + } + + if ((modifiers & InputEvent.CTRL_MASK) == InputEvent.CTRL_MASK) { + modifiers = modifiers ^ InputEvent.CTRL_MASK; + modifiers = modifiers | DockingUtils.CONTROL_KEY_MODIFIER_MASK; + } + + if ((modifiers & ActionEvent.CTRL_MASK) == ActionEvent.CTRL_MASK) { + modifiers = modifiers ^ ActionEvent.CTRL_MASK; + modifiers = modifiers | DockingUtils.CONTROL_KEY_MODIFIER_MASK; + } + + if ((modifiers & InputEvent.SHIFT_MASK) == InputEvent.SHIFT_MASK) { + modifiers = modifiers ^ InputEvent.SHIFT_MASK; + modifiers = modifiers | InputEvent.SHIFT_DOWN_MASK; + } + + if ((modifiers & InputEvent.ALT_MASK) == InputEvent.ALT_MASK) { + modifiers = modifiers ^ InputEvent.ALT_MASK; + modifiers = modifiers | InputEvent.ALT_DOWN_MASK; + } + + if ((modifiers & InputEvent.META_MASK) == InputEvent.META_MASK) { + modifiers = modifiers ^ InputEvent.META_MASK; + modifiers = modifiers | InputEvent.META_DOWN_MASK; + } + + int eventType = keyStroke.getKeyEventType(); + if (eventType == KeyEvent.KEY_TYPED) { + // we know that typed events have a key code of VK_UNDEFINED + return KeyStroke.getKeyStroke(Character.valueOf(keyStroke.getKeyChar()), modifiers); + } + + // key pressed or released + boolean isOnKeyRelease = keyStroke.isOnKeyRelease(); + return KeyStroke.getKeyStroke(keyStroke.getKeyCode(), modifiers, isOnKeyRelease); + } + + /** + * Convert the toString() form of the keyStroke. + *
    In Java 1.4.2 & earlier, Ctrl-M is returned as "keyCode CtrlM-P" + * and we want it to look like: "Ctrl-M". + *
    In Java 1.5.0, Ctrl-M is returned as "ctrl pressed M" + * and we want it to look like: "Ctrl-M". + * + * @param keyStroke the key stroke + * @return the string value; the empty string if the key stroke is null + */ + public static String parseKeyStroke(KeyStroke keyStroke) { + + if (keyStroke == null) { + return ""; + } + + final String keyPressSuffix = "-P"; + String keyString = keyStroke.toString(); + int type = keyStroke.getKeyEventType(); + if (type == KeyEvent.KEY_TYPED) { + return String.valueOf(keyStroke.getKeyChar()); + } + + // get the character used in the key stroke + int firstIndex = keyString.lastIndexOf(' ') + 1; + int ctrlIndex = keyString.indexOf(CTRL, firstIndex); + if (ctrlIndex >= 0) { + firstIndex = ctrlIndex + CTRL.length(); + } + int altIndex = keyString.indexOf(ALT, firstIndex); + if (altIndex >= 0) { + firstIndex = altIndex + ALT.length(); + } + int shiftIndex = keyString.indexOf(SHIFT, firstIndex); + if (shiftIndex >= 0) { + firstIndex = shiftIndex + SHIFT.length(); + } + int metaIndex = keyString.indexOf(META, firstIndex); + if (metaIndex >= 0) { + firstIndex = metaIndex + META.length(); + } + + int lastIndex = keyString.length(); + if (keyString.endsWith(keyPressSuffix)) { + lastIndex -= keyPressSuffix.length(); + } + if (lastIndex >= 0) { + keyString = keyString.substring(firstIndex, lastIndex); + } + + int modifiers = keyStroke.getModifiers(); + StringBuilder buffy = new StringBuilder(); + if (isShift(modifiers)) { + buffy.insert(0, SHIFT + MODIFIER_SEPARATOR); + } + if (isAlt(modifiers)) { + buffy.insert(0, ALT + MODIFIER_SEPARATOR); + } + if (isControl(modifiers)) { + buffy.insert(0, CTRL + MODIFIER_SEPARATOR); + } + if (isMeta(modifiers)) { + buffy.insert(0, META + MODIFIER_SEPARATOR); + } + buffy.append(keyString); + return buffy.toString(); + } + + // ignore the deprecated; remove when we are confident that all tool actions no longer use the + // deprecated InputEvent mask types + @SuppressWarnings("deprecation") + private static boolean isShift(int mask) { + return (mask & InputEvent.SHIFT_DOWN_MASK) != 0 || (mask & InputEvent.SHIFT_MASK) != 0; + } + + // ignore the deprecated; remove when we are confident that all tool actions no longer use the + // deprecated InputEvent mask types + @SuppressWarnings("deprecation") + private static boolean isAlt(int mask) { + return (mask & InputEvent.ALT_DOWN_MASK) != 0 || (mask & InputEvent.ALT_MASK) != 0; + } + + // ignore the deprecated; remove when we are confident that all tool actions no longer use the + // deprecated InputEvent mask types + @SuppressWarnings("deprecation") + private static boolean isControl(int mask) { + return (mask & InputEvent.CTRL_DOWN_MASK) != 0 || (mask & InputEvent.CTRL_MASK) != 0; + } + + // ignore the deprecated; remove when we are confident that all tool actions no longer use the + // deprecated InputEvent mask types + @SuppressWarnings("deprecation") + private static boolean isMeta(int mask) { + return (mask & InputEvent.META_DOWN_MASK) != 0 || (mask & InputEvent.META_MASK) != 0; + } + + /** + * Parses the given text into a KeyStroke. This method relies upon + * {@link KeyStroke#getKeyStroke(String)} for parsing. Before making that call, this method + * will perform fixup on the given text for added flexibility. For example, the given + * text may contain spaces or dashes as the separators between parts in the string. Also, + * the text is converted such that it is not case-sensitive. So, the following example + * formats are allowed: + *

    +	 *    Alt-F
    +	 *    alt p
    +	 *    Ctrl-Alt-Z
    +	 *    ctrl Z
    +	 * 
    + * + * @param keyStroke + * @return the new key stroke (as returned by {@link KeyStroke#getKeyStroke(String)} + */ + public static KeyStroke parseKeyStroke(String keyStroke) { + List pieces = new ArrayList<>(); + StringTokenizer tokenizer = new StringTokenizer(keyStroke, "- "); + while (tokenizer.hasMoreTokens()) { + String token = tokenizer.nextToken(); + if (!pieces.contains(token)) { + pieces.add(token); + } + } + + StringBuilder buffy = new StringBuilder(); + for (Iterator iterator = pieces.iterator(); iterator.hasNext();) { + String piece = iterator.next(); + if (indexOfIgnoreCase(piece, SHIFT) != -1) { + buffy.append("shift "); + iterator.remove(); + } + else if (indexOfIgnoreCase(piece, CTRL) != -1) { + buffy.append("ctrl "); + iterator.remove(); + } + else if (indexOfIgnoreCase(piece, CONTROL) != -1) { + buffy.append("ctrl "); + iterator.remove(); + } + else if (indexOfIgnoreCase(piece, ALT) != -1) { + buffy.append("alt "); + iterator.remove(); + } + else if (indexOfIgnoreCase(piece, META) != -1) { + buffy.append("meta "); + iterator.remove(); + } + else if (indexOfIgnoreCase(piece, PRESSED) != -1) { + iterator.remove(); + } + else if (indexOfIgnoreCase(piece, TYPED) != -1) { + iterator.remove(); + } + else if (indexOfIgnoreCase(piece, RELEASED) != -1) { + iterator.remove(); + } + + } + + buffy.append(PRESSED).append(' '); + + // at this point we should only have left one piece--the key ID + int leftover = pieces.size(); + if (leftover > 1 || leftover == 0) { + Msg.warn(KeyBindingUtils.class, "Invalid keystroke string found. Expected " + + "format of '[modifier] ... key'. Found: '" + keyStroke + "'"); + + if (leftover == 0) { + return null; // nothing to do + } + } + + String key = pieces.get(0); + buffy.append(key.toUpperCase()); + + return KeyStroke.getKeyStroke(buffy.toString()); + } + +//================================================================================================== +// Private Methods +//================================================================================================== + + private static boolean isIgnored(DockingActionIf action) { + // a shared keybinding implies that this action should not be in + // the UI, as there will be a single proxy in place of all actions sharing that binding + return !action.getKeyBindingType().isManaged(); + } + private static KeyStroke getKeyStroke(KeyBindingData data) { if (data == null) { return null; @@ -500,12 +745,7 @@ public class KeyBindingUtils { return data.getKeyBinding(); } -//================================================================================================== -// Private Methods -//================================================================================================== - - // prompts the user for a file location from which to read key binding - // data + // prompts the user for a file location from which to read key binding data private static InputStream getInputStreamForFile(File startingDir) { File selectedFile = getFileFromUser(startingDir); @@ -587,4 +827,5 @@ public class KeyBindingUtils { return selectedFile; } + } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/action/KeyEntryDialog.java b/Ghidra/Framework/Docking/src/main/java/docking/actions/KeyEntryDialog.java similarity index 83% rename from Ghidra/Framework/Docking/src/main/java/docking/action/KeyEntryDialog.java rename to Ghidra/Framework/Docking/src/main/java/docking/actions/KeyEntryDialog.java index f415c740f9..110c3a22f6 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/action/KeyEntryDialog.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/actions/KeyEntryDialog.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package docking.action; +package docking.actions; import java.awt.*; import java.util.*; @@ -22,8 +22,9 @@ import java.util.List; import javax.swing.*; import javax.swing.text.*; -import docking.*; -import docking.actions.KeyBindingUtils; +import docking.DialogComponentProvider; +import docking.KeyEntryTextField; +import docking.action.*; import docking.widgets.label.GIconLabel; import ghidra.util.HelpLocation; import ghidra.util.ReservedKeyBindings; @@ -35,7 +36,7 @@ import resources.ResourceManager; */ public class KeyEntryDialog extends DialogComponentProvider { - private ActionToGuiMapper actionManager; + private ToolActions toolActions; private DockingActionIf action; private JPanel defaultPanel; private KeyEntryTextField keyEntryField; @@ -45,10 +46,10 @@ public class KeyEntryDialog extends DialogComponentProvider { private SimpleAttributeSet textAttrSet; private Color bgColor; - public KeyEntryDialog(DockingActionIf action, ActionToGuiMapper actionManager) { + public KeyEntryDialog(DockingActionIf action, ToolActions actions) { super("Set Key Binding for " + action.getName(), true); - this.actionManager = actionManager; this.action = action; + this.toolActions = actions; setUpAttributes(); createPanel(); KeyStroke keyBinding = action.getKeyBinding(); @@ -105,7 +106,7 @@ public class KeyEntryDialog extends DialogComponentProvider { p.add(keyEntryField); KeyStroke keyBinding = action.getKeyBinding(); if (keyBinding != null) { - keyEntryField.setText(DockingKeyBindingAction.parseKeyStroke(keyBinding)); + keyEntryField.setText(KeyBindingUtils.parseKeyStroke(keyBinding)); } setFocusComponent(keyEntryField); defaultPanel.add(p, BorderLayout.CENTER); @@ -129,6 +130,14 @@ public class KeyEntryDialog extends DialogComponentProvider { return p; } + /** + * Sets the given keystroke value into the text field of this dialog + * @param ks the keystroke to set + */ + public void setKeyStroke(KeyStroke ks) { + keyEntryField.setKeyStroke(ks); + } + @Override protected void cancelCallback() { close(); @@ -136,23 +145,24 @@ public class KeyEntryDialog extends DialogComponentProvider { @Override protected void okCallback() { - KeyStroke keyStroke = keyEntryField.getCurrentKeyStroke(); - if (keyStroke != null && ReservedKeyBindings.isReservedKeystroke(keyStroke)) { + KeyStroke newKeyStroke = keyEntryField.getKeyStroke(); + if (newKeyStroke != null && ReservedKeyBindings.isReservedKeystroke(newKeyStroke)) { setStatusText(keyEntryField.getText() + " is a reserved keystroke"); return; } clearStatusText(); - Set allActions = actionManager.getAllActions(); - Set actions = - KeyBindingUtils.getActions(allActions, action.getOwner(), action.getName()); - for (DockingActionIf element : actions) { - if (element.isKeyBindingManaged()) { - element.setUnvalidatedKeyBindingData(new KeyBindingData(keyStroke)); - } + KeyStroke existingKeyStroke = action.getKeyBinding(); + if (Objects.equals(existingKeyStroke, newKeyStroke)) { + setStatusText("Key binding unchanged"); + return; } + action.setUnvalidatedKeyBindingData(new KeyBindingData(newKeyStroke)); + + toolActions.keyBindingsChanged(); + close(); } @@ -168,23 +178,30 @@ public class KeyEntryDialog extends DialogComponentProvider { } private void updateCollisionPane(KeyStroke ks) { + clearStatusText(); collisionPane.setText(""); if (ks == null) { return; } + KeyStroke existingKeyStroke = action.getKeyBinding(); + if (Objects.equals(existingKeyStroke, ks)) { + setStatusText("Key binding unchanged"); + return; + } + List list = getManagedActionsForKeyStroke(ks); if (list.size() == 0) { return; } - String ksName = DockingKeyBindingAction.parseKeyStroke(ks); + String ksName = KeyBindingUtils.parseKeyStroke(ks); try { doc.insertString(0, "Actions mapped to " + ksName + "\n\n", textAttrSet); for (int i = 0; i < list.size(); i++) { DockingActionIf a = list.get(i); - String collisionStr = "\t" + a.getName() + " (" + a.getOwner() + ")\n"; + String collisionStr = "\t" + a.getName() + " (" + a.getOwnerDescription() + ")\n"; int offset = doc.getLength(); doc.insertString(offset, collisionStr, textAttrSet); doc.setParagraphAttributes(offset, 1, tabAttrSet, false); @@ -202,6 +219,7 @@ public class KeyEntryDialog extends DialogComponentProvider { if (multiAction == null) { return Collections.emptyList(); } + List list = multiAction.getActions(); Map nameMap = new HashMap<>(list.size()); @@ -218,7 +236,7 @@ public class KeyEntryDialog extends DialogComponentProvider { } private MultipleKeyAction getMultipleKeyAction(KeyStroke ks) { - Action keyAction = actionManager.getDockingKeyAction(ks); + Action keyAction = toolActions.getAction(ks); if (keyAction instanceof MultipleKeyAction) { return (MultipleKeyAction) keyAction; } @@ -226,11 +244,6 @@ public class KeyEntryDialog extends DialogComponentProvider { } private boolean shouldAddAction(DockingActionIf dockableAction) { - if (dockableAction.isKeyBindingManaged()) { - return true; - } - - // shared key bindings are handled specially - return !dockableAction.usesSharedKeyBinding(); + return dockableAction.getKeyBindingType().isManaged(); } } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/actions/SharedStubKeyBindingAction.java b/Ghidra/Framework/Docking/src/main/java/docking/actions/SharedStubKeyBindingAction.java index f8362089da..d16ebce648 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/actions/SharedStubKeyBindingAction.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/actions/SharedStubKeyBindingAction.java @@ -20,6 +20,8 @@ import java.util.Map.Entry; import javax.swing.KeyStroke; +import org.apache.commons.lang3.StringUtils; + import docking.ActionContext; import docking.DockingWindowManager; import docking.action.*; @@ -83,6 +85,33 @@ public class SharedStubKeyBindingAction extends DockingAction implements Options updateActionKeyStrokeFromOptions(action, defaultKs); } + @Override + public String getOwnerDescription() { + List owners = getDistinctOwners(); + Collections.sort(owners); + if (owners.size() == 1) { + return owners.get(0); + } + return StringUtils.join(owners, ", "); + } + + private List getDistinctOwners() { + List results = new ArrayList<>(); + Set actions = clientActions.keySet(); + for (DockingActionIf action : actions) { + String owner = action.getOwner(); + if (DockingWindowManager.DOCKING_WINDOWS_OWNER.equals(owner)) { + // special case: this is the owner for special system-level actions + continue; + } + + if (!results.contains(owner)) { + results.add(owner); + } + } + return results; + } + private KeyStroke validateActionsHaveTheSameDefaultKeyStroke(DockingActionIf newAction) { // this value may be null @@ -116,12 +145,16 @@ public class SharedStubKeyBindingAction extends DockingAction implements Options private void updateActionKeyStrokeFromOptions(DockingActionIf action, KeyStroke defaultKs) { + KeyStroke stubKs = defaultKs; KeyStroke optionsKs = getKeyStrokeFromOptions(defaultKs); if (!Objects.equals(defaultKs, optionsKs)) { // we use the 'unvalidated' call since this value is provided by the user--we assume // that user input is correct; we only validate programmer input action.setUnvalidatedKeyBindingData(new KeyBindingData(optionsKs)); + stubKs = optionsKs; } + + setUnvalidatedKeyBindingData(new KeyBindingData(stubKs)); } private KeyStroke getKeyStrokeFromOptions(KeyStroke validatedKeyStroke) { diff --git a/Ghidra/Framework/Docking/src/main/java/docking/actions/ToolActions.java b/Ghidra/Framework/Docking/src/main/java/docking/actions/ToolActions.java index 545f20a659..12d701837e 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/actions/ToolActions.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/actions/ToolActions.java @@ -19,23 +19,29 @@ import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.*; +import javax.swing.Action; import javax.swing.KeyStroke; +import org.apache.commons.collections4.IteratorUtils; +import org.apache.commons.collections4.Predicate; import org.apache.commons.collections4.map.LazyMap; +import com.google.common.collect.Iterators; + import docking.*; import docking.action.*; import docking.tool.util.DockingToolConstants; import ghidra.framework.options.*; +import ghidra.util.ReservedKeyBindings; +import ghidra.util.SystemUtilities; import ghidra.util.exception.AssertException; import util.CollectionUtils; /** * An class to manage actions registered with the tool */ -public class ToolActions implements PropertyChangeListener { +public class ToolActions implements DockingToolActions, PropertyChangeListener { - private DockingWindowManager winMgr; private ActionToGuiHelper actionGuiHelper; /* @@ -51,24 +57,44 @@ public class ToolActions implements PropertyChangeListener { private ToolOptions keyBindingOptions; private DockingTool dockingTool; + private KeyBindingsManager keyBindingsManager; /** * Construct an ActionManager * * @param tool tool using this ActionManager - * @param windowManager manager of the "Docking" arrangement of a set of components - * and actions in the tool + * @param actionToGuiHelper the class that takes actions and maps them to GUI widgets */ - public ToolActions(DockingTool tool, DockingWindowManager windowManager) { + public ToolActions(DockingTool tool, ActionToGuiHelper actionToGuiHelper) { this.dockingTool = tool; - this.winMgr = windowManager; - this.actionGuiHelper = new ActionToGuiHelper(winMgr); - keyBindingOptions = tool.getOptions(DockingToolConstants.KEY_BINDINGS); + this.actionGuiHelper = actionToGuiHelper; + this.keyBindingsManager = new KeyBindingsManager(tool); + this.keyBindingOptions = tool.getOptions(DockingToolConstants.KEY_BINDINGS); + + createReservedKeyBindings(); + } + + private void createReservedKeyBindings() { + KeyBindingAction keyBindingAction = new KeyBindingAction(this); + keyBindingsManager.addReservedAction(keyBindingAction, + ReservedKeyBindings.UPDATE_KEY_BINDINGS_KEY); + + keyBindingsManager.addReservedAction(new HelpAction(false, ReservedKeyBindings.HELP_KEY1)); + keyBindingsManager.addReservedAction(new HelpAction(false, ReservedKeyBindings.HELP_KEY2)); + keyBindingsManager.addReservedAction( + new HelpAction(true, ReservedKeyBindings.HELP_INFO_KEY)); + + // these are diagnostic + if (SystemUtilities.isInDevelopmentMode()) { + keyBindingsManager.addReservedAction(new ShowFocusInfoAction()); + keyBindingsManager.addReservedAction(new ShowFocusCycleAction()); + } } public void dispose() { actionsByNameByOwner.clear(); sharedActionMap.clear(); + keyBindingsManager.dispose(); } private void addActionToMap(DockingActionIf action) { @@ -83,34 +109,35 @@ public class ToolActions implements PropertyChangeListener { * @param provider provider associated with the action * @param action local action to the provider */ + @Override public synchronized void addLocalAction(ComponentProvider provider, DockingActionIf action) { checkForAlreadyAddedAction(provider, action); action.addPropertyChangeListener(this); addActionToMap(action); - setKeyBindingOption(action); + initializeKeyBinding(provider, action); actionGuiHelper.addLocalAction(provider, action); } - /** - * Adds the action to the tool. - * @param action the action to be added. - */ - public synchronized void addToolAction(DockingActionIf action) { + @Override + public synchronized void addGlobalAction(DockingActionIf action) { + checkForAlreadyAddedAction(null, action); + action.addPropertyChangeListener(this); addActionToMap(action); - setKeyBindingOption(action); + initializeKeyBinding(null, action); actionGuiHelper.addToolAction(action); } - private void setKeyBindingOption(DockingActionIf action) { + private void initializeKeyBinding(ComponentProvider provider, DockingActionIf action) { - if (action.usesSharedKeyBinding()) { - installSharedKeyBinding(action); + KeyBindingType type = action.getKeyBindingType(); + if (!type.supportsKeyBindings()) { return; } - if (!action.isKeyBindingManaged()) { + if (type.isShared()) { + installSharedKeyBinding(provider, action); return; } @@ -121,9 +148,11 @@ public class ToolActions implements PropertyChangeListener { if (!Objects.equals(ks, newKs)) { action.setUnvalidatedKeyBindingData(new KeyBindingData(newKs)); } + + keyBindingsManager.addAction(provider, action); } - private void installSharedKeyBinding(DockingActionIf action) { + private void installSharedKeyBinding(ComponentProvider provider, DockingActionIf action) { String name = action.getName(); KeyStroke defaultKeyStroke = action.getKeyBinding(); @@ -132,29 +161,31 @@ public class ToolActions implements PropertyChangeListener { SharedStubKeyBindingAction newStub = new SharedStubKeyBindingAction(name, keyBindingOptions); + newStub.addPropertyChangeListener(this); keyBindingOptions.registerOption(newStub.getFullName(), OptionType.KEYSTROKE_TYPE, defaultKeyStroke, null, null); return newStub; }); stub.addClientAction(action); + + // note: only put the stub in the manager, not the actual action + keyBindingsManager.addAction(provider, stub); } /** * Removes the given action from the tool * @param action the action to be removed. */ - public synchronized void removeToolAction(DockingActionIf action) { + @Override + public synchronized void removeGlobalAction(DockingActionIf action) { action.removePropertyChangeListener(this); removeAction(action); actionGuiHelper.removeToolAction(action); } - /** - * Remove all actions that have the given owner. - * @param owner owner of the actions to remove - */ - public synchronized void removeToolActions(String owner) { + @Override + public synchronized void removeActions(String owner) { // remove from the outer map first, to prevent concurrent modification exceptions Map> toCleanup = actionsByNameByOwner.remove(owner); @@ -166,15 +197,17 @@ public class ToolActions implements PropertyChangeListener { toCleanup.values() .stream() .flatMap(set -> set.stream()) - .forEach(action -> removeToolAction(action)) + .forEach(action -> removeGlobalAction(action)) ; //@formatter:on } private void checkForAlreadyAddedAction(ComponentProvider provider, DockingActionIf action) { if (getActionStorage(action).contains(action)) { - throw new AssertException("Cannot add the same action more than once. Provider " + - provider.getName() + " - action: " + action.getFullName()); + String providerString = + provider == null ? "Action: " : "Provider " + provider.getName() + " - action: "; + throw new AssertException("Cannot add the same action more than once. " + + providerString + action.getFullName()); } } @@ -184,6 +217,7 @@ public class ToolActions implements PropertyChangeListener { * @return array of actions; zero length array is returned if no * action exists with the given name */ + @Override public synchronized Set getActions(String owner) { Set result = new HashSet<>(); @@ -201,8 +235,10 @@ public class ToolActions implements PropertyChangeListener { /** * Get a set of all actions in the tool - * @return the actions + * + * @return a new set of the existing actions */ + @Override public synchronized Set getAllActions() { Set result = new HashSet<>(); @@ -218,56 +254,83 @@ public class ToolActions implements PropertyChangeListener { return result; } + private Iterator getAllActionsIterator() { + + // chain all items together, rather than copy the data + Iterator iterator = IteratorUtils.emptyIterator(); + Collection>> maps = actionsByNameByOwner.values(); + for (Map> actionsByName : maps) { + for (Set actions : actionsByName.values()) { + Iterator next = actions.iterator(); + + // Note: do not use apache commons here--the code below degrades exponentially + //iterator = IteratorUtils.chainedIterator(iterator, next); + iterator = Iterators.concat(iterator, next); + } + } + + return Iterators.concat(iterator, sharedActionMap.values().iterator()); + } + /** * Get the keybindings for each action so that they are still registered as being used; * otherwise the options will be removed because they are noted as not being used. */ public synchronized void restoreKeyBindings() { keyBindingOptions = dockingTool.getOptions(DockingToolConstants.KEY_BINDINGS); - Set actions = getAllActions(); - for (DockingActionIf action : actions) { - if (!action.isKeyBindingManaged()) { - continue; - } + + Iterator it = getKeyBindingActionsIterator(); + for (DockingActionIf action : CollectionUtils.asIterable(it)) { KeyStroke ks = action.getKeyBinding(); KeyStroke newKs = keyBindingOptions.getKeyStroke(action.getFullName(), ks); - if (ks != newKs) { + if (!Objects.equals(ks, newKs)) { action.setUnvalidatedKeyBindingData(new KeyBindingData(newKs)); } } } + // return only actions that allow key bindings + private Iterator getKeyBindingActionsIterator() { + Predicate filter = a -> a.getKeyBindingType() == KeyBindingType.INDIVIDUAL; + return IteratorUtils.filteredIterator(getAllActionsIterator(), filter); + } + /** * Remove an action that works specifically with a component provider. * @param provider provider associated with the action * @param action local action to the provider */ - public synchronized void removeProviderAction(ComponentProvider provider, - DockingActionIf action) { + @Override + public synchronized void removeLocalAction(ComponentProvider provider, DockingActionIf action) { action.removePropertyChangeListener(this); removeAction(action); + keyBindingsManager.removeAction(action); actionGuiHelper.removeProviderAction(provider, action); } - /** - * Get the actions for the given provider and remove them from the action map - * @param provider provider whose actions are to be removed - */ - public synchronized void removeComponentActions(ComponentProvider provider) { + @Override + public synchronized void removeActions(ComponentProvider provider) { Iterator it = actionGuiHelper.getComponentActions(provider); + + // copy the data to avoid concurrent modification exceptions Set set = CollectionUtils.asSet(it); for (DockingActionIf action : set) { - removeProviderAction(provider, action); + removeLocalAction(provider, action); } } private void removeAction(DockingActionIf action) { + + keyBindingsManager.removeAction(action); + getActionStorage(action).remove(action); - if (action.usesSharedKeyBinding()) { - SharedStubKeyBindingAction stub = sharedActionMap.get(action.getName()); - if (stub != null) { - stub.removeClientAction(action); - } + if (!action.getKeyBindingType().isShared()) { + return; + } + + SharedStubKeyBindingAction stub = sharedActionMap.get(action.getName()); + if (stub != null) { + stub.removeClientAction(action); } } @@ -279,27 +342,59 @@ public class ToolActions implements PropertyChangeListener { @Override public void propertyChange(PropertyChangeEvent evt) { - if (evt.getPropertyName().equals(DockingActionIf.KEYBINDING_DATA_PROPERTY)) { - DockingAction action = (DockingAction) evt.getSource(); - if (!action.isKeyBindingManaged()) { - dockingTool.setConfigChanged(true); - return; - } - KeyBindingData keyBindingData = (KeyBindingData) evt.getNewValue(); - KeyStroke newKeyStroke = keyBindingData.getKeyBinding(); - Options opt = dockingTool.getOptions(DockingToolConstants.KEY_BINDINGS); - KeyStroke optKeyStroke = opt.getKeyStroke(action.getFullName(), null); - if (newKeyStroke == null) { - opt.removeOption(action.getFullName()); - } - else if (!newKeyStroke.equals(optKeyStroke)) { - opt.setKeyStroke(action.getFullName(), newKeyStroke); - dockingTool.setConfigChanged(true); + if (!evt.getPropertyName().equals(DockingActionIf.KEYBINDING_DATA_PROPERTY)) { + return; + } + + DockingAction action = (DockingAction) evt.getSource(); + if (!action.getKeyBindingType().isManaged()) { + // this reads unusually, but we need to notify the tool to rebuild its 'Window' menu + // in the case that this action is one of the tool's special actions + keyBindingsChanged(); + return; + } + + KeyBindingData newKeyBindingData = (KeyBindingData) evt.getNewValue(); + KeyStroke newKeyStroke = null; + if (newKeyBindingData != null) { + newKeyStroke = newKeyBindingData.getKeyBinding(); + } + + Options opt = dockingTool.getOptions(DockingToolConstants.KEY_BINDINGS); + KeyStroke optKeyStroke = opt.getKeyStroke(action.getFullName(), null); + if (newKeyStroke == null) { + opt.removeOption(action.getFullName()); + } + else if (!newKeyStroke.equals(optKeyStroke)) { + opt.setKeyStroke(action.getFullName(), newKeyStroke); + keyBindingsChanged(); + } + } + + @Override + public DockingActionIf getLocalAction(ComponentProvider provider, String actionName) { + + Iterator it = actionGuiHelper.getComponentActions(provider); + while (it.hasNext()) { + DockingActionIf action = it.next(); + if (action.getName().equals(actionName)) { + return action; } } + return null; + } + + public Action getAction(KeyStroke ks) { + return keyBindingsManager.getDockingKeyAction(ks); } DockingActionIf getSharedStubKeyBindingAction(String name) { return sharedActionMap.get(name); } + + // triggered by a user-initiated action + void keyBindingsChanged() { + dockingTool.setConfigChanged(true); + actionGuiHelper.keyBindingsChanged(); + } } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/menu/MenuBarManager.java b/Ghidra/Framework/Docking/src/main/java/docking/menu/MenuBarManager.java index 2c661e6478..8726327506 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/menu/MenuBarManager.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/menu/MenuBarManager.java @@ -1,6 +1,5 @@ /* ### * 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. @@ -24,7 +23,7 @@ import docking.action.DockingActionIf; import docking.action.MenuData; /** - * Manages the main menu bar on the main frame. + * Manages the main menu bar on the main frame */ public class MenuBarManager implements MenuGroupListener { @@ -32,36 +31,41 @@ public class MenuBarManager implements MenuGroupListener { private Map menuManagers; private final MenuGroupMap menuGroupMap; - /** - * Constructs a new MenuBarManager - */ public MenuBarManager(MenuHandler actionHandler, MenuGroupMap menuGroupMap) { this.menuGroupMap = menuGroupMap; - menuManagers = new TreeMap(); + menuManagers = new TreeMap<>(); this.menuHandler = actionHandler; } public void clearActions() { - menuManagers = new TreeMap(); + menuManagers = new TreeMap<>(); } /** - * Adds an action to the menu. - * @param action the action to be added. - * @param groupMgr the MenuGroupMap + * Adds an action to the menu + * @param action the action to be added */ public void addAction(DockingActionIf action) { + MenuManager menuManager = getMenuManager(action); + if (menuManager == null) { + return; + } + + menuManager.addAction(action); + } + + private MenuManager getMenuManager(DockingActionIf action) { MenuData menuBarData = action.getMenuBarData(); if (menuBarData == null) { - return; + return null; } + String[] menuPath = menuBarData.getMenuPath(); if (menuPath == null || menuPath.length <= 1) { - return; + return null; } - MenuManager menuMgr = getMenuManager(menuPath[0]); - menuMgr.addAction(action); + return getMenuManager(menuPath[0]); } /** @@ -104,17 +108,13 @@ public class MenuBarManager implements MenuGroupListener { MenuManager mgr = menuManagers.get(menuName); if (mgr == null) { - mgr = - new MenuManager(menuName, new String[] { menuName }, mk, 1, null, false, - menuHandler, menuGroupMap); + mgr = new MenuManager(menuName, new String[] { menuName }, mk, 1, null, false, + menuHandler, menuGroupMap); menuManagers.put(menuName, mgr); } return mgr; } - /** - * Returns a JMenuBar for all the actions. - */ public JMenuBar getMenuBar() { MenuManager fileMenu = menuManagers.get("File"); MenuManager editMenu = menuManagers.get("Edit"); @@ -153,6 +153,7 @@ public class MenuBarManager implements MenuGroupListener { * @param menuPath the menu path whose group changed. * @param group the new group for the given menuPath. */ + @Override public void menuGroupChanged(String[] menuPath, String group) { if (menuPath != null && menuPath.length > 1) { MenuManager mgr = getMenuManager(menuPath[0]); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/menu/MenuItemManager.java b/Ghidra/Framework/Docking/src/main/java/docking/menu/MenuItemManager.java index a33263e068..48ec1c5ba7 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/menu/MenuItemManager.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/menu/MenuItemManager.java @@ -43,13 +43,10 @@ class MenuItemManager implements ManagedMenuItem, PropertyChangeListener, Action // listeners to handle help activation // -this listener covers activation by keyboard and by mouse *when enabled* private ChangeListener buttonModelChangeListener; + // -this listener covers activation by mouse *when the action is disabled* private MouseAdapter menuHoverListener; - /** - * Constructs a new MenuItemManger - * @param dockableAction the action whose menuItem is being managed. - */ MenuItemManager(MenuHandler actionHandler, DockingActionIf dockingAction, boolean usePopupPath) { this.menuHandler = actionHandler; @@ -104,9 +101,6 @@ class MenuItemManager implements ManagedMenuItem, PropertyChangeListener, Action }; } - /** - * @see ghidra.framework.docking.menu.ManagedMenuItem#getWindowGroup() - */ @Override public String getGroup() { MenuData menuData = isPopup ? action.getPopupMenuData() : action.getMenuBarData(); @@ -119,10 +113,6 @@ class MenuItemManager implements ManagedMenuItem, PropertyChangeListener, Action return menuData == null ? null : menuData.getMenuSubGroup(); } - /** - * - * @see ghidra.framework.docking.menu.ManagedMenuItem#dispose() - */ @Override public void dispose() { if (action != null) { @@ -138,9 +128,6 @@ class MenuItemManager implements ManagedMenuItem, PropertyChangeListener, Action action = null; } - /** - * @see ghidra.framework.docking.menu.ManagedMenuItem#getMenuItem() - */ @Override public JMenuItem getMenuItem() { if (menuItem != null) { @@ -158,17 +145,10 @@ class MenuItemManager implements ManagedMenuItem, PropertyChangeListener, Action return menuItem; } - /** - * Returns the owner associated with this items action. - */ public String getOwner() { return action.getOwner(); } - /** - * Changes the menuItem to reflect changes in the actions properties. - * @see java.beans.PropertyChangeListener#propertyChange(java.beans.PropertyChangeEvent) - */ @Override public void propertyChange(PropertyChangeEvent e) { if (menuItem == null) { @@ -209,16 +189,10 @@ class MenuItemManager implements ManagedMenuItem, PropertyChangeListener, Action } } - /** - * Returns the action associated with this menu item. - */ public DockingActionIf getAction() { return action; } - /** - * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent) - */ @Override public void actionPerformed(ActionEvent e) { if (menuHandler != null) { diff --git a/Ghidra/Framework/Docking/src/main/java/docking/menu/MenuManager.java b/Ghidra/Framework/Docking/src/main/java/docking/menu/MenuManager.java index bf366dd596..e99d55a0d1 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/menu/MenuManager.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/menu/MenuManager.java @@ -1,6 +1,5 @@ /* ### * 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. @@ -30,8 +29,8 @@ import docking.action.MenuData; public class MenuManager implements ManagedMenuItem { private static String NULL_GROUP_NAME = ""; - private Set managedMenuItems = new HashSet(); - private Map subMenus = new HashMap(); + private Set managedMenuItems = new HashSet<>(); + private Map subMenus = new HashMap<>(); private String name; private final String[] menuPath; @@ -50,9 +49,9 @@ public class MenuManager implements ManagedMenuItem { * @param name the name of the menu. * @param mnemonicKey the key to use for the menu mnemonic * @param group the group of the menu. - * @param showKeyBindings if true, includes the keybinding text on the menu item. * @param usePopupPath if true, registers actions with popup paths as popup items. * @param menuHandler Listener to be notified of menu behavior. + * @param menuGroupMap maps menu groups to menu paths */ public MenuManager(String name, char mnemonicKey, String group, boolean usePopupPath, MenuHandler menuHandler, MenuGroupMap menuGroupMap) { @@ -68,9 +67,9 @@ public class MenuManager implements ManagedMenuItem { * @param mnemonicKey the key to use for the menu mnemonic * @param level the number of parent menus that this menu is in. * @param group the group of this menu. - * @param showKeyBindings if true, includes the keybinding text on the menu item. * @param usePopupPath if true, registers actions with popup paths as popup items. * @param menuHandler Listener to be notified of menu behavior. + * @param menuGroupMap maps menu groups to menu paths */ MenuManager(String name, String[] menuPath, char mnemonicKey, int level, String group, boolean usePopupPath, MenuHandler menuHandler, MenuGroupMap menuGroupMap) { @@ -96,10 +95,8 @@ public class MenuManager implements ManagedMenuItem { } /** - * Adds an action to this menu. Can create subMenus depending on the menuPath of the - * action. - * @param action the action to be added. - * @param menuGroupMap group map for menuItems + * Adds an action to this menu. Can create subMenus depending on the menuPath of the action + * @param action the action to be added */ public void addAction(DockingActionIf action) { checkForSwingThread(); @@ -122,9 +119,8 @@ public class MenuManager implements ManagedMenuItem { submenuGroup = subMenuName; } - mgr = - new MenuManager(cleanSubMenuName, submenuPath, mnemonic, submenuLevel, - submenuGroup, usePopupPath, menuHandler, menuGroupMap); + mgr = new MenuManager(cleanSubMenuName, submenuPath, mnemonic, submenuLevel, + submenuGroup, usePopupPath, menuHandler, menuGroupMap); subMenus.put(cleanSubMenuName, mgr); managedMenuItems.add(mgr); } @@ -198,7 +194,7 @@ public class MenuManager implements ManagedMenuItem { menu.addMenuListener(menuHandler); } - List list = new ArrayList(managedMenuItems); + List list = new ArrayList<>(managedMenuItems); Collections.sort(list, comparator); String lastGroup = null; @@ -259,7 +255,7 @@ public class MenuManager implements ManagedMenuItem { if (popupMenu == null) { popupMenu = new JPopupMenu(name); - List list = new ArrayList(managedMenuItems); + List list = new ArrayList<>(managedMenuItems); Collections.sort(list, comparator); String lastGroup = NULL_GROUP_NAME; boolean hasMenuItems = false; diff --git a/Ghidra/Framework/Docking/src/main/java/docking/menu/ToolBarItemManager.java b/Ghidra/Framework/Docking/src/main/java/docking/menu/ToolBarItemManager.java index 8237baff21..c1fd4bff3a 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/menu/ToolBarItemManager.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/menu/ToolBarItemManager.java @@ -15,16 +15,18 @@ */ package docking.menu; -import ghidra.util.StringUtilities; - import java.awt.event.*; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import javax.swing.*; +import org.apache.commons.lang3.StringUtils; + import docking.*; import docking.action.*; +import ghidra.docking.util.DockingWindowsLookAndFeelUtils; +import ghidra.util.StringUtilities; /** * Class to manager toolbar buttons. @@ -78,7 +80,7 @@ public class ToolBarItemManager implements PropertyChangeListener, ActionListene } private void setToolTipText(JButton button, DockingActionIf action, String toolTipText) { - String keyBindingText = getKeyBindingAcceleratorText(action.getKeyBinding()); + String keyBindingText = getKeyBindingAcceleratorText(button, action.getKeyBinding()); if (keyBindingText != null) { button.setToolTipText(combingToolTipTextWithKeyBinding(toolTipText, keyBindingText)); } @@ -93,7 +95,7 @@ public class ToolBarItemManager implements PropertyChangeListener, ActionListene StringBuilder buffy = new StringBuilder(toolTipText); if (StringUtilities.startsWithIgnoreCase(toolTipText, "")) { String endHTMLTag = ""; - int closeTagIndex = StringUtilities.indexOfIgnoreCase(toolTipText, endHTMLTag); + int closeTagIndex = StringUtils.indexOfIgnoreCase(toolTipText, endHTMLTag); if (closeTagIndex < 0) { // no closing tag, which is acceptable buffy.append(START_KEYBINDING_TEXT).append(keyBindingText).append( @@ -120,7 +122,7 @@ public class ToolBarItemManager implements PropertyChangeListener, ActionListene return action.getName(); } - private String getKeyBindingAcceleratorText(KeyStroke keyStroke) { + private String getKeyBindingAcceleratorText(JButton button, KeyStroke keyStroke) { if (keyStroke == null) { return null; } @@ -129,8 +131,12 @@ public class ToolBarItemManager implements PropertyChangeListener, ActionListene StringBuilder builder = new StringBuilder(); int modifiers = keyStroke.getModifiers(); if (modifiers > 0) { - builder.append(KeyEvent.getKeyModifiersText(modifiers)); - builder.append('+'); + builder.append(InputEvent.getModifiersExText(modifiers)); + + // The Aqua LaF does not use the '+' symbol between modifiers + if (!DockingWindowsLookAndFeelUtils.isUsingAquaUI(button.getUI())) { + builder.append('+'); + } } int keyCode = keyStroke.getKeyCode(); if (keyCode != 0) { @@ -204,16 +210,13 @@ public class ToolBarItemManager implements PropertyChangeListener, ActionListene final ActionContext finalContext = tempContext; // this gives the UI some time to repaint before executing the action - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - if (toolBarAction.isEnabledForContext(finalContext)) { - if (toolBarAction instanceof ToggleDockingActionIf) { - ToggleDockingActionIf toggleAction = (ToggleDockingActionIf) toolBarAction; - toggleAction.setSelected(!toggleAction.isSelected()); - } - toolBarAction.actionPerformed(finalContext); + SwingUtilities.invokeLater(() -> { + if (toolBarAction.isEnabledForContext(finalContext)) { + if (toolBarAction instanceof ToggleDockingActionIf) { + ToggleDockingActionIf toggleAction = (ToggleDockingActionIf) toolBarAction; + toggleAction.setSelected(!toggleAction.isSelected()); } + toolBarAction.actionPerformed(finalContext); } }); } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/menu/ToolBarManager.java b/Ghidra/Framework/Docking/src/main/java/docking/menu/ToolBarManager.java index b60a018cdf..d4dfa9006e 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/menu/ToolBarManager.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/menu/ToolBarManager.java @@ -31,7 +31,7 @@ import docking.widgets.VariableHeightPanel; */ public class ToolBarManager { private Map> groupToItemsMap = - new TreeMap>(new GroupComparator()); + new TreeMap<>(new GroupComparator()); private Comparator toolBarItemComparator = new ToolBarItemManagerComparator(); @@ -47,9 +47,6 @@ public class ToolBarManager { toolBar = null; } - /** - * Adds the action to the toolbar. - */ public void addAction(DockingActionIf action) { ToolBarData toolBarData = action.getToolBarData(); if (toolBarData == null) { @@ -61,7 +58,7 @@ public class ToolBarManager { String group = toolBarData.getToolBarGroup(); List items = groupToItemsMap.get(group); if (items == null) { - items = new ArrayList(); + items = new ArrayList<>(); groupToItemsMap.put(group, items); } items.add(new ToolBarItemManager(action, windowManager)); @@ -96,9 +93,6 @@ public class ToolBarManager { groupToItemsMap.clear(); } - /** - * Returns true if the toolbar is empty. - */ public boolean isEmpty() { return groupToItemsMap.isEmpty(); } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/test/AbstractDockingTest.java b/Ghidra/Framework/Docking/src/main/java/docking/test/AbstractDockingTest.java index b9cc998e54..bff919d5b7 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/test/AbstractDockingTest.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/test/AbstractDockingTest.java @@ -43,6 +43,7 @@ import com.google.common.collect.Sets; import docking.*; import docking.action.DockingActionIf; import docking.action.ToggleDockingActionIf; +import docking.actions.DockingToolActions; import docking.dnd.GClipboard; import docking.framework.DockingApplicationConfiguration; import docking.menu.DockingToolbarButton; @@ -1114,7 +1115,8 @@ public abstract class AbstractDockingTest extends AbstractGenericTest { } /** - * A helper method to find all actions with the given owner's name + * A helper method to find all actions with the given owner's name (this will not include + * reserved system actions) * * @param tool the tool containing all system actions * @param name the owner's name to match @@ -1125,7 +1127,8 @@ public abstract class AbstractDockingTest extends AbstractGenericTest { } /** - * A helper method to find all actions by name, with the given owner's name + * A helper method to find all actions by name, with the given owner's name (this will not + * include reserved system actions) * * @param tool the tool containing all system actions * @param owner the owner's name @@ -1167,7 +1170,8 @@ public abstract class AbstractDockingTest extends AbstractGenericTest { /** * Finds the action by the given owner name and action name. * If you do not know the owner name, then use - * the call {@link #getActionsByName(DockingTool, String)} instead. + * the call {@link #getActionsByName(DockingTool, String)} instead (this will not include + * reserved system actions). * *

    Note: more specific test case subclasses provide other methods for finding actions * when you have an owner name (which is usually the plugin name). @@ -1192,8 +1196,18 @@ public abstract class AbstractDockingTest extends AbstractGenericTest { return CollectionUtils.any(actions); } + /** + * Returns the action by the given name that belongs to the given provider + * + * @param provider the provider + * @param actionName the action name + * @return the action + */ public static DockingActionIf getLocalAction(ComponentProvider provider, String actionName) { - return getAction(provider.getTool(), provider.getName(), actionName); + DockingTool tool = provider.getTool(); + DockingToolActions toolActions = tool.getToolActions(); + DockingActionIf action = toolActions.getLocalAction(provider, actionName); + return action; } /** @@ -1847,10 +1861,12 @@ public abstract class AbstractDockingTest extends AbstractGenericTest { * * @param tool the tool in which the provider lives * @param name the name of the provider to show + * @return the newly shown provider */ - public void showProvider(DockingTool tool, String name) { + public ComponentProvider showProvider(DockingTool tool, String name) { ComponentProvider provider = tool.getComponentProvider(name); tool.showComponentProvider(provider, true); + return provider; } /** diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTable.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTable.java index fd1371f49e..58e884d7a8 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTable.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTable.java @@ -15,9 +15,9 @@ */ package docking.widgets.table; -import static docking.DockingUtils.*; -import static docking.action.MenuData.*; -import static java.awt.event.InputEvent.*; +import static docking.DockingUtils.CONTROL_KEY_MODIFIER_MASK; +import static docking.action.MenuData.NO_MNEMONIC; +import static java.awt.event.InputEvent.SHIFT_DOWN_MASK; import java.awt.*; import java.awt.event.*; @@ -509,23 +509,14 @@ public class GTable extends JTable implements KeyStrokeConsumer, DockingActionPr return autoLookupKeyStrokeConsumer.isKeyConsumed(keyStroke); } - /** - * {@inheritDoc} - */ @Override - public List getDockingActions(ActionContext context) { - Object sourceObject = context.getSourceObject(); - if (sourceObject != this) { - // we are only interested in providing actions when we are the source of the event - return Collections.emptyList(); - } - + public List getDockingActions() { return getDefaultDockingActions(); } /** * Returns the default actions of this table. Normally, the Docking Windows systems uses - * {@link #getDockingActions(ActionContext)} to get the correct actions to show. However, + * {@link #getDockingActions()} to get the correct actions to show. However, * there are some cases where clients override what appears when you click on a table (such * as in {@link DialogComponentProvider}s. For those clients that are creating their own * action building, they need a way to get the default actions, hence this method. @@ -1178,7 +1169,8 @@ public class GTable extends JTable implements KeyStrokeConsumer, DockingActionPr int subGroupIndex = 1; // order by insertion String owner = getClass().getSimpleName(); - copyAction = new DockingAction("Table Data Copy", owner, false) { + owner = "GTable"; + copyAction = new DockingAction("Table Data Copy", owner, KeyBindingType.SHARED) { @Override public void actionPerformed(ActionContext context) { copying = true; @@ -1209,7 +1201,7 @@ public class GTable extends JTable implements KeyStrokeConsumer, DockingActionPr //@formatter:on copyCurrentColumnAction = - new DockingAction("Table Data Copy Current Column", owner, false) { + new DockingAction("Table Data Copy Current Column", owner, KeyBindingType.SHARED) { @Override public void actionPerformed(ActionContext context) { @@ -1246,17 +1238,18 @@ public class GTable extends JTable implements KeyStrokeConsumer, DockingActionPr copyCurrentColumnAction.setHelpLocation(new HelpLocation("Tables", "Copy_Current_Column")); //@formatter:on - copyColumnsAction = new DockingAction("Table Data Copy by Columns", owner, false) { - @Override - public void actionPerformed(ActionContext context) { - int[] userColumns = promptUserForColumns(); - if (userColumns == null) { - return; // cancelled - } + copyColumnsAction = + new DockingAction("Table Data Copy by Columns", owner, KeyBindingType.SHARED) { + @Override + public void actionPerformed(ActionContext context) { + int[] userColumns = promptUserForColumns(); + if (userColumns == null) { + return; // cancelled + } - copyColumns(userColumns); - } - }; + copyColumns(userColumns); + } + }; //@formatter:off copyColumnsAction.setPopupMenuData(new MenuData( new String[] { "Copy", "Copy Columns..." }, @@ -1269,7 +1262,7 @@ public class GTable extends JTable implements KeyStrokeConsumer, DockingActionPr copyColumnsAction.setHelpLocation(new HelpLocation("Tables", "Copy_Columns")); //@formatter:on - exportAction = new DockingAction("Table Data CSV Export", owner, false) { + exportAction = new DockingAction("Table Data CSV Export", owner, KeyBindingType.SHARED) { @Override public void actionPerformed(ActionContext context) { File file = chooseExportFile(); @@ -1291,7 +1284,7 @@ public class GTable extends JTable implements KeyStrokeConsumer, DockingActionPr //@formatter:on exportColumnsAction = - new DockingAction("Table Data CSV Export (by Columns)", owner, false) { + new DockingAction("Table Data CSV Export (by Columns)", owner, KeyBindingType.SHARED) { @Override public void actionPerformed(ActionContext context) { int[] userColumns = promptUserForColumns(); @@ -1323,7 +1316,7 @@ public class GTable extends JTable implements KeyStrokeConsumer, DockingActionPr exportColumnsAction.setHelpLocation(new HelpLocation("Tables", "ExportCSV_Columns")); //@formatter:on - selectAllAction = new DockingAction("Table Select All", owner, false) { + selectAllAction = new DockingAction("Table Select All", owner, KeyBindingType.SHARED) { @Override public void actionPerformed(ActionContext context) { selectAll(); diff --git a/Ghidra/Framework/Docking/src/test.slow/java/docking/actions/SharedKeyBindingDockingActionTest.java b/Ghidra/Framework/Docking/src/test.slow/java/docking/actions/SharedKeyBindingDockingActionTest.java index 3e387be964..3e59a4ee16 100644 --- a/Ghidra/Framework/Docking/src/test.slow/java/docking/actions/SharedKeyBindingDockingActionTest.java +++ b/Ghidra/Framework/Docking/src/test.slow/java/docking/actions/SharedKeyBindingDockingActionTest.java @@ -35,9 +35,11 @@ import docking.tool.util.DockingToolConstants; import ghidra.framework.options.ToolOptions; import ghidra.util.Msg; import ghidra.util.SpyErrorLogger; +import ghidra.util.exception.AssertException; public class SharedKeyBindingDockingActionTest extends AbstractDockingTest { + private static final String NON_SHARED_NAME = "Non-Shared Action Name"; private static final String SHARED_NAME = "Shared Action Name"; private static final String SHARED_OWNER = SharedStubKeyBindingAction.SHARED_OWNER; @@ -167,7 +169,14 @@ public class SharedKeyBindingDockingActionTest extends AbstractDockingTest { TestAction action1 = new TestAction(OWNER_1, DEFAULT_KS_1); tool.addAction(action1); - tool.addAction(action1); + + try { + tool.addAction(action1); + fail("Did not get expected exception"); + } + catch (AssertException e) { + // expected + } assertOnlyOneVersionOfActionInTool(action1); @@ -294,7 +303,7 @@ public class SharedKeyBindingDockingActionTest extends AbstractDockingTest { } @Test - public void testNonSharedKeyBinding_SameActionAddedTwice() { + public void testSharedKeyBinding_SameActionAddedTwice() { // // We support adding the same action twice. (This can happen when a transient component // provider is repeatedly shown, such as a search results provider.) Make sure we get @@ -323,7 +332,7 @@ public class SharedKeyBindingDockingActionTest extends AbstractDockingTest { } @Test - public void testNonSharedKeyBinding_DifferentActionsWithSameFullName() { + public void testSharedKeyBinding_DifferentActionsWithSameFullName() { // // We support adding the same action twice. (This can happen when a transient component // provider is repeatedly shown, such as a search results provider.) Make sure we get @@ -351,17 +360,47 @@ public class SharedKeyBindingDockingActionTest extends AbstractDockingTest { assertActionNotInTool(action1Copy); } + @Test + public void testNonSharedKeyBinding_DifferentActionsWithSameFullName() { + // + // We support adding the same action twice. (This can happen when a transient component + // provider is repeatedly shown, such as a search results provider.) Make sure we get + // a warning if the same action is added twice, but with different key bindings. + // + // Note: in this context, two actions are considered to be the same if they share the + // same name and owner. + // + + TestNonSharedAction action1 = new TestNonSharedAction(OWNER_1, DEFAULT_KS_1); + TestNonSharedAction action1Copy = + new TestNonSharedAction(OWNER_1, DEFAULT_KS_DIFFERENT_THAN_1); + + tool.addAction(action1); + tool.addAction(action1Copy); + assertActionInTool(action1); + assertActionInTool(action1Copy); + + assertImproperDefaultBindingMessage(); + + tool.removeAction(action1); + assertActionNotInTool(action1); + assertActionInTool(action1Copy); + + tool.removeAction(action1Copy); + assertActionNotInTool(action1Copy); + } + //================================================================================================== // Private Methods //================================================================================================== private void assertSharedStubInTool() { - ToolActions actionManager = (ToolActions) getInstanceField("actionMgr", tool); + ToolActions actionManager = (ToolActions) getInstanceField("toolActions", tool); DockingActionIf action = actionManager.getSharedStubKeyBindingAction(SHARED_NAME); assertNotNull("Shared action stub is not in the tool", action); } - private void assertOnlyOneVersionOfActionInTool(TestAction action) { + private void assertOnlyOneVersionOfActionInTool(DockingActionIf action) { // this method will fail if more than one action is registered DockingActionIf registeredAction = getAction(tool, action.getOwner(), action.getName()); @@ -369,7 +408,7 @@ public class SharedKeyBindingDockingActionTest extends AbstractDockingTest { registeredAction); } - private void assertActionInTool(TestAction action) { + private void assertActionInTool(DockingActionIf action) { Set actions = getActionsByName(tool, action.getName()); for (DockingActionIf toolAction : actions) { @@ -381,7 +420,7 @@ public class SharedKeyBindingDockingActionTest extends AbstractDockingTest { fail("Action is not in the tool: " + action); } - private void assertActionNotInTool(TestAction action) { + private void assertActionNotInTool(DockingActionIf action) { Set actions = getActionsByName(tool, action.getName()); for (DockingActionIf toolAction : actions) { assertNotSame(toolAction, action); @@ -426,16 +465,21 @@ public class SharedKeyBindingDockingActionTest extends AbstractDockingTest { private class TestAction extends DockingAction { public TestAction(String owner, KeyStroke ks) { - super(SHARED_NAME, owner); - - if (ks != null) { - setKeyBindingData(new KeyBindingData(ks)); - } + super(SHARED_NAME, owner, KeyBindingType.SHARED); + setKeyBindingData(new KeyBindingData(ks)); } @Override - public boolean usesSharedKeyBinding() { - return true; + public void actionPerformed(ActionContext context) { + fail("Action performed should not have been called"); + } + } + + private class TestNonSharedAction extends DockingAction { + + public TestNonSharedAction(String owner, KeyStroke ks) { + super(NON_SHARED_NAME, owner, KeyBindingType.INDIVIDUAL); + setKeyBindingData(new KeyBindingData(ks)); } @Override diff --git a/Ghidra/Framework/Docking/src/test/java/docking/FakeDockingTool.java b/Ghidra/Framework/Docking/src/test/java/docking/FakeDockingTool.java index 0398267581..bbbba7ab68 100644 --- a/Ghidra/Framework/Docking/src/test/java/docking/FakeDockingTool.java +++ b/Ghidra/Framework/Docking/src/test/java/docking/FakeDockingTool.java @@ -35,9 +35,9 @@ public class FakeDockingTool extends AbstractDockingTool { DockWinListener listener = new DummyListener(); List windowIcons = ApplicationInformationDisplayFactory.getWindowIcons(); - winMgr = new DockingWindowManager("EMPTY", windowIcons, listener, false /*isModal*/, + winMgr = new DockingWindowManager(this, windowIcons, listener, false /*isModal*/, true /*isDockable*/, true /*hasStatus*/, null /*DropTargetFactory*/); - actionMgr = new ToolActions(this, winMgr); + toolActions = new ToolActions(this, new ActionToGuiHelper(winMgr)); } @Override @@ -71,7 +71,5 @@ public class FakeDockingTool extends AbstractDockingTool { public List getPopupActions(ActionContext context) { return null; } - } - } diff --git a/Ghidra/Framework/Generic/src/main/java/generic/test/AbstractGenericTest.java b/Ghidra/Framework/Generic/src/main/java/generic/test/AbstractGenericTest.java index 6cf64d94a0..b209b92556 100644 --- a/Ghidra/Framework/Generic/src/main/java/generic/test/AbstractGenericTest.java +++ b/Ghidra/Framework/Generic/src/main/java/generic/test/AbstractGenericTest.java @@ -1092,6 +1092,26 @@ public abstract class AbstractGenericTest extends AbstractGTest { runSwing(runnable, true); } + /** + * Call this version of {@link #runSwing(Runnable)} when you expect your runnable to throw + * an exception + * @param runnable the runnable + * @param wait true signals to wait for the Swing operation to finish + * @throws Throwable any excption that is thrown on the Swing thread + */ + public static void runSwingWithExceptions(Runnable runnable, boolean wait) throws Throwable { + + if (Swing.isSwingThread()) { + throw new AssertException("Unexpectedly called from the Swing thread"); + } + + ExceptionHandlingRunner exceptionHandlingRunner = new ExceptionHandlingRunner(runnable); + Throwable throwable = exceptionHandlingRunner.getException(); + if (throwable != null) { + throw throwable; + } + } + public static void runSwing(Runnable runnable, boolean wait) { if (SwingUtilities.isEventDispatchThread()) { runnable.run(); @@ -1115,7 +1135,6 @@ public abstract class AbstractGenericTest extends AbstractGTest { }; SwingUtilities.invokeLater(swingExceptionCatcher); - } protected static class ExceptionHandlingRunner { diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/util/JavaSourceFile.java b/Ghidra/Framework/Generic/src/main/java/ghidra/util/JavaSourceFile.java index 5e7651bcc9..3149f42ba9 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/util/JavaSourceFile.java +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/util/JavaSourceFile.java @@ -303,7 +303,7 @@ public class JavaSourceFile { if (nameAndMaybeDeclaraction.length == 2) { return nameAndMaybeDeclaraction[0].endsWith("Action"); } - return StringUtilities.containsIgnoreCase(nameAndMaybeDeclaraction[0], "action"); + return StringUtils.containsIgnoreCase(nameAndMaybeDeclaraction[0], "action"); } private JavaSourceLine findEndOfUnknownLine(int lineNumber) { diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/util/StringUtilities.java b/Ghidra/Framework/Generic/src/main/java/ghidra/util/StringUtilities.java index 0d98af4381..0af65b0978 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/util/StringUtilities.java +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/util/StringUtilities.java @@ -348,22 +348,6 @@ public class StringUtilities { return string.regionMatches(true, startIndex, postfix, 0, postfix.length()); } - /** - * Returns true if the given containingString contains the given - * substring, ignoring case. - * - * @param containingString the string which may contain the prefix - * @param substring the string for which to search within the containing string - * @return true if the given containingString contains the given - * substring, ignoring case. - */ - public static boolean containsIgnoreCase(String containingString, String substring) { - if ((containingString == null) || (substring == null)) { - return false; - } - return (indexOfIgnoreCase(containingString, substring, 0) >= 0); - } - /** * Returns true if all the given searches are contained in the given string. * @@ -455,61 +439,6 @@ public class StringUtilities { return true; } - /** - * Returns the index of the first occurrence the given substring in the given - * containingString, ignoring case to look for the substring. - *

    - * This method is a convenience method for calling: - *

    -	 *     indexOfIgnoreCase( containingString, substring, 0 );
    -	 * 
    - * @param containingString the string which may contain the substring - * @param substring the string for which to search within the containing string - * @return index of substring within the given containing string - */ - public static int indexOfIgnoreCase(String containingString, String substring) { - if ((containingString == null) || (substring == null)) { - return -1; - } - return indexOfIgnoreCase(containingString, substring, 0); - } - - /** - * Returns the index of the first occurrence the given substring in the given - * containingString, starting at the given index, - * ignoring case to look for the substring. - *

    - * @param containingString the string which may contain the substring - * @param substring the string for which to search within the containing string - * @param index the index from which to start the comparison - * @return index of substring within the given containing string - */ - public static int indexOfIgnoreCase(String containingString, String substring, int index) { - if ((containingString == null) || (substring == null)) { - return -1; - } - return (containingString.toLowerCase().indexOf(substring.toLowerCase(), index)); - } - - /** - * Returns the index of the last occurrence the given substring in the given - * containingString, ignoring case to look for the substring. - *

    - * This method is a convenience method for calling: - *

    -	 *     lastIndexOfIgnoreCase( containingString, substring, 0 );
    -	 * 
    - * @param containingString the string which may contain the substring - * @param substring the string for which to search within the containing string - * @return index of substring within the given containing string - */ - public static int lastIndexOfIgnoreCase(String containingString, String substring) { - if ((containingString == null) || (substring == null)) { - return -1; - } - return (containingString.toLowerCase().lastIndexOf(substring.toLowerCase())); - } - /** * Convert tabs in the given string to spaces. * @@ -532,10 +461,8 @@ public class StringUtilities { char c = str.charAt(i); if (c == '\t') { int nSpaces = tabSize - (linepos % tabSize); - String pad = padString("", ' ', nSpaces); - + String pad = pad("", ' ', nSpaces); buffer.append(pad); - linepos += nSpaces; } else { @@ -606,23 +533,6 @@ public class StringUtilities { return padded; } - /** - * Pads the source string to the specified length, using the filler string - * as the pad. If length is negative, left justifies the string, appending - * the filler; if length is positive, right justifies the source string. - * - * @param source the original string to pad. - * @param filler the type of characters with which to pad - * @param length the length of padding to add (0 results in no changes) - * @return the padded string - * @deprecated use {@link #pad(String, char, int)}; functionally the same, but smaller - * and more consistent name - */ - @Deprecated - public static String padString(String source, char filler, int length) { - return pad(source, filler, length); - } - /** * Pads the source string to the specified length, using the filler string * as the pad. If length is negative, left justifies the string, appending diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/util/datastruct/WeakSet.java b/Ghidra/Framework/Generic/src/main/java/ghidra/util/datastruct/WeakSet.java index 6b80e9de1c..8fc66614ef 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/util/datastruct/WeakSet.java +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/util/datastruct/WeakSet.java @@ -53,6 +53,9 @@ public abstract class WeakSet implements Iterable { return; } + // Note: sadly, this code does not work with labmda's, as we cannot get the enclosing + // method/constructor + Class clazz = t.getClass(); if (!clazz.isAnonymousClass()) { return; // O.K. diff --git a/Ghidra/Framework/Generic/src/main/java/resources/Icons.java b/Ghidra/Framework/Generic/src/main/java/resources/Icons.java index 9dad8abb58..9cb48c5a39 100644 --- a/Ghidra/Framework/Generic/src/main/java/resources/Icons.java +++ b/Ghidra/Framework/Generic/src/main/java/resources/Icons.java @@ -33,6 +33,8 @@ import resources.icons.TranslateIcon; */ public class Icons { + public static final ImageIcon EMPTY_ICON = ResourceManager.loadImage("images/EmptyIcon16.gif"); + public static final ImageIcon ADD_ICON = ResourceManager.loadImage("images/Plus2.png"); public static final ImageIcon COLLAPSE_ALL_ICON = diff --git a/Ghidra/Framework/Generic/src/test/java/ghidra/util/HTMLUtilitiesTest.java b/Ghidra/Framework/Generic/src/test/java/ghidra/util/HTMLUtilitiesTest.java index 92c1203df4..c95425fd1e 100644 --- a/Ghidra/Framework/Generic/src/test/java/ghidra/util/HTMLUtilitiesTest.java +++ b/Ghidra/Framework/Generic/src/test/java/ghidra/util/HTMLUtilitiesTest.java @@ -17,10 +17,8 @@ package ghidra.util; import static ghidra.util.HTMLUtilities.HTML; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; import java.awt.Color; -import java.util.Arrays; import org.junit.Before; import org.junit.Test; @@ -53,7 +51,7 @@ public class HTMLUtilitiesTest { String s = "This text has
    an existing BR tag"; String html = HTMLUtilities.toHTML(s); assertEquals(HTML + s, html); - assertLogMessage("cannot", "wrap"); + spyLogger.assertLogMessage("cannot", "wrap"); } @Test @@ -61,7 +59,7 @@ public class HTMLUtilitiesTest { String s = "This text has
    \nan existing BR tag and a newline"; String html = HTMLUtilities.toHTML(s); assertEquals(HTML + s, html); - assertLogMessage("cannot", "wrap"); + spyLogger.assertLogMessage("cannot", "wrap"); } @Test @@ -140,16 +138,6 @@ public class HTMLUtilitiesTest { assertEquals("#FF0000", rgb); } - private void assertLogMessage(String... words) { - for (String message : spyLogger) { - if (StringUtilities.containsAllIgnoreCase(message, words)) { - return; - } - } - - fail("Did not find log message containing all these words: " + Arrays.toString(words)); - } - @Test public void testLinkPlaceholder() { String placeholderStr = diff --git a/Ghidra/Framework/Generic/src/test/java/ghidra/util/StringUtilitiesTest.java b/Ghidra/Framework/Generic/src/test/java/ghidra/util/StringUtilitiesTest.java index 58a36b28e9..4f5fcd6c61 100644 --- a/Ghidra/Framework/Generic/src/test/java/ghidra/util/StringUtilitiesTest.java +++ b/Ghidra/Framework/Generic/src/test/java/ghidra/util/StringUtilitiesTest.java @@ -94,26 +94,6 @@ public class StringUtilitiesTest { assertEquals(-1, StringUtilities.indexOfWord(sentenceWithTestNotAsAWord, word)); } - @Test - public void testLastIndexOfIgnoresCase() { - String bob = "bob"; - String endsWithBob = "endsWithBob"; - - assertEquals(8, StringUtilities.lastIndexOfIgnoreCase(endsWithBob, bob)); - - String endsWithBobUpperCase = "endsWithBOB"; - assertEquals(8, StringUtilities.lastIndexOfIgnoreCase(endsWithBobUpperCase, bob)); - - String startsWithBob = "bobWithTrailingText"; - assertEquals(0, StringUtilities.lastIndexOfIgnoreCase(startsWithBob, bob)); - - String justBob = "bOb"; - assertEquals(0, StringUtilities.lastIndexOfIgnoreCase(justBob, bob)); - - String manyBobs = "This is a string, bob, that has bob, many bobs...and then some text"; - assertEquals(42, StringUtilities.lastIndexOfIgnoreCase(manyBobs, bob)); - } - @Test public void testIsAllBlank() { diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/datatable/ProjectDataTablePanel.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/datatable/ProjectDataTablePanel.java index 789316cc3b..d3fe4da3e8 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/datatable/ProjectDataTablePanel.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/datatable/ProjectDataTablePanel.java @@ -490,7 +490,10 @@ public class ProjectDataTablePanel extends JPanel { } @Override - public List getDockingActions(ActionContext context) { + public List getDockingActions() { + + // TODO we should at least add the 'copy' action + // the table's default actions aren't that useful in the Front End return Collections.emptyList(); } diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/datatree/VersionHistoryDialog.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/datatree/VersionHistoryDialog.java index a44a6ee82c..6272482da1 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/datatree/VersionHistoryDialog.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/datatree/VersionHistoryDialog.java @@ -133,7 +133,7 @@ public class VersionHistoryDialog extends DialogComponentProvider } @Override - public List getDockingActions(ActionContext context) { - return versionPanel.getDockingActions(context); + public List getDockingActions() { + return versionPanel.getDockingActions(); } } diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/datatree/VersionHistoryPanel.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/datatree/VersionHistoryPanel.java index 29ec2902bc..ffb621583b 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/datatree/VersionHistoryPanel.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/main/datatree/VersionHistoryPanel.java @@ -402,7 +402,7 @@ public class VersionHistoryPanel extends JPanel implements Draggable { provider.addAction(new DeleteAction()); } - public List getDockingActions(ActionContext currentContext) { + public List getDockingActions() { List list = new ArrayList<>(table.getDefaultDockingActions()); Project project = tool.getProject(); ToolChest toolChest = project.getLocalToolChest(); diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/PluginTool.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/PluginTool.java index 91152002bd..d590519d76 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/PluginTool.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/PluginTool.java @@ -15,7 +15,8 @@ */ package ghidra.framework.plugintool; -import static ghidra.framework.model.ToolTemplate.*; +import static ghidra.framework.model.ToolTemplate.TOOL_INSTANCE_NAME_XML_NAME; +import static ghidra.framework.model.ToolTemplate.TOOL_NAME_XML_NAME; import java.awt.*; import java.awt.event.KeyEvent; @@ -149,16 +150,16 @@ public abstract class PluginTool extends AbstractDockingTool this.projectManager = projectManager; this.toolServices = toolServices; propertyChangeMgr = new PropertyChangeSupport(this); - winMgr = createDockingWindowManager(isDockable, hasStatus, isModal); - taskMgr = new ToolTaskManager(this); optionsMgr = new OptionsManager(this); + winMgr = createDockingWindowManager(isDockable, hasStatus, isModal); + toolActions = new ToolActions(this, new ActionToGuiHelper(winMgr)); + taskMgr = new ToolTaskManager(this); setToolOptionsHelpLocation(); winMgr.addStatusItem(taskMgr.getMonitorComponent(), false, true); winMgr.removeStatusItem(taskMgr.getMonitorComponent()); eventMgr = new EventManager(this); serviceMgr = new ServiceManager(); installServices(); - actionMgr = new ToolActions(this, winMgr); pluginMgr = new PluginManager(this, serviceMgr); dialogMgr = new DialogManager(this); initActions(); @@ -189,8 +190,8 @@ public abstract class PluginTool extends AbstractDockingTool boolean isModal) { List windowIcons = ApplicationInformationDisplayFactory.getWindowIcons(); - DockingWindowManager newManager = new DockingWindowManager("EMPTY", windowIcons, this, - isModal, isDockable, hasStatus, null); + DockingWindowManager newManager = + new DockingWindowManager(this, windowIcons, this, isModal, isDockable, hasStatus, null); return newManager; } @@ -460,7 +461,7 @@ public abstract class PluginTool extends AbstractDockingTool winMgr.setVisible(false); eventMgr.clearLastEvents(); pluginMgr.dispose(); - actionMgr.dispose(); + toolActions.dispose(); if (project != null) { project.releaseFiles(this); @@ -1300,7 +1301,7 @@ public abstract class PluginTool extends AbstractDockingTool protected void restoreOptionsFromXml(Element root) { optionsMgr.setConfigState(root.getChild("OPTIONS")); - actionMgr.restoreKeyBindings(); + toolActions.restoreKeyBindings(); setToolOptionsHelpLocation(); } @@ -1321,8 +1322,8 @@ public abstract class PluginTool extends AbstractDockingTool } void removeAll(String owner) { - actionMgr.removeToolActions(owner); - winMgr.removeAll(owner); + toolActions.removeActions(owner); + winMgr.ownerRemoved(owner); } void registerEventProduced(Class eventClass) { @@ -1463,40 +1464,17 @@ public abstract class PluginTool extends AbstractDockingTool DockingWindowManager.showDialog(getToolFrame(), dialogComponent, centeredOnComponent); } - /** - * Returns the ComponentProvider with the given name. If more than one provider exists with the name, - * one will be returned, but it could be any one of them. - * @param name the name of the provider to return. - * @return a provider with the given name, or null if no providers with that name exist. - */ - @Override - public ComponentProvider getComponentProvider(String name) { - return winMgr.getComponentProvider(name); - } - public Window getActiveWindow() { return winMgr.getActiveWindow(); } + @Override public ComponentProvider getActiveComponentProvider() { return winMgr.getActiveComponentProvider(); } - @Override - public void contextChanged(ComponentProvider provider) { - winMgr.contextChanged(provider); - } - - public void addContextListener(DockingContextListener listener) { - winMgr.addContextListener(listener); - } - - public void removeContextListener(DockingContextListener listener) { - winMgr.removeContextListener(listener); - } - public void refreshKeybindings() { - actionMgr.restoreKeyBindings(); + toolActions.restoreKeyBindings(); } public void setUnconfigurable() { diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/dialog/KeyBindingsPanel.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/dialog/KeyBindingsPanel.java index 9d6e39f9cc..56fe52455e 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/dialog/KeyBindingsPanel.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/dialog/KeyBindingsPanel.java @@ -66,7 +66,7 @@ public class KeyBindingsPanel extends JPanel { private ListSelectionModel selectionModel; private Options options; - private Map actionsByFullName; + private Map> actionsByFullName; private Map> actionNamesByKeyStroke; private Map keyStrokesByFullName; private Map originalValues; // to know what has been changed @@ -113,11 +113,11 @@ public class KeyBindingsPanel extends JPanel { changesMade(false); } - private boolean updateOptions(String fullActionName, KeyStroke currentKeyStroke, + private void updateOptions(String fullActionName, KeyStroke currentKeyStroke, KeyStroke newKeyStroke) { - if ((currentKeyStroke != null && currentKeyStroke.equals(newKeyStroke)) || - (currentKeyStroke == null && newKeyStroke == null)) { - return false; + + if (Objects.equals(currentKeyStroke, newKeyStroke)) { + return; } if (newKeyStroke != null) { @@ -129,19 +129,12 @@ public class KeyBindingsPanel extends JPanel { originalValues.put(fullActionName, newKeyStroke); keyStrokesByFullName.put(fullActionName, newKeyStroke); - Set actions = tool.getAllActions(); + List actions = actionsByFullName.get(fullActionName); for (DockingActionIf action : actions) { - if (action.getFullName().equals(fullActionName)) { - action.setUnvalidatedKeyBindingData(new KeyBindingData(newKeyStroke)); - } + action.setUnvalidatedKeyBindingData(new KeyBindingData(newKeyStroke)); } - - return true; } - /** - * Cancel the changes to the actions. - */ public void cancel() { Iterator iter = originalValues.keySet().iterator(); while (iter.hasNext()) { @@ -172,10 +165,12 @@ public class KeyBindingsPanel extends JPanel { String longestName = ""; actionsByFullName = KeyBindingUtils.getAllActionsByFullName(tool); - Set> entries = actionsByFullName.entrySet(); - for (Entry entry : entries) { + Set>> entries = actionsByFullName.entrySet(); + for (Entry> entry : entries) { - DockingActionIf action = entry.getValue(); + // pick one action, they are all conceptually the same + List actions = entry.getValue(); + DockingActionIf action = actions.get(0); tableActions.add(action); String actionName = entry.getKey(); @@ -412,11 +407,13 @@ public class KeyBindingsPanel extends JPanel { Iterator iter = keyStrokesByFullName.keySet().iterator(); while (iter.hasNext()) { String actionName = iter.next(); - DockingActionIf action = actionsByFullName.get(actionName); - if (action == null) { + List actions = actionsByFullName.get(actionName); + if (actions.isEmpty()) { throw new AssertException("No actions defined for " + actionName); } + // pick one action, they are all conceptually the same + DockingActionIf action = actions.get(0); KeyStroke currentKeyStroke = keyStrokesByFullName.get(actionName); KeyBindingData defaultBinding = action.getDefaultKeyBindingData(); KeyStroke newKeyStroke = @@ -429,9 +426,6 @@ public class KeyBindingsPanel extends JPanel { tableModel.fireTableDataChanged(); } - /** - * Add listeners. Valid modifiers are CTRL and ALT and SHIFT. - */ private void addListeners() { selectionModel = actionTable.getSelectionModel(); selectionModel.addListSelectionListener(new TableSelectionListener()); @@ -463,11 +457,6 @@ public class KeyBindingsPanel extends JPanel { unappliedChanges = changes; } - /** - * Get the action that is selected in the table. - * - * @return String - */ private String getSelectedAction() { if (selectionModel.isSelectionEmpty()) { return null; @@ -477,9 +466,6 @@ public class KeyBindingsPanel extends JPanel { return tableActions.get(modelRow).getFullName(); } - /** - * Add the action name to the list for the given keystroke. - */ private void addToKeyMap(KeyStroke ks, String actionName) { if (ks == null) { return; @@ -495,9 +481,6 @@ public class KeyBindingsPanel extends JPanel { } } - /** - * Remove the given actionName from from the list for the keystroke. - */ private void removeFromKeyMap(KeyStroke ks, String actionName) { if (ks == null) { return; @@ -512,11 +495,7 @@ public class KeyBindingsPanel extends JPanel { } } - /** - * Display actions mapped to the given keystroke name. - * @param ksName name of Keystroke that has multiple actions mapped - */ - private void showActionMapped(String ksName) { + private void showActionsMappedToKeyStroke(String ksName) { List list = actionNamesByKeyStroke.get(ksName); if (list == null) { return; @@ -538,17 +517,10 @@ public class KeyBindingsPanel extends JPanel { } } - /** - * Clear the info panel. - */ private void clearInfoPanel() { updateInfoPanel(" "); } - /** - * Replace multiline label in the info panel. - * @param text new text to show - */ private void updateInfoPanel(String text) { infoPanel.removeAll(); infoPanel.repaint(); @@ -559,8 +531,6 @@ public class KeyBindingsPanel extends JPanel { validate(); } - ////////////////////////////////////////////////////////////////////// - private void processKeyBindingsFromOptions(Options keyBindingOptions) { if (keyBindingOptions == null) { return; @@ -578,7 +548,7 @@ public class KeyBindingsPanel extends JPanel { while (iterator.hasNext()) { String name = iterator.next(); KeyStroke keyStroke = keyBindingsMap.get(name); - keyStroke = KeyBindingData.validateKeyStroke(keyStroke); + keyStroke = KeyBindingUtils.validateKeyStroke(keyStroke); // prevent non-existing keybindings from being added to Ghidra (this can happen // when actions exist in the imported bindings, but have been removed from @@ -619,7 +589,7 @@ public class KeyBindingsPanel extends JPanel { if (selectedActionName != null) { if (processKeyStroke(selectedActionName, ks)) { String keyStrokeText = KeyEntryTextField.parseKeyStroke(ks); - showActionMapped(keyStrokeText); + showActionsMappedToKeyStroke(keyStrokeText); tableModel.fireTableDataChanged(); } } @@ -691,15 +661,18 @@ public class KeyBindingsPanel extends JPanel { if (ks != null) { ksName = KeyEntryTextField.parseKeyStroke(ks); - showActionMapped(ksName); + showActionsMappedToKeyStroke(ksName); } ksField.setText(ksName); + // make sure the label gets enough space statusLabel.setPreferredSize( new Dimension(statusLabel.getPreferredSize().width, STATUS_LABEL_HEIGHT)); - DockingActionIf action = actionsByFullName.get(fullActionName); + // pick one action, they are all conceptually the same + List actions = actionsByFullName.get(fullActionName); + DockingActionIf action = actions.get(0); String description = action.getDescription(); if (description == null || description.trim().isEmpty()) { description = action.getName(); @@ -734,7 +707,7 @@ public class KeyBindingsPanel extends JPanel { } return ""; case PLUGIN_NAME: - return action.getOwner(); + return action.getOwnerDescription(); } return "Unknown Column!"; } diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/dialog/PluginDetailsPanel.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/dialog/PluginDetailsPanel.java index 5fed0e4e1b..e416aa6851 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/dialog/PluginDetailsPanel.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/dialog/PluginDetailsPanel.java @@ -23,9 +23,9 @@ import javax.swing.KeyStroke; import javax.swing.text.SimpleAttributeSet; import javax.swing.text.StyleConstants; -import docking.DockingKeyBindingAction; import docking.action.DockingActionIf; import docking.action.MenuData; +import docking.actions.KeyBindingUtils; import ghidra.framework.plugintool.PluginConfigurationModel; import ghidra.framework.plugintool.util.PluginDescription; import ghidra.framework.plugintool.util.PluginStatus; @@ -201,7 +201,7 @@ class PluginDetailsPanel extends AbstractDetailsPanel { buffer.append(""); KeyStroke keyBinding = dockableAction.getKeyBinding(); if (keyBinding != null) { - String keyStrokeString = DockingKeyBindingAction.parseKeyStroke(keyBinding); + String keyStrokeString = KeyBindingUtils.parseKeyStroke(keyBinding); insertHTMLString(keyStrokeString, locAttrSet, buffer); } else { diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/util/ToolConstants.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/util/ToolConstants.java index 22f353fb58..0663be45a3 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/util/ToolConstants.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/util/ToolConstants.java @@ -34,6 +34,12 @@ public interface ToolConstants extends DockingToolConstants { * Used when placing a PluginAction in the "Navigation" menu of the tool. */ String MENU_NAVIGATION = "&Navigation"; + + /** + * Group name for actions to navigate between windows + */ + String MENU_NAVIGATION_GROUP_WINDOWS = "GoToWindow"; + /** * Used when placing a PluginAction in the "Search" menu of the tool. */ diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/project/tool/GhidraTool.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/project/tool/GhidraTool.java index 0be74beed0..5c5d5f225a 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/project/tool/GhidraTool.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/project/tool/GhidraTool.java @@ -83,7 +83,7 @@ public class GhidraTool extends PluginTool { @Override protected DockingWindowManager createDockingWindowManager(boolean isDockable, boolean hasStatus, boolean isModal) { - return new DockingWindowManager("EMPTY", null, this, isModal, isDockable, hasStatus, + return new DockingWindowManager(this, null, this, isModal, isDockable, hasStatus, new OpenFileDropHandlerFactory(this)); } diff --git a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/ToolScreenShots.java b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/ToolScreenShots.java index c239c5904e..72e5171005 100644 --- a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/ToolScreenShots.java +++ b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/ToolScreenShots.java @@ -27,9 +27,11 @@ import javax.swing.border.Border; import org.junit.Before; import org.junit.Test; -import docking.*; +import docking.DialogComponentProvider; +import docking.StatusBar; import docking.action.DockingActionIf; -import docking.action.KeyEntryDialog; +import docking.actions.KeyEntryDialog; +import docking.actions.ToolActions; import docking.widgets.OptionDialog; import docking.widgets.table.GTable; import generic.jar.ResourceFile; @@ -280,12 +282,10 @@ public class ToolScreenShots extends GhidraScreenShotGenerator { public void testSetKeyBindings() { tool = env.launchDefaultTool(); - DockingWindowManager windowManager = tool.getWindowManager(); - ActionToGuiMapper actionMgr = - (ActionToGuiMapper) getInstanceField("actionManager", windowManager); + ToolActions toolActions = (ToolActions) getInstanceField("toolActions", tool); DockingActionIf action = getAction(tool, "FunctionPlugin", "Delete Function"); - final KeyEntryDialog keyEntryDialog = new KeyEntryDialog(action, actionMgr); + final KeyEntryDialog keyEntryDialog = new KeyEntryDialog(action, toolActions); runSwing(() -> tool.showDialog(keyEntryDialog), false); captureDialog();