GT-2925 - Key Bindings - Support Window Menu Provider Key Bindings -

Step 7 - Untangled and removed the key binding management from the
ActionToGuiMapper; fixed bugs and tests
This commit is contained in:
dragonmacher 2019-06-28 15:20:13 -04:00
parent d684ee3ce6
commit 3946a05ded
27 changed files with 515 additions and 345 deletions

View file

@ -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<DockingActionIf> getAllActions() {
Set<DockingActionIf> 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;
}
}

View file

@ -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
*/

View file

@ -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<DockingActionIf> 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<DockingActionIf> iter = new ArrayList<>(globalActions).iterator();
List<DockingActionIf> 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<DockingActionIf> 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<DockingActionIf> actions = new HashSet<>();
SystemUtilities.runSwingNow(() -> {
actions.addAll(globalActions);
actions.addAll(keyBindingsManager.getLocalActions());
});
return actions;
}
Set<DockingActionIf> 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);
}
}

View file

@ -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);
}
}

View file

@ -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);

View file

@ -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<DockingActionIf> 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.
*
* <p>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();
}

View file

@ -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<DockingWindowManager> 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<Image> images, DockWinListener docListener) {
this(toolName, images, docListener, false, true, true, null);
public DockingWindowManager(DockingTool tool, List<Image> 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<Image> images, DockWinListener docListener,
public DockingWindowManager(DockingTool tool, List<Image> 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<DockingActionIf> 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<DockingActionIf> 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<String, List<ComponentPlaceholder>> permanentMap = new HashMap<>();
Map<String, List<ComponentPlaceholder>> 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());
}

View file

@ -36,7 +36,6 @@ class ShowComponentAction extends DockingAction implements Comparable<ShowCompon
protected DockingWindowManager winMgr;
private ComponentPlaceholder info;
private String title;
private boolean isTransient;
private static String truncateTitleAsNeeded(String title) {
if (title.length() <= MAX_LENGTH) {
@ -58,7 +57,6 @@ class ShowComponentAction extends DockingAction implements Comparable<ShowCompon
this.info = placeholder;
this.winMgr = winMgr;
this.title = truncateTitleAsNeeded(placeholder.getTitle());
this.isTransient = isTransient;
String group = isTransient ? "Transient" : "Permanent";
Icon icon = placeholder.getIcon();
@ -139,6 +137,10 @@ class ShowComponentAction extends DockingAction implements Comparable<ShowCompon
@Override
public String getHelpInfo() {
if (info == null) {
return super.getHelpInfo();
}
StringBuilder buffy = new StringBuilder(super.getHelpInfo());
ComponentProvider provider = info.getProvider();

View file

@ -17,7 +17,8 @@ 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;
@ -30,11 +31,10 @@ public class KeyBindingsManager implements PropertyChangeListener {
protected Map<KeyStroke, DockingKeyBindingAction> dockingKeyMap;
protected Map<DockingActionIf, ComponentProvider> 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<DockingActionIf> getLocalActions() {
return new ArrayList<>(actionToProviderMap.keySet());
}
public Action getDockingKeyAction(KeyStroke keyStroke) {
return dockingKeyMap.get(keyStroke);
}
public void dispose() {
winMgr = null;
dockingKeyMap.clear();
actionToProviderMap.clear();
}
}

View file

@ -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<ExecutableKeyActionAdapter> 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<ExecutableKeyActionAdapter> 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;
}
}
}

View file

@ -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;
}
}

View file

@ -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<DockingActionIf> getActions(String owner);
/**
* Returns all actions known to the tool
* @return the actions
*/
public Set<DockingActionIf> getAllActions();
}

View file

@ -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}.
*
* <p>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<String, DockingActionIf> getAllActionsByFullName(DockingTool tool) {
public static Map<String, List<DockingActionIf>> getAllActionsByFullName(DockingTool tool) {
Map<String, DockingActionIf> deduper = new HashMap<>();
Map<String, List<DockingActionIf>> result =
LazyMap.lazyMap(new HashMap<>(), s -> new LinkedList<>());
Set<DockingActionIf> 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) {

View file

@ -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<String, Set<DockingActionIf>> 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<DockingActionIf> getActions(String owner) {
Set<DockingActionIf> 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<DockingActionIf> getAllActions() {
Set<DockingActionIf> 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<DockingActionIf> it = actionGuiHelper.getComponentActions(provider);
// copy the data to avoid concurrent modification exceptions
Set<DockingActionIf> 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

View file

@ -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<DockingActionIf> 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<DockingActionIf> 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");

View file

@ -35,9 +35,9 @@ public class FakeDockingTool extends AbstractDockingTool {
DockWinListener listener = new DummyListener();
List<Image> 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<DockingActionIf> getPopupActions(ActionContext context) {
return null;
}
}
}