Merge remote-tracking branch 'origin/GT-2925-dragonmacher-window-menu-key-bindings'

This commit is contained in:
Ryan Kurtz 2019-07-15 16:12:09 -04:00
commit 10372c2e13
200 changed files with 3955 additions and 3195 deletions

View file

@ -23,7 +23,7 @@ import javax.swing.JFrame;
import docking.action.DockingActionIf;
import docking.actions.ToolActions;
import ghidra.framework.options.ToolOptions;
import ghidra.util.SystemUtilities;
import ghidra.util.Swing;
/**
* A partial implementation of {@link DockingTool} that serves as a place to share common
@ -32,7 +32,7 @@ import ghidra.util.SystemUtilities;
public abstract class AbstractDockingTool implements DockingTool {
protected DockingWindowManager winMgr;
protected ToolActions actionMgr;
protected ToolActions toolActions;
protected Map<String, ToolOptions> optionsMap = new HashMap<>();
protected boolean configChangedFlag;
@ -57,17 +57,21 @@ public abstract class AbstractDockingTool implements DockingTool {
@Override
public void addComponentProvider(ComponentProvider provider, boolean show) {
Runnable r = () -> winMgr.addComponent(provider, show);
SystemUtilities.runSwingNow(r);
Runnable r = () -> {
winMgr.addComponent(provider, show);
toolActions.addGlobalAction(provider.getShowProviderAction());
};
Swing.runNow(r);
}
@Override
public void removeComponentProvider(ComponentProvider provider) {
Runnable r = () -> {
actionMgr.removeComponentActions(provider);
toolActions.removeGlobalAction(provider.getShowProviderAction());
toolActions.removeActions(provider);
winMgr.removeComponent(provider);
};
SystemUtilities.runSwingNow(r);
Swing.runNow(r);
}
@Override
@ -96,41 +100,43 @@ public abstract class AbstractDockingTool implements DockingTool {
@Override
public void addAction(DockingActionIf action) {
actionMgr.addToolAction(action);
toolActions.addGlobalAction(action);
}
@Override
public void removeAction(DockingActionIf action) {
actionMgr.removeToolAction(action);
toolActions.removeGlobalAction(action);
}
@Override
public void addLocalAction(ComponentProvider provider, DockingActionIf action) {
actionMgr.addLocalAction(provider, action);
toolActions.addLocalAction(provider, action);
}
@Override
public void removeLocalAction(ComponentProvider provider, DockingActionIf action) {
actionMgr.removeProviderAction(provider, action);
toolActions.removeLocalAction(provider, action);
}
@Override
public Set<DockingActionIf> getAllActions() {
Set<DockingActionIf> actions = actionMgr.getAllActions();
ActionToGuiMapper am = winMgr.getActionManager();
actions.addAll(am.getAllActions());
return actions;
return toolActions.getAllActions();
}
@Override
public Set<DockingActionIf> getDockingActionsByOwnerName(String owner) {
return actionMgr.getActions(owner);
return toolActions.getActions(owner);
}
@Override
public ComponentProvider getActiveComponentProvider() {
return winMgr.getActiveComponentProvider();
}
@Override
public void showComponentProvider(ComponentProvider provider, boolean visible) {
Runnable r = () -> winMgr.showComponent(provider, visible);
SystemUtilities.runSwingNow(r);
Swing.runNow(r);
}
@Override
@ -150,7 +156,7 @@ public abstract class AbstractDockingTool implements DockingTool {
@Override
public void toFront(ComponentProvider provider) {
Runnable r = () -> winMgr.toFront(provider);
SystemUtilities.runSwingNow(r);
Swing.runNow(r);
}
@Override
@ -173,6 +179,21 @@ public abstract class AbstractDockingTool implements DockingTool {
winMgr.contextChanged(provider);
}
@Override
public ActionContext getGlobalContext() {
return winMgr.getGlobalContext();
}
@Override
public void addContextListener(DockingContextListener listener) {
winMgr.addContextListener(listener);
}
@Override
public void removeContextListener(DockingContextListener listener) {
winMgr.removeContextListener(listener);
}
@Override
public DockingWindowManager getWindowManager() {
return winMgr;
@ -187,4 +208,9 @@ public abstract class AbstractDockingTool implements DockingTool {
public boolean hasConfigChanged() {
return configChangedFlag;
}
@Override
public ToolActions getToolActions() {
return toolActions;
}
}

View file

@ -78,4 +78,11 @@ public class ActionToGuiHelper {
public void removeProviderAction(ComponentProvider provider, DockingActionIf action) {
windowManager.removeProviderAction(provider, action);
}
/**
* Call this method to signal that key bindings for one or more actions have changed
*/
public void keyBindingsChanged() {
windowManager.scheduleUpdate();
}
}

View file

