GT-2869 - Key Bindings - review fixes

This commit is contained in:
dragonmacher 2019-06-06 18:59:27 -04:00
parent 43fa7e3f92
commit 2de5c40cd6
23 changed files with 142 additions and 455 deletions

View file

@ -102,7 +102,7 @@ public class CreateHelpTemplateScript extends GhidraScript {
}
private List<DockingActionIf> getActions(PluginTool tool, Plugin plugin) {
Set<DockingActionIf> actions = KeyBindingUtils.getKeyBindingActions(tool, plugin.getName());
Set<DockingActionIf> actions = KeyBindingUtils.getKeyBindingActionsForOwner(tool, plugin.getName());
List<DockingActionIf> list = new ArrayList<>(actions);
Comparator<DockingActionIf> comparator = (action1, action2) -> {
try {

View file

@ -186,8 +186,8 @@ public class KeyEntryDialogTest extends AbstractGhidraHeadedIntegrationTest {
public DockingAction getKeyBindingAction() {
DockingWindowManager dwm = DockingWindowManager.getInstance(tool.getToolFrame());
DockingActionManager dockingActionManager =
(DockingActionManager) getInstanceField("actionManager", dwm);
ActionToGuiMapper dockingActionManager =
(ActionToGuiMapper) getInstanceField("actionManager", dwm);
return (DockingAction) getInstanceField("keyBindingsAction", dockingActionManager);
}

View file

@ -21,7 +21,7 @@ import java.util.*;
import javax.swing.JFrame;
import docking.action.DockingActionIf;
import docking.actions.DockingToolActionManager;
import docking.actions.ToolActions;
import ghidra.framework.options.ToolOptions;
import ghidra.util.SystemUtilities;
@ -32,7 +32,7 @@ import ghidra.util.SystemUtilities;
public abstract class AbstractDockingTool implements DockingTool {
protected DockingWindowManager winMgr;
protected DockingToolActionManager actionMgr;
protected ToolActions actionMgr;
protected Map<String, ToolOptions> optionsMap = new HashMap<>();
protected boolean configChangedFlag;
@ -117,7 +117,7 @@ public abstract class AbstractDockingTool implements DockingTool {
@Override
public Set<DockingActionIf> getAllActions() {
Set<DockingActionIf> actions = actionMgr.getAllActions();
DockingActionManager am = winMgr.getActionManager();
ActionToGuiMapper am = winMgr.getActionManager();
actions.addAll(am.getAllActions());
return actions;
}

View file

@ -28,7 +28,7 @@ import ghidra.util.*;
/**
* Manages the global actions for the menu and toolbar.
*/
public class DockingActionManager {
public class ActionToGuiMapper {
private HashSet<DockingActionIf> globalActions = new LinkedHashSet<>();
@ -44,7 +44,7 @@ public class DockingActionManager {
private PopupActionManager popupActionManager;
private DockingAction keyBindingsAction;
DockingActionManager(DockingWindowManager winMgr) {
ActionToGuiMapper(DockingWindowManager winMgr) {
menuGroupMap = new MenuGroupMap();
menuBarMenuHandler = new MenuBarMenuHandler(winMgr);

View file

@ -69,7 +69,7 @@ public class DialogComponentProviderPopupActionManager {
return;
}
DockingActionManager actionManager = dwm.getActionManager();
ActionToGuiMapper actionManager = dwm.getActionManager();
MenuGroupMap menuGroupMap = actionManager.getMenuGroupMap();
MenuManager menuMgr =
new MenuManager("Popup", '\0', null, true, popupMenuHandler, menuGroupMap);

View file

@ -52,7 +52,7 @@ public class DockableComponent extends JPanel implements ContainerListener {
private JComponent providerComp;
private Component focusedComponent;
private DockingWindowManager winMgr;
private DockingActionManager actionMgr;
private ActionToGuiMapper actionMgr;
private DropTarget dockableDropTarget;
/**

View file

@ -60,7 +60,7 @@ class DockableToolBarManager {
ComponentPlaceholder placeholder = dockableComp.getComponentWindowingPlaceholder();
DockingWindowManager winMgr =
dockableComp.getComponentWindowingPlaceholder().getNode().winMgr;
DockingActionManager actionManager = winMgr.getActionManager();
ActionToGuiMapper actionManager = winMgr.getActionManager();
menuGroupMap = actionManager.getMenuGroupMap();
MenuHandler menuHandler = actionManager.getMenuHandler();

View file

@ -24,11 +24,11 @@ import docking.action.DockingActionIf;
* {@link DockingWindowManager}. This allows the manager's interface to hide methods that
* don't make sense for public consumption.
*/
public class DockingWindowManagerActionUpdater {
public class DockingActionPackageHelper {
private DockingWindowManager windowManager;
public DockingWindowManagerActionUpdater(DockingWindowManager windowManager) {
public DockingActionPackageHelper(DockingWindowManager windowManager) {
this.windowManager = windowManager;
}

View file

@ -89,7 +89,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 DockingActionManager actionManager;
private ActionToGuiMapper actionManager;
private WeakSet<DockingContextListener> contextListeners =
WeakDataStructureFactory.createSingleThreadAccessWeakSet();
@ -140,7 +140,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
}
root = new RootNode(this, toolName, images, modal, factory);
actionManager = new DockingActionManager(this);
actionManager = new ActionToGuiMapper(this);
KeyboardFocusManager km = KeyboardFocusManager.getCurrentKeyboardFocusManager();
km.addPropertyChangeListener("permanentFocusOwner", this);
@ -161,7 +161,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
* @param enable
*/
public static void enableDiagnosticActions(boolean enable) {
DockingActionManager.enableDiagnosticActions(enable);
ActionToGuiMapper.enableDiagnosticActions(enable);
}
/**
@ -293,7 +293,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
* @param helpLocation help content location
*/
public static void setHelpLocation(JComponent c, HelpLocation helpLocation) {
DockingActionManager.setHelpLocation(c, helpLocation);
ActionToGuiMapper.setHelpLocation(c, helpLocation);
}
/**
@ -338,7 +338,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
return placeholderManager;
}
DockingActionManager getActionManager() {
ActionToGuiMapper getActionManager() {
return actionManager;
}

View file

@ -130,7 +130,7 @@ public class GlobalMenuAndToolBarManager implements DockingWindowListener {
}
private List<DockingActionIf> getActionsForWindow(WindowNode windowNode) {
DockingActionManager actionManager = windowManager.getActionManager();
ActionToGuiMapper actionManager = windowManager.getActionManager();
Collection<DockingActionIf> globalActions = actionManager.getGlobalActions();
List<DockingActionIf> actionsForWindow = new ArrayList<>(globalActions.size());
Set<Class<?>> contextTypes = windowNode.getContextTypes();

View file

@ -24,9 +24,9 @@ import ghidra.util.Msg;
import ghidra.util.ReservedKeyBindings;
public class KeyBindingAction extends DockingAction {
private final DockingActionManager dockingActionManager;
private final ActionToGuiMapper dockingActionManager;
public KeyBindingAction(DockingActionManager dockingActionManager) {
public KeyBindingAction(ActionToGuiMapper dockingActionManager) {
super("Set KeyBinding", DockingWindowManager.DOCKING_WINDOWS_OWNER);
this.dockingActionManager = dockingActionManager;
createReservedKeyBinding(ReservedKeyBindings.UPDATE_KEY_BINDINGS_KEY);

View file

@ -35,7 +35,7 @@ import resources.ResourceManager;
*/
public class KeyEntryDialog extends DialogComponentProvider {
private DockingActionManager actionManager;
private ActionToGuiMapper actionManager;
private DockingActionIf action;
private JPanel defaultPanel;
private KeyEntryTextField keyEntryField;
@ -45,7 +45,7 @@ public class KeyEntryDialog extends DialogComponentProvider {
private SimpleAttributeSet textAttrSet;
private Color bgColor;
public KeyEntryDialog(DockingActionIf action, DockingActionManager actionManager) {
public KeyEntryDialog(DockingActionIf action, ActionToGuiMapper actionManager) {
super("Set Key Binding for " + action.getName(), true);
this.actionManager = actionManager;
this.action = action;

View file

@ -336,7 +336,7 @@ public class KeyBindingUtils {
* @param tool the tool containing the actions
* @return the actions mapped by their full name (e.g., 'Name (OwnerName)')
*/
public static Map<String, DockingActionIf> getAllKeyBindingActions(DockingTool tool) {
public static Map<String, DockingActionIf> getAllKeyBindingActionsByFullName(DockingTool tool) {
Map<String, DockingActionIf> deduper = new HashMap<>();
Set<DockingActionIf> actions = tool.getAllActions();
@ -363,7 +363,8 @@ public class KeyBindingUtils {
* @param owner the action owner name
* @return the actions
*/
public static Set<DockingActionIf> getKeyBindingActions(DockingTool tool, String owner) {
public static Set<DockingActionIf> getKeyBindingActionsForOwner(DockingTool tool,
String owner) {
Map<String, DockingActionIf> deduper = new HashMap<>();
Set<DockingActionIf> actions = tool.getDockingActionsByOwnerName(owner);
@ -381,19 +382,6 @@ public class KeyBindingUtils {
return CollectionUtils.asSet(deduper.values());
}
/**
* Returns all actions that match the given owner and name
*
* @param tool the tool containing the actions
* @param owner the owner
* @param name the name
* @return the actions
*/
public static Set<DockingActionIf> getActions(DockingTool tool, String owner, String name) {
Set<DockingActionIf> actions = tool.getDockingActionsByOwnerName(owner);
return getActions(actions, owner, name);
}
/**
* Returns all actions that match the given owner and name
*
@ -421,16 +409,17 @@ public class KeyBindingUtils {
public static DockingActionIf getSharedKeyBindingAction(Set<DockingActionIf> allActions,
String sharedName) {
Set<DockingActionIf> toolActions = getActions(allActions, "Tool", sharedName);
String owner = "Tool";
for (DockingActionIf action : allActions) {
if (!(action instanceof SharedStubKeyBindingAction)) {
continue;
}
//@formatter:off
return toolActions
.stream()
.filter(action -> action instanceof SharedStubKeyBindingAction)
.findAny()
.orElse(null)
;
//@formatter:on
if (action.getOwner().equals(owner) && action.getName().equals(sharedName)) {
return action;
}
}
return null;
}
private static boolean isIgnored(DockingActionIf action) {
@ -456,8 +445,15 @@ public class KeyBindingUtils {
return new ActionAdapter(action);
}
/**
* Checks each action in the given collection against the given new action to make sure that
* they share the same default key binding.
*
* @param newAction the action to check
* @param existingActions the actions that have already been checked
*/
public static void assertSameDefaultKeyBindings(DockingActionIf newAction,
List<DockingActionIf> existingActions) {
Collection<DockingActionIf> existingActions) {
KeyBindingData newDefaultBinding = newAction.getDefaultKeyBindingData();
KeyStroke defaultKs = getKeyStroke(newDefaultBinding);
@ -471,6 +467,14 @@ public class KeyBindingUtils {
}
}
/**
* Logs a warning message for the two given actions to signal that they do not share the
* same default key binding
*
* @param newAction the new action
* @param existingAction the action that has already been validated
* @param existingDefaultKs the current validated key stroke
*/
public static void logDifferentKeyBindingsWarnigMessage(DockingActionIf newAction,
DockingActionIf existingAction, KeyStroke existingDefaultKs) {

View file

@ -18,7 +18,7 @@ package docking.actions;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import javax.swing.KeyStroke;
@ -33,14 +33,22 @@ import ghidra.util.exception.AssertException;
/**
* An class to manage actions registered with the tool
*/
public class DockingToolActionManager implements PropertyChangeListener {
public class ToolActions implements PropertyChangeListener {
private DockingWindowManager winMgr;
private DockingWindowManagerActionUpdater winMgrActionUpdater;
private DockingActionPackageHelper actionGuiHelper;
/*
Map of Maps of Sets
Owner Name ->
Action Name -> Set of Actions
*/
private Map<String, Map<String, Set<DockingActionIf>>> actionsByNameByOwner = LazyMap.lazyMap(
new HashMap<>(), () -> LazyMap.lazyMap(new HashMap<>(), () -> new HashSet<>()));
private Map<String, List<DockingActionIf>> actionMap =
LazyMap.lazyMap(new HashMap<>(), () -> new ArrayList<>());
private Map<String, SharedStubKeyBindingAction> sharedActionMap = new HashMap<>();
private ToolOptions keyBindingOptions;
private DockingTool dockingTool;
@ -51,32 +59,27 @@ public class DockingToolActionManager implements PropertyChangeListener {
* @param windowManager manager of the "Docking" arrangement of a set of components
* and actions in the tool
*/
public DockingToolActionManager(DockingTool tool, DockingWindowManager windowManager) {
public ToolActions(DockingTool tool, DockingWindowManager windowManager) {
this.dockingTool = tool;
this.winMgr = windowManager;
this.winMgrActionUpdater = new DockingWindowManagerActionUpdater(winMgr);
this.actionGuiHelper = new DockingActionPackageHelper(winMgr);
keyBindingOptions = tool.getOptions(DockingToolConstants.KEY_BINDINGS);
}
public void dispose() {
actionMap.clear();
actionsByNameByOwner.clear();
sharedActionMap.clear();
}
private void addActionToMap(DockingActionIf action) {
String name = action.getFullName();
List<DockingActionIf> actionList = actionMap.get(name);
List<DockingActionIf> list = actionMap.get(name);
if (!list.isEmpty()) {
KeyBindingUtils.assertSameDefaultKeyBindings(action, actionList);
}
actionList.add(action);
Set<DockingActionIf> actions = getActionStorage(action);
KeyBindingUtils.assertSameDefaultKeyBindings(action, actions);
actions.add(action);
}
private void removeActionFromMap(DockingActionIf action) {
String name = action.getFullName();
actionMap.get(name).remove(action);
getActionStorage(action).remove(action);
}
/**
@ -90,7 +93,7 @@ public class DockingToolActionManager implements PropertyChangeListener {
action.addPropertyChangeListener(this);
addActionToMap(action);
setKeyBindingOption(action);
winMgrActionUpdater.addLocalAction(provider, action);
actionGuiHelper.addLocalAction(provider, action);
}
/**
@ -101,7 +104,7 @@ public class DockingToolActionManager implements PropertyChangeListener {
action.addPropertyChangeListener(this);
addActionToMap(action);
setKeyBindingOption(action);
winMgrActionUpdater.addToolAction(action);
actionGuiHelper.addToolAction(action);
}
private void setKeyBindingOption(DockingActionIf action) {
@ -148,7 +151,7 @@ public class DockingToolActionManager implements PropertyChangeListener {
public synchronized void removeToolAction(DockingActionIf action) {
action.removePropertyChangeListener(this);
removeActionFromMap(action);
winMgrActionUpdater.removeToolAction(action);
actionGuiHelper.removeToolAction(action);
}
/**
@ -156,19 +159,23 @@ public class DockingToolActionManager implements PropertyChangeListener {
* @param owner owner of the actions to remove
*/
public synchronized void removeToolActions(String owner) {
Predicate<String> ownerMatches = actionOwner -> actionOwner.equals(owner);
Set<DockingActionIf> actions = getActions(ownerMatches);
for (DockingActionIf action : actions) {
removeToolAction(action);
}
//@formatter:off
Set<DockingActionIf> toRemove = actionsByNameByOwner.get(owner).values()
.stream()
.flatMap(set -> set.stream())
.collect(Collectors.toSet())
;
//@formatter:on
// must do this later to avoid concurrent modification exceptions
toRemove.forEach(action -> removeToolAction(action));
}
private void checkForAlreadyAddedAction(ComponentProvider provider, DockingActionIf action) {
String name = action.getFullName();
List<DockingActionIf> actionList = actionMap.get(name);
if (actionList.contains(action)) {
if (getActionStorage(action).contains(action)) {
throw new AssertException("Cannot add the same action more than once. Provider " +
provider.getName() + " - action: " + name);
provider.getName() + " - action: " + action.getFullName());
}
}
@ -184,46 +191,6 @@ public class DockingToolActionManager implements PropertyChangeListener {
winMgr.removeProviderAction(provider, action);
}
/**
* Get all actions that have the action name which includes the action owner's name.
*
* @param fullName full name for the action, e.g., "My Action (My Plugin)"
* @return list of actions; empty if no action exists with the given name
*/
public Set<DockingActionIf> getDockingActionsByFullActionName(String fullName) {
List<DockingActionIf> list = actionMap.get(fullName);
return new HashSet<>(list);
}
/**
* Returns a list of actions whose owner matches the given predicate.
*
* Note: Actions with the same name are assumed to be different instances of the same action.
*
* @param ownerFilter the predicate that is used to test if the owners are the same; to get
* all actions, return an 'always true' predicate
* @return a list of deduped actions.
*/
private Set<DockingActionIf> getActions(Predicate<String> ownerFilter) {
Set<DockingActionIf> result = new HashSet<>();
for (List<DockingActionIf> list : actionMap.values()) {
for (DockingActionIf action : list) {
if (ownerFilter.test(action.getOwner())) {
result.addAll(list);
}
}
}
for (DockingActionIf action : sharedActionMap.values()) {
if (ownerFilter.test(action.getOwner())) {
result.add(action);
}
}
return result;
}
/**
* Get all actions for the given owner.
* @param owner owner of the actions
@ -231,24 +198,42 @@ public class DockingToolActionManager implements PropertyChangeListener {
* action exists with the given name
*/
public synchronized Set<DockingActionIf> getActions(String owner) {
Predicate<String> ownerMatches = actionOwner -> actionOwner.equals(owner);
return getActions(ownerMatches);
Set<DockingActionIf> result = new HashSet<>();
Map<String, Set<DockingActionIf>> actionsByName = actionsByNameByOwner.get(owner);
for (Set<DockingActionIf> actions : actionsByName.values()) {
result.addAll(actions);
}
if (SharedStubKeyBindingAction.SHARED_OWNER.equals(owner)) {
result.addAll(sharedActionMap.values());
}
return result;
}
/**
* Get a list of all actions in the tool
* @return list of PluginAction objects
* Get a set of all actions in the tool
* @return the actions
*/
public synchronized Set<DockingActionIf> getAllActions() {
Predicate<String> allOwnersMatch = name -> true;
return getActions(allOwnersMatch);
Set<DockingActionIf> result = new HashSet<>();
Collection<Map<String, Set<DockingActionIf>>> maps = actionsByNameByOwner.values();
for (Map<String, Set<DockingActionIf>> actionsByName : maps) {
for (Set<DockingActionIf> actions : actionsByName.values()) {
result.addAll(actions);
}
}
result.addAll(sharedActionMap.values());
return result;
}
/**
* 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.
*
* 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);
@ -272,12 +257,16 @@ public class DockingToolActionManager implements PropertyChangeListener {
public void removeComponentActions(ComponentProvider provider) {
Iterator<DockingActionIf> iterator = winMgr.getComponentActions(provider);
while (iterator.hasNext()) {
DockingActionIf action = iterator.next();
String name = action.getFullName();
actionMap.get(name).remove(action);
removeActionFromMap(iterator.next());
}
}
private Set<DockingActionIf> getActionStorage(DockingActionIf action) {
String owner = action.getOwner();
String name = action.getName();
return actionsByNameByOwner.get(owner).get(name);
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getPropertyName().equals(DockingActionIf.KEYBINDING_DATA_PROPERTY)) {

View file

@ -442,9 +442,10 @@ public abstract class AbstractSortedTableModel<T> extends AbstractGTableModel<T>
public int compare(T t1, T t2) {
// at this point we compare the rows, since all of the sorting column values are equal
// (Warning: there is a chance that the two objects are comparable, but not on each
// other. In this case, a ClassCastException will be thrown)
if (t1 instanceof Comparable && t2 instanceof Comparable) {
// (Warning: due to comparable being specific to the class upon which it is defined,
// we have to make sure the class is the same to prevent class cast
// exceptions when the table has mixed implementations of 'T')
if (t1 instanceof Comparable && t1.getClass().equals(t2.getClass())) {
return ((Comparable) t1).compareTo(t2);
}

View file

@ -357,8 +357,8 @@ public class SharedKeyBindingDockingActionTest extends AbstractDockingTest {
//==================================================================================================
private void assertSharedStubInTool() {
DockingToolActionManager actionManager =
(DockingToolActionManager) getInstanceField("actionMgr", tool);
ToolActions actionManager =
(ToolActions) getInstanceField("actionMgr", tool);
DockingActionIf action = actionManager.getSharedStubKeyBindingAction(SHARED_NAME);
assertNotNull("Shared action stub is not in the tool", action);
}

View file

@ -21,7 +21,7 @@ import java.util.List;
import javax.swing.ImageIcon;
import docking.action.DockingActionIf;
import docking.actions.DockingToolActionManager;
import docking.actions.ToolActions;
import docking.framework.ApplicationInformationDisplayFactory;
import ghidra.framework.options.ToolOptions;
@ -37,7 +37,7 @@ public class FakeDockingTool extends AbstractDockingTool {
List<Image> windowIcons = ApplicationInformationDisplayFactory.getWindowIcons();
winMgr = new DockingWindowManager("EMPTY", windowIcons, listener, false /*isModal*/,
true /*isDockable*/, true /*hasStatus*/, null /*DropTargetFactory*/);
actionMgr = new DockingToolActionManager(this, winMgr);
actionMgr = new ToolActions(this, winMgr);
}
@Override

View file

@ -18,6 +18,7 @@ package ghidra.util;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
@ -857,18 +858,9 @@ public class StringUtilities {
return null;
}
int i = 0;
StringBuffer buffer = new StringBuffer("[ ");
for (Object o : collection) {
buffer.append(o.toString());
if (i + 1 < collection.size()) {
buffer.append(separator);
}
i++;
}
buffer.append(" ]");
return buffer.toString();
String asString =
collection.stream().map(o -> o.toString()).collect(Collectors.joining(separator));
return "[ " + asString + " ]";
}
public static String toStringWithIndent(Object o) {

View file

@ -218,7 +218,7 @@ public class PluginConfigurationModel {
return Collections.emptySet();
}
return KeyBindingUtils.getKeyBindingActions(tool, pluginDescription.getName());
return KeyBindingUtils.getKeyBindingActionsForOwner(tool, pluginDescription.getName());
}
/**

View file

@ -33,7 +33,7 @@ import org.jdom.Element;
import docking.*;
import docking.action.*;
import docking.actions.DockingToolActionManager;
import docking.actions.ToolActions;
import docking.framework.AboutDialog;
import docking.framework.ApplicationInformationDisplayFactory;
import docking.framework.SplashScreen;
@ -158,7 +158,7 @@ public abstract class PluginTool extends AbstractDockingTool
eventMgr = new EventManager(this);
serviceMgr = new ServiceManager();
installServices();
actionMgr = new DockingToolActionManager(this, winMgr);
actionMgr = new ToolActions(this, winMgr);
pluginMgr = new PluginManager(this, serviceMgr);
dialogMgr = new DialogManager(this);
initActions();

View file

@ -171,7 +171,7 @@ public class KeyBindingsPanel extends JPanel {
originalValues = new HashMap<>();
String longestName = "";
actionsByFullName = KeyBindingUtils.getAllKeyBindingActions(tool);
actionsByFullName = KeyBindingUtils.getAllKeyBindingActionsByFullName(tool);
Set<Entry<String, DockingActionIf>> entries = actionsByFullName.entrySet();
for (Entry<String, DockingActionIf> entry : entries) {

View file

@ -1,299 +0,0 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.framework.plugintool.mgr;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.*;
import javax.swing.KeyStroke;
import docking.*;
import docking.action.*;
import docking.tool.util.DockingToolConstants;
import ghidra.framework.options.OptionType;
import ghidra.framework.options.Options;
import ghidra.framework.plugintool.PluginTool;
import ghidra.util.exception.AssertException;
/**
* Helper class to manage plugin actions for the tool.
*/
public class ProjectActionManager implements PropertyChangeListener {
private DockingWindowManager winMgr;
private DockingWindowManagerActionUpdater winMgrActionUpdater;
// TODO
// TODO
// TODO does this class need the shared action concept (probably not)
// TODO
// TODO
private Map<String, List<DockingActionIf>> actionMap;
private Options keyBindingOptions;
private PluginTool tool;
/**
* Construct an ActionManager
* @param tool plugin tool using this ActionManager
* @param winMgr manager of the "Docking" arrangement
* of a set of components and actions in the tool
*/
public ProjectActionManager(PluginTool tool, DockingWindowManager winMgr) {
this.tool = tool;
this.winMgr = winMgr;
this.winMgrActionUpdater = new DockingWindowManagerActionUpdater(winMgr);
actionMap = new HashMap<>();
keyBindingOptions = tool.getOptions(DockingToolConstants.KEY_BINDINGS);
}
public void dispose() {
actionMap.clear();
}
private void addActionToMap(DockingActionIf action) {
String name = action.getFullName();
List<DockingActionIf> actionList = actionMap.get(name);
if (actionList == null) {
List<DockingActionIf> newList = new ArrayList<>();
newList.add(action);
actionMap.put(name, newList);
}
else {
actionList.add(action);
}
}
private void removeActionFromMap(DockingActionIf action) {
String name = action.getFullName();
List<DockingActionIf> actionList = actionMap.get(name);
if (actionList == null) {
return;
}
if (actionList.remove(action) && actionList.isEmpty()) {
actionMap.remove(name);
}
}
/**
* Adds the action to the tool.
* @param action the action to be added.
*/
public synchronized void addToolAction(DockingActionIf action) {
action.addPropertyChangeListener(this);
addActionToMap(action);
if (action.isKeyBindingManaged()) {
KeyStroke ks = action.getKeyBinding();
keyBindingOptions.registerOption(action.getFullName(), OptionType.KEYSTROKE_TYPE, ks,
null, null);
KeyStroke newKs = keyBindingOptions.getKeyStroke(action.getFullName(), ks);
if (ks != newKs) {
action.setUnvalidatedKeyBindingData(new KeyBindingData(newKs));
}
}
winMgrActionUpdater.addToolAction(action);
}
/**
* Removes the given action from the tool
* @param action the action to be removed.
*/
public synchronized void removeToolAction(DockingActionIf action) {
action.removePropertyChangeListener(this);
removeActionFromMap(action);
winMgrActionUpdater.removeToolAction(action);
}
/**
* Remove all actions that have the given owner.
* @param owner owner of the actions to remove
*/
public synchronized void removeToolActions(String owner) {
List<DockingActionIf> actions = getActions(owner);
for (DockingActionIf action : actions) {
removeToolAction(action);
}
}
/**
* Add 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 addLocalAction(ComponentProvider provider, DockingActionIf action) {
checkForAlreadyAddedAction(provider, action);
action.addPropertyChangeListener(this);
addActionToMap(action);
if (action.isKeyBindingManaged()) {
KeyStroke ks = action.getKeyBinding();
keyBindingOptions.registerOption(action.getFullName(), OptionType.KEYSTROKE_TYPE, ks,
null, null);
KeyStroke newKs = keyBindingOptions.getKeyStroke(action.getFullName(), ks);
if (ks != newKs) {
action.setUnvalidatedKeyBindingData(new KeyBindingData(newKs));
}
}
winMgrActionUpdater.addLocalAction(provider, action);
}
private void checkForAlreadyAddedAction(ComponentProvider provider, DockingActionIf action) {
String name = action.getFullName();
List<DockingActionIf> actionList = actionMap.get(name);
if (actionList == null) {
return;
}
if (actionList.contains(action)) {
throw new AssertException("Cannot add the same action more than once. Provider " +
provider.getName() + " - action: " + name);
}
}
/**
* 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) {
action.removePropertyChangeListener(this);
removeActionFromMap(action);
winMgr.removeProviderAction(provider, action);
}
// TODO delete me
/**
* Get all actions that have the action name which includes the action owner's name.
*
* @param fullName full name for the action, e.g., "My Action (My Plugin)"
* @return list of actions; empty if no action exists with the given name
*/
public List<DockingActionIf> getDockingActionsByFullActionName(String fullName) {
List<DockingActionIf> list = actionMap.get(fullName);
if (list == null) {
return new ArrayList<>();
}
return new ArrayList<>(list);
}
/**
* Returns a list of actions whose owner matches the given owner or all actions if the given
* owner is null.
* <p>
* This method will only return a single instance of any named action, even if multiple
* actions have been registered with the same name.
* <p>
* Note: Actions with the same name are assumed to be different instances of the same action.
*
* @param owner The of the action, or null to get all actions
* @return a list of deduped actions.
*/
private List<DockingActionIf> getUniqueActionList(String owner) {
List<DockingActionIf> matchingActionList = new ArrayList<>();
for (List<DockingActionIf> actionList : actionMap.values()) {
// we only want *one* instance of duplicate actions
DockingActionIf action = actionList.get(0);
if (owner == null || action.getOwner().equals(owner)) {
matchingActionList.add(action);
}
}
return matchingActionList;
}
/**
* Get all actions for the given owner.
* @param owner owner of the actions
* @return array of actions; zero length array is returned if no
* action exists with the given name
*/
public synchronized List<DockingActionIf> getActions(String owner) {
List<DockingActionIf> list = getUniqueActionList(owner);
return list;
}
/**
* Get a list of all actions in the tool.
* @return list of PluginAction objects
*/
public List<DockingActionIf> getAllActions() {
return getUniqueActionList(null);
}
/**
* 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 = tool.getOptions(DockingToolConstants.KEY_BINDINGS);
List<DockingActionIf> actions = getAllActions();
for (DockingActionIf action : actions) {
if (!action.isKeyBindingManaged()) {
continue;
}
KeyStroke ks = action.getKeyBinding();
KeyStroke newKs = keyBindingOptions.getKeyStroke(action.getFullName(), ks);
if (ks != newKs) {
action.setUnvalidatedKeyBindingData(new KeyBindingData(newKs));
}
}
}
/**
* Get the actions for the given provider and remove them from the
* actionMap; call the window manager to remove the provider.
* @param provider provider to be removed
*/
public void removeComponent(ComponentProvider provider) {
Iterator<DockingActionIf> iterator = winMgr.getComponentActions(provider);
while (iterator.hasNext()) {
DockingActionIf action = iterator.next();
String name = action.getFullName();
List<DockingActionIf> actionList = actionMap.get(name);
if (actionList != null && actionList.remove(action) && actionList.isEmpty()) {
actionMap.remove(name);
}
}
winMgr.removeComponent(provider);
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getPropertyName().equals(DockingActionIf.KEYBINDING_DATA_PROPERTY)) {
DockingAction action = (DockingAction) evt.getSource();
if (!action.isKeyBindingManaged()) {
tool.setConfigChanged(true);
return;
}
KeyBindingData keyBindingData = (KeyBindingData) evt.getNewValue();
KeyStroke newKeyStroke = keyBindingData.getKeyBinding();
Options opt = tool.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);
tool.setConfigChanged(true);
}
}
}
}

View file

@ -281,8 +281,8 @@ public class ToolScreenShots extends GhidraScreenShotGenerator {
tool = env.launchDefaultTool();
DockingWindowManager windowManager = tool.getWindowManager();
DockingActionManager actionMgr =
(DockingActionManager) getInstanceField("actionManager", windowManager);
ActionToGuiMapper actionMgr =
(ActionToGuiMapper) getInstanceField("actionManager", windowManager);
DockingActionIf action = getAction(tool, "FunctionPlugin", "Delete Function");
final KeyEntryDialog keyEntryDialog = new KeyEntryDialog(action, actionMgr);