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/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/test.slow/java/docking/ComponentProviderActionsTest.java b/Ghidra/Features/Base/src/test.slow/java/docking/ComponentProviderActionsTest.java index 8501ba3685..b40407bad3 100644 --- a/Ghidra/Features/Base/src/test.slow/java/docking/ComponentProviderActionsTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/docking/ComponentProviderActionsTest.java @@ -27,6 +27,7 @@ import org.junit.*; import docking.action.DockingActionIf; import docking.action.KeyBindingData; import docking.actions.KeyEntryDialog; +import docking.actions.ToolActions; import docking.tool.util.DockingToolConstants; import ghidra.framework.options.ToolOptions; import ghidra.framework.plugintool.PluginTool; @@ -235,15 +236,6 @@ public class ComponentProviderActionsTest extends AbstractGhidraHeadedIntegratio // Private Methods //================================================================================================== - private void assertShowProviderActionIsInToolbar() { - assertNotNull("The 'Show Provider' action is not in the toolbar", - getToolbarShowProviderAction()); - } - - private void assertShowProviderActionNotInToolbar() { - assertNull("The 'Show Provider' action is in the toolbar", getToolbarShowProviderAction()); - } - private void switchToTransientProvider() { provider.setTransient(); } @@ -390,9 +382,8 @@ public class ComponentProviderActionsTest extends AbstractGhidraHeadedIntegratio } private void performLaunchKeyStrokeDialogAction() { - DockingWindowManager dwm = tool.getWindowManager(); - ActionToGuiMapper actionMapper = dwm.getActionToGuiMapper(); - Action action = actionMapper.getDockingKeyAction(KeyStroke.getKeyStroke("F4")); + ToolActions toolActions = ((AbstractDockingTool) tool).getToolActions(); + Action action = toolActions.getAction(KeyStroke.getKeyStroke("F4")); assertNotNull(action); runSwing(() -> action.actionPerformed(new ActionEvent(this, 0, "")), false); } 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/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/java/ghidra/test/DummyTool.java b/Ghidra/Features/Base/src/test/java/ghidra/test/DummyTool.java index f5e4897bdd..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); } @@ -305,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 @@ -345,6 +353,11 @@ public class DummyTool implements Tool { //do nothing } + @Override + public ActionContext getGlobalContext() { + return null; + } + @Override public void setStatusInfo(String text) { //do nothing @@ -389,4 +402,9 @@ public class DummyTool implements Tool { 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..81a680cb91 --- /dev/null +++ b/Ghidra/Features/Base/src/test/java/ghidra/test/DummyToolActions.java @@ -0,0 +1,66 @@ +/* ### + * 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 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/Framework/Docking/src/main/java/docking/AbstractDockingTool.java b/Ghidra/Framework/Docking/src/main/java/docking/AbstractDockingTool.java index 5b1633ff21..3152821c5a 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/AbstractDockingTool.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/AbstractDockingTool.java @@ -59,7 +59,7 @@ public abstract class AbstractDockingTool implements DockingTool { public void addComponentProvider(ComponentProvider provider, boolean show) { Runnable r = () -> { winMgr.addComponent(provider, show); - toolActions.addToolAction(provider.getShowProviderAction()); + toolActions.addGlobalAction(provider.getShowProviderAction()); }; Swing.runNow(r); } @@ -67,7 +67,7 @@ public abstract class AbstractDockingTool implements DockingTool { @Override public void removeComponentProvider(ComponentProvider provider) { Runnable r = () -> { - toolActions.removeComponentActions(provider); + toolActions.removeActions(provider); winMgr.removeComponent(provider); }; Swing.runNow(r); @@ -99,12 +99,12 @@ public abstract class AbstractDockingTool implements DockingTool { @Override public void addAction(DockingActionIf action) { - toolActions.addToolAction(action); + toolActions.addGlobalAction(action); } @Override public void removeAction(DockingActionIf action) { - toolActions.removeToolAction(action); + toolActions.removeGlobalAction(action); } @Override @@ -114,15 +114,12 @@ public abstract class AbstractDockingTool implements DockingTool { @Override public void removeLocalAction(ComponentProvider provider, DockingActionIf action) { - toolActions.removeProviderAction(provider, action); + toolActions.removeLocalAction(provider, action); } @Override public Set getAllActions() { - Set actions = toolActions.getAllActions(); - ActionToGuiMapper am = winMgr.getActionToGuiMapper(); - actions.addAll(am.getAllActions()); - return actions; + return toolActions.getAllActions(); } @Override @@ -130,6 +127,11 @@ public abstract class AbstractDockingTool implements DockingTool { 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); @@ -176,6 +178,11 @@ 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); @@ -200,4 +207,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 d667f5ce46..41aa235be4 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/ActionToGuiHelper.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/ActionToGuiHelper.java @@ -18,7 +18,6 @@ package docking; import java.util.Iterator; import docking.action.DockingActionIf; -import docking.action.KeyBindingsManager; /** * A class that exists primarily to provide access to action-related package-level methods of the @@ -80,15 +79,6 @@ public class ActionToGuiHelper { windowManager.removeProviderAction(provider, action); } - /** - * Initializes key bindings manager of docking window manager - * - * @param keyBindingsManager key bindings manager - */ - public void setKeyBindingsManager(KeyBindingsManager keyBindingsManager) { - windowManager.setKeyBindingsManager(keyBindingsManager); - } - /** * Call this method to signal that key bindings for one or more actions have changed */ diff --git a/Ghidra/Framework/Docking/src/main/java/docking/ActionToGuiMapper.java b/Ghidra/Framework/Docking/src/main/java/docking/ActionToGuiMapper.java index 813408692f..3f84748118 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/ActionToGuiMapper.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/ActionToGuiMapper.java @@ -16,63 +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 static boolean enableDiagnosticActions; - private Set globalActions = new LinkedHashSet<>(); private MenuHandler menuBarMenuHandler; private MenuGroupMap menuGroupMap; - private KeyBindingsManager keyBindingsManager; private GlobalMenuAndToolBarManager menuAndToolBarManager; private PopupActionManager popupActionManager; - ActionToGuiMapper(DockingWindowManager winMgr, KeyBindingsManager keyBindingsManager) { - this.keyBindingsManager = keyBindingsManager; + ActionToGuiMapper(DockingWindowManager winMgr) { menuGroupMap = new MenuGroupMap(); menuBarMenuHandler = new MenuBarMenuHandler(winMgr); menuAndToolBarManager = new GlobalMenuAndToolBarManager(winMgr, menuBarMenuHandler, menuGroupMap); popupActionManager = new PopupActionManager(winMgr, menuGroupMap); - initializeHelpActions(); - } - - private void initializeHelpActions() { DockingWindowsContextSensitiveHelpListener.install(); - - 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)); - - 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; } /** @@ -87,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); } @@ -132,24 +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; - } - Set getGlobalActions() { return globalActions; } @@ -162,28 +95,16 @@ public class ActionToGuiMapper { } } - /** - * Close all menus (includes popup menus) - */ 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(); @@ -216,8 +137,4 @@ public class ActionToGuiMapper { public void showPopupMenu(ComponentPlaceholder componentInfo, MouseEvent e) { popupActionManager.popupMenu(componentInfo, e); } - - Action getDockingKeyAction(KeyStroke keyStroke) { - return keyBindingsManager.getDockingKeyAction(keyStroke); - } } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/ComponentProvider.java b/Ghidra/Framework/Docking/src/main/java/docking/ComponentProvider.java index 6d02f6a005..caeb6a5218 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/ComponentProvider.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/ComponentProvider.java @@ -544,21 +544,6 @@ public abstract class ComponentProvider implements HelpDescriptor, ActionContext } if (isInTool()) { - - /* - TODO - - 4) Wire default 'close' action to keybinding - 5) Add global action for (show last provider) - --Navigation menu? - 8) Update help locations - - Questions: - - C) How to wire universal close action (it is focus-dependent) - - */ - dockingTool.getWindowManager().setIcon(this, icon); } } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/DockingKeyBindingAction.java b/Ghidra/Framework/Docking/src/main/java/docking/DockingKeyBindingAction.java index 3965b72640..15512a423d 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/DockingKeyBindingAction.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/DockingKeyBindingAction.java @@ -27,17 +27,16 @@ 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 { +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) { + public DockingKeyBindingAction(DockingTool tool, DockingActionIf action, KeyStroke keyStroke) { super(KeyBindingUtils.parseKeyStroke(keyStroke)); - this.winMgr = winMgr; + this.tool = tool; this.docakbleAction = action; this.keyStroke = keyStroke; } @@ -52,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); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/DockingTool.java b/Ghidra/Framework/Docking/src/main/java/docking/DockingTool.java index 0dd9951e71..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,15 @@ 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 @@ -246,4 +262,15 @@ public interface DockingTool { * @return true if the tool's configuration has changed */ 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 d96abc6bb6..da74dd8293 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/DockingWindowManager.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/DockingWindowManager.java @@ -29,7 +29,8 @@ import javax.swing.*; import org.jdom.Element; import docking.action.DockingActionIf; -import docking.action.KeyBindingsManager; +import docking.actions.DockingToolActions; +import docking.actions.ToolActions; import docking.help.HelpService; import generic.util.WindowUtilities; import ghidra.framework.OperatingSystem; @@ -73,6 +74,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder private static List instanceList = new ArrayList<>(); + private DockingTool tool; private RootNode root; private PlaceholderManager placeholderManager; @@ -105,18 +107,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 +127,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,7 +140,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder images = new ArrayList<>(); } - root = new RootNode(this, toolName, images, modal, factory); + root = new RootNode(this, tool.getName(), images, modal, factory); KeyboardFocusManager km = KeyboardFocusManager.getCurrentKeyboardFocusManager(); km.addPropertyChangeListener("permanentFocusOwner", this); @@ -145,6 +148,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder addInstance(this); placeholderManager = new PlaceholderManager(this); + actionToGuiMapper = new ActionToGuiMapper(this); } @Override @@ -152,15 +156,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. @@ -310,17 +305,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 actionToGuiMapper.getDockingKeyAction(keyStroke); - } - /** * Returns true if this manager contains the given provider. * @@ -362,6 +346,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); @@ -640,24 +631,10 @@ 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) { - actionToGuiMapper.removeAll(owner); - placeholderManager.removeAll(owner); - scheduleUpdate(); - } - //================================================================================================== // Package-level Action Methods //================================================================================================== - void setKeyBindingsManager(KeyBindingsManager keyBindingsManager) { - actionToGuiMapper = new ActionToGuiMapper(this, keyBindingsManager); - } - Iterator getComponentActions(ComponentProvider provider) { ComponentPlaceholder placeholder = getActivePlaceholder(provider); if (placeholder != null) { @@ -671,7 +648,6 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder void removeProviderAction(ComponentProvider provider, DockingActionIf action) { ComponentPlaceholder placeholder = getActivePlaceholder(provider); if (placeholder != null) { - actionToGuiMapper.removeLocalAction(action); placeholder.removeAction(action); } } @@ -682,7 +658,6 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder throw new IllegalArgumentException("Unknown component provider: " + provider); } placeholder.addAction(action); - actionToGuiMapper.addLocalAction(action, provider); } void addToolAction(DockingActionIf action) { @@ -695,9 +670,31 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder 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. @@ -1002,7 +999,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder Iterator iter = placeholder.getActions(); while (iter.hasNext()) { DockingActionIf action = iter.next(); - actionToGuiMapper.removeLocalAction(action); + placeholder.removeAction(action); } ComponentNode node = placeholder.getNode(); @@ -1093,7 +1090,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder return; } - actionToGuiMapper.removeAll(DOCKING_WINDOWS_OWNER); + tool.getToolActions().removeActions(DOCKING_WINDOWS_OWNER); Map> permanentMap = new HashMap<>(); Map> transientMap = new HashMap<>(); @@ -1160,9 +1157,11 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder actionList.add(new ShowAllComponentsAction(this, placeholders, subMenuName)); } } + + DockingToolActions toolActions = tool.getToolActions(); Collections.sort(actionList); for (ShowComponentAction action : actionList) { - actionToGuiMapper.addToolAction(action); + toolActions.addGlobalAction(action); } } @@ -1198,9 +1197,10 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder } } + DockingToolActions toolActions = tool.getToolActions(); Collections.sort(actions); for (ShowWindowAction action : actions) { - actionToGuiMapper.addToolAction(action); + toolActions.addGlobalAction(action); } } @@ -1208,6 +1208,9 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder * Notifies the window manager that an update is needed */ void scheduleUpdate() { + if (rebuildUpdater.isBusy()) { + return; + } rebuildUpdater.updateLater(); } @@ -1225,7 +1228,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()); } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/ShowComponentAction.java b/Ghidra/Framework/Docking/src/main/java/docking/ShowComponentAction.java index 1fe95a7e32..5fb3afc650 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/ShowComponentAction.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/ShowComponentAction.java @@ -36,7 +36,6 @@ class ShowComponentAction extends DockingAction implements Comparable dockingKeyMap; protected Map actionToProviderMap; + private DockingTool tool; - private DockingWindowManager winMgr; - - public KeyBindingsManager(DockingWindowManager winMgr) { - this.winMgr = winMgr; + public KeyBindingsManager(DockingTool tool) { + this.tool = tool; dockingKeyMap = new HashMap<>(); actionToProviderMap = new HashMap<>(); } @@ -76,8 +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; } @@ -98,7 +97,7 @@ public class KeyBindingsManager implements PropertyChangeListener { KeyBindingData binding = KeyBindingData.createReservedKeyBindingData(keyStroke); action.setKeyBindingData(binding); - dockingKeyMap.put(keyStroke, new ReservedKeyBindingAction(winMgr, action, keyStroke)); + dockingKeyMap.put(keyStroke, new ReservedKeyBindingAction(tool, action, keyStroke)); } /** @@ -151,18 +150,12 @@ public class KeyBindingsManager implements PropertyChangeListener { } } - 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 8534e4bc06..cb97c5fed5 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/action/MultipleKeyAction.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/action/MultipleKeyAction.java @@ -32,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); } @@ -134,11 +116,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 @@ -163,12 +145,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); } } @@ -230,9 +212,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); @@ -274,4 +256,22 @@ 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; + } + } } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/action/ReservedKeyBindingAction.java b/Ghidra/Framework/Docking/src/main/java/docking/action/ReservedKeyBindingAction.java index d22414d0f7..2d69b23cd0 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/action/ReservedKeyBindingAction.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/action/ReservedKeyBindingAction.java @@ -17,18 +17,21 @@ package docking.action; import javax.swing.KeyStroke; -import docking.DockingKeyBindingAction; -import docking.DockingWindowManager; +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/actions/DockingToolActions.java b/Ghidra/Framework/Docking/src/main/java/docking/actions/DockingToolActions.java new file mode 100644 index 0000000000..2666ad2161 --- /dev/null +++ b/Ghidra/Framework/Docking/src/main/java/docking/actions/DockingToolActions.java @@ -0,0 +1,86 @@ +/* ### + * 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); + + /** + * 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/actions/KeyBindingUtils.java b/Ghidra/Framework/Docking/src/main/java/docking/actions/KeyBindingUtils.java index 96b013eb5f..2c1ba332e6 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/actions/KeyBindingUtils.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/actions/KeyBindingUtils.java @@ -25,6 +25,7 @@ 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.*; @@ -344,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 support {@link KeyBindingType key bindings}. + * 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)) { @@ -362,10 +369,10 @@ public class KeyBindingUtils { continue; } - deduper.put(action.getFullName(), action); + result.get(action.getFullName()).add(action); } - return deduper; + return result; } /** @@ -720,7 +727,7 @@ public class KeyBindingUtils { 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().isShared(); + return !action.getKeyBindingType().isManaged(); } private static KeyStroke getKeyStroke(KeyBindingData data) { 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 87b5afc73c..9bb89d0d84 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/actions/ToolActions.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/actions/ToolActions.java @@ -33,13 +33,14 @@ 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 ActionToGuiHelper actionGuiHelper; @@ -62,25 +63,38 @@ public class ToolActions implements PropertyChangeListener { * 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.actionGuiHelper = new ActionToGuiHelper(windowManager); - this.keyBindingsManager = new KeyBindingsManager(windowManager); + 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); - actionGuiHelper.setKeyBindingsManager(keyBindingsManager); + 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) { @@ -95,12 +109,14 @@ 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); + keyBindingsManager.addAction(action, provider); actionGuiHelper.addLocalAction(provider, action); } @@ -108,10 +124,14 @@ public class ToolActions implements PropertyChangeListener { * 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); + keyBindingsManager.addAction(action, null); actionGuiHelper.addToolAction(action); } @@ -158,17 +178,15 @@ public class ToolActions implements PropertyChangeListener { * 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); @@ -180,15 +198,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()); } } @@ -198,6 +218,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<>(); @@ -215,8 +236,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<>(); @@ -278,23 +301,24 @@ public class ToolActions implements PropertyChangeListener { * @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) { @@ -346,12 +370,12 @@ public class ToolActions implements PropertyChangeListener { } } - DockingActionIf getSharedStubKeyBindingAction(String name) { - return sharedActionMap.get(name); + public Action getAction(KeyStroke ks) { + return keyBindingsManager.getDockingKeyAction(ks); } - Action getAction(KeyStroke ks) { - return keyBindingsManager.getDockingKeyAction(ks); + DockingActionIf getSharedStubKeyBindingAction(String name) { + return sharedActionMap.get(name); } // triggered by a user-initiated action 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 f2f1f7b199..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,6 +360,36 @@ 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 //================================================================================================== @@ -361,7 +400,7 @@ public class SharedKeyBindingDockingActionTest extends AbstractDockingTest { 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); @@ -436,6 +475,19 @@ public class SharedKeyBindingDockingActionTest extends AbstractDockingTest { } } + private class TestNonSharedAction extends DockingAction { + + public TestNonSharedAction(String owner, KeyStroke ks) { + super(NON_SHARED_NAME, owner, KeyBindingType.INDIVIDUAL); + setKeyBindingData(new KeyBindingData(ks)); + } + + @Override + public void actionPerformed(ActionContext context) { + fail("Action performed should not have been called"); + } + } + private class DummyComponentProvider extends ComponentProvider { public DummyComponentProvider() { super(tool, "Dummy", "Dummy Owner"); diff --git a/Ghidra/Framework/Docking/src/test/java/docking/FakeDockingTool.java b/Ghidra/Framework/Docking/src/test/java/docking/FakeDockingTool.java index b879205e1c..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*/); - toolActions = 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/Project/src/main/java/ghidra/framework/plugintool/PluginTool.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/PluginTool.java index d3519663ab..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 @@ -150,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(); - toolActions = new ToolActions(this, winMgr); pluginMgr = new PluginManager(this, serviceMgr); dialogMgr = new DialogManager(this); initActions(); @@ -190,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; } @@ -1322,8 +1322,8 @@ public abstract class PluginTool extends AbstractDockingTool } void removeAll(String owner) { - toolActions.removeToolActions(owner); - winMgr.removeAll(owner); + toolActions.removeActions(owner); + winMgr.ownerRemoved(owner); } void registerEventProduced(Class eventClass) { @@ -1468,6 +1468,7 @@ public abstract class PluginTool extends AbstractDockingTool return winMgr.getActiveWindow(); } + @Override public ComponentProvider getActiveComponentProvider() { return winMgr.getActiveComponentProvider(); } 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 f7ed6f585d..d5ca1146ed 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 @@ -67,7 +67,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 @@ -114,11 +114,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) { @@ -130,19 +130,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()) { @@ -173,10 +166,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(); @@ -413,11 +408,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 = @@ -669,11 +666,14 @@ public class KeyBindingsPanel extends JPanel { } 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(); 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)); }