@ -16,70 +16,38 @@
package docking;
import java.awt.event.MouseEvent;
import java.util.*;
import java.util.LinkedHashSet;
import java.util.Set;
import javax.swing.*;
import javax.swing.JComponent;
import javax.swing.MenuSelectionManager;
import docking.action.*;
import docking.action.DockingActionIf;
import docking.menu.MenuGroupMap;
import docking.menu.MenuHandler;
import ghidra.util.*;
import ghidra.util.HelpLocation;
/**
* Manages the global actions for the menu and toolbar.
*/
public class ActionToGuiMapper {
private HashSet<DockingActionIf> globalActions = new LinkedHashSet<>();
private Set<DockingActionIf> globalActions = new LinkedHashSet<>();
private MenuHandler menuBarMenuHandler;
private MenuGroupMap menuGroupMap;
private static boolean enableDiagnosticActions;
private KeyBindingsManager keyBindingsManager;
private GlobalMenuAndToolBarManager menuAndToolBarManager;
private PopupActionManager popupActionManager;
private DockingAction keyBindingsAction;
ActionToGuiMapper(DockingWindowManager winMgr) {
menuGroupMap = new MenuGroupMap();
menuBarMenuHandler = new MenuBarMenuHandler(winMgr);
keyBindingsManager = new KeyBindingsManager(winMgr);
menuAndToolBarManager =
new GlobalMenuAndToolBarManager(winMgr, menuBarMenuHandler, menuGroupMap);
popupActionManager = new PopupActionManager(winMgr, menuGroupMap);
initializeHelpActions();
}
private void initializeHelpActions() {
DockingWindowsContextSensitiveHelpListener.install();
keyBindingsAction = new KeyBindingAction(this);
keyBindingsManager.addReservedAction(new HelpAction(false, ReservedKeyBindings.HELP_KEY1));
keyBindingsManager.addReservedAction(new HelpAction(false, ReservedKeyBindings.HELP_KEY2));
keyBindingsManager.addReservedAction(
new HelpAction(true, ReservedKeyBindings.HELP_INFO_KEY));
keyBindingsManager.addReservedAction(keyBindingsAction);
if (enableDiagnosticActions) {
keyBindingsManager.addReservedAction(new ShowFocusInfoAction());
keyBindingsManager.addReservedAction(new ShowFocusCycleAction());
}
}
/**
* A static initializer allowing additional diagnostic actions
* to be added to all frame and dialog windows.
* @param enable
*/
static void enableDiagnosticActions(boolean enable) {
enableDiagnosticActions = enable;
}
/**
@ -94,41 +62,12 @@ public class ActionToGuiMapper {
DockingWindowManager.getHelpService().registerHelp(c, helpLocation);
}
/**
* Removes all actions associated with the given owner
* @param owner the owner of all actions to be removed.
*/
void removeAll(String owner) {
Iterator<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);
}
@ -139,28 +78,11 @@ public class ActionToGuiMapper {
* @param action the action to be removed.
*/
void removeToolAction(DockingActionIf action) {
keyBindingsManager.removeAction(action);
popupActionManager.removeAction(action);
menuAndToolBarManager.removeAction(action);
globalActions.remove(action);
}
public Set<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;
}
public Action getDockingKeyAction(KeyStroke keyStroke) {
return keyBindingsManager.getDockingKeyAction(keyStroke);
}
Set<DockingActionIf> getGlobalActions() {
return globalActions;
}
@ -173,28 +95,16 @@ public class ActionToGuiMapper {
}
}
/**
* Close all menus (includes popup menus)
*/
static void dismissMenus() {
private void dismissMenus() {
MenuSelectionManager.defaultManager().clearSelectedPath();
}
/**
* Updates the menu and toolbar to reflect any changes in the set of actions.
*
*/
void update() {
menuAndToolBarManager.update();
contextChangedAll();
}
/**
* Releases all resources and makes this object unavailable for future use.
*
*/
void dispose() {
keyBindingsManager.dispose();
popupActionManager.dispose();
menuAndToolBarManager.dispose();
globalActions.clear();

View file

@ -326,7 +326,7 @@ class ComponentNode extends Node {
DockingTabRenderer tabRenderer) {
final ComponentProvider provider = placeholder.getProvider();
if (!provider.isTransient()) {
if (!provider.isTransient() || provider.isSnapshot()) {
return; // don't muck with the title of 'real' providers--only transients, like search
}

View file

@ -449,6 +449,16 @@ public class ComponentPlaceholder {
}
}
void removeAllActions() {
if (comp != null) {
for (DockingActionIf action : actions) {
comp.actionRemoved(action);
}
}
actions.clear();
}
/**
* Removes an action from this component
* @param action the action to be removed.

View file

@ -25,9 +25,9 @@ import javax.swing.*;
import docking.action.*;
import docking.help.HelpDescriptor;
import docking.help.HelpService;
import ghidra.util.HelpLocation;
import ghidra.util.UniversalIdGenerator;
import ghidra.util.*;
import ghidra.util.exception.AssertException;
import utilities.util.reflection.ReflectionUtilities;
/**
* Abstract base class for creating dockable GUI components within a tool.
@ -61,13 +61,28 @@ import ghidra.util.exception.AssertException;
* <li>{@link #componentActivated()} and {@link #componentDeactived()}
* <li>{@link #componentHidden()} and {@link #componentShown()}
* </ul>
*
* <p>
* Note: This class was created so that implementors could add local actions within the constructor
* <b><u>Show Provider Action</u></b> - Each provider has an action to show the provider. For
* typical, non-transient providers (see {@link #setTransient()}) the action will appear in
* the tool's <b>Window</b> menu. You can have your provider also appear in the tool's toolbar
* by calling {@link #setIcon(Icon, boolean)}, passing <code>true</code> for
* <code>isToolbarAction</code>.
* <p>
* Historical Note: This class was created so that implementors could add local actions within the constructor
* without having to understand that they must first add themselves to the WindowManager.
*/
public abstract class ComponentProvider implements HelpDescriptor, ActionContextProvider {
private static final String TRANSIENT_PROVIDER_TOOLBAR_WARNING_MESSAGE =
"Transient providers are not added to the toolbar";
private static final String TRANSIENT_PROVIDER_KEY_BINDING_WARNING_MESSAGE =
"Transient providers cannot have key bindings";
public static final String DEFAULT_WINDOW_GROUP = "Default";
private static final String TOOLBAR_GROUP = "View";
// maps for mapping old provider names and owner to new names and/or owner
private static Map<String, String> oldOwnerMap = new HashMap<>();
private static Map<String, String> oldNameMap = new HashMap<>();
@ -77,19 +92,29 @@ public abstract class ComponentProvider implements HelpDescriptor, ActionContext
private String title;
private String subTitle;
private String tabText;
private Icon icon;
private Set<DockingActionIf> actionSet = new LinkedHashSet<>();
private String windowMenuGroup;
/** True if this provider's action should appear in the toolbar */
private boolean addToolbarAction;
private boolean isTransient;
private HelpLocation helpLocation;
private KeyBindingData defaultKeyBindingData;
private Icon icon;
private String windowMenuGroup;
private String group = DEFAULT_WINDOW_GROUP;
private WindowPosition defaultWindowPosition = WindowPosition.WINDOW;
private WindowPosition defaultIntraGroupPosition = WindowPosition.STACK;
private DockingAction showProviderAction;
private HelpLocation helpLocation;
private final Class<?> contextType;
private long instanceID = UniversalIdGenerator.nextID().getValue();
private boolean instanceIDHasBeenInitialized;
private String inceptionInformation;
/**
* Creates a new component provider with a default location of {@link WindowPosition#WINDOW}.
* @param tool The tool will manage and show this provider
@ -116,6 +141,32 @@ public abstract class ComponentProvider implements HelpDescriptor, ActionContext
this.owner = owner;
this.title = name;
this.contextType = contextType;
recordInception();
}
/**
* Returns the action used to show this provider
* @return the action
*/
DockingActionIf getShowProviderAction() {
createShowProviderAction();
return showProviderAction;
}
private void createShowProviderAction() {
if (showProviderAction != null) {
return;
}
if (addToolbarAction) {
Objects.requireNonNull(icon,
"The provider's icon cannot be null when requesting the provider's action " +
"appear in the toolbar");
}
boolean supportsKeyBindings = !isTransient;
showProviderAction = new ShowProviderAction(supportsKeyBindings);
}
/**
@ -361,6 +412,10 @@ public abstract class ComponentProvider implements HelpDescriptor, ActionContext
this.helpLocation = helpLocation;
HelpService helpService = DockingWindowManager.getHelpService();
helpService.registerHelp(this, helpLocation);
if (showProviderAction != null) {
showProviderAction.setHelpLocation(helpLocation);
}
}
/**
@ -449,13 +504,56 @@ public abstract class ComponentProvider implements HelpDescriptor, ActionContext
}
/**
* Convenience method for setting the provider's icon.
* @param icon the icon to use for this provider.
* Sets the default key binding that will show this provider when pressed. This value can
* be changed by the user and saved as part of the Tool options.
*
* @param kbData the key binding
*/
public void setIcon(Icon icon) {
this.icon = icon;
protected void setKeyBinding(KeyBindingData kbData) {
if (isInTool()) {
dockingTool.getWindowManager().setIcon(this, icon);
throw new IllegalStateException(
"Cannot set the default key binding after the provider is added to the tool");
}
this.defaultKeyBindingData = kbData;
if (isTransient && kbData != null) {
Msg.error(this, TRANSIENT_PROVIDER_KEY_BINDING_WARNING_MESSAGE,
ReflectionUtilities.createJavaFilteredThrowable());
this.defaultKeyBindingData = null;
}
}
/**
* Convenience method for setting the provider's icon
* @param icon the icon to use for this provider
*/
protected void setIcon(Icon icon) {
this.icon = icon;
if (!isInTool()) {
return;
}
if (addToolbarAction && showProviderAction != null) {
Objects.requireNonNull(icon, "Icon cannot be set to null when using a toolbar action");
showProviderAction.setToolBarData(new ToolBarData(icon));
}
dockingTool.getWindowManager().setIcon(this, icon);
}
/**
* Signals that this provider's action for showing the provider should appear in the main
* toolbar
*/
protected void addToToolbar() {
this.addToolbarAction = true;
if (isTransient) {
Msg.error(this, TRANSIENT_PROVIDER_TOOLBAR_WARNING_MESSAGE,
ReflectionUtilities.createJavaFilteredThrowable());
addToolbarAction = false;
}
}
@ -465,7 +563,6 @@ public abstract class ComponentProvider implements HelpDescriptor, ActionContext
* @return the menu group for this provider or null if this provider should appear in the
* top-level menu.
*/
public String getWindowSubMenuName() {
return windowMenuGroup;
}
@ -473,10 +570,20 @@ public abstract class ComponentProvider implements HelpDescriptor, ActionContext
/**
* Returns true if this component goes away during a user session (most providers remain in
* the tool all session long, visible or not)
* @return true if transitent
* @return true if transient
*/
public boolean isTransient() {
return isTransient;
return isTransient || isSnapshot();
}
/**
* A special marker that indicates this provider is a snapshot of a primary provider,
* somewhat like a picture of the primary provider.
*
* @return true if a snapshot
*/
public boolean isSnapshot() {
return false;
}
/**
@ -485,6 +592,25 @@ public abstract class ComponentProvider implements HelpDescriptor, ActionContext
*/
protected void setTransient() {
isTransient = true;
if (isInTool()) {
throw new IllegalStateException(
"A component provider cannot be marked as 'transient' " +
"after it is added to the tool");
}
// avoid visually disturbing the user by adding/removing toolbar actions for temp providers
if (addToolbarAction) {
addToolbarAction = false;
Msg.error(this, TRANSIENT_PROVIDER_TOOLBAR_WARNING_MESSAGE,
ReflectionUtilities.createJavaFilteredThrowable());
}
if (defaultKeyBindingData != null) {
defaultKeyBindingData = null;
Msg.error(this, TRANSIENT_PROVIDER_KEY_BINDING_WARNING_MESSAGE,
ReflectionUtilities.createJavaFilteredThrowable());
}
}
/**
@ -591,6 +717,22 @@ public abstract class ComponentProvider implements HelpDescriptor, ActionContext
return name + " - " + getTitle() + " - " + getSubTitle();
}
private void recordInception() {
if (!SystemUtilities.isInDevelopmentMode()) {
inceptionInformation = "";
return;
}
inceptionInformation = getInceptionFromTheFirstClassThatIsNotUs();
}
private String getInceptionFromTheFirstClassThatIsNotUs() {
Throwable t = ReflectionUtilities.createThrowableWithStackOlderThan(getClass());
StackTraceElement[] trace = t.getStackTrace();
String classInfo = trace[0].toString();
return classInfo;
}
/**
* Returns any registered new provider name for the oldName/oldOwner pair.
* @param oldOwner the old owner name
@ -633,4 +775,38 @@ public abstract class ComponentProvider implements HelpDescriptor, ActionContext
return "owner=" + oldOwner + "name=" + oldName;
}
private class ShowProviderAction extends DockingAction {
ShowProviderAction(boolean supportsKeyBindings) {
super(name, owner,
supportsKeyBindings ? KeyBindingType.SHARED : KeyBindingType.UNSUPPORTED);
if (addToolbarAction) {
setToolBarData(new ToolBarData(icon, TOOLBAR_GROUP));
}
if (supportsKeyBindings && defaultKeyBindingData != null) {
// this action itself is not 'key binding managed', but the system *will* use
// any key binding value we set when connecting 'shared' actions
setKeyBindingData(defaultKeyBindingData);
}
setDescription("Display " + name);
HelpLocation providerHelp = ComponentProvider.this.getHelpLocation();
if (providerHelp != null) {
setHelpLocation(providerHelp);
}
}
@Override
public void actionPerformed(ActionContext context) {
dockingTool.showComponentProvider(ComponentProvider.this, true);
}
@Override
protected String getInceptionFromTheFirstClassThatIsNotUs() {
// overridden to show who created the provider, as that is what this action represents
return inceptionInformation;
}
}
}

View file

@ -69,7 +69,7 @@ public class DialogComponentProviderPopupActionManager {
return;
}
ActionToGuiMapper actionManager = dwm.getActionManager();
ActionToGuiMapper actionManager = dwm.getActionToGuiMapper();
MenuGroupMap menuGroupMap = actionManager.getMenuGroupMap();
MenuManager menuMgr =
new MenuManager("Popup", '\0', null, true, popupMenuHandler, menuGroupMap);
@ -102,7 +102,7 @@ public class DialogComponentProviderPopupActionManager {
Object source = actionContext.getSourceObject();
if (source instanceof DockingActionProviderIf) {
DockingActionProviderIf actionProvider = (DockingActionProviderIf) source;
List<DockingActionIf> dockingActions = actionProvider.getDockingActions(actionContext);
List<DockingActionIf> dockingActions = actionProvider.getDockingActions();
for (DockingActionIf action : dockingActions) {
MenuData popupMenuData = action.getPopupMenuData();
if (popupMenuData != null && action.isValidContext(actionContext) &&

View file

@ -65,7 +65,7 @@ public class DockableComponent extends JPanel implements ContainerListener {
this.componentInfo = placeholder;
winMgr = placeholder.getNode().winMgr;
actionMgr = winMgr.getActionManager();
actionMgr = winMgr.getActionToGuiMapper();
popupListener = new MouseAdapter() {
@Override

View file

@ -87,13 +87,12 @@ public class DockableHeader extends GenericHeader
private boolean isDocking;
private Animator focusAnimator;
private int focusToggle = -1;
/**
* Constructs a new DockableHeader for the given dockableComponent.
*
* @param dockableComp
* the dockableComponent that this header is for.
* @param dockableComp the dockableComponent that this header is for.
* @param isDocking true means this widget can be dragged and docked by the user
*/
DockableHeader(DockableComponent dockableComp, boolean isDocking) {
this.dockComp = dockableComp;
@ -173,8 +172,11 @@ public class DockableHeader extends GenericHeader
}
protected Animator createEmphasizingAnimator(JFrame parentFrame) {
focusToggle += 1;
switch (focusToggle) {
double random = Math.random();
int choices = 4;
int value = (int) (choices * random);
switch (value) {
case 0:
return AnimationUtils.shakeComponent(component);
case 1:
@ -182,7 +184,6 @@ public class DockableHeader extends GenericHeader
case 2:
return raiseComponent(parentFrame);
default:
focusToggle = -1;
return AnimationUtils.pulseComponent(component);
}
}
@ -226,12 +227,12 @@ public class DockableHeader extends GenericHeader
if (!isDocking) {
return;
}
// check input event: if any button other than MB1 is pressed,
// don't attempt to process the drag and drop event.
// if any button other than MB1 is pressed, don't attempt to process the drag and drop event
InputEvent ie = event.getTriggerEvent();
int modifiers = ie.getModifiers();
if ((modifiers & InputEvent.BUTTON2_MASK) != 0 ||
(modifiers & InputEvent.BUTTON3_MASK) != 0) {
int modifiers = ie.getModifiersEx();
if ((modifiers & InputEvent.BUTTON2_DOWN_MASK) != 0 ||
(modifiers & InputEvent.BUTTON3_DOWN_MASK) != 0) {
return;
}
DockableComponent.DROP_CODE = DockableComponent.DropCode.WINDOW;

View file

@ -60,7 +60,7 @@ class DockableToolBarManager {
ComponentPlaceholder placeholder = dockableComp.getComponentWindowingPlaceholder();
DockingWindowManager winMgr =
dockableComp.getComponentWindowingPlaceholder().getNode().winMgr;
ActionToGuiMapper actionManager = winMgr.getActionManager();
ActionToGuiMapper actionManager = winMgr.getActionToGuiMapper();
menuGroupMap = actionManager.getMenuGroupMap();
MenuHandler menuHandler = actionManager.getMenuHandler();
@ -168,7 +168,8 @@ class DockableToolBarManager {
private DockableComponent dockableComponent;
ToolBarCloseAction(DockableComponent dockableComponent) {
super("Close Window", DockingWindowManager.DOCKING_WINDOWS_OWNER);
super("Close Window", DockingWindowManager.DOCKING_WINDOWS_OWNER,
KeyBindingType.UNSUPPORTED);
this.dockableComponent = dockableComponent;
setDescription("Close Window");
setToolBarData(new ToolBarData(closeIcon, null));

View file

@ -200,8 +200,8 @@ public class DockingActionProxy
}
@Override
public boolean isKeyBindingManaged() {
return dockingAction.isKeyBindingManaged();
public KeyBindingType getKeyBindingType() {
return dockingAction.getKeyBindingType();
}
@Override

View file

@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,6 +15,18 @@
*/
package docking;
import docking.action.DockingActionIf;
/**
* A listener to be notified when the tool's context changes. Normally context is used to
* manage {@link DockingActionIf} enablement directly by the system. This class allows
* clients to listen to context change as well.
*/
public interface DockingContextListener {
void contextChanged(ActionContext context);
/**
* Called when the context changes
* @param context the context
*/
public void contextChanged(ActionContext context);
}

View file

@ -15,42 +15,28 @@
*/
package docking;
import java.awt.event.*;
import java.util.*;
import java.awt.event.ActionEvent;
import javax.swing.AbstractAction;
import javax.swing.KeyStroke;
import docking.action.DockingActionIf;
import ghidra.util.Msg;
import ghidra.util.StringUtilities;
import docking.actions.KeyBindingUtils;
/**
* A class that can be used as an interface for using actions associated with keybindings. This
* class is meant to only by used by internal Ghidra key event processing.
*/
public class DockingKeyBindingAction extends AbstractAction {
private static final String RELEASED = "released";
private static final String TYPED = "typed";
private static final String PRESSED = "pressed";
private static final String SHIFT = "Shift";
private static final String CTRL = "Ctrl";
private static final String CONTROL = "Control";
private static final String ALT = "Alt";
private static final String META = "Meta";
private static final String MODIFIER_SEPARATOR = "-";
public abstract class DockingKeyBindingAction extends AbstractAction {
private DockingActionIf docakbleAction;
protected KeyStroke keyStroke;
protected final DockingWindowManager winMgr;
protected final KeyStroke keyStroke;
protected final DockingTool tool;
public DockingKeyBindingAction(DockingWindowManager winMgr, DockingActionIf action,
KeyStroke keyStroke) {
super(parseKeyStroke(keyStroke));
this.winMgr = winMgr;
public DockingKeyBindingAction(DockingTool tool, DockingActionIf action, KeyStroke keyStroke) {
super(KeyBindingUtils.parseKeyStroke(keyStroke));
this.tool = tool;
this.docakbleAction = action;
this.keyStroke = keyStroke;
}
@ -65,18 +51,16 @@ public class DockingKeyBindingAction extends AbstractAction {
return true;
}
public boolean isReservedKeybindingPrecedence() {
return getKeyBindingPrecedence() == KeyBindingPrecedence.ReservedActionsLevel;
}
public abstract KeyBindingPrecedence getKeyBindingPrecedence();
public KeyBindingPrecedence getKeyBindingPrecedence() {
return KeyBindingPrecedence.ReservedActionsLevel;
public boolean isReservedKeybindingPrecedence() {
return false;
}
@Override
public void actionPerformed(final ActionEvent e) {
winMgr.setStatusText("");
ComponentProvider provider = winMgr.getActiveComponentProvider();
tool.setStatusInfo("");
ComponentProvider provider = tool.getActiveComponentProvider();
ActionContext context = getLocalContext(provider);
context.setSource(e.getSource());
docakbleAction.actionPerformed(context);
@ -94,147 +78,4 @@ public class DockingKeyBindingAction extends AbstractAction {
return new ActionContext(localProvider, null);
}
/**
* Convert the toString() form of the keyStroke.
* <br>In Java 1.4.2 & earlier, Ctrl-M is returned as "keyCode CtrlM-P"
* and we want it to look like: "Ctrl-M".
* <br>In Java 1.5.0, Ctrl-M is returned as "ctrl pressed M"
* and we want it to look like: "Ctrl-M".
*/
public static String parseKeyStroke(KeyStroke keyStroke) {
final String keyPressSuffix = "-P";
String keyString = keyStroke.toString();
int type = keyStroke.getKeyEventType();
if (type == KeyEvent.KEY_TYPED) {
return String.valueOf(keyStroke.getKeyChar());
}
// get the character used in the key stroke
int firstIndex = keyString.lastIndexOf(' ') + 1;
int ctrlIndex = keyString.indexOf(CTRL, firstIndex);
if (ctrlIndex >= 0) {
firstIndex = ctrlIndex + CTRL.length();
}
int altIndex = keyString.indexOf(ALT, firstIndex);
if (altIndex >= 0) {
firstIndex = altIndex + ALT.length();
}
int shiftIndex = keyString.indexOf(SHIFT, firstIndex);
if (shiftIndex >= 0) {
firstIndex = shiftIndex + SHIFT.length();
}
int metaIndex = keyString.indexOf(META, firstIndex);
if (metaIndex >= 0) {
firstIndex = metaIndex + META.length();
}
int lastIndex = keyString.length();
if (keyString.endsWith(keyPressSuffix)) {
lastIndex -= keyPressSuffix.length();
}
if (lastIndex >= 0) {
keyString = keyString.substring(firstIndex, lastIndex);
}
int modifiers = keyStroke.getModifiers();
StringBuffer buffer = new StringBuffer();
if ((modifiers & InputEvent.SHIFT_MASK) != 0) {
buffer.insert(0, SHIFT + MODIFIER_SEPARATOR);
}
if ((modifiers & InputEvent.ALT_MASK) != 0) {
buffer.insert(0, ALT + MODIFIER_SEPARATOR);
}
if ((modifiers & InputEvent.CTRL_MASK) != 0) {
buffer.insert(0, CTRL + MODIFIER_SEPARATOR);
}
if ((modifiers & InputEvent.META_MASK) != 0) {
buffer.insert(0, META + MODIFIER_SEPARATOR);
}
buffer.append(keyString);
return buffer.toString();
}
/**
* Parses the given text into a KeyStroke. This method relies upon
* {@link KeyStroke#getKeyStroke(String)} for parsing. Before making that call, this method
* will perform fixup on the given text for added flexibility. For example, the given
* text may contain spaces or dashes as the separators between parts in the string. Also,
* the text is converted such that it is not case-sensitive. So, the following example
* formats are allowed:
* <pre>
* Alt-F
* alt p
* Ctrl-Alt-Z
* ctrl Z
* </pre>
*
* @param keyStroke
* @return
*/
public static KeyStroke parseKeyStroke(String keyStroke) {
List<String> pieces = new ArrayList<>();
StringTokenizer tokenizer = new StringTokenizer(keyStroke, "- ");
while (tokenizer.hasMoreTokens()) {
String token = tokenizer.nextToken();
if (!pieces.contains(token)) {
pieces.add(token);
}
}
StringBuffer keyStrokeBuff = new StringBuffer();
for (Iterator<String> iterator = pieces.iterator(); iterator.hasNext();) {
String piece = iterator.next();
if (StringUtilities.indexOfIgnoreCase(piece, SHIFT) != -1) {
keyStrokeBuff.append("shift ");
iterator.remove();
}
else if (StringUtilities.indexOfIgnoreCase(piece, CTRL) != -1) {
keyStrokeBuff.append("ctrl ");
iterator.remove();
}
else if (StringUtilities.indexOfIgnoreCase(piece, CONTROL) != -1) {
keyStrokeBuff.append("ctrl ");
iterator.remove();
}
else if (StringUtilities.indexOfIgnoreCase(piece, ALT) != -1) {
keyStrokeBuff.append("alt ");
iterator.remove();
}
else if (StringUtilities.indexOfIgnoreCase(piece, META) != -1) {
keyStrokeBuff.append("meta ");
iterator.remove();
}
else if (StringUtilities.indexOfIgnoreCase(piece, PRESSED) != -1) {
iterator.remove();
}
else if (StringUtilities.indexOfIgnoreCase(piece, TYPED) != -1) {
iterator.remove();
}
else if (StringUtilities.indexOfIgnoreCase(piece, RELEASED) != -1) {
iterator.remove();
}
}
keyStrokeBuff.append(PRESSED).append(' ');
// at this point we should only have left one piece--the key ID
int leftover = pieces.size();
if (leftover > 1 || leftover == 0) {
Msg.warn(DockingKeyBindingAction.class, "Invalid keystroke string found. Expected " +
"format of '[modifier] ... key'. Found: '" + keyStroke + "'");
if (leftover == 0) {
return null; // nothing to do
}
}
String key = pieces.get(0);
keyStrokeBuff.append(key.toUpperCase());
return KeyStroke.getKeyStroke(keyStrokeBuff.toString());
}
}

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,27 @@ public interface DockingTool {
*/
public void contextChanged(ComponentProvider provider);
/**
* Returns this tool's notion of the current action context, which is based upon the active
* {@link ComponentProvider}. If there is not active provider, then a generic context will
* be returned.
*
* @return the context
*/
public ActionContext getGlobalContext();
/**
* Adds the given context listener to this tool
* @param listener the listener to add
*/
public void addContextListener(DockingContextListener listener);
/**
* Removes the given context listener to this tool
* @param listener the listener to add
*/
public void removeContextListener(DockingContextListener listener);
/**
* Returns the DockingWindowManger for this tool.
* @return the DockingWindowManger for this tool.
@ -235,4 +263,14 @@ public interface DockingTool {
*/
public boolean hasConfigChanged();
/**
* Returns the class that manages actions for the tool.
*
* <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

@ -26,16 +26,18 @@ import java.util.Map.Entry;
import javax.swing.*;
import org.apache.commons.collections4.map.LazyMap;
import org.jdom.Element;
import docking.action.DockingActionIf;
import docking.actions.DockingToolActions;
import docking.actions.ToolActions;
import docking.help.HelpService;
import generic.util.WindowUtilities;
import ghidra.framework.OperatingSystem;
import ghidra.framework.Platform;
import ghidra.framework.options.PreferenceState;
import ghidra.util.HelpLocation;
import ghidra.util.SystemUtilities;
import ghidra.util.*;
import ghidra.util.datastruct.*;
import ghidra.util.exception.AssertException;
import ghidra.util.task.SwingUpdateManager;
@ -45,16 +47,14 @@ import util.CollectionUtils;
* Manages the "Docking" arrangement of a set of components and actions. The components can be "docked"
* together or exist in their own window. Actions can be associated with components so they
* "move" with the component as it moved from one location to another.
*
* <P>
* Components are added via ComponentProviders. A ComponentProvider is an interface for getting
* a component and its related information. The docking window manager will get the component
* from the provider as needed. It is up to the provider if it wants to reuse the component or
* recreate a new one when the component is requested. When the user "hides" (by using
* the x button on the component area) a component, the docking window manager removes all
* recreate a new one when the component is requested. When the user hides a component (by using
* the x button on the component header), the docking window manager removes all
* knowledge of the component and will request it again from the provider if the component
* becomes "unhidden". The provider is also notified whenever a component is hidden. Some
* providers will use the notification to remove the provider from the docking window manager so
* that the can not "unhide" the component using the built-in window menu.
* is again shown. The provider is also notified whenever a component is hidden and shown.
*/
public class DockingWindowManager implements PropertyChangeListener, PlaceholderInstaller {
@ -73,6 +73,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
private static List<DockingWindowManager> instanceList = new ArrayList<>();
private DockingTool tool;
private RootNode root;
private PlaceholderManager placeholderManager;
@ -87,7 +88,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
private Map<String, ComponentProvider> providerNameCache = new HashMap<>();
private Map<String, PreferenceState> preferenceStateMap = new HashMap<>();
private DockWinListener docListener;
private ActionToGuiMapper actionManager;
private ActionToGuiMapper actionToGuiMapper;
private WeakSet<DockingContextListener> contextListeners =
WeakDataStructureFactory.createSingleThreadAccessWeakSet();
@ -105,18 +106,18 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
/**
* Constructs a new DockingWindowManager
* @param toolName the name of the tool.
* @param tool the tool
* @param images the images to use for windows in this window manager
* @param docListener the listener to be notified when the user closes the manager.
* @param docListener the listener to be notified when the user closes the manager
*/
public DockingWindowManager(String toolName, List<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 +126,12 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
* @param hasStatusBar if true a status bar will be created for the main window
* @param factory the drop target factory
*/
public DockingWindowManager(String toolName, List<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,8 +139,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
images = new ArrayList<>();
}
root = new RootNode(this, toolName, images, modal, factory);
actionManager = new ActionToGuiMapper(this);
root = new RootNode(this, tool.getName(), images, modal, factory);
KeyboardFocusManager km = KeyboardFocusManager.getCurrentKeyboardFocusManager();
km.addPropertyChangeListener("permanentFocusOwner", this);
@ -146,6 +147,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
addInstance(this);
placeholderManager = new PlaceholderManager(this);
actionToGuiMapper = new ActionToGuiMapper(this);
}
@Override
@ -153,15 +155,6 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
return "DockingWindowManager: " + root.getTitle();
}
/**
* A static initializer allowing additional diagnostic actions
* to be enabled added to all frame and dialog windows.
* @param enable
*/
public static void enableDiagnosticActions(boolean enable) {
ActionToGuiMapper.enableDiagnosticActions(enable);
}
/**
* Sets the help service for the all docking window managers.
* @param helpSvc the help service to use.
@ -311,17 +304,6 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
root.setIcon(icon);
}
/**
* Returns any action that is bound to the given keystroke for the tool associated with this
* DockingWindowManager instance.
* @param keyStroke The keystroke to check for key bindings.
* @return The action that is bound to the keystroke, or null of there is no binding for the
* given keystroke.
*/
Action getActionForKeyStroke(KeyStroke keyStroke) {
return actionManager.getDockingKeyAction(keyStroke);
}
/**
* Returns true if this manager contains the given provider.
*
@ -336,8 +318,8 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
return placeholderManager;
}
ActionToGuiMapper getActionManager() {
return actionManager;
ActionToGuiMapper getActionToGuiMapper() {
return actionToGuiMapper;
}
RootNode getRootNode() {
@ -363,6 +345,13 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
defaultProvider = provider;
}
/**
* Returns this tool's notion of the current action context, which is based upon the active
* {@link ComponentProvider}. If there is not active provider, then a generic context will
* be returned.
*
* @return the context
*/
public ActionContext getGlobalContext() {
if (defaultProvider != null) {
ActionContext actionContext = defaultProvider.getActionContext(null);
@ -641,16 +630,6 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
placeholderManager.removeComponent(provider);
}
/**
* Removes all components and actions associated with the given owner.
* @param owner the name of the owner whose associated component and actions should be removed.
*/
public void removeAll(String owner) {
actionManager.removeAll(owner);
placeholderManager.removeAll(owner);
scheduleUpdate();
}
//==================================================================================================
// Package-level Action Methods
//==================================================================================================
@ -668,7 +647,6 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
void removeProviderAction(ComponentProvider provider, DockingActionIf action) {
ComponentPlaceholder placeholder = getActivePlaceholder(provider);
if (placeholder != null) {
actionManager.removeLocalAction(action);
placeholder.removeAction(action);
}
}
@ -679,22 +657,43 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
throw new IllegalArgumentException("Unknown component provider: " + provider);
}
placeholder.addAction(action);
actionManager.addLocalAction(action, provider);
}
void addToolAction(DockingActionIf action) {
actionManager.addToolAction(action);
actionToGuiMapper.addToolAction(action);
scheduleUpdate();
}
void removeToolAction(DockingActionIf action) {
actionManager.removeToolAction(action);
actionToGuiMapper.removeToolAction(action);
scheduleUpdate();
}
/**
* Returns any action that is bound to the given keystroke for the tool associated with this
* DockingWindowManager instance.
* @param keyStroke The keystroke to check for key bindings.
* @return The action that is bound to the keystroke, or null of there is no binding for the
* given keystroke.
*/
Action getActionForKeyStroke(KeyStroke keyStroke) {
DockingToolActions toolActions = tool.getToolActions();
if (toolActions instanceof ToolActions) {
// Using a cast here; it didn't make sense to include this 'getAction' on the
// DockingToolActions
return ((ToolActions) toolActions).getAction(keyStroke);
}
return null;
}
//==================================================================================================
// End Package-level Methods
//==================================================================================================
//==================================================================================================
public void ownerRemoved(String owner) {
placeholderManager.removeAll(owner);
scheduleUpdate();
}
/**
* Hides or shows the component associated with the given provider.
@ -711,6 +710,15 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
ComponentPlaceholder placeholder = getActivePlaceholder(provider);
if (placeholder != null) {
showComponent(placeholder, visibleState, true);
return;
}
if (visibleState) {
// a null placeholder implies the client is trying to show a provider that has not
// been added to the tool
Msg.warn(this, "Attempting to show an unknown Component Provider '" +
provider.getName() + "' - " + "check that the provider has been added to the tool");
}
}
@ -791,7 +799,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
KeyboardFocusManager mgr = KeyboardFocusManager.getCurrentKeyboardFocusManager();
mgr.removePropertyChangeListener("permanentFocusOwner", this);
actionManager.dispose();
actionToGuiMapper.dispose();
root.dispose();
placeholderManager.disposePlaceholders();
@ -990,11 +998,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
}
private void disposePlaceholder(ComponentPlaceholder placeholder, boolean keepAround) {
Iterator<DockingActionIf> iter = placeholder.getActions();
while (iter.hasNext()) {
DockingActionIf action = iter.next();
actionManager.removeLocalAction(action);
}
placeholder.removeAllActions();
ComponentNode node = placeholder.getNode();
if (node == null) {
@ -1084,10 +1088,12 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
return;
}
actionManager.removeAll(DOCKING_WINDOWS_OWNER);
tool.getToolActions().removeActions(DOCKING_WINDOWS_OWNER);
Map<String, List<ComponentPlaceholder>> permanentMap = new HashMap<>();
Map<String, List<ComponentPlaceholder>> transientMap = new HashMap<>();
Map<String, List<ComponentPlaceholder>> permanentMap =
LazyMap.lazyMap(new HashMap<>(), menuName -> new ArrayList<>());
Map<String, List<ComponentPlaceholder>> transientMap =
LazyMap.lazyMap(new HashMap<>(), menuName -> new ArrayList<>());
Map<ComponentProvider, ComponentPlaceholder> map =
placeholderManager.getActiveProvidersToPlaceholders();
@ -1097,21 +1103,21 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
ComponentPlaceholder placeholder = entry.getValue();
String subMenuName = provider.getWindowSubMenuName();
if (provider.isTransient()) {
addToMap(transientMap, subMenuName, placeholder);
if (provider.isTransient() && !provider.isSnapshot()) {
transientMap.get(subMenuName).add(placeholder);
}
else {
addToMap(permanentMap, subMenuName, placeholder);
permanentMap.get(subMenuName).add(placeholder);
}
}
promoteSingleMenuGroups(permanentMap);
promoteSingleMenuGroups(transientMap);
createActions(transientMap, true);
createActions(permanentMap, false);
createActions(transientMap);
createActions(permanentMap);
createWindowActions();
actionManager.update();
actionToGuiMapper.update();
}
private boolean isWindowMenuShowing() {
@ -1138,47 +1144,45 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
return null;
}
private void createActions(Map<String, List<ComponentPlaceholder>> map, boolean isTransient) {
private void createActions(Map<String, List<ComponentPlaceholder>> map) {
List<ShowComponentAction> actionList = new ArrayList<>();
for (String subMenuName : map.keySet()) {
List<ComponentPlaceholder> placeholders = map.get(subMenuName);
for (ComponentPlaceholder placeholder : placeholders) {
ComponentProvider provider = placeholder.getProvider();
boolean isTransient = provider.isTransient();
actionList.add(
new ShowComponentAction(this, placeholder, subMenuName, isTransient));
}
if (subMenuName != null) {
// add an 'add all' action for the sub-menu
actionList.add(new ShowAllComponentsAction(this, placeholders, subMenuName));
}
}
DockingToolActions toolActions = tool.getToolActions();
Collections.sort(actionList);
for (ShowComponentAction action : actionList) {
actionManager.addToolAction(action);
toolActions.addGlobalAction(action);
}
}
private void promoteSingleMenuGroups(Map<String, List<ComponentPlaceholder>> map) {
List<String> lists = new ArrayList<>(map.keySet());
private void promoteSingleMenuGroups(Map<String, List<ComponentPlaceholder>> lazyMap) {
List<String> lists = new ArrayList<>(lazyMap.keySet());
for (String key : lists) {
List<ComponentPlaceholder> list = map.get(key);
if (key != null && list.size() == 1) {
addToMap(map, null, list.get(0));
map.remove(key);
if (key == null) {
continue;
}
List<ComponentPlaceholder> list = lazyMap.get(key);
if (list.size() == 1) {
lazyMap.get(null /*submenu name*/).add(list.get(0));
lazyMap.remove(key);
}
}
}
private void addToMap(Map<String, List<ComponentPlaceholder>> map, String menuGroup,
ComponentPlaceholder placeholder) {
List<ComponentPlaceholder> list = map.get(menuGroup);
if (list == null) {
list = new ArrayList<>();
map.put(menuGroup, list);
}
list.add(placeholder);
}
private void createWindowActions() {
List<DetachedWindowNode> windows = root.getDetachedWindows();
List<ShowWindowAction> actions = new ArrayList<>();
@ -1189,9 +1193,10 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
}
}
DockingToolActions toolActions = tool.getToolActions();
Collections.sort(actions);
for (ShowWindowAction action : actions) {
actionManager.addToolAction(action);
toolActions.addGlobalAction(action);
}
}
@ -1199,6 +1204,9 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
* Notifies the window manager that an update is needed
*/
void scheduleUpdate() {
if (rebuildUpdater.isBusy()) {
return;
}
rebuildUpdater.updateLater();
}
@ -1216,7 +1224,6 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
}
root.update(); // do this before rebuilding the menu, as new windows may be opened
buildComponentMenu();
SystemUtilities.runSwingLater(() -> updateFocus());
}
@ -1387,7 +1394,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
if (root == null) {
return;
}
actionManager.setActive(active);
actionToGuiMapper.setActive(active);
if (active) {
setActiveManager(this);
if (focusedPlaceholder != null && root.getWindow(focusedPlaceholder) == window) {
@ -1938,7 +1945,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
* when we know that an update is not need, as we are in the middle of an update.
*/
void doSetMenuGroup(String[] menuPath, String group) {
actionManager.setMenuGroup(menuPath, group);
actionToGuiMapper.setMenuGroup(menuPath, group);
}
/**
@ -1954,7 +1961,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
* its level
*/
public void setMenuGroup(String[] menuPath, String group, String menuSubGroup) {
actionManager.setMenuGroup(menuPath, group, menuSubGroup);
actionToGuiMapper.setMenuGroup(menuPath, group, menuSubGroup);
scheduleUpdate();
}
@ -2060,7 +2067,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
public void contextChanged(ComponentProvider provider) {
if (provider == null) {
actionManager.contextChangedAll(); // update all windows;
actionToGuiMapper.contextChangedAll(); // update all windows;
return;
}
@ -2069,7 +2076,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
return;
}
placeHolder.contextChanged();
actionManager.contextChanged(placeHolder);
actionToGuiMapper.contextChanged(placeHolder);
}
public void addContextListener(DockingContextListener listener) {
@ -2080,8 +2087,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
contextListeners.remove(listener);
}
public void notifyContextListeners(ComponentPlaceholder placeHolder,
ActionContext actionContext) {
void notifyContextListeners(ComponentPlaceholder placeHolder, ActionContext actionContext) {
if (placeHolder == focusedPlaceholder) {
for (DockingContextListener listener : contextListeners) {

View file

@ -17,11 +17,10 @@ package docking;
import java.util.*;
import javax.swing.SwingUtilities;
import docking.action.DockingActionIf;
import docking.menu.MenuGroupMap;
import docking.menu.MenuHandler;
import ghidra.util.Swing;
public class GlobalMenuAndToolBarManager implements DockingWindowListener {
@ -66,6 +65,17 @@ public class GlobalMenuAndToolBarManager implements DockingWindowListener {
}
}
public DockingActionIf getToolbarAction(String actionName) {
for (WindowActionManager actionManager : windowToActionManagerMap.values()) {
DockingActionIf action = actionManager.getToolbarAction(actionName);
if (action != null) {
return action;
}
}
return null;
}
public void dispose() {
for (WindowActionManager actionManager : windowToActionManagerMap.values()) {
actionManager.dispose();
@ -130,7 +140,7 @@ public class GlobalMenuAndToolBarManager implements DockingWindowListener {
}
private List<DockingActionIf> getActionsForWindow(WindowNode windowNode) {
ActionToGuiMapper actionManager = windowManager.getActionManager();
ActionToGuiMapper actionManager = windowManager.getActionToGuiMapper();
Collection<DockingActionIf> globalActions = actionManager.getGlobalActions();
List<DockingActionIf> actionsForWindow = new ArrayList<>(globalActions.size());
Set<Class<?>> contextTypes = windowNode.getContextTypes();
@ -143,12 +153,7 @@ public class GlobalMenuAndToolBarManager implements DockingWindowListener {
}
public void contextChangedAll() {
if (SwingUtilities.isEventDispatchThread()) {
updateAllWindowActions();
}
else {
SwingUtilities.invokeLater(() -> updateAllWindowActions());
}
Swing.runIfSwingOrRunLater(this::updateAllWindowActions);
}
private void updateAllWindowActions() {

View file

@ -425,7 +425,9 @@ class KeyBindingOverrideKeyEventDispatcher implements KeyEventDispatcher {
}
KeyStroke keyStroke = KeyStroke.getKeyStrokeForEvent(event);
return (DockingKeyBindingAction) activeManager.getActionForKeyStroke(keyStroke);
DockingKeyBindingAction bindingAction =
(DockingKeyBindingAction) activeManager.getActionForKeyStroke(keyStroke);
return bindingAction;
}
private DockingWindowManager getActiveDockingWindowManager() {

View file

@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -22,6 +21,8 @@ import java.awt.event.KeyListener;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import docking.actions.KeyBindingUtils;
/**
* Text field captures key strokes and notifies a listener to process the key entry.
*/
@ -43,21 +44,31 @@ public class KeyEntryTextField extends JTextField {
}
/**
* Get the current key stroke.
* Get the current key stroke
* @return the key stroke
*/
public KeyStroke getCurrentKeyStroke() {
public KeyStroke getKeyStroke() {
return currentKeyStroke;
}
/**
* Converts the toString() form of the keyStroke, e.g., Ctrl-M
* is returned as "keyCode CtrlM-P" and we want it to look like:
* "Ctrl-M"
* @param ks the keystroke to parse.
* @return the parse string for the keystroke.
* Sets the current key stroke
* @param ks the new key stroke
*/
public void setKeyStroke(KeyStroke ks) {
processEntry(ks);
setText(parseKeyStroke(ks));
}
/**
* Converts the toString() form of the keyStroke, e.g., Ctrl-M is returned as
* "keyCode CtrlM-P" and we want it to look like: "Ctrl-M"
*
* @param ks the keystroke to parse
* @return the parse string for the keystroke
*/
public static String parseKeyStroke(KeyStroke ks) {
return DockingKeyBindingAction.parseKeyStroke(ks);
return KeyBindingUtils.parseKeyStroke(ks);
}
public void clearField() {
@ -66,8 +77,6 @@ public class KeyEntryTextField extends JTextField {
currentKeyStroke = null;
}
//////////////////////////////////////////////////////////////////////
private void processEntry(KeyStroke ks) {
ksName = null;
@ -78,7 +87,7 @@ public class KeyEntryTextField extends JTextField {
char keyChar = ks.getKeyChar();
if (!Character.isWhitespace(keyChar) &&
Character.getType(keyChar) != Character.DIRECTIONALITY_LEFT_TO_RIGHT_OVERRIDE) {
ksName = DockingKeyBindingAction.parseKeyStroke(ks);
ksName = KeyBindingUtils.parseKeyStroke(ks);
}
}
listener.processEntry(ks);
@ -111,7 +120,7 @@ public class KeyEntryTextField extends JTextField {
KeyStroke keyStroke = null;
if (!isClearKey(keyCode) && !isModifiersOnly(e)) {
keyStroke = KeyStroke.getKeyStroke(keyCode, e.getModifiersEx() | e.getModifiers());
keyStroke = KeyStroke.getKeyStroke(keyCode, e.getModifiersEx());
}
processEntry(keyStroke);

View file

@ -101,7 +101,7 @@ public class PopupActionManager implements PropertyChangeListener {
Object source = actionContext.getSourceObject();
if (source instanceof DockingActionProviderIf) {
DockingActionProviderIf actionProvider = (DockingActionProviderIf) source;
List<DockingActionIf> dockingActions = actionProvider.getDockingActions(actionContext);
List<DockingActionIf> dockingActions = actionProvider.getDockingActions();
for (DockingActionIf action : dockingActions) {
MenuData popupMenuData = action.getPopupMenuData();
if (popupMenuData != null && action.isValidContext(actionContext) &&

View file

@ -319,7 +319,7 @@ class RootNode extends WindowNode {
invalid = false;
}
winMgr.getActionManager().update();
winMgr.getActionToGuiMapper().update();
windowWrapper.validate();
}

View file

@ -18,8 +18,7 @@ package docking;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import docking.action.DockingAction;
import docking.action.MenuData;
import docking.action.*;
import ghidra.util.HelpLocation;
import resources.ResourceManager;
@ -33,8 +32,10 @@ class ShowComponentAction extends DockingAction implements Comparable<ShowCompon
protected static final ImageIcon EMPTY_ICON =
ResourceManager.loadImage("images/EmptyIcon16.gif");
protected static final String MENU_WINDOW = "&" + DockingWindowManager.COMPONENT_MENU_NAME;
private ComponentPlaceholder info;
protected DockingWindowManager winMgr;
private ComponentPlaceholder info;
private String title;
private static String truncateTitleAsNeeded(String title) {
if (title.length() <= MAX_LENGTH) {
@ -48,36 +49,36 @@ class ShowComponentAction extends DockingAction implements Comparable<ShowCompon
super(truncateTitleAsNeeded(name), DockingWindowManager.DOCKING_WINDOWS_OWNER);
}
/**
* Constructs a new ShowComponentAction object.
* @param winMgr the DockingWindowManager that this action belongs to.
* @param info the info of the component to be shown when this action is invoked.
*/
ShowComponentAction(DockingWindowManager winMgr, ComponentPlaceholder info, String subMenuName,
boolean isTransient) {
super(truncateTitleAsNeeded(info.getTitle()), DockingWindowManager.DOCKING_WINDOWS_OWNER);
ShowComponentAction(DockingWindowManager winMgr, ComponentPlaceholder placeholder,
String subMenuName, boolean isTransient) {
super(placeholder.getProvider().getName(), DockingWindowManager.DOCKING_WINDOWS_OWNER,
createKeyBindingType(isTransient, placeholder));
this.info = placeholder;
this.winMgr = winMgr;
this.title = truncateTitleAsNeeded(placeholder.getTitle());
String group = isTransient ? "Transient" : "Permanent";
Icon icon = info.getIcon();
Icon icon = placeholder.getIcon();
if (icon == null) {
icon = EMPTY_ICON;
}
if (subMenuName != null) {
setMenuBarData(new MenuData(
new String[] { MENU_WINDOW, subMenuName, info.getFullTitle() }, icon, "Permanent"));
setMenuBarData(
new MenuData(new String[] { MENU_WINDOW, subMenuName, placeholder.getFullTitle() },
icon, "Permanent"));
winMgr.doSetMenuGroup(new String[] { MENU_WINDOW, subMenuName }, group);
}
else {
setMenuBarData(
new MenuData(new String[] { MENU_WINDOW, getName() }, icon, "Permanent"));
setMenuBarData(new MenuData(new String[] { MENU_WINDOW, title }, icon, "Permanent"));
}
this.info = info;
this.winMgr = winMgr;
// keybinding data used to show the binding in the menu
ComponentProvider provider = placeholder.getProvider();
synchronizeKeyBinding(provider);
// Use provider Help for this action
ComponentProvider provider = info.getProvider();
// Use provider Help for this action
HelpLocation helpLocation = provider.getHelpLocation();
if (helpLocation != null) {
setHelpLocation(helpLocation);
@ -89,6 +90,32 @@ class ShowComponentAction extends DockingAction implements Comparable<ShowCompon
}
}
/**
* Ensures that the given provider's key binding matches this class's key binding
* @param provider the provider
*/
private void synchronizeKeyBinding(ComponentProvider provider) {
DockingActionIf action = provider.getShowProviderAction();
KeyBindingData defaultBinding = action.getDefaultKeyBindingData();
setKeyBindingData(defaultBinding);
KeyBindingData kbData = action.getKeyBindingData();
if (kbData != null) {
setUnvalidatedKeyBindingData(kbData);
}
}
private static KeyBindingType createKeyBindingType(boolean isTransient,
ComponentPlaceholder placeholder) {
if (isTransient) {
return KeyBindingType.UNSUPPORTED; // temporary window
}
// 'info' is null when this action is used to 'show all' instances of a given provider
return placeholder == null ? KeyBindingType.UNSUPPORTED : KeyBindingType.SHARED;
}
@Override
public void actionPerformed(ActionContext context) {
winMgr.showComponent(info, true, true);
@ -121,15 +148,23 @@ class ShowComponentAction extends DockingAction implements Comparable<ShowCompon
@Override
public String getHelpInfo() {
if (info == null) {
return super.getHelpInfo();
}
StringBuilder buffy = new StringBuilder(super.getHelpInfo());
Class<? extends ComponentProvider> clazz = info.getProvider().getClass();
ComponentProvider provider = info.getProvider();
Class<? extends ComponentProvider> clazz = provider.getClass();
String className = clazz.getName();
String filename = className.substring(className.lastIndexOf('.') + 1);
String javaName = filename + ".java";
buffy.append(" ").append("PROVIDER: ").append(info.getName()).append(' ');
buffy.append('(').append(javaName).append(":1)");
DockingActionIf showAction = provider.getShowProviderAction();
String realInception = showAction.getInceptionInformation();
buffy.append(" ").append(realInception).append("\n ");
buffy.append(" ").append("PROVIDER: ").append(filename).append(' ');
buffy.append('(').append(provider.getOwner()).append(")");
buffy.append("\n ");
return buffy.toString();

View file

@ -72,6 +72,10 @@ public class WindowActionManager {
}
}
public DockingActionIf getToolbarAction(String actionName) {
return toolBarMgr.getAction(actionName);
}
public void update() {
JMenuBar menuBar = menuBarMgr.getMenuBar();
if (menuBar.getMenuCount() > 0) {
@ -92,9 +96,6 @@ public class WindowActionManager {
toolBarMgr.dispose();
}
/**
* Notifies the window manager that an action context update is needed.
*/
synchronized void contextChanged(ComponentPlaceholder placeHolder) {
placeHolderForScheduledActionUpdate = placeHolder;

View file

@ -17,6 +17,7 @@ package docking.action;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Objects;
import java.util.Set;
import javax.swing.*;
@ -63,8 +64,8 @@ public abstract class DockingAction implements DockingActionIf {
private String inceptionInformation;
private boolean isEnabled = true;
private boolean isKeyBindingManaged = true;
private KeyBindingType keyBindingType = KeyBindingType.INDIVIDUAL;
private KeyBindingData defaultKeyBindingData;
private KeyBindingData keyBindingData;
private MenuBarData menuBarData;
@ -72,19 +73,29 @@ public abstract class DockingAction implements DockingActionIf {
private ToolBarData toolBarData;
public DockingAction(String name, String owner) {
this(name, owner, true);
}
public DockingAction(String name, String owner, boolean isKeyBindingManaged) {
this.name = name;
this.owner = owner;
this.isKeyBindingManaged = isKeyBindingManaged;
recordInception();
HelpLocation location = new HelpLocation(owner, name, inceptionInformation);
setHelpLocation(location);
}
public DockingAction(String name, String owner, KeyBindingType kbType) {
this(name, owner);
this.keyBindingType = Objects.requireNonNull(kbType);
}
public DockingAction(String name, String owner, boolean supportsKeyBindings) {
this(name, owner);
this.keyBindingType =
supportsKeyBindings ? KeyBindingType.INDIVIDUAL : KeyBindingType.UNSUPPORTED;
}
protected KeyBindingType getPreferredKeyBindingType() {
return KeyBindingType.INDIVIDUAL;
}
@Override
public abstract void actionPerformed(ActionContext context);
@ -98,11 +109,6 @@ public abstract class DockingAction implements DockingActionIf {
propertyListeners.remove(listener);
}
@Override
public boolean isKeyBindingManaged() {
return isKeyBindingManaged;
}
@Override
public String getDescription() {
return description;
@ -258,6 +264,11 @@ public abstract class DockingAction implements DockingActionIf {
return menuItem;
}
@Override
public KeyBindingType getKeyBindingType() {
return keyBindingType;
}
@Override
public KeyStroke getKeyBinding() {
return keyBindingData == null ? null : keyBindingData.getKeyBinding();
@ -489,7 +500,7 @@ public abstract class DockingAction implements DockingActionIf {
inceptionInformation = getInceptionFromTheFirstClassThatIsNotUs();
}
private String getInceptionFromTheFirstClassThatIsNotUs() {
protected String getInceptionFromTheFirstClassThatIsNotUs() {
Throwable t = ReflectionUtilities.createThrowableWithStackOlderThan(getClass());
StackTraceElement[] trace = t.getStackTrace();
String classInfo = trace[0].toString();

View file

@ -23,6 +23,17 @@ import javax.swing.*;
import docking.ActionContext;
import docking.help.HelpDescriptor;
/**
* The base interface for clients that wish to create commands to be registered with a tool.
*
* <p>An action may appear in a primary menu, a popup menu or a toolbar. Further, an action
* may have a key binding assigned.
*
* <p>The particular support for key bindings is defined by {@link KeyBindingType}. Almost all
* client actions will use the default setting of {@link KeyBindingType#INDIVIDUAL}. To control
* the level of key binding support, you can pass the desired {@link KeyBindingType} to the
* base implementation of this interface.
*/
public interface DockingActionIf extends HelpDescriptor {
public static final String ENABLEMENT_PROPERTY = "enabled";
public static final String GLOBALCONTEXT_PROPERTY = "globalContext";
@ -44,6 +55,16 @@ public interface DockingActionIf extends HelpDescriptor {
*/
public String getOwner();
/**
* Returns a description of this actions owner. For most actions this will return the
* same value as {@link #getOwner()}.
*
* @return the description
*/
public default String getOwnerDescription() {
return getOwner();
}
/**
* Returns a short description of this action. Generally used for a tooltip
* @return the description
@ -248,10 +269,17 @@ public interface DockingActionIf extends HelpDescriptor {
public boolean shouldAddToWindow(boolean isMainWindow, Set<Class<?>> contextTypes);
/**
* Returns true if this action can have its keybinding information changed by the user.
* @return true if this action can have its keybinding information changed by the user.
* Returns this actions level of support for key binding accelerator keys
*
* <p>Actions support key bindings by default. Some reserved actions do not support
* key bindings, while others wish to share the same key bindings with multiple, equivalent
* actions (this allows the user to set one binding that works in many different contexts).
*
* @return the key binding support
*/
public boolean isKeyBindingManaged();
public default KeyBindingType getKeyBindingType() {
return KeyBindingType.INDIVIDUAL;
}
/**
* Sets the {@link KeyBindingData} on an action to either assign a keybinding or remove it
@ -272,22 +300,4 @@ public interface DockingActionIf extends HelpDescriptor {
* @param newKeyBindingData the KeyBindingData to be used to assign this action to a keybinding
*/
public void setUnvalidatedKeyBindingData(KeyBindingData newKeyBindingData);
/**
* Returns true if this action shares a keybinding with other actions. If this returns true,
* then this action, and any action that shares a name with this action, will be updated
* to the same key binding value whenever the key binding options change.
*
* <p>This will be false for the vast majority of actions. If you are unsure if your action
* should use a shared keybinding, then do not set this value to true.
*
* <p>This value is not meant to change over the life of the action. Thus, there is no
* <code>set</code> method to change this value. Rather, you should override this method
* to return <code>true</code> as desired.
*
* @return true to share a shared keybinding
*/
public default boolean usesSharedKeyBinding() {
return false;
}
}

View file

@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -18,8 +17,6 @@ package docking.action;
import java.util.List;
import docking.ActionContext;
/**
* An interface for objects (really Components) to implement that signals they provide actions
* for the Docking environment. This interface will be called when the implementor is the source
@ -32,9 +29,9 @@ import docking.ActionContext;
*/
public interface DockingActionProviderIf {
/**
* Returns actions that are compatible with the given context.
* @param context the current context of the Docking system
*/
public List<DockingActionIf> getDockingActions( ActionContext context );
/**
* Returns actions that are compatible with the given context.
* @return the actions
*/
public List<DockingActionIf> getDockingActions();
}

View file

@ -15,12 +15,10 @@
*/
package docking.action;
import java.awt.event.*;
import javax.swing.KeyStroke;
import docking.DockingUtils;
import docking.KeyBindingPrecedence;
import docking.actions.KeyBindingUtils;
public class KeyBindingData {
private KeyStroke keyStroke;
@ -49,6 +47,7 @@ public class KeyBindingData {
/**
* Returns an accelerator keystroke to be associated with this action.
* @return the binding
*/
public KeyStroke getKeyBinding() {
return keyStroke;
@ -56,6 +55,7 @@ public class KeyBindingData {
/**
* Returns the keyBindingPrecedence for this action
* @return the precedence
*/
public KeyBindingPrecedence getKeyBindingPrecedence() {
return keyBindingPrecedence;
@ -80,6 +80,10 @@ public class KeyBindingData {
* @return the potentially changed data
*/
public static KeyBindingData validateKeyBindingData(KeyBindingData newKeyBindingData) {
if (newKeyBindingData == null) {
return null;
}
KeyStroke keyBinding = newKeyBindingData.getKeyBinding();
if (keyBinding == null) {
// not sure when this can happen
@ -88,63 +92,8 @@ public class KeyBindingData {
KeyBindingPrecedence precedence = newKeyBindingData.getKeyBindingPrecedence();
if (precedence == KeyBindingPrecedence.ReservedActionsLevel) {
return createReservedKeyBindingData(validateKeyStroke(keyBinding));
return createReservedKeyBindingData(KeyBindingUtils.validateKeyStroke(keyBinding));
}
return new KeyBindingData(validateKeyStroke(keyBinding), precedence);
}
/**
* Updates the given data with system-independent versions of key modifiers. For example,
* the <tt>control</tt> key will be converted to the <tt>command</tt> key on the Mac.
*
* @param keyStroke the keystroke to validate
* @return the potentially changed keystroke
*/
public static KeyStroke validateKeyStroke(KeyStroke keyStroke) {
if (keyStroke == null) {
return null;
}
// remove system-dependent control key mask and transform deprecated modifiers
int modifiers = keyStroke.getModifiers();
if ((modifiers & InputEvent.CTRL_DOWN_MASK) == InputEvent.CTRL_DOWN_MASK) {
modifiers = modifiers ^ InputEvent.CTRL_DOWN_MASK;
modifiers = modifiers | DockingUtils.CONTROL_KEY_MODIFIER_MASK;
}
if ((modifiers & InputEvent.CTRL_MASK) == InputEvent.CTRL_MASK) {
modifiers = modifiers ^ InputEvent.CTRL_MASK;
modifiers = modifiers | DockingUtils.CONTROL_KEY_MODIFIER_MASK;
}
if ((modifiers & ActionEvent.CTRL_MASK) == ActionEvent.CTRL_MASK) {
modifiers = modifiers ^ ActionEvent.CTRL_MASK;
modifiers = modifiers | DockingUtils.CONTROL_KEY_MODIFIER_MASK;
}
if ((modifiers & InputEvent.SHIFT_MASK) == InputEvent.SHIFT_MASK) {
modifiers = modifiers ^ InputEvent.SHIFT_MASK;
modifiers = modifiers | InputEvent.SHIFT_DOWN_MASK;
}
if ((modifiers & InputEvent.ALT_MASK) == InputEvent.ALT_MASK) {
modifiers = modifiers ^ InputEvent.ALT_MASK;
modifiers = modifiers | InputEvent.ALT_DOWN_MASK;
}
if ((modifiers & InputEvent.META_MASK) == InputEvent.META_MASK) {
modifiers = modifiers ^ InputEvent.META_MASK;
modifiers = modifiers | InputEvent.META_DOWN_MASK;
}
int eventType = keyStroke.getKeyEventType();
if (eventType == KeyEvent.KEY_TYPED) {
// we know that typed events have a key code of VK_UNDEFINED
return KeyStroke.getKeyStroke(keyStroke.getKeyChar(), modifiers);
}
// key pressed or released
boolean isOnKeyRelease = keyStroke.isOnKeyRelease();
return KeyStroke.getKeyStroke(keyStroke.getKeyCode(), modifiers, isOnKeyRelease);
return new KeyBindingData(KeyBindingUtils.validateKeyStroke(keyBinding), precedence);
}
}

View file

@ -0,0 +1,82 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package docking.action;
/**
* Allows clients to signal their support for the assigning of key binding shortcut keys. Most
* action clients need not be concerned with this class. The default settings of
* {@link DockingAction} work correctly for almost all cases, which is to have the action
* support individual key bindings, which are managed by the system via the UI.
*
* @see DockingActionIf
*/
public enum KeyBindingType {
//@formatter:off
/**
* Indicates the setting of key bindings through the UI is not supported
*/
UNSUPPORTED,
/**
* Supports the assignment of key bindings via the UI. Setting a key binding on an action
* with this type will not affect any other action.
*/
INDIVIDUAL,
/**
* When the key binding is set via the UI, this action, and any action that shares a
* name with this action, will be updated to the same key binding value whenever the key
* binding options change.
*
* <p>Most actions will not be shared. If you are unsure if your action
* should use a shared keybinding, then do not do so.
*/
SHARED;
//@formatter:on
/**
* Returns true if this type supports key bindings. This is a convenience method for
* checking that this type is not {@link #UNSUPPORTED}.
* @return true if key bindings are supported
*/
public boolean supportsKeyBindings() {
return this != UNSUPPORTED;
}
/**
* Convenience method for checking if this type is the {@link #SHARED} type
* @return true if shared
*/
public boolean isShared() {
return this == SHARED;
}
/**
* A convenience method for clients to check whether this key binding type should be
* managed directly by the system.
*
* <p>Shared actions are not managed directly by the system, but are instead managed through
* a proxy action.
*
* @return true if managed directly by the system; false if key binding are not supported
* or are managed through a proxy
*/
public boolean isManaged() {
return this == INDIVIDUAL;
}
}

View file

@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -14,34 +13,33 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package docking;
import ghidra.util.ReservedKeyBindings;
import ghidra.util.exception.AssertException;
package docking.action;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.*;
import java.util.HashMap;
import java.util.Map;
import javax.swing.Action;
import javax.swing.KeyStroke;
import docking.action.*;
import docking.*;
import ghidra.util.ReservedKeyBindings;
import ghidra.util.exception.AssertException;
public class KeyBindingsManager implements PropertyChangeListener {
protected Map<KeyStroke, DockingKeyBindingAction> dockingKeyMap;
protected Map<DockingActionIf, ComponentProvider> actionToProviderMap;
private DockingTool tool;
private DockingWindowManager winMgr;
public KeyBindingsManager(DockingWindowManager winMgr) {
this.winMgr = winMgr;
dockingKeyMap = new HashMap<KeyStroke, DockingKeyBindingAction>();
actionToProviderMap = new HashMap<DockingActionIf, ComponentProvider>();
public KeyBindingsManager(DockingTool tool) {
this.tool = tool;
dockingKeyMap = new HashMap<>();
actionToProviderMap = new HashMap<>();
}
public void addAction(DockingActionIf action, ComponentProvider optionalProvider) {
public void addAction(ComponentProvider optionalProvider, DockingActionIf action) {
action.addPropertyChangeListener(this);
if (optionalProvider != null) {
actionToProviderMap.put(action, optionalProvider);
@ -50,7 +48,7 @@ public class KeyBindingsManager implements PropertyChangeListener {
KeyStroke keyBinding = action.getKeyBinding();
if (keyBinding != null) {
addKeyBinding(action, optionalProvider, keyBinding);
addKeyBinding(optionalProvider, action, keyBinding);
}
}
@ -59,13 +57,17 @@ public class KeyBindingsManager implements PropertyChangeListener {
addReservedKeyBinding(action, keyBinding);
}
public void addReservedAction(DockingActionIf action, KeyStroke ks) {
addReservedKeyBinding(action, ks);
}
public void removeAction(DockingActionIf action) {
action.removePropertyChangeListener(this);
actionToProviderMap.remove(action);
removeKeyBinding(action.getKeyBinding(), action);
}
private void addKeyBinding(DockingActionIf action, ComponentProvider provider,
private void addKeyBinding(ComponentProvider provider, DockingActionIf action,
KeyStroke keyStroke) {
if (ReservedKeyBindings.isReservedKeystroke(keyStroke)) {
throw new AssertException("Cannot assign action to a reserved keystroke. " +
@ -74,7 +76,7 @@ public class KeyBindingsManager implements PropertyChangeListener {
DockingKeyBindingAction existingAction = dockingKeyMap.get(keyStroke);
if (existingAction == null) {
dockingKeyMap.put(keyStroke, new MultipleKeyAction(winMgr, provider, action, keyStroke));
dockingKeyMap.put(keyStroke, new MultipleKeyAction(tool, provider, action, keyStroke));
return;
}
@ -93,7 +95,9 @@ public class KeyBindingsManager implements PropertyChangeListener {
"action to a given keystroke: " + keyStroke);
}
dockingKeyMap.put(keyStroke, new ReservedKeyBindingAction(winMgr, action, keyStroke));
KeyBindingData binding = KeyBindingData.createReservedKeyBindingData(keyStroke);
action.setKeyBindingData(binding);
dockingKeyMap.put(keyStroke, new ReservedKeyBindingAction(tool, action, keyStroke));
}
/**
@ -141,23 +145,17 @@ public class KeyBindingsManager implements PropertyChangeListener {
if (newKeyData != null) {
KeyStroke ks = newKeyData.getKeyBinding();
if (ks != null) {
addKeyBinding(action, actionToProviderMap.get(action), ks);
addKeyBinding(actionToProviderMap.get(action), action, ks);
}
}
}
public List<DockingActionIf> getLocalActions() {
return new ArrayList<DockingActionIf>(actionToProviderMap.keySet());
}
public Action getDockingKeyAction(KeyStroke keyStroke) {
return dockingKeyMap.get(keyStroke);
}
public void dispose() {
winMgr = null;
dockingKeyMap.clear();
actionToProviderMap.clear();
}
}

View file

@ -21,6 +21,7 @@ import java.util.*;
import javax.swing.*;
import docking.*;
import docking.actions.KeyBindingUtils;
import ghidra.util.Swing;
/**
@ -31,35 +32,17 @@ public class MultipleKeyAction extends DockingKeyBindingAction {
private ActionDialog dialog;
class ActionData {
DockingActionIf action;
ComponentProvider provider;
ActionData(DockingActionIf action, ComponentProvider provider) {
this.action = action;
this.provider = provider;
}
boolean isGlobalAction() {
return provider == null;
}
boolean isMyProvider(ComponentProvider otherProvider) {
return provider == otherProvider;
}
}
/**
* Creates new MultipleKeyAction
*
* @param winMgr window manager used to determine context.
* @param tool used to determine context
* @param provider the provider, if any, associated with the action
* @param action action that will be added to the list of actions bound to a keystroke
* @param keyStroke the keystroke, if any, associated with the action
*/
public MultipleKeyAction(DockingWindowManager winMgr, ComponentProvider provider,
DockingActionIf action, KeyStroke keyStroke) {
super(winMgr, action, keyStroke);
public MultipleKeyAction(DockingTool tool, ComponentProvider provider, DockingActionIf action,
KeyStroke keyStroke) {
super(tool, action, keyStroke);
addAction(provider, action);
}
@ -80,6 +63,7 @@ public class MultipleKeyAction extends DockingKeyBindingAction {
throw new IllegalArgumentException(
"KeyStrokes don't match - was: " + keyStroke + " new: " + keyBinding);
}
actions.add(new ActionData(action, provider));
}
@ -133,11 +117,11 @@ public class MultipleKeyAction extends DockingKeyBindingAction {
@Override
public void actionPerformed(final ActionEvent event) {
// Build list of actions which are valid in current context
ComponentProvider localProvider = winMgr.getActiveComponentProvider();
ComponentProvider localProvider = tool.getActiveComponentProvider();
ActionContext localContext = getLocalContext(localProvider);
localContext.setSource(event.getSource());
ActionContext globalContext = winMgr.getGlobalContext();
ActionContext globalContext = tool.getGlobalContext();
List<ExecutableKeyActionAdapter> list = getValidContextActions(localContext, globalContext);
// If menu active, disable all key bindings
@ -149,7 +133,7 @@ public class MultipleKeyAction extends DockingKeyBindingAction {
if (list.size() > 1) {
// popup dialog to show multiple actions
if (dialog == null) {
dialog = new ActionDialog(parseKeyStroke(keyStroke), list);
dialog = new ActionDialog(KeyBindingUtils.parseKeyStroke(keyStroke), list);
}
else {
dialog.setActionList(list);
@ -162,12 +146,12 @@ public class MultipleKeyAction extends DockingKeyBindingAction {
}
else if (list.size() == 1) {
final ExecutableKeyActionAdapter actionProxy = list.get(0);
winMgr.setStatusText("");
tool.setStatusInfo("");
actionProxy.execute();
}
else {
String name = (String) getValue(Action.NAME);
winMgr.setStatusText("Action (" + name + ") not valid in this context!", true);
tool.setStatusInfo("Action (" + name + ") not valid in this context!", true);
}
}
@ -229,9 +213,9 @@ public class MultipleKeyAction extends DockingKeyBindingAction {
@Override
public KeyBindingPrecedence getKeyBindingPrecedence() {
ComponentProvider localProvider = winMgr.getActiveComponentProvider();
ComponentProvider localProvider = tool.getActiveComponentProvider();
ActionContext localContext = getLocalContext(localProvider);
ActionContext globalContext = winMgr.getGlobalContext();
ActionContext globalContext = tool.getGlobalContext();
List<ExecutableKeyActionAdapter> validActions =
getValidContextActions(localContext, globalContext);
@ -273,4 +257,27 @@ public class MultipleKeyAction extends DockingKeyBindingAction {
return buildy.toString();
}
private class ActionData {
DockingActionIf action;
ComponentProvider provider;
ActionData(DockingActionIf action, ComponentProvider provider) {
this.action = action;
this.provider = provider;
}
boolean isGlobalAction() {
return provider == null;
}
boolean isMyProvider(ComponentProvider otherProvider) {
return provider == otherProvider;
}
@Override
public String toString() {
return provider.toString() + " - " + action;
}
}
}

View file

@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -14,21 +13,25 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package docking;
package docking.action;
import javax.swing.KeyStroke;
import docking.action.DockingActionIf;
import docking.*;
class ReservedKeyBindingAction extends DockingKeyBindingAction {
ReservedKeyBindingAction(DockingWindowManager winMgr, DockingActionIf action,
KeyStroke keyStroke) {
super(winMgr, action, keyStroke);
ReservedKeyBindingAction(DockingTool tool, DockingActionIf action, KeyStroke keyStroke) {
super(tool, action, keyStroke);
}
@Override
public boolean isReservedKeybindingPrecedence() {
return true;
}
@Override
public KeyBindingPrecedence getKeyBindingPrecedence() {
return KeyBindingPrecedence.ReservedActionsLevel;
}
}

View file

@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -29,14 +28,16 @@ public abstract class ToggleDockingAction extends DockingAction implements Toggl
super(name, owner);
}
public ToggleDockingAction(String name, String owner, boolean isKeybindingManaged) {
super(name, owner, isKeybindingManaged);
public ToggleDockingAction(String name, String owner, boolean supportsKeyBindings) {
super(name, owner, supportsKeyBindings);
}
@Override
public boolean isSelected() {
return isSelected;
}
@Override
public void setSelected(boolean newValue) {
isSelected = newValue;
firePropertyChanged(SELECTED_STATE_PROPERTY, !isSelected, isSelected);

View file

@ -0,0 +1,95 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package docking.actions;
import java.util.Set;
import docking.ComponentProvider;
import docking.action.DockingActionIf;
/**
* Represents the collection of actions registered with the tool, along with method for adding
* and removing actions.
*/
public interface DockingToolActions {
/**
* Adds the given action that enabled when the given provider is active
*
* @param provider the provider
* @param action the action
*/
public void addLocalAction(ComponentProvider provider, DockingActionIf action);
/**
* Gets the provider action by the given name
*
* @param provider the provider
* @param actionName the action name
* @return the action
*/
public DockingActionIf getLocalAction(ComponentProvider provider, String actionName);
/**
* Removes the given provider's local action
*
* @param provider the provider
* @param action the action
*/
public void removeLocalAction(ComponentProvider provider, DockingActionIf action);
/**
* Adds the given action that is enabled, regardless of the active provider
*
* @param action the action
*/
public void addGlobalAction(DockingActionIf action);
/**
* Removes the given global action
* @param action the action
*/
public void removeGlobalAction(DockingActionIf action);
/**
* Removes all global actions for the given owner
*
* @param owner the owner
*/
public void removeActions(String owner);
/**
* Removes all local actions for the given provider
*
* @param provider the provider
*/
public void removeActions(ComponentProvider provider);
/**
* Returns all actions with the given owner
*
* @param owner the owner
* @return the actions
*/
public Set<DockingActionIf> getActions(String owner);
/**
* Returns all actions known to the tool
* @return the actions
*/
public Set<DockingActionIf> getAllActions();
}

View file

@ -13,24 +13,23 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package docking.action;
package docking.actions;
import java.awt.Component;
import java.util.Set;
import docking.*;
import docking.actions.KeyBindingUtils;
import docking.ActionContext;
import docking.DockingWindowManager;
import docking.action.*;
import ghidra.util.Msg;
import ghidra.util.ReservedKeyBindings;
public class KeyBindingAction extends DockingAction {
private final ActionToGuiMapper dockingActionManager;
public KeyBindingAction(ActionToGuiMapper dockingActionManager) {
super("Set KeyBinding", DockingWindowManager.DOCKING_WINDOWS_OWNER);
this.dockingActionManager = dockingActionManager;
createReservedKeyBinding(ReservedKeyBindings.UPDATE_KEY_BINDINGS_KEY);
setEnabled(true);
public static String NAME = "Set KeyBinding";
private ToolActions toolActions;
public KeyBindingAction(ToolActions toolActions) {
super(NAME, DockingWindowManager.DOCKING_WINDOWS_OWNER);
this.toolActions = toolActions;
// Help actions don't have help
DockingWindowManager.getHelpService().excludeFromHelp(this);
@ -50,15 +49,14 @@ public class KeyBindingAction extends DockingAction {
action = maybeGetToolLevelAction(action);
if (!action.isKeyBindingManaged()) {
if (!action.getKeyBindingType().supportsKeyBindings()) {
Component parent = windowManager.getActiveComponent();
Msg.showInfo(getClass(), parent, "Unable to Set Keybinding",
"Action \"" + getActionName(action) + "\" is not keybinding managed and thus a " +
"keybinding cannot be set.");
"Action \"" + getActionName(action) + "\" does not support key bindings");
return;
}
KeyEntryDialog d = new KeyEntryDialog(action, dockingActionManager);
KeyEntryDialog d = new KeyEntryDialog(action, toolActions);
DockingWindowManager.showDialog(d);
}
@ -69,17 +67,15 @@ public class KeyBindingAction extends DockingAction {
* @return A tool-level action if one is found; otherwise, the original action
*/
private DockingActionIf maybeGetToolLevelAction(DockingActionIf dockingAction) {
if (dockingAction.isKeyBindingManaged()) {
return dockingAction;
}
// It is not key binding managed, which means that it may be a shared key binding
String actionName = dockingAction.getName();
Set<DockingActionIf> allActions = dockingActionManager.getAllActions();
DockingActionIf sharedAction =
KeyBindingUtils.getSharedKeyBindingAction(allActions, actionName);
if (sharedAction != null) {
return sharedAction;
if (dockingAction.getKeyBindingType().isShared()) {
// It is not key binding managed, which means that it may be a shared key binding
String actionName = dockingAction.getName();
DockingActionIf sharedAction = toolActions.getSharedStubKeyBindingAction(actionName);
if (sharedAction != null) {
return sharedAction;
}
}
return dockingAction;

View file

@ -15,13 +15,17 @@
*/
package docking.actions;
import static org.apache.commons.lang3.StringUtils.indexOfIgnoreCase;
import java.awt.Component;
import java.awt.KeyboardFocusManager;
import java.awt.event.*;
import java.io.*;
import java.util.*;
import javax.swing.*;
import org.apache.commons.collections4.map.LazyMap;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jdom.*;
@ -31,6 +35,7 @@ import org.jdom.output.XMLOutputter;
import com.google.common.collect.Sets;
import docking.DockingTool;
import docking.DockingUtils;
import docking.action.*;
import docking.widgets.filechooser.GhidraFileChooser;
import ghidra.framework.options.ToolOptions;
@ -53,10 +58,20 @@ import utilities.util.reflection.ReflectionUtilities;
public class KeyBindingUtils {
private static final String LAST_KEY_BINDING_EXPORT_DIRECTORY = "LastKeyBindingExportDirectory";
private static final String RELEASED = "released";
private static final String TYPED = "typed";
private static final String PRESSED = "pressed";
private static final String SHIFT = "Shift";
private static final String CTRL = "Ctrl";
private static final String CONTROL = "Control";
private static final String ALT = "Alt";
private static final String META = "Meta";
private static final String MODIFIER_SEPARATOR = "-";
private static final Logger log = LogManager.getLogger(KeyBindingUtils.class);
public static final String PREFERENCES_FILE_EXTENSION = ".kbxml";
private static final GhidraFileFilter FILE_FILTER = new GhidraFileFilter() {
@Override
public boolean accept(File pathname, GhidraFileChooserModel model) {
@ -330,15 +345,21 @@ public class KeyBindingUtils {
}
/**
* A utility method to get all key binding actions. This method will remove duplicate
* actions and will only return actions that are {@link DockingActionIf#isKeyBindingManaged()}
* A utility method to get all key binding actions. This method will
* only return actions that support {@link KeyBindingType key bindings}.
*
* <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)) {
@ -348,16 +369,16 @@ public class KeyBindingUtils {
continue;
}
deduper.put(action.getFullName(), action);
result.get(action.getFullName()).add(action);
}
return deduper;
return result;
}
/**
* A utility method to get all key binding actions that have the given owner.
* This method will remove duplicate actions and will only return actions
* that are {@link DockingActionIf#isKeyBindingManaged()}
* that support {@link KeyBindingType key bindings}.
*
* @param tool the tool containing the actions
* @param owner the action owner name
@ -398,36 +419,6 @@ public class KeyBindingUtils {
return Sets.filter(ownerMatch, action -> action.getName().equals(name));
}
/**
* A method to locate the {@link SharedStubKeyBindingAction} representative for the given
* action name. This method is not useful to general clients.
*
* @param allActions all actions in the system
* @param sharedName the name of the shared action
* @return the shared action representative
*/
public static DockingActionIf getSharedKeyBindingAction(Set<DockingActionIf> allActions,
String sharedName) {
String owner = "Tool";
for (DockingActionIf action : allActions) {
if (!(action instanceof SharedStubKeyBindingAction)) {
continue;
}
if (action.getOwner().equals(owner) && action.getName().equals(sharedName)) {
return action;
}
}
return null;
}
private static boolean isIgnored(DockingActionIf action) {
// not keybinding managed; a shared keybinding implies that this action should not be in
// the UI, as there will be a single proxy in place of all actions sharing that binding
return !action.isKeyBindingManaged() || action.usesSharedKeyBinding();
}
/**
* Takes the existing docking action and allows it to be registered with
* Swing components
@ -455,9 +446,17 @@ public class KeyBindingUtils {
public static void assertSameDefaultKeyBindings(DockingActionIf newAction,
Collection<DockingActionIf> existingActions) {
if (!newAction.getKeyBindingType().supportsKeyBindings()) {
return;
}
KeyBindingData newDefaultBinding = newAction.getDefaultKeyBindingData();
KeyStroke defaultKs = getKeyStroke(newDefaultBinding);
for (DockingActionIf action : existingActions) {
if (!action.getKeyBindingType().supportsKeyBindings()) {
continue;
}
KeyBindingData existingDefaultBinding = action.getDefaultKeyBindingData();
KeyStroke existingKs = getKeyStroke(existingDefaultBinding);
if (!Objects.equals(defaultKs, existingKs)) {
@ -493,6 +492,252 @@ public class KeyBindingUtils {
Msg.warn(KeyBindingUtils.class, s, ReflectionUtilities.createJavaFilteredThrowable());
}
/**
* Updates the given data with system-independent versions of key modifiers. For example,
* the <tt>control</tt> key will be converted to the <tt>command</tt> key on the Mac.
*
* @param keyStroke the keystroke to validate
* @return the potentially changed keystroke
*/
// TODO ignore the deprecation, as this method is responsible for fixing deprecated usage.
// When all actions no longer user the deprecated modifiers, the deprecated elements
// of this method can be removed
@SuppressWarnings("deprecation")
public static KeyStroke validateKeyStroke(KeyStroke keyStroke) {
if (keyStroke == null) {
return null;
}
// remove system-dependent control key mask and transform deprecated modifiers
int modifiers = keyStroke.getModifiers();
if ((modifiers & InputEvent.CTRL_DOWN_MASK) == InputEvent.CTRL_DOWN_MASK) {
modifiers = modifiers ^ InputEvent.CTRL_DOWN_MASK;
modifiers = modifiers | DockingUtils.CONTROL_KEY_MODIFIER_MASK;
}
if ((modifiers & InputEvent.CTRL_MASK) == InputEvent.CTRL_MASK) {
modifiers = modifiers ^ InputEvent.CTRL_MASK;
modifiers = modifiers | DockingUtils.CONTROL_KEY_MODIFIER_MASK;
}
if ((modifiers & ActionEvent.CTRL_MASK) == ActionEvent.CTRL_MASK) {
modifiers = modifiers ^ ActionEvent.CTRL_MASK;
modifiers = modifiers | DockingUtils.CONTROL_KEY_MODIFIER_MASK;
}
if ((modifiers & InputEvent.SHIFT_MASK) == InputEvent.SHIFT_MASK) {
modifiers = modifiers ^ InputEvent.SHIFT_MASK;
modifiers = modifiers | InputEvent.SHIFT_DOWN_MASK;
}
if ((modifiers & InputEvent.ALT_MASK) == InputEvent.ALT_MASK) {
modifiers = modifiers ^ InputEvent.ALT_MASK;
modifiers = modifiers | InputEvent.ALT_DOWN_MASK;
}
if ((modifiers & InputEvent.META_MASK) == InputEvent.META_MASK) {
modifiers = modifiers ^ InputEvent.META_MASK;
modifiers = modifiers | InputEvent.META_DOWN_MASK;
}
int eventType = keyStroke.getKeyEventType();
if (eventType == KeyEvent.KEY_TYPED) {
// we know that typed events have a key code of VK_UNDEFINED
return KeyStroke.getKeyStroke(Character.valueOf(keyStroke.getKeyChar()), modifiers);
}
// key pressed or released
boolean isOnKeyRelease = keyStroke.isOnKeyRelease();
return KeyStroke.getKeyStroke(keyStroke.getKeyCode(), modifiers, isOnKeyRelease);
}
/**
* Convert the toString() form of the keyStroke.
* <br>In Java 1.4.2 & earlier, Ctrl-M is returned as "keyCode CtrlM-P"
* and we want it to look like: "Ctrl-M".
* <br>In Java 1.5.0, Ctrl-M is returned as "ctrl pressed M"
* and we want it to look like: "Ctrl-M".
*
* @param keyStroke the key stroke
* @return the string value; the empty string if the key stroke is null
*/
public static String parseKeyStroke(KeyStroke keyStroke) {
if (keyStroke == null) {
return "";
}
final String keyPressSuffix = "-P";
String keyString = keyStroke.toString();
int type = keyStroke.getKeyEventType();
if (type == KeyEvent.KEY_TYPED) {
return String.valueOf(keyStroke.getKeyChar());
}
// get the character used in the key stroke
int firstIndex = keyString.lastIndexOf(' ') + 1;
int ctrlIndex = keyString.indexOf(CTRL, firstIndex);
if (ctrlIndex >= 0) {
firstIndex = ctrlIndex + CTRL.length();
}
int altIndex = keyString.indexOf(ALT, firstIndex);
if (altIndex >= 0) {
firstIndex = altIndex + ALT.length();
}
int shiftIndex = keyString.indexOf(SHIFT, firstIndex);
if (shiftIndex >= 0) {
firstIndex = shiftIndex + SHIFT.length();
}
int metaIndex = keyString.indexOf(META, firstIndex);
if (metaIndex >= 0) {
firstIndex = metaIndex + META.length();
}
int lastIndex = keyString.length();
if (keyString.endsWith(keyPressSuffix)) {
lastIndex -= keyPressSuffix.length();
}
if (lastIndex >= 0) {
keyString = keyString.substring(firstIndex, lastIndex);
}
int modifiers = keyStroke.getModifiers();
StringBuilder buffy = new StringBuilder();
if (isShift(modifiers)) {
buffy.insert(0, SHIFT + MODIFIER_SEPARATOR);
}
if (isAlt(modifiers)) {
buffy.insert(0, ALT + MODIFIER_SEPARATOR);
}
if (isControl(modifiers)) {
buffy.insert(0, CTRL + MODIFIER_SEPARATOR);
}
if (isMeta(modifiers)) {
buffy.insert(0, META + MODIFIER_SEPARATOR);
}
buffy.append(keyString);
return buffy.toString();
}
// ignore the deprecated; remove when we are confident that all tool actions no longer use the
// deprecated InputEvent mask types
@SuppressWarnings("deprecation")
private static boolean isShift(int mask) {
return (mask & InputEvent.SHIFT_DOWN_MASK) != 0 || (mask & InputEvent.SHIFT_MASK) != 0;
}
// ignore the deprecated; remove when we are confident that all tool actions no longer use the
// deprecated InputEvent mask types
@SuppressWarnings("deprecation")
private static boolean isAlt(int mask) {
return (mask & InputEvent.ALT_DOWN_MASK) != 0 || (mask & InputEvent.ALT_MASK) != 0;
}
// ignore the deprecated; remove when we are confident that all tool actions no longer use the
// deprecated InputEvent mask types
@SuppressWarnings("deprecation")
private static boolean isControl(int mask) {
return (mask & InputEvent.CTRL_DOWN_MASK) != 0 || (mask & InputEvent.CTRL_MASK) != 0;
}
// ignore the deprecated; remove when we are confident that all tool actions no longer use the
// deprecated InputEvent mask types
@SuppressWarnings("deprecation")
private static boolean isMeta(int mask) {
return (mask & InputEvent.META_DOWN_MASK) != 0 || (mask & InputEvent.META_MASK) != 0;
}
/**
* Parses the given text into a KeyStroke. This method relies upon
* {@link KeyStroke#getKeyStroke(String)} for parsing. Before making that call, this method
* will perform fixup on the given text for added flexibility. For example, the given
* text may contain spaces or dashes as the separators between parts in the string. Also,
* the text is converted such that it is not case-sensitive. So, the following example
* formats are allowed:
* <pre>
* Alt-F
* alt p
* Ctrl-Alt-Z
* ctrl Z
* </pre>
*
* @param keyStroke
* @return the new key stroke (as returned by {@link KeyStroke#getKeyStroke(String)}
*/
public static KeyStroke parseKeyStroke(String keyStroke) {
List<String> pieces = new ArrayList<>();
StringTokenizer tokenizer = new StringTokenizer(keyStroke, "- ");
while (tokenizer.hasMoreTokens()) {
String token = tokenizer.nextToken();
if (!pieces.contains(token)) {
pieces.add(token);
}
}
StringBuilder buffy = new StringBuilder();
for (Iterator<String> iterator = pieces.iterator(); iterator.hasNext();) {
String piece = iterator.next();
if (indexOfIgnoreCase(piece, SHIFT) != -1) {
buffy.append("shift ");
iterator.remove();
}
else if (indexOfIgnoreCase(piece, CTRL) != -1) {
buffy.append("ctrl ");
iterator.remove();
}
else if (indexOfIgnoreCase(piece, CONTROL) != -1) {
buffy.append("ctrl ");
iterator.remove();
}
else if (indexOfIgnoreCase(piece, ALT) != -1) {
buffy.append("alt ");
iterator.remove();
}
else if (indexOfIgnoreCase(piece, META) != -1) {
buffy.append("meta ");
iterator.remove();
}
else if (indexOfIgnoreCase(piece, PRESSED) != -1) {
iterator.remove();
}
else if (indexOfIgnoreCase(piece, TYPED) != -1) {
iterator.remove();
}
else if (indexOfIgnoreCase(piece, RELEASED) != -1) {
iterator.remove();
}
}
buffy.append(PRESSED).append(' ');
// at this point we should only have left one piece--the key ID
int leftover = pieces.size();
if (leftover > 1 || leftover == 0) {
Msg.warn(KeyBindingUtils.class, "Invalid keystroke string found. Expected " +
"format of '[modifier] ... key'. Found: '" + keyStroke + "'");
if (leftover == 0) {
return null; // nothing to do
}
}
String key = pieces.get(0);
buffy.append(key.toUpperCase());
return KeyStroke.getKeyStroke(buffy.toString());
}
//==================================================================================================
// Private Methods
//==================================================================================================
private static boolean isIgnored(DockingActionIf action) {
// a shared keybinding implies that this action should not be in
// the UI, as there will be a single proxy in place of all actions sharing that binding
return !action.getKeyBindingType().isManaged();
}
private static KeyStroke getKeyStroke(KeyBindingData data) {
if (data == null) {
return null;
@ -500,12 +745,7 @@ public class KeyBindingUtils {
return data.getKeyBinding();
}
//==================================================================================================
// Private Methods
//==================================================================================================
// prompts the user for a file location from which to read key binding
// data
// prompts the user for a file location from which to read key binding data
private static InputStream getInputStreamForFile(File startingDir) {
File selectedFile = getFileFromUser(startingDir);
@ -587,4 +827,5 @@ public class KeyBindingUtils {
return selectedFile;
}
}

View file

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package docking.action;
package docking.actions;
import java.awt.*;
import java.util.*;
@ -22,8 +22,9 @@ import java.util.List;
import javax.swing.*;
import javax.swing.text.*;
import docking.*;
import docking.actions.KeyBindingUtils;
import docking.DialogComponentProvider;
import docking.KeyEntryTextField;
import docking.action.*;
import docking.widgets.label.GIconLabel;
import ghidra.util.HelpLocation;
import ghidra.util.ReservedKeyBindings;
@ -35,7 +36,7 @@ import resources.ResourceManager;
*/
public class KeyEntryDialog extends DialogComponentProvider {
private ActionToGuiMapper actionManager;
private ToolActions toolActions;
private DockingActionIf action;
private JPanel defaultPanel;
private KeyEntryTextField keyEntryField;
@ -45,10 +46,10 @@ public class KeyEntryDialog extends DialogComponentProvider {
private SimpleAttributeSet textAttrSet;
private Color bgColor;
public KeyEntryDialog(DockingActionIf action, ActionToGuiMapper actionManager) {
public KeyEntryDialog(DockingActionIf action, ToolActions actions) {
super("Set Key Binding for " + action.getName(), true);
this.actionManager = actionManager;
this.action = action;
this.toolActions = actions;
setUpAttributes();
createPanel();
KeyStroke keyBinding = action.getKeyBinding();
@ -105,7 +106,7 @@ public class KeyEntryDialog extends DialogComponentProvider {
p.add(keyEntryField);
KeyStroke keyBinding = action.getKeyBinding();
if (keyBinding != null) {
keyEntryField.setText(DockingKeyBindingAction.parseKeyStroke(keyBinding));
keyEntryField.setText(KeyBindingUtils.parseKeyStroke(keyBinding));
}
setFocusComponent(keyEntryField);
defaultPanel.add(p, BorderLayout.CENTER);
@ -129,6 +130,14 @@ public class KeyEntryDialog extends DialogComponentProvider {
return p;
}
/**
* Sets the given keystroke value into the text field of this dialog
* @param ks the keystroke to set
*/
public void setKeyStroke(KeyStroke ks) {
keyEntryField.setKeyStroke(ks);
}
@Override
protected void cancelCallback() {
close();
@ -136,23 +145,24 @@ public class KeyEntryDialog extends DialogComponentProvider {
@Override
protected void okCallback() {
KeyStroke keyStroke = keyEntryField.getCurrentKeyStroke();
if (keyStroke != null && ReservedKeyBindings.isReservedKeystroke(keyStroke)) {
KeyStroke newKeyStroke = keyEntryField.getKeyStroke();
if (newKeyStroke != null && ReservedKeyBindings.isReservedKeystroke(newKeyStroke)) {
setStatusText(keyEntryField.getText() + " is a reserved keystroke");
return;
}
clearStatusText();
Set<DockingActionIf> allActions = actionManager.getAllActions();
Set<DockingActionIf> actions =
KeyBindingUtils.getActions(allActions, action.getOwner(), action.getName());
for (DockingActionIf element : actions) {
if (element.isKeyBindingManaged()) {
element.setUnvalidatedKeyBindingData(new KeyBindingData(keyStroke));
}
KeyStroke existingKeyStroke = action.getKeyBinding();
if (Objects.equals(existingKeyStroke, newKeyStroke)) {
setStatusText("Key binding unchanged");
return;
}
action.setUnvalidatedKeyBindingData(new KeyBindingData(newKeyStroke));
toolActions.keyBindingsChanged();
close();
}
@ -168,23 +178,30 @@ public class KeyEntryDialog extends DialogComponentProvider {
}
private void updateCollisionPane(KeyStroke ks) {
clearStatusText();
collisionPane.setText("");
if (ks == null) {
return;
}
KeyStroke existingKeyStroke = action.getKeyBinding();
if (Objects.equals(existingKeyStroke, ks)) {
setStatusText("Key binding unchanged");
return;
}
List<DockingActionIf> list = getManagedActionsForKeyStroke(ks);
if (list.size() == 0) {
return;
}
String ksName = DockingKeyBindingAction.parseKeyStroke(ks);
String ksName = KeyBindingUtils.parseKeyStroke(ks);
try {
doc.insertString(0, "Actions mapped to " + ksName + "\n\n", textAttrSet);
for (int i = 0; i < list.size(); i++) {
DockingActionIf a = list.get(i);
String collisionStr = "\t" + a.getName() + " (" + a.getOwner() + ")\n";
String collisionStr = "\t" + a.getName() + " (" + a.getOwnerDescription() + ")\n";
int offset = doc.getLength();
doc.insertString(offset, collisionStr, textAttrSet);
doc.setParagraphAttributes(offset, 1, tabAttrSet, false);
@ -202,6 +219,7 @@ public class KeyEntryDialog extends DialogComponentProvider {
if (multiAction == null) {
return Collections.emptyList();
}
List<DockingActionIf> list = multiAction.getActions();
Map<String, DockingActionIf> nameMap = new HashMap<>(list.size());
@ -218,7 +236,7 @@ public class KeyEntryDialog extends DialogComponentProvider {
}
private MultipleKeyAction getMultipleKeyAction(KeyStroke ks) {
Action keyAction = actionManager.getDockingKeyAction(ks);
Action keyAction = toolActions.getAction(ks);
if (keyAction instanceof MultipleKeyAction) {
return (MultipleKeyAction) keyAction;
}
@ -226,11 +244,6 @@ public class KeyEntryDialog extends DialogComponentProvider {
}
private boolean shouldAddAction(DockingActionIf dockableAction) {
if (dockableAction.isKeyBindingManaged()) {
return true;
}
// shared key bindings are handled specially
return !dockableAction.usesSharedKeyBinding();
return dockableAction.getKeyBindingType().isManaged();
}
}

View file

@ -20,6 +20,8 @@ import java.util.Map.Entry;
import javax.swing.KeyStroke;
import org.apache.commons.lang3.StringUtils;
import docking.ActionContext;
import docking.DockingWindowManager;
import docking.action.*;
@ -83,6 +85,33 @@ public class SharedStubKeyBindingAction extends DockingAction implements Options
updateActionKeyStrokeFromOptions(action, defaultKs);
}
@Override
public String getOwnerDescription() {
List<String> owners = getDistinctOwners();
Collections.sort(owners);
if (owners.size() == 1) {
return owners.get(0);
}
return StringUtils.join(owners, ", ");
}
private List<String> getDistinctOwners() {
List<String> results = new ArrayList<>();
Set<DockingActionIf> actions = clientActions.keySet();
for (DockingActionIf action : actions) {
String owner = action.getOwner();
if (DockingWindowManager.DOCKING_WINDOWS_OWNER.equals(owner)) {
// special case: this is the owner for special system-level actions
continue;
}
if (!results.contains(owner)) {
results.add(owner);
}
}
return results;
}
private KeyStroke validateActionsHaveTheSameDefaultKeyStroke(DockingActionIf newAction) {
// this value may be null
@ -116,12 +145,16 @@ public class SharedStubKeyBindingAction extends DockingAction implements Options
private void updateActionKeyStrokeFromOptions(DockingActionIf action, KeyStroke defaultKs) {
KeyStroke stubKs = defaultKs;
KeyStroke optionsKs = getKeyStrokeFromOptions(defaultKs);
if (!Objects.equals(defaultKs, optionsKs)) {
// we use the 'unvalidated' call since this value is provided by the user--we assume
// that user input is correct; we only validate programmer input
action.setUnvalidatedKeyBindingData(new KeyBindingData(optionsKs));
stubKs = optionsKs;
}
setUnvalidatedKeyBindingData(new KeyBindingData(stubKs));
}
private KeyStroke getKeyStrokeFromOptions(KeyStroke validatedKeyStroke) {

View file

@ -19,23 +19,29 @@ import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.*;
import javax.swing.Action;
import javax.swing.KeyStroke;
import org.apache.commons.collections4.IteratorUtils;
import org.apache.commons.collections4.Predicate;
import org.apache.commons.collections4.map.LazyMap;
import com.google.common.collect.Iterators;
import docking.*;
import docking.action.*;
import docking.tool.util.DockingToolConstants;
import ghidra.framework.options.*;
import ghidra.util.ReservedKeyBindings;
import ghidra.util.SystemUtilities;
import ghidra.util.exception.AssertException;
import util.CollectionUtils;
/**
* An class to manage actions registered with the tool
*/
public class ToolActions implements PropertyChangeListener {
public class ToolActions implements DockingToolActions, PropertyChangeListener {
private DockingWindowManager winMgr;
private ActionToGuiHelper actionGuiHelper;
/*
@ -51,24 +57,44 @@ public class ToolActions implements PropertyChangeListener {
private ToolOptions keyBindingOptions;
private DockingTool dockingTool;
private KeyBindingsManager keyBindingsManager;
/**
* Construct an ActionManager
*
* @param tool tool using this ActionManager
* @param windowManager manager of the "Docking" arrangement of a set of components
* and actions in the tool
* @param actionToGuiHelper the class that takes actions and maps them to GUI widgets
*/
public ToolActions(DockingTool tool, DockingWindowManager windowManager) {
public ToolActions(DockingTool tool, ActionToGuiHelper actionToGuiHelper) {
this.dockingTool = tool;
this.winMgr = windowManager;
this.actionGuiHelper = new ActionToGuiHelper(winMgr);
keyBindingOptions = tool.getOptions(DockingToolConstants.KEY_BINDINGS);
this.actionGuiHelper = actionToGuiHelper;
this.keyBindingsManager = new KeyBindingsManager(tool);
this.keyBindingOptions = tool.getOptions(DockingToolConstants.KEY_BINDINGS);
createReservedKeyBindings();
}
private void createReservedKeyBindings() {
KeyBindingAction keyBindingAction = new KeyBindingAction(this);
keyBindingsManager.addReservedAction(keyBindingAction,
ReservedKeyBindings.UPDATE_KEY_BINDINGS_KEY);
keyBindingsManager.addReservedAction(new HelpAction(false, ReservedKeyBindings.HELP_KEY1));
keyBindingsManager.addReservedAction(new HelpAction(false, ReservedKeyBindings.HELP_KEY2));
keyBindingsManager.addReservedAction(
new HelpAction(true, ReservedKeyBindings.HELP_INFO_KEY));
// these are diagnostic
if (SystemUtilities.isInDevelopmentMode()) {
keyBindingsManager.addReservedAction(new ShowFocusInfoAction());
keyBindingsManager.addReservedAction(new ShowFocusCycleAction());
}
}
public void dispose() {
actionsByNameByOwner.clear();
sharedActionMap.clear();
keyBindingsManager.dispose();
}
private void addActionToMap(DockingActionIf action) {
@ -83,34 +109,35 @@ public class ToolActions implements PropertyChangeListener {
* @param provider provider associated with the action
* @param action local action to the provider
*/
@Override
public synchronized void addLocalAction(ComponentProvider provider, DockingActionIf action) {
checkForAlreadyAddedAction(provider, action);
action.addPropertyChangeListener(this);
addActionToMap(action);
setKeyBindingOption(action);
initializeKeyBinding(provider, action);
actionGuiHelper.addLocalAction(provider, action);
}
/**
* Adds the action to the tool.
* @param action the action to be added.
*/
public synchronized void addToolAction(DockingActionIf action) {
@Override
public synchronized void addGlobalAction(DockingActionIf action) {
checkForAlreadyAddedAction(null, action);
action.addPropertyChangeListener(this);
addActionToMap(action);
setKeyBindingOption(action);
initializeKeyBinding(null, action);
actionGuiHelper.addToolAction(action);
}
private void setKeyBindingOption(DockingActionIf action) {
private void initializeKeyBinding(ComponentProvider provider, DockingActionIf action) {
if (action.usesSharedKeyBinding()) {
installSharedKeyBinding(action);
KeyBindingType type = action.getKeyBindingType();
if (!type.supportsKeyBindings()) {
return;
}
if (!action.isKeyBindingManaged()) {
if (type.isShared()) {
installSharedKeyBinding(provider, action);
return;
}
@ -121,9 +148,11 @@ public class ToolActions implements PropertyChangeListener {
if (!Objects.equals(ks, newKs)) {
action.setUnvalidatedKeyBindingData(new KeyBindingData(newKs));
}
keyBindingsManager.addAction(provider, action);
}
private void installSharedKeyBinding(DockingActionIf action) {
private void installSharedKeyBinding(ComponentProvider provider, DockingActionIf action) {
String name = action.getName();
KeyStroke defaultKeyStroke = action.getKeyBinding();
@ -132,29 +161,31 @@ public class ToolActions implements PropertyChangeListener {
SharedStubKeyBindingAction newStub =
new SharedStubKeyBindingAction(name, keyBindingOptions);
newStub.addPropertyChangeListener(this);
keyBindingOptions.registerOption(newStub.getFullName(), OptionType.KEYSTROKE_TYPE,
defaultKeyStroke, null, null);
return newStub;
});
stub.addClientAction(action);
// note: only put the stub in the manager, not the actual action
keyBindingsManager.addAction(provider, stub);
}
/**
* Removes the given action from the tool
* @param action the action to be removed.
*/
public synchronized void removeToolAction(DockingActionIf action) {
@Override
public synchronized void removeGlobalAction(DockingActionIf action) {
action.removePropertyChangeListener(this);
removeAction(action);
actionGuiHelper.removeToolAction(action);
}
/**
* Remove all actions that have the given owner.
* @param owner owner of the actions to remove
*/
public synchronized void removeToolActions(String owner) {
@Override
public synchronized void removeActions(String owner) {
// remove from the outer map first, to prevent concurrent modification exceptions
Map<String, Set<DockingActionIf>> toCleanup = actionsByNameByOwner.remove(owner);
@ -166,15 +197,17 @@ public class ToolActions implements PropertyChangeListener {
toCleanup.values()
.stream()
.flatMap(set -> set.stream())
.forEach(action -> removeToolAction(action))
.forEach(action -> removeGlobalAction(action))
;
//@formatter:on
}
private void checkForAlreadyAddedAction(ComponentProvider provider, DockingActionIf action) {
if (getActionStorage(action).contains(action)) {
throw new AssertException("Cannot add the same action more than once. Provider " +
provider.getName() + " - action: " + action.getFullName());
String providerString =
provider == null ? "Action: " : "Provider " + provider.getName() + " - action: ";
throw new AssertException("Cannot add the same action more than once. " +
providerString + action.getFullName());
}
}
@ -184,6 +217,7 @@ public class ToolActions implements PropertyChangeListener {
* @return array of actions; zero length array is returned if no
* action exists with the given name
*/
@Override
public synchronized Set<DockingActionIf> getActions(String owner) {
Set<DockingActionIf> result = new HashSet<>();
@ -201,8 +235,10 @@ public class ToolActions implements PropertyChangeListener {
/**
* Get a set of all actions in the tool
* @return the actions
*
* @return a new set of the existing actions
*/
@Override
public synchronized Set<DockingActionIf> getAllActions() {
Set<DockingActionIf> result = new HashSet<>();
@ -218,56 +254,83 @@ public class ToolActions implements PropertyChangeListener {
return result;
}
private Iterator<DockingActionIf> getAllActionsIterator() {
// chain all items together, rather than copy the data
Iterator<DockingActionIf> iterator = IteratorUtils.emptyIterator();
Collection<Map<String, Set<DockingActionIf>>> maps = actionsByNameByOwner.values();
for (Map<String, Set<DockingActionIf>> actionsByName : maps) {
for (Set<DockingActionIf> actions : actionsByName.values()) {
Iterator<DockingActionIf> next = actions.iterator();
// Note: do not use apache commons here--the code below degrades exponentially
//iterator = IteratorUtils.chainedIterator(iterator, next);
iterator = Iterators.concat(iterator, next);
}
}
return Iterators.concat(iterator, sharedActionMap.values().iterator());
}
/**
* Get the keybindings for each action so that they are still registered as being used;
* otherwise the options will be removed because they are noted as not being used.
*/
public synchronized void restoreKeyBindings() {
keyBindingOptions = dockingTool.getOptions(DockingToolConstants.KEY_BINDINGS);
Set<DockingActionIf> actions = getAllActions();
for (DockingActionIf action : actions) {
if (!action.isKeyBindingManaged()) {
continue;
}
Iterator<DockingActionIf> it = getKeyBindingActionsIterator();
for (DockingActionIf action : CollectionUtils.asIterable(it)) {
KeyStroke ks = action.getKeyBinding();
KeyStroke newKs = keyBindingOptions.getKeyStroke(action.getFullName(), ks);
if (ks != newKs) {
if (!Objects.equals(ks, newKs)) {
action.setUnvalidatedKeyBindingData(new KeyBindingData(newKs));
}
}
}
// return only actions that allow key bindings
private Iterator<DockingActionIf> getKeyBindingActionsIterator() {
Predicate<DockingActionIf> filter = a -> a.getKeyBindingType() == KeyBindingType.INDIVIDUAL;
return IteratorUtils.filteredIterator(getAllActionsIterator(), filter);
}
/**
* Remove an action that works specifically with a component provider.
* @param provider provider associated with the action
* @param action local action to the provider
*/
public synchronized void removeProviderAction(ComponentProvider provider,
DockingActionIf action) {
@Override
public synchronized void removeLocalAction(ComponentProvider provider, DockingActionIf action) {
action.removePropertyChangeListener(this);
removeAction(action);
keyBindingsManager.removeAction(action);
actionGuiHelper.removeProviderAction(provider, action);
}
/**
* Get the actions for the given provider and remove them from the action map
* @param provider provider whose actions are to be removed
*/
public synchronized void removeComponentActions(ComponentProvider provider) {
@Override
public synchronized void removeActions(ComponentProvider provider) {
Iterator<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) {
keyBindingsManager.removeAction(action);
getActionStorage(action).remove(action);
if (action.usesSharedKeyBinding()) {
SharedStubKeyBindingAction stub = sharedActionMap.get(action.getName());
if (stub != null) {
stub.removeClientAction(action);
}
if (!action.getKeyBindingType().isShared()) {
return;
}
SharedStubKeyBindingAction stub = sharedActionMap.get(action.getName());
if (stub != null) {
stub.removeClientAction(action);
}
}
@ -279,27 +342,59 @@ public class ToolActions implements PropertyChangeListener {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getPropertyName().equals(DockingActionIf.KEYBINDING_DATA_PROPERTY)) {
DockingAction action = (DockingAction) evt.getSource();
if (!action.isKeyBindingManaged()) {
dockingTool.setConfigChanged(true);
return;
}
KeyBindingData keyBindingData = (KeyBindingData) evt.getNewValue();
KeyStroke newKeyStroke = keyBindingData.getKeyBinding();
Options opt = dockingTool.getOptions(DockingToolConstants.KEY_BINDINGS);
KeyStroke optKeyStroke = opt.getKeyStroke(action.getFullName(), null);
if (newKeyStroke == null) {
opt.removeOption(action.getFullName());
}
else if (!newKeyStroke.equals(optKeyStroke)) {
opt.setKeyStroke(action.getFullName(), newKeyStroke);
dockingTool.setConfigChanged(true);
if (!evt.getPropertyName().equals(DockingActionIf.KEYBINDING_DATA_PROPERTY)) {
return;
}
DockingAction action = (DockingAction) evt.getSource();
if (!action.getKeyBindingType().isManaged()) {
// this reads unusually, but we need to notify the tool to rebuild its 'Window' menu
// in the case that this action is one of the tool's special actions
keyBindingsChanged();
return;
}
KeyBindingData newKeyBindingData = (KeyBindingData) evt.getNewValue();
KeyStroke newKeyStroke = null;
if (newKeyBindingData != null) {
newKeyStroke = newKeyBindingData.getKeyBinding();
}
Options opt = dockingTool.getOptions(DockingToolConstants.KEY_BINDINGS);
KeyStroke optKeyStroke = opt.getKeyStroke(action.getFullName(), null);
if (newKeyStroke == null) {
opt.removeOption(action.getFullName());
}
else if (!newKeyStroke.equals(optKeyStroke)) {
opt.setKeyStroke(action.getFullName(), newKeyStroke);
keyBindingsChanged();
}
}
@Override
public DockingActionIf getLocalAction(ComponentProvider provider, String actionName) {
Iterator<DockingActionIf> it = actionGuiHelper.getComponentActions(provider);
while (it.hasNext()) {
DockingActionIf action = it.next();
if (action.getName().equals(actionName)) {
return action;
}
}
return null;
}
public Action getAction(KeyStroke ks) {
return keyBindingsManager.getDockingKeyAction(ks);
}
DockingActionIf getSharedStubKeyBindingAction(String name) {
return sharedActionMap.get(name);
}
// triggered by a user-initiated action
void keyBindingsChanged() {
dockingTool.setConfigChanged(true);
actionGuiHelper.keyBindingsChanged();
}
}

View file

@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -24,7 +23,7 @@ import docking.action.DockingActionIf;
import docking.action.MenuData;
/**
* Manages the main menu bar on the main frame.
* Manages the main menu bar on the main frame
*/
public class MenuBarManager implements MenuGroupListener {
@ -32,36 +31,41 @@ public class MenuBarManager implements MenuGroupListener {
private Map<String, MenuManager> menuManagers;
private final MenuGroupMap menuGroupMap;
/**
* Constructs a new MenuBarManager
*/
public MenuBarManager(MenuHandler actionHandler, MenuGroupMap menuGroupMap) {
this.menuGroupMap = menuGroupMap;
menuManagers = new TreeMap<String, MenuManager>();
menuManagers = new TreeMap<>();
this.menuHandler = actionHandler;
}
public void clearActions() {
menuManagers = new TreeMap<String, MenuManager>();
menuManagers = new TreeMap<>();
}
/**
* Adds an action to the menu.
* @param action the action to be added.
* @param groupMgr the MenuGroupMap
* Adds an action to the menu
* @param action the action to be added
*/
public void addAction(DockingActionIf action) {
MenuManager menuManager = getMenuManager(action);
if (menuManager == null) {
return;
}
menuManager.addAction(action);
}
private MenuManager getMenuManager(DockingActionIf action) {
MenuData menuBarData = action.getMenuBarData();
if (menuBarData == null) {
return;
return null;
}
String[] menuPath = menuBarData.getMenuPath();
if (menuPath == null || menuPath.length <= 1) {
return;
return null;
}
MenuManager menuMgr = getMenuManager(menuPath[0]);
menuMgr.addAction(action);
return getMenuManager(menuPath[0]);
}
/**
@ -104,17 +108,13 @@ public class MenuBarManager implements MenuGroupListener {
MenuManager mgr = menuManagers.get(menuName);
if (mgr == null) {
mgr =
new MenuManager(menuName, new String[] { menuName }, mk, 1, null, false,
menuHandler, menuGroupMap);
mgr = new MenuManager(menuName, new String[] { menuName }, mk, 1, null, false,
menuHandler, menuGroupMap);
menuManagers.put(menuName, mgr);
}
return mgr;
}
/**
* Returns a JMenuBar for all the actions.
*/
public JMenuBar getMenuBar() {
MenuManager fileMenu = menuManagers.get("File");
MenuManager editMenu = menuManagers.get("Edit");
@ -153,6 +153,7 @@ public class MenuBarManager implements MenuGroupListener {
* @param menuPath the menu path whose group changed.
* @param group the new group for the given menuPath.
*/
@Override
public void menuGroupChanged(String[] menuPath, String group) {
if (menuPath != null && menuPath.length > 1) {
MenuManager mgr = getMenuManager(menuPath[0]);

View file

@ -43,13 +43,10 @@ class MenuItemManager implements ManagedMenuItem, PropertyChangeListener, Action
// listeners to handle help activation
// -this listener covers activation by keyboard and by mouse *when enabled*
private ChangeListener buttonModelChangeListener;
// -this listener covers activation by mouse *when the action is disabled*
private MouseAdapter menuHoverListener;
/**
* Constructs a new MenuItemManger
* @param dockableAction the action whose menuItem is being managed.
*/
MenuItemManager(MenuHandler actionHandler, DockingActionIf dockingAction,
boolean usePopupPath) {
this.menuHandler = actionHandler;
@ -104,9 +101,6 @@ class MenuItemManager implements ManagedMenuItem, PropertyChangeListener, Action
};
}
/**
* @see ghidra.framework.docking.menu.ManagedMenuItem#getWindowGroup()
*/
@Override
public String getGroup() {
MenuData menuData = isPopup ? action.getPopupMenuData() : action.getMenuBarData();
@ -119,10 +113,6 @@ class MenuItemManager implements ManagedMenuItem, PropertyChangeListener, Action
return menuData == null ? null : menuData.getMenuSubGroup();
}
/**
*
* @see ghidra.framework.docking.menu.ManagedMenuItem#dispose()
*/
@Override
public void dispose() {
if (action != null) {
@ -138,9 +128,6 @@ class MenuItemManager implements ManagedMenuItem, PropertyChangeListener, Action
action = null;
}
/**
* @see ghidra.framework.docking.menu.ManagedMenuItem#getMenuItem()
*/
@Override
public JMenuItem getMenuItem() {
if (menuItem != null) {
@ -158,17 +145,10 @@ class MenuItemManager implements ManagedMenuItem, PropertyChangeListener, Action
return menuItem;
}
/**
* Returns the owner associated with this items action.
*/
public String getOwner() {
return action.getOwner();
}
/**
* Changes the menuItem to reflect changes in the actions properties.
* @see java.beans.PropertyChangeListener#propertyChange(java.beans.PropertyChangeEvent)
*/
@Override
public void propertyChange(PropertyChangeEvent e) {
if (menuItem == null) {
@ -209,16 +189,10 @@ class MenuItemManager implements ManagedMenuItem, PropertyChangeListener, Action
}
}
/**
* Returns the action associated with this menu item.
*/
public DockingActionIf getAction() {
return action;
}
/**
* @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
*/
@Override
public void actionPerformed(ActionEvent e) {
if (menuHandler != null) {

View file

@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -30,8 +29,8 @@ import docking.action.MenuData;
public class MenuManager implements ManagedMenuItem {
private static String NULL_GROUP_NAME = "<null group>";
private Set<ManagedMenuItem> managedMenuItems = new HashSet<ManagedMenuItem>();
private Map<String, MenuManager> subMenus = new HashMap<String, MenuManager>();
private Set<ManagedMenuItem> managedMenuItems = new HashSet<>();
private Map<String, MenuManager> subMenus = new HashMap<>();
private String name;
private final String[] menuPath;
@ -50,9 +49,9 @@ public class MenuManager implements ManagedMenuItem {
* @param name the name of the menu.
* @param mnemonicKey the key to use for the menu mnemonic
* @param group the group of the menu.
* @param showKeyBindings if true, includes the keybinding text on the menu item.
* @param usePopupPath if true, registers actions with popup paths as popup items.
* @param menuHandler Listener to be notified of menu behavior.
* @param menuGroupMap maps menu groups to menu paths
*/
public MenuManager(String name, char mnemonicKey, String group, boolean usePopupPath,
MenuHandler menuHandler, MenuGroupMap menuGroupMap) {
@ -68,9 +67,9 @@ public class MenuManager implements ManagedMenuItem {
* @param mnemonicKey the key to use for the menu mnemonic
* @param level the number of parent menus that this menu is in.
* @param group the group of this menu.
* @param showKeyBindings if true, includes the keybinding text on the menu item.
* @param usePopupPath if true, registers actions with popup paths as popup items.
* @param menuHandler Listener to be notified of menu behavior.
* @param menuGroupMap maps menu groups to menu paths
*/
MenuManager(String name, String[] menuPath, char mnemonicKey, int level, String group,
boolean usePopupPath, MenuHandler menuHandler, MenuGroupMap menuGroupMap) {
@ -96,10 +95,8 @@ public class MenuManager implements ManagedMenuItem {
}
/**
* Adds an action to this menu. Can create subMenus depending on the menuPath of the
* action.
* @param action the action to be added.
* @param menuGroupMap group map for menuItems
* Adds an action to this menu. Can create subMenus depending on the menuPath of the action
* @param action the action to be added
*/
public void addAction(DockingActionIf action) {
checkForSwingThread();
@ -122,9 +119,8 @@ public class MenuManager implements ManagedMenuItem {
submenuGroup = subMenuName;
}
mgr =
new MenuManager(cleanSubMenuName, submenuPath, mnemonic, submenuLevel,
submenuGroup, usePopupPath, menuHandler, menuGroupMap);
mgr = new MenuManager(cleanSubMenuName, submenuPath, mnemonic, submenuLevel,
submenuGroup, usePopupPath, menuHandler, menuGroupMap);
subMenus.put(cleanSubMenuName, mgr);
managedMenuItems.add(mgr);
}
@ -198,7 +194,7 @@ public class MenuManager implements ManagedMenuItem {
menu.addMenuListener(menuHandler);
}
List<ManagedMenuItem> list = new ArrayList<ManagedMenuItem>(managedMenuItems);
List<ManagedMenuItem> list = new ArrayList<>(managedMenuItems);
Collections.sort(list, comparator);
String lastGroup = null;
@ -259,7 +255,7 @@ public class MenuManager implements ManagedMenuItem {
if (popupMenu == null) {
popupMenu = new JPopupMenu(name);
List<ManagedMenuItem> list = new ArrayList<ManagedMenuItem>(managedMenuItems);
List<ManagedMenuItem> list = new ArrayList<>(managedMenuItems);
Collections.sort(list, comparator);
String lastGroup = NULL_GROUP_NAME;
boolean hasMenuItems = false;

View file

@ -15,16 +15,18 @@
*/
package docking.menu;
import ghidra.util.StringUtilities;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.*;
import org.apache.commons.lang3.StringUtils;
import docking.*;
import docking.action.*;
import ghidra.docking.util.DockingWindowsLookAndFeelUtils;
import ghidra.util.StringUtilities;
/**
* Class to manager toolbar buttons.
@ -78,7 +80,7 @@ public class ToolBarItemManager implements PropertyChangeListener, ActionListene
}
private void setToolTipText(JButton button, DockingActionIf action, String toolTipText) {
String keyBindingText = getKeyBindingAcceleratorText(action.getKeyBinding());
String keyBindingText = getKeyBindingAcceleratorText(button, action.getKeyBinding());
if (keyBindingText != null) {
button.setToolTipText(combingToolTipTextWithKeyBinding(toolTipText, keyBindingText));
}
@ -93,7 +95,7 @@ public class ToolBarItemManager implements PropertyChangeListener, ActionListene
StringBuilder buffy = new StringBuilder(toolTipText);
if (StringUtilities.startsWithIgnoreCase(toolTipText, "<HTML>")) {
String endHTMLTag = "</HTML>";
int closeTagIndex = StringUtilities.indexOfIgnoreCase(toolTipText, endHTMLTag);
int closeTagIndex = StringUtils.indexOfIgnoreCase(toolTipText, endHTMLTag);
if (closeTagIndex < 0) {
// no closing tag, which is acceptable
buffy.append(START_KEYBINDING_TEXT).append(keyBindingText).append(
@ -120,7 +122,7 @@ public class ToolBarItemManager implements PropertyChangeListener, ActionListene
return action.getName();
}
private String getKeyBindingAcceleratorText(KeyStroke keyStroke) {
private String getKeyBindingAcceleratorText(JButton button, KeyStroke keyStroke) {
if (keyStroke == null) {
return null;
}
@ -129,8 +131,12 @@ public class ToolBarItemManager implements PropertyChangeListener, ActionListene
StringBuilder builder = new StringBuilder();
int modifiers = keyStroke.getModifiers();
if (modifiers > 0) {
builder.append(KeyEvent.getKeyModifiersText(modifiers));
builder.append('+');
builder.append(InputEvent.getModifiersExText(modifiers));
// The Aqua LaF does not use the '+' symbol between modifiers
if (!DockingWindowsLookAndFeelUtils.isUsingAquaUI(button.getUI())) {
builder.append('+');
}
}
int keyCode = keyStroke.getKeyCode();
if (keyCode != 0) {
@ -204,16 +210,13 @@ public class ToolBarItemManager implements PropertyChangeListener, ActionListene
final ActionContext finalContext = tempContext;
// this gives the UI some time to repaint before executing the action
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
if (toolBarAction.isEnabledForContext(finalContext)) {
if (toolBarAction instanceof ToggleDockingActionIf) {
ToggleDockingActionIf toggleAction = (ToggleDockingActionIf) toolBarAction;
toggleAction.setSelected(!toggleAction.isSelected());
}
toolBarAction.actionPerformed(finalContext);
SwingUtilities.invokeLater(() -> {
if (toolBarAction.isEnabledForContext(finalContext)) {
if (toolBarAction instanceof ToggleDockingActionIf) {
ToggleDockingActionIf toggleAction = (ToggleDockingActionIf) toolBarAction;
toggleAction.setSelected(!toggleAction.isSelected());
}
toolBarAction.actionPerformed(finalContext);
}
});
}

View file

@ -31,7 +31,7 @@ import docking.widgets.VariableHeightPanel;
*/
public class ToolBarManager {
private Map<String, List<ToolBarItemManager>> groupToItemsMap =
new TreeMap<String, List<ToolBarItemManager>>(new GroupComparator());
new TreeMap<>(new GroupComparator());
private Comparator<? super ToolBarItemManager> toolBarItemComparator =
new ToolBarItemManagerComparator();
@ -47,9 +47,6 @@ public class ToolBarManager {
toolBar = null;
}
/**
* Adds the action to the toolbar.
*/
public void addAction(DockingActionIf action) {
ToolBarData toolBarData = action.getToolBarData();
if (toolBarData == null) {
@ -61,7 +58,7 @@ public class ToolBarManager {
String group = toolBarData.getToolBarGroup();
List<ToolBarItemManager> items = groupToItemsMap.get(group);
if (items == null) {
items = new ArrayList<ToolBarItemManager>();
items = new ArrayList<>();
groupToItemsMap.put(group, items);
}
items.add(new ToolBarItemManager(action, windowManager));
@ -96,9 +93,6 @@ public class ToolBarManager {
groupToItemsMap.clear();
}
/**
* Returns true if the toolbar is empty.
*/
public boolean isEmpty() {
return groupToItemsMap.isEmpty();
}

View file

@ -43,6 +43,7 @@ import com.google.common.collect.Sets;
import docking.*;
import docking.action.DockingActionIf;
import docking.action.ToggleDockingActionIf;
import docking.actions.DockingToolActions;
import docking.dnd.GClipboard;
import docking.framework.DockingApplicationConfiguration;
import docking.menu.DockingToolbarButton;
@ -1114,7 +1115,8 @@ public abstract class AbstractDockingTest extends AbstractGenericTest {
}
/**
* A helper method to find all actions with the given owner's name
* A helper method to find all actions with the given owner's name (this will not include
* reserved system actions)
*
* @param tool the tool containing all system actions
* @param name the owner's name to match
@ -1125,7 +1127,8 @@ public abstract class AbstractDockingTest extends AbstractGenericTest {
}
/**
* A helper method to find all actions by name, with the given owner's name
* A helper method to find all actions by name, with the given owner's name (this will not
* include reserved system actions)
*
* @param tool the tool containing all system actions
* @param owner the owner's name
@ -1167,7 +1170,8 @@ public abstract class AbstractDockingTest extends AbstractGenericTest {
/**
* Finds the action by the given owner name and action name.
* If you do not know the owner name, then use
* the call {@link #getActionsByName(DockingTool, String)} instead.
* the call {@link #getActionsByName(DockingTool, String)} instead (this will not include
* reserved system actions).
*
* <P>Note: more specific test case subclasses provide other methods for finding actions
* when you have an owner name (which is usually the plugin name).
@ -1192,8 +1196,18 @@ public abstract class AbstractDockingTest extends AbstractGenericTest {
return CollectionUtils.any(actions);
}
/**
* Returns the action by the given name that belongs to the given provider
*
* @param provider the provider
* @param actionName the action name
* @return the action
*/
public static DockingActionIf getLocalAction(ComponentProvider provider, String actionName) {
return getAction(provider.getTool(), provider.getName(), actionName);
DockingTool tool = provider.getTool();
DockingToolActions toolActions = tool.getToolActions();
DockingActionIf action = toolActions.getLocalAction(provider, actionName);
return action;
}
/**
@ -1847,10 +1861,12 @@ public abstract class AbstractDockingTest extends AbstractGenericTest {
*
* @param tool the tool in which the provider lives
* @param name the name of the provider to show
* @return the newly shown provider
*/
public void showProvider(DockingTool tool, String name) {
public ComponentProvider showProvider(DockingTool tool, String name) {
ComponentProvider provider = tool.getComponentProvider(name);
tool.showComponentProvider(provider, true);
return provider;
}
/**

View file

@ -15,9 +15,9 @@
*/
package docking.widgets.table;
import static docking.DockingUtils.*;
import static docking.action.MenuData.*;
import static java.awt.event.InputEvent.*;
import static docking.DockingUtils.CONTROL_KEY_MODIFIER_MASK;
import static docking.action.MenuData.NO_MNEMONIC;
import static java.awt.event.InputEvent.SHIFT_DOWN_MASK;
import java.awt.*;
import java.awt.event.*;
@ -509,23 +509,14 @@ public class GTable extends JTable implements KeyStrokeConsumer, DockingActionPr
return autoLookupKeyStrokeConsumer.isKeyConsumed(keyStroke);
}
/**
* {@inheritDoc}
*/
@Override
public List<DockingActionIf> getDockingActions(ActionContext context) {
Object sourceObject = context.getSourceObject();
if (sourceObject != this) {
// we are only interested in providing actions when we are the source of the event
return Collections.emptyList();
}
public List<DockingActionIf> getDockingActions() {
return getDefaultDockingActions();
}
/**
* Returns the default actions of this table. Normally, the Docking Windows systems uses
* {@link #getDockingActions(ActionContext)} to get the correct actions to show. However,
* {@link #getDockingActions()} to get the correct actions to show. However,
* there are some cases where clients override what appears when you click on a table (such
* as in {@link DialogComponentProvider}s. For those clients that are creating their own
* action building, they need a way to get the default actions, hence this method.
@ -1178,7 +1169,8 @@ public class GTable extends JTable implements KeyStrokeConsumer, DockingActionPr
int subGroupIndex = 1; // order by insertion
String owner = getClass().getSimpleName();
copyAction = new DockingAction("Table Data Copy", owner, false) {
owner = "GTable";
copyAction = new DockingAction("Table Data Copy", owner, KeyBindingType.SHARED) {
@Override
public void actionPerformed(ActionContext context) {
copying = true;
@ -1209,7 +1201,7 @@ public class GTable extends JTable implements KeyStrokeConsumer, DockingActionPr
//@formatter:on
copyCurrentColumnAction =
new DockingAction("Table Data Copy Current Column", owner, false) {
new DockingAction("Table Data Copy Current Column", owner, KeyBindingType.SHARED) {
@Override
public void actionPerformed(ActionContext context) {
@ -1246,17 +1238,18 @@ public class GTable extends JTable implements KeyStrokeConsumer, DockingActionPr
copyCurrentColumnAction.setHelpLocation(new HelpLocation("Tables", "Copy_Current_Column"));
//@formatter:on
copyColumnsAction = new DockingAction("Table Data Copy by Columns", owner, false) {
@Override
public void actionPerformed(ActionContext context) {
int[] userColumns = promptUserForColumns();
if (userColumns == null) {
return; // cancelled
}
copyColumnsAction =
new DockingAction("Table Data Copy by Columns", owner, KeyBindingType.SHARED) {
@Override
public void actionPerformed(ActionContext context) {
int[] userColumns = promptUserForColumns();
if (userColumns == null) {
return; // cancelled
}
copyColumns(userColumns);
}
};
copyColumns(userColumns);
}
};
//@formatter:off
copyColumnsAction.setPopupMenuData(new MenuData(
new String[] { "Copy", "Copy Columns..." },
@ -1269,7 +1262,7 @@ public class GTable extends JTable implements KeyStrokeConsumer, DockingActionPr
copyColumnsAction.setHelpLocation(new HelpLocation("Tables", "Copy_Columns"));
//@formatter:on
exportAction = new DockingAction("Table Data CSV Export", owner, false) {
exportAction = new DockingAction("Table Data CSV Export", owner, KeyBindingType.SHARED) {
@Override
public void actionPerformed(ActionContext context) {
File file = chooseExportFile();
@ -1291,7 +1284,7 @@ public class GTable extends JTable implements KeyStrokeConsumer, DockingActionPr
//@formatter:on
exportColumnsAction =
new DockingAction("Table Data CSV Export (by Columns)", owner, false) {
new DockingAction("Table Data CSV Export (by Columns)", owner, KeyBindingType.SHARED) {
@Override
public void actionPerformed(ActionContext context) {
int[] userColumns = promptUserForColumns();
@ -1323,7 +1316,7 @@ public class GTable extends JTable implements KeyStrokeConsumer, DockingActionPr
exportColumnsAction.setHelpLocation(new HelpLocation("Tables", "ExportCSV_Columns"));
//@formatter:on
selectAllAction = new DockingAction("Table Select All", owner, false) {
selectAllAction = new DockingAction("Table Select All", owner, KeyBindingType.SHARED) {
@Override
public void actionPerformed(ActionContext context) {
selectAll();