getAllActions();
@@ -103,4 +103,5 @@ public interface DockingToolActions {
* @param placeholder the placeholder containing information related to the action it represents
*/
public void registerSharedActionPlaceholder(SharedDockingActionPlaceholder placeholder);
+
}
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/actions/KeyBindings.java b/Ghidra/Framework/Docking/src/main/java/docking/actions/KeyBindings.java
new file mode 100644
index 0000000000..61caa804db
--- /dev/null
+++ b/Ghidra/Framework/Docking/src/main/java/docking/actions/KeyBindings.java
@@ -0,0 +1,262 @@
+/* ###
+ * 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.*;
+import java.util.Map.Entry;
+
+import javax.swing.KeyStroke;
+
+import docking.Tool;
+import docking.action.DockingActionIf;
+import docking.action.KeyBindingData;
+import docking.tool.util.DockingToolConstants;
+import ghidra.framework.options.ToolOptions;
+import util.CollectionUtils;
+
+/**
+ * An object that maps actions to key strokes.
+ *
+ * This class knows how to load all system actions and how to load any key bindings for those
+ * actions from the tool's options. Clients can make changes to the state of this class that can
+ * then be applied to the system by calling {@link #applyChanges()}.
+ */
+public class KeyBindings {
+
+ private Tool tool;
+
+ private Map> actionsByFullName;
+ private Map> actionNamesByKeyStroke = new HashMap<>();
+ private Map keyStrokesByFullName = new HashMap<>();
+ private List uniqueActions = new ArrayList<>();
+
+ // to know what has been changed
+ private Map originalKeyStrokesByFullName = new HashMap<>();
+ private String longestActionName = "";
+
+ private ToolOptions options;
+
+ public KeyBindings(Tool tool) {
+ this.tool = tool;
+
+ options = tool.getOptions(DockingToolConstants.KEY_BINDINGS);
+
+ init();
+ }
+
+ public List getUniqueActions() {
+ return Collections.unmodifiableList(uniqueActions);
+ }
+
+ public Map getKeyStrokesByFullActionName() {
+ return Collections.unmodifiableMap(keyStrokesByFullName);
+ }
+
+ public boolean containsAction(String fullName) {
+ return actionsByFullName.containsKey(fullName);
+ }
+
+ public KeyStroke getKeyStroke(String fullName) {
+ return keyStrokesByFullName.get(fullName);
+ }
+
+ public String getActionsForKeyStrokeText(String keyStrokeText) {
+
+ StringBuffer sb = new StringBuffer();
+ List names = actionNamesByKeyStroke.get(keyStrokeText);
+ if (CollectionUtils.isBlank(names)) {
+ return sb.toString();
+ }
+
+ names.sort((n1, n2) -> {
+ return n1.compareToIgnoreCase(n2);
+ });
+
+ sb.append("Actions mapped to key " + keyStrokeText + ":\n");
+ for (int i = 0; i < names.size(); i++) {
+ sb.append(" ");
+
+ String name = names.get(i);
+ List actions = actionsByFullName.get(name);
+ DockingActionIf action = actions.get(0);
+ sb.append(action.getName());
+ sb.append(" (").append(action.getOwnerDescription()).append(')');
+ if (i < names.size() - 1) {
+ sb.append("\n");
+ }
+ }
+ return sb.toString();
+ }
+
+ public String getLongestActionName() {
+ return longestActionName;
+ }
+
+ public boolean setActionKeyStroke(String actionName, KeyStroke keyStroke) {
+ String ksName = KeyBindingUtils.parseKeyStroke(keyStroke);
+
+ // remove old keystroke for action name
+ KeyStroke oldKs = keyStrokesByFullName.get(actionName);
+ if (oldKs != null) {
+ String oldName = KeyBindingUtils.parseKeyStroke(oldKs);
+ if (oldName.equals(ksName)) {
+ return false;
+ }
+ removeFromKeyMap(oldKs, actionName);
+ }
+ addActionKeyStroke(keyStroke, actionName);
+
+ keyStrokesByFullName.put(actionName, keyStroke);
+ return true;
+ }
+
+ public boolean removeKeyStroke(String actionName) {
+ if (keyStrokesByFullName.containsKey(actionName)) {
+ KeyStroke stroke = keyStrokesByFullName.get(actionName);
+ if (stroke == null) {
+ // nothing to remove; nothing has changed
+ return false;
+ }
+
+ removeFromKeyMap(stroke, actionName);
+ keyStrokesByFullName.put(actionName, null);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Restores the tool options key bindings to the default values originally loaded when the
+ * system started.
+ */
+ public void restoreOptions() {
+
+ Set>> entries = actionsByFullName.entrySet();
+ for (Entry> entry : entries) {
+ List actions = entry.getValue();
+
+ // pick one action, they are all conceptually the same
+ DockingActionIf action = actions.get(0);
+ String actionName = entry.getKey();
+ KeyStroke currentKeyStroke = keyStrokesByFullName.get(actionName);
+ KeyBindingData defaultBinding = action.getDefaultKeyBindingData();
+ KeyStroke newKeyStroke =
+ (defaultBinding == null) ? null : defaultBinding.getKeyBinding();
+
+ updateOptions(actionName, currentKeyStroke, newKeyStroke);
+ }
+ }
+
+ /**
+ * Cancels any pending changes that have not yet been applied.
+ */
+ public void cancelChanges() {
+ Iterator iter = originalKeyStrokesByFullName.keySet().iterator();
+ while (iter.hasNext()) {
+ String actionName = iter.next();
+ KeyStroke originalKS = originalKeyStrokesByFullName.get(actionName);
+ KeyStroke modifiedKS = keyStrokesByFullName.get(actionName);
+ if (modifiedKS != null && !modifiedKS.equals(originalKS)) {
+ keyStrokesByFullName.put(actionName, originalKS);
+ }
+ }
+ }
+
+ /**
+ * Applies any pending changes.
+ */
+ public void applyChanges() {
+ Iterator iter = keyStrokesByFullName.keySet().iterator();
+ while (iter.hasNext()) {
+ String actionName = iter.next();
+ KeyStroke currentKeyStroke = keyStrokesByFullName.get(actionName);
+ KeyStroke originalKeyStroke = originalKeyStrokesByFullName.get(actionName);
+ updateOptions(actionName, originalKeyStroke, currentKeyStroke);
+ }
+ }
+
+ private void removeFromKeyMap(KeyStroke ks, String actionName) {
+ if (ks == null) {
+ return;
+ }
+ String ksName = KeyBindingUtils.parseKeyStroke(ks);
+ List list = actionNamesByKeyStroke.get(ksName);
+ if (list != null) {
+ list.remove(actionName);
+ if (list.isEmpty()) {
+ actionNamesByKeyStroke.remove(ksName);
+ }
+ }
+ }
+
+ private void updateOptions(String fullActionName, KeyStroke currentKeyStroke,
+ KeyStroke newKeyStroke) {
+
+ if (Objects.equals(currentKeyStroke, newKeyStroke)) {
+ return;
+ }
+
+ options.setKeyStroke(fullActionName, newKeyStroke);
+ originalKeyStrokesByFullName.put(fullActionName, newKeyStroke);
+ keyStrokesByFullName.put(fullActionName, newKeyStroke);
+
+ List actions = actionsByFullName.get(fullActionName);
+ for (DockingActionIf action : actions) {
+ action.setUnvalidatedKeyBindingData(new KeyBindingData(newKeyStroke));
+ }
+
+ }
+
+ private void init() {
+
+ actionsByFullName = KeyBindingUtils.getAllActionsByFullName(tool);
+ Set>> entries = actionsByFullName.entrySet();
+ for (Entry> entry : entries) {
+
+ // pick one action, they are all conceptually the same
+ List actions = entry.getValue();
+ DockingActionIf action = actions.get(0);
+ uniqueActions.add(action);
+
+ String actionName = entry.getKey();
+ KeyStroke ks = options.getKeyStroke(actionName, null);
+ keyStrokesByFullName.put(actionName, ks);
+ addActionKeyStroke(ks, actionName);
+ originalKeyStrokesByFullName.put(actionName, ks);
+
+ String shortName = action.getName();
+ if (shortName.length() > longestActionName.length()) {
+ longestActionName = shortName;
+ }
+ }
+ }
+
+ private void addActionKeyStroke(KeyStroke ks, String actionName) {
+ if (ks == null) {
+ return;
+ }
+ String ksName = KeyBindingUtils.parseKeyStroke(ks);
+ List list = actionNamesByKeyStroke.get(ksName);
+ if (list == null) {
+ list = new ArrayList<>();
+ actionNamesByKeyStroke.put(ksName, list);
+ }
+ if (!list.contains(actionName)) {
+ list.add(actionName);
+ }
+ }
+
+}
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/actions/KeyEntryDialog.java b/Ghidra/Framework/Docking/src/main/java/docking/actions/KeyEntryDialog.java
index 7e1a114377..0f72911e2d 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/actions/KeyEntryDialog.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/actions/KeyEntryDialog.java
@@ -16,42 +16,43 @@
package docking.actions;
import java.awt.*;
-import java.util.*;
-import java.util.List;
+import java.util.Objects;
import javax.swing.*;
import javax.swing.text.*;
-import docking.DialogComponentProvider;
-import docking.KeyEntryTextField;
-import docking.action.*;
+import docking.*;
+import docking.action.DockingActionIf;
+import docking.action.KeyBindingData;
import docking.tool.ToolConstants;
import docking.widgets.label.GIconLabel;
import generic.theme.GThemeDefaults.Colors.Messages;
-import generic.util.action.ReservedKeyBindings;
import ghidra.util.HelpLocation;
import resources.Icons;
/**
- * Dialog to set the key binding on an action; it is popped up when the F4 key
- * is hit.
+ * Dialog to set the key binding on an action. It is triggered by the F4 key.
*/
public class KeyEntryDialog extends DialogComponentProvider {
+ private KeyBindings keyBindings;
private ToolActions toolActions;
private DockingActionIf action;
+
private JPanel defaultPanel;
private KeyEntryTextField keyEntryField;
private JTextPane collisionPane;
private StyledDocument doc;
- private SimpleAttributeSet tabAttrSet;
private SimpleAttributeSet textAttrSet;
private Color bgColor;
- public KeyEntryDialog(DockingActionIf action, ToolActions actions) {
+ public KeyEntryDialog(Tool tool, DockingActionIf action) {
super("Set Key Binding for " + action.getName(), true);
this.action = action;
- this.toolActions = actions;
+ this.toolActions = (ToolActions) tool.getToolActions();
+
+ this.keyBindings = new KeyBindings(tool);
+
setUpAttributes();
createPanel();
KeyStroke keyBinding = action.getKeyBinding();
@@ -150,21 +151,22 @@ public class KeyEntryDialog extends DialogComponentProvider {
@Override
protected void okCallback() {
- KeyStroke newKeyStroke = keyEntryField.getKeyStroke();
- if (newKeyStroke != null && ReservedKeyBindings.isReservedKeystroke(newKeyStroke)) {
- setStatusText(keyEntryField.getText() + " is a reserved keystroke");
+ KeyStroke newKs = keyEntryField.getKeyStroke();
+ String errorMessage = toolActions.validateActionKeyBinding(action, newKs);
+ if (errorMessage != null) {
+ setStatusText(errorMessage);
return;
}
clearStatusText();
KeyStroke existingKeyStroke = action.getKeyBinding();
- if (Objects.equals(existingKeyStroke, newKeyStroke)) {
+ if (Objects.equals(existingKeyStroke, newKs)) {
setStatusText("Key binding unchanged");
return;
}
- action.setUnvalidatedKeyBindingData(new KeyBindingData(newKeyStroke));
+ action.setUnvalidatedKeyBindingData(new KeyBindingData(newKs));
close();
}
@@ -174,10 +176,6 @@ public class KeyEntryDialog extends DialogComponentProvider {
textAttrSet.addAttribute(StyleConstants.FontFamily, "Tahoma");
textAttrSet.addAttribute(StyleConstants.FontSize, Integer.valueOf(11));
textAttrSet.addAttribute(StyleConstants.Foreground, Messages.NORMAL);
-
- tabAttrSet = new SimpleAttributeSet();
- TabStop tabs = new TabStop(20, StyleConstants.ALIGN_LEFT, TabStop.LEAD_NONE);
- StyleConstants.setTabSet(tabAttrSet, new TabSet(new TabStop[] { tabs }));
}
private void updateCollisionPane(KeyStroke ks) {
@@ -193,26 +191,10 @@ public class KeyEntryDialog extends DialogComponentProvider {
return;
}
- List list = getManagedActionsForKeyStroke(ks);
- if (list.size() == 0) {
- return;
- }
-
- list.sort((a1, a2) -> {
- String s1 = a1.getName() + a1.getOwnerDescription();
- String s2 = a2.getName() + a2.getOwnerDescription();
- return s1.compareToIgnoreCase(s2);
- });
-
String ksName = KeyBindingUtils.parseKeyStroke(ks);
+ String text = keyBindings.getActionsForKeyStrokeText(ksName);
try {
- doc.insertString(0, "Actions mapped to " + ksName + "\n\n", textAttrSet);
- for (DockingActionIf a : list) {
- String collisionStr = "\t" + a.getName() + " (" + a.getOwnerDescription() + ")\n";
- int offset = doc.getLength();
- doc.insertString(offset, collisionStr, textAttrSet);
- doc.setParagraphAttributes(offset, 1, tabAttrSet, false);
- }
+ doc.insertString(0, text, textAttrSet);
collisionPane.setCaretPosition(0);
}
catch (BadLocationException e) {
@@ -220,37 +202,4 @@ public class KeyEntryDialog extends DialogComponentProvider {
}
}
-
- private List getManagedActionsForKeyStroke(KeyStroke keyStroke) {
- MultipleKeyAction multiAction = getMultipleKeyAction(keyStroke);
- if (multiAction == null) {
- return Collections.emptyList();
- }
-
- List list = multiAction.getActions();
- Map nameMap = new HashMap<>(list.size());
-
- // the list may have multiple matches for a single owner, which we do not want (see
- // SharedStubKeyBindingAction)
- for (DockingActionIf dockableAction : list) {
- if (shouldAddAction(dockableAction)) {
- // this overwrites same named actions
- nameMap.put(dockableAction.getName() + dockableAction.getOwner(), dockableAction);
- }
- }
-
- return new ArrayList<>(nameMap.values());
- }
-
- private MultipleKeyAction getMultipleKeyAction(KeyStroke ks) {
- Action keyAction = toolActions.getAction(ks);
- if (keyAction instanceof MultipleKeyAction) {
- return (MultipleKeyAction) keyAction;
- }
- return null;
- }
-
- private boolean shouldAddAction(DockingActionIf dockableAction) {
- return dockableAction.getKeyBindingType().isManaged();
- }
}
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/actions/KeyBindingAction.java b/Ghidra/Framework/Docking/src/main/java/docking/actions/SetKeyBindingAction.java
similarity index 87%
rename from Ghidra/Framework/Docking/src/main/java/docking/actions/KeyBindingAction.java
rename to Ghidra/Framework/Docking/src/main/java/docking/actions/SetKeyBindingAction.java
index 9857879a72..0321556538 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/actions/KeyBindingAction.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/actions/SetKeyBindingAction.java
@@ -17,19 +17,22 @@ package docking.actions;
import java.awt.Component;
-import docking.ActionContext;
-import docking.DockingWindowManager;
+import javax.swing.KeyStroke;
+
+import docking.*;
import docking.action.*;
import ghidra.util.Msg;
-public class KeyBindingAction extends DockingAction {
+public class SetKeyBindingAction extends DockingAction {
public static String NAME = "Set KeyBinding";
- private ToolActions toolActions;
+ private Tool tool;
- public KeyBindingAction(ToolActions toolActions) {
+ public SetKeyBindingAction(Tool tool, KeyStroke keyStroke) {
super(NAME, DockingWindowManager.DOCKING_WINDOWS_OWNER);
- this.toolActions = toolActions;
+ this.tool = tool;
+
+ setKeyBindingData(new KeyBindingData(keyStroke));
// Help actions don't have help
DockingWindowManager.getHelpService().excludeFromHelp(this);
@@ -56,7 +59,7 @@ public class KeyBindingAction extends DockingAction {
return;
}
- KeyEntryDialog d = new KeyEntryDialog(action, toolActions);
+ KeyEntryDialog d = new KeyEntryDialog(tool, action);
DockingWindowManager.showDialog(d);
}
@@ -72,6 +75,7 @@ public class KeyBindingAction extends DockingAction {
// It is not key binding managed, which means that it may be a shared key binding
String actionName = dockingAction.getName();
+ ToolActions toolActions = (ToolActions) tool.getToolActions();
DockingActionIf sharedAction = toolActions.getSharedStubKeyBindingAction(actionName);
if (sharedAction != null) {
return sharedAction;
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/actions/ToolActions.java b/Ghidra/Framework/Docking/src/main/java/docking/actions/ToolActions.java
index af10efc603..e7de843e40 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/actions/ToolActions.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/actions/ToolActions.java
@@ -15,6 +15,8 @@
*/
package docking.actions;
+import static generic.util.action.SystemKeyBindings.*;
+
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.*;
@@ -32,7 +34,6 @@ import org.apache.commons.collections4.map.LazyMap;
import docking.*;
import docking.action.*;
import docking.tool.util.DockingToolConstants;
-import generic.util.action.ReservedKeyBindings;
import ghidra.framework.options.*;
import ghidra.util.Msg;
import ghidra.util.exception.AssertException;
@@ -60,7 +61,7 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener {
private Map sharedActionMap = new HashMap<>();
private ToolOptions keyBindingOptions;
- private Tool dockingTool;
+ private Tool tool;
private KeyBindingsManager keyBindingsManager;
private OptionsChangeListener optionChangeListener = (options, optionName, oldValue,
newValue) -> updateKeyBindingsFromOptions(options, optionName, (KeyStroke) newValue);
@@ -72,45 +73,49 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener {
* @param actionToGuiHelper the class that takes actions and maps them to GUI widgets
*/
public ToolActions(Tool tool, ActionToGuiHelper actionToGuiHelper) {
- this.dockingTool = tool;
+ this.tool = tool;
this.actionGuiHelper = actionToGuiHelper;
this.keyBindingsManager = new KeyBindingsManager(tool);
this.keyBindingOptions = tool.getOptions(DockingToolConstants.KEY_BINDINGS);
this.keyBindingOptions.addOptionsChangeListener(optionChangeListener);
- createReservedKeyBindings();
+ createSystemActions();
SharedActionRegistry.installSharedActions(tool, this);
}
- private void createReservedKeyBindings() {
- KeyBindingAction keyBindingAction = new KeyBindingAction(this);
- keyBindingsManager.addReservedAction(keyBindingAction,
- ReservedKeyBindings.UPDATE_KEY_BINDINGS_KEY);
+ private void createSystemActions() {
- 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(
- new ShowContextMenuAction(ReservedKeyBindings.CONTEXT_MENU_KEY1));
- keyBindingsManager.addReservedAction(
- new ShowContextMenuAction(ReservedKeyBindings.CONTEXT_MENU_KEY2));
+ addSystemAction(new SetKeyBindingAction(tool, UPDATE_KEY_BINDINGS_KEY));
- keyBindingsManager.addReservedAction(
- new NextPreviousWindowAction(ReservedKeyBindings.FOCUS_NEXT_WINDOW_KEY, true));
- keyBindingsManager.addReservedAction(
- new NextPreviousWindowAction(ReservedKeyBindings.FOCUS_PREVIOUS_WINDOW_KEY, false));
+ addSystemAction(new HelpAction(HELP_KEY1, false));
+ addSystemAction(new HelpAction(HELP_KEY2, true));
+ addSystemAction(new HelpInfoAction(HELP_INFO_KEY));
+ addSystemAction(new ShowContextMenuAction(CONTEXT_MENU_KEY1, true));
+ addSystemAction(new ShowContextMenuAction(CONTEXT_MENU_KEY2, false));
- keyBindingsManager.addReservedAction(
- new GlobalFocusTraversalAction(ReservedKeyBindings.FOCUS_NEXT_COMPONENT_KEY, true));
- keyBindingsManager.addReservedAction(
- new GlobalFocusTraversalAction(ReservedKeyBindings.FOCUS_PREVIOUS_COMPONENT_KEY,
- false));
+ addSystemAction(new NextPreviousWindowAction(FOCUS_NEXT_WINDOW_KEY, true));
+ addSystemAction(new NextPreviousWindowAction(FOCUS_PREVIOUS_WINDOW_KEY, false));
+
+ addSystemAction(new GlobalFocusTraversalAction(FOCUS_NEXT_COMPONENT_KEY, true));
+ addSystemAction(new GlobalFocusTraversalAction(FOCUS_PREVIOUS_COMPONENT_KEY, false));
// helpful debugging actions
- keyBindingsManager.addReservedAction(new ShowFocusInfoAction());
- keyBindingsManager.addReservedAction(new ShowFocusCycleAction());
- keyBindingsManager.addReservedAction(new ComponentThemeInspectorAction());
+ addSystemAction(new ShowFocusInfoAction());
+ addSystemAction(new ShowFocusCycleAction());
+ addSystemAction(new ComponentThemeInspectorAction());
+ }
+
+ private void addSystemAction(DockingAction action) {
+
+ // Some System actions support changing the keybinding. In the future, all System actions
+ // may support this.
+ if (action.getKeyBindingType().isManaged()) {
+ KeyBindingData kbd = action.getKeyBindingData();
+ KeyStroke ks = kbd.getKeyBinding();
+ loadKeyBindingFromOptions(action, ks);
+ }
+
+ keyBindingsManager.addSystemAction(action);
}
public void dispose() {
@@ -170,7 +175,6 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener {
}
private void loadKeyBindingFromOptions(DockingActionIf action, KeyStroke ks) {
-
String description = "Keybinding for " + action.getFullName();
keyBindingOptions.registerOption(action.getFullName(), OptionType.KEYSTROKE_TYPE, ks, null,
description);
@@ -288,6 +292,8 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener {
result.addAll(sharedActionMap.values());
+ result.addAll(keyBindingsManager.getSystemActions());
+
return result;
}
@@ -311,7 +317,7 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener {
* otherwise the options will be removed because they are noted as not being used.
*/
public synchronized void restoreKeyBindings() {
- keyBindingOptions = dockingTool.getOptions(DockingToolConstants.KEY_BINDINGS);
+ keyBindingOptions = tool.getOptions(DockingToolConstants.KEY_BINDINGS);
Iterator it = getKeyBindingActionsIterator();
for (DockingActionIf action : CollectionUtils.asIterable(it)) {
@@ -406,8 +412,7 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener {
}
DockingActionIf action = (DockingActionIf) evt.getSource();
- if (!action.getKeyBindingType()
- .isManaged()) {
+ 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();
@@ -429,7 +434,7 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener {
// triggered by a user-initiated action; called by propertyChange()
private void keyBindingsChanged() {
- dockingTool.setConfigChanged(true);
+ tool.setConfigChanged(true);
actionGuiHelper.keyBindingsChanged();
}
@@ -447,6 +452,17 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener {
return null;
}
+ /**
+ * Checks whether the given key stroke can be used for the given action for restrictions such as
+ * those for System level actions.
+ * @param action the action; may be null
+ * @param ks the key stroke
+ * @return A null value if valid; a non-null error message if invalid
+ */
+ public String validateActionKeyBinding(DockingActionIf action, KeyStroke ks) {
+ return keyBindingsManager.validateActionKeyBinding(action, ks);
+ }
+
public Action getAction(KeyStroke ks) {
return keyBindingsManager.getDockingKeyAction(ks);
}
diff --git a/Ghidra/Framework/Generic/src/main/java/generic/util/action/ReservedKeyBindings.java b/Ghidra/Framework/Generic/src/main/java/generic/util/action/ReservedKeyBindings.java
deleted file mode 100644
index 410833e74b..0000000000
--- a/Ghidra/Framework/Generic/src/main/java/generic/util/action/ReservedKeyBindings.java
+++ /dev/null
@@ -1,94 +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 generic.util.action;
-
-import java.awt.Toolkit;
-import java.awt.event.InputEvent;
-import java.awt.event.KeyEvent;
-
-import javax.swing.KeyStroke;
-
-public class ReservedKeyBindings {
-
- private static final int CONTROL_KEY_MODIFIER_MASK =
- Toolkit.getDefaultToolkit()
- .getMenuShortcutKeyMaskEx();
-
- private ReservedKeyBindings() {
- // utils class
- }
-
- public static final KeyStroke HELP_KEY1 = KeyStroke.getKeyStroke(KeyEvent.VK_HELP, 0);
- public static final KeyStroke HELP_KEY2 = KeyStroke.getKeyStroke(KeyEvent.VK_F1, 0);
- public static final KeyStroke HELP_INFO_KEY =
- KeyStroke.getKeyStroke(KeyEvent.VK_F1, CONTROL_KEY_MODIFIER_MASK);
-
- public static final KeyStroke CONTEXT_MENU_KEY1 =
- KeyStroke.getKeyStroke(KeyEvent.VK_F10, InputEvent.SHIFT_DOWN_MASK);
- public static final KeyStroke CONTEXT_MENU_KEY2 =
- KeyStroke.getKeyStroke(KeyEvent.VK_CONTEXT_MENU, 0);
-
- public static final KeyStroke FOCUS_NEXT_WINDOW_KEY =
- KeyStroke.getKeyStroke(KeyEvent.VK_F3, InputEvent.CTRL_DOWN_MASK);
- public static final KeyStroke FOCUS_PREVIOUS_WINDOW_KEY =
- KeyStroke.getKeyStroke(KeyEvent.VK_F3,
- InputEvent.SHIFT_DOWN_MASK | InputEvent.CTRL_DOWN_MASK);
-
- public static final KeyStroke FOCUS_NEXT_COMPONENT_KEY =
- KeyStroke.getKeyStroke(KeyEvent.VK_TAB, InputEvent.CTRL_DOWN_MASK);
- public static final KeyStroke FOCUS_PREVIOUS_COMPONENT_KEY =
- KeyStroke.getKeyStroke(KeyEvent.VK_TAB,
- InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK);
-
- public static final KeyStroke FOCUS_INFO_KEY = KeyStroke.getKeyStroke(KeyEvent.VK_F2,
- CONTROL_KEY_MODIFIER_MASK | InputEvent.ALT_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK);
- public static final KeyStroke FOCUS_CYCLE_INFO_KEY = KeyStroke.getKeyStroke(KeyEvent.VK_F3,
- CONTROL_KEY_MODIFIER_MASK | InputEvent.ALT_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK);
-
- public static final KeyStroke UPDATE_KEY_BINDINGS_KEY =
- KeyStroke.getKeyStroke(KeyEvent.VK_F4, 0);
-
- public static final KeyStroke COMPONENT_THEME_INFO_KEY = KeyStroke.getKeyStroke(KeyEvent.VK_F9,
- CONTROL_KEY_MODIFIER_MASK | InputEvent.ALT_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK);
-
- // @formatter:off
- public static boolean isReservedKeystroke(KeyStroke keyStroke) {
- int code = keyStroke.getKeyCode();
- if (code == KeyEvent.VK_SHIFT ||
- code == KeyEvent.VK_ALT ||
- code == KeyEvent.VK_CONTROL ||
- code == KeyEvent.VK_CAPS_LOCK ||
- code == KeyEvent.VK_TAB ||
- HELP_KEY1.equals(keyStroke) ||
- HELP_KEY2.equals(keyStroke) ||
- HELP_INFO_KEY.equals(keyStroke) ||
- UPDATE_KEY_BINDINGS_KEY.equals(keyStroke) ||
- FOCUS_INFO_KEY.equals(keyStroke) ||
- FOCUS_CYCLE_INFO_KEY.equals(keyStroke) ||
- COMPONENT_THEME_INFO_KEY.equals(keyStroke) ||
- CONTEXT_MENU_KEY1.equals(keyStroke) ||
- CONTEXT_MENU_KEY2.equals(keyStroke) ||
- FOCUS_NEXT_WINDOW_KEY.equals(keyStroke) ||
- FOCUS_PREVIOUS_WINDOW_KEY.equals(keyStroke) ||
- FOCUS_NEXT_COMPONENT_KEY.equals(keyStroke) ||
- FOCUS_PREVIOUS_COMPONENT_KEY.equals(keyStroke)) {
- return true;
- }
- // @formatter:on
-
- return false;
- }
-}
diff --git a/Ghidra/Framework/Generic/src/main/java/generic/util/action/SystemKeyBindings.java b/Ghidra/Framework/Generic/src/main/java/generic/util/action/SystemKeyBindings.java
new file mode 100644
index 0000000000..4966bd33a1
--- /dev/null
+++ b/Ghidra/Framework/Generic/src/main/java/generic/util/action/SystemKeyBindings.java
@@ -0,0 +1,58 @@
+/* ###
+ * 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 generic.util.action;
+
+import static java.awt.event.InputEvent.*;
+import static java.awt.event.KeyEvent.*;
+import static javax.swing.KeyStroke.*;
+
+import java.awt.Toolkit;
+
+import javax.swing.KeyStroke;
+
+/**
+ * Default key strokes for System actions.
+ */
+public class SystemKeyBindings {
+
+ private static final int CTRL = Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx();
+ private static final int CTRL_SHIFT = CTRL | SHIFT_DOWN_MASK;
+ private static final int CTRL_ALT_SHIFT = CTRL_SHIFT | ALT_DOWN_MASK;
+
+ public static final KeyStroke HELP_KEY1 = KeyStroke.getKeyStroke(VK_HELP, 0);
+ public static final KeyStroke HELP_KEY2 = KeyStroke.getKeyStroke(VK_F1, 0);
+ public static final KeyStroke HELP_INFO_KEY = getKeyStroke(VK_F1, CTRL);
+
+ public static final KeyStroke CONTEXT_MENU_KEY1 = getKeyStroke(VK_F10, SHIFT_DOWN_MASK);
+ public static final KeyStroke CONTEXT_MENU_KEY2 = getKeyStroke(VK_CONTEXT_MENU, 0);
+
+ public static final KeyStroke FOCUS_NEXT_WINDOW_KEY = getKeyStroke(VK_F3, CTRL);
+ public static final KeyStroke FOCUS_PREVIOUS_WINDOW_KEY = getKeyStroke(VK_F3, CTRL_SHIFT);
+
+ public static final KeyStroke FOCUS_NEXT_COMPONENT_KEY = getKeyStroke(VK_TAB, CTRL);
+ public static final KeyStroke FOCUS_PREVIOUS_COMPONENT_KEY = getKeyStroke(VK_TAB, CTRL_SHIFT);
+
+ public static final KeyStroke FOCUS_INFO_KEY = getKeyStroke(VK_F2, CTRL_ALT_SHIFT);
+ public static final KeyStroke FOCUS_CYCLE_INFO_KEY = getKeyStroke(VK_F3, CTRL_ALT_SHIFT);
+
+ public static final KeyStroke UPDATE_KEY_BINDINGS_KEY = getKeyStroke(VK_F4, 0);
+
+ public static final KeyStroke COMPONENT_THEME_INFO_KEY = getKeyStroke(VK_F9, CTRL_ALT_SHIFT);
+
+ private SystemKeyBindings() {
+ // utils class
+ }
+}
diff --git a/Ghidra/Framework/Generic/src/main/resources/generic.log4jdev.xml b/Ghidra/Framework/Generic/src/main/resources/generic.log4jdev.xml
index 0f7596650f..77ad375dc1 100644
--- a/Ghidra/Framework/Generic/src/main/resources/generic.log4jdev.xml
+++ b/Ghidra/Framework/Generic/src/main/resources/generic.log4jdev.xml
@@ -53,7 +53,10 @@
-
+
+
+
+
diff --git a/Ghidra/Framework/Generic/src/main/resources/generic.log4jtest.xml b/Ghidra/Framework/Generic/src/main/resources/generic.log4jtest.xml
index d411692844..cd8562c310 100644
--- a/Ghidra/Framework/Generic/src/main/resources/generic.log4jtest.xml
+++ b/Ghidra/Framework/Generic/src/main/resources/generic.log4jtest.xml
@@ -53,6 +53,9 @@
+
+
+
diff --git a/Ghidra/Framework/Gui/src/main/java/generic/test/AbstractGuiTest.java b/Ghidra/Framework/Gui/src/main/java/generic/test/AbstractGuiTest.java
index 2e7bc58151..dac8f37167 100644
--- a/Ghidra/Framework/Gui/src/main/java/generic/test/AbstractGuiTest.java
+++ b/Ghidra/Framework/Gui/src/main/java/generic/test/AbstractGuiTest.java
@@ -660,12 +660,26 @@ public class AbstractGuiTest extends AbstractGenericTest {
* @param s the supplier
* @return the value returned by the supplier
*/
- public static T runSwing(Supplier s) {
+ public static T getSwing(Supplier s) {
AtomicReference ref = new AtomicReference<>();
runSwing(() -> ref.set(s.get()));
return ref.get();
}
+ /**
+ * Returns the value from the given {@link Supplier}, invoking the call in
+ * the Swing thread. This is useful when you may have values that are being
+ * changed on the Swing thread and you need the test thread to see the
+ * changes.
+ *
+ * @param s the supplier
+ * @return the value returned by the supplier
+ * @see #getSwing(Supplier)
+ */
+ public static T runSwing(Supplier s) {
+ return getSwing(s);
+ }
+
/**
* Run the given code snippet on the Swing thread and wait for it to finish
* @param r the runnable code snippet
diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DefaultProjectData.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DefaultProjectData.java
index 4f9fe14078..34bb352fc1 100644
--- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DefaultProjectData.java
+++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/data/DefaultProjectData.java
@@ -1260,7 +1260,6 @@ public class DefaultProjectData implements ProjectData {
public void close() {
synchronized (this) {
if (!closed) {
- Msg.debug(this, "Closing ProjectData: " + projectDir);
closed = true;
}
if (inUseCount != 0) {
@@ -1301,8 +1300,6 @@ public class DefaultProjectData implements ProjectData {
return;
}
- Msg.debug(this, "Disposing ProjectData: " + projectDir);
-
closed = true;
disposed = true;
diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/dialog/KeyBindingsPanel.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/dialog/KeyBindingsPanel.java
index fcc5185041..1917aeac76 100644
--- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/dialog/KeyBindingsPanel.java
+++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/dialog/KeyBindingsPanel.java
@@ -20,30 +20,28 @@ import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.*;
import java.util.List;
-import java.util.Map.Entry;
import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.TableColumn;
+import org.apache.commons.lang3.StringUtils;
+
import docking.DockingUtils;
import docking.KeyEntryTextField;
import docking.action.DockingActionIf;
-import docking.action.KeyBindingData;
-import docking.actions.KeyBindingUtils;
+import docking.actions.*;
import docking.tool.util.DockingToolConstants;
import docking.widgets.*;
import docking.widgets.label.GIconLabel;
import docking.widgets.table.*;
import generic.theme.Gui;
-import generic.util.action.ReservedKeyBindings;
import ghidra.framework.options.Options;
import ghidra.framework.options.ToolOptions;
import ghidra.framework.plugintool.PluginTool;
import ghidra.util.HTMLUtilities;
import ghidra.util.Swing;
-import ghidra.util.exception.AssertException;
import ghidra.util.layout.PairLayout;
import ghidra.util.layout.VerticalLayout;
import help.Help;
@@ -68,31 +66,25 @@ public class KeyBindingsPanel extends JPanel {
private JPanel infoPanel;
private MultiLineLabel collisionLabel;
private KeyBindingsTableModel tableModel;
- private ListSelectionModel selectionModel;
- private Options options;
-
- private Map> actionsByFullName;
- private Map> actionNamesByKeyStroke = new HashMap<>();
- private Map keyStrokesByFullName = new HashMap<>();
- private Map originalValues = new HashMap<>(); // to know what has been changed
- private List tableActions = new ArrayList<>();
-
private KeyEntryTextField ksField;
+ private GTableFilterPanel tableFilterPanel;
+ private EmptyBorderButton helpButton;
+
+ private KeyBindings keyBindings;
private boolean unappliedChanges;
private PluginTool tool;
private boolean firingTableDataChanged;
private PropertyChangeListener propertyChangeListener;
- private GTableFilterPanel tableFilterPanel;
- private EmptyBorderButton helpButton;
- public KeyBindingsPanel(PluginTool tool, Options options) {
+ public KeyBindingsPanel(PluginTool tool) {
this.tool = tool;
- this.options = options;
+
+ this.keyBindings = new KeyBindings(tool);
createPanelComponents();
- createActionMap();
- addListeners();
+
+ initializeTableWidth();
}
public void setOptionsPropertyChangeListener(PropertyChangeListener listener) {
@@ -105,82 +97,31 @@ public class KeyBindingsPanel extends JPanel {
}
public void apply() {
- Iterator iter = keyStrokesByFullName.keySet().iterator();
- while (iter.hasNext()) {
- String actionName = iter.next();
- KeyStroke currentKeyStroke = keyStrokesByFullName.get(actionName);
- KeyStroke originalKeyStroke = originalValues.get(actionName);
- updateOptions(actionName, originalKeyStroke, currentKeyStroke);
- }
-
+ keyBindings.applyChanges();
changesMade(false);
}
- private void updateOptions(String fullActionName, KeyStroke currentKeyStroke,
- KeyStroke newKeyStroke) {
-
- if (Objects.equals(currentKeyStroke, newKeyStroke)) {
- return;
- }
-
- options.setKeyStroke(fullActionName, newKeyStroke);
- originalValues.put(fullActionName, newKeyStroke);
- keyStrokesByFullName.put(fullActionName, newKeyStroke);
-
- List actions = actionsByFullName.get(fullActionName);
- for (DockingActionIf action : actions) {
- action.setUnvalidatedKeyBindingData(new KeyBindingData(newKeyStroke));
- }
-
- }
-
public void cancel() {
- Iterator iter = originalValues.keySet().iterator();
- while (iter.hasNext()) {
- String actionName = iter.next();
- KeyStroke originalKS = originalValues.get(actionName);
- KeyStroke modifiedKS = keyStrokesByFullName.get(actionName);
- if (modifiedKS != null && !modifiedKS.equals(originalKS)) {
- keyStrokesByFullName.put(actionName, originalKS);
- }
- }
+ keyBindings.cancelChanges();
tableModel.fireTableDataChanged();
}
public void reload() {
Swing.runLater(() -> {
- // clear the current user key stroke so that it does not appear as though the
- // user is editing while restoring
+ // clear the action to avoid the appearance of editing while restoring
actionTable.clearSelection();
restoreDefaultKeybindings();
});
}
- private void createActionMap() {
+ public String getStatusText() {
+ return statusLabel.getText();
+ }
- String longestName = "";
+ private void initializeTableWidth() {
- actionsByFullName = KeyBindingUtils.getAllActionsByFullName(tool);
- Set>> entries = actionsByFullName.entrySet();
- for (Entry> entry : entries) {
-
- // pick one action, they are all conceptually the same
- List actions = entry.getValue();
- DockingActionIf action = actions.get(0);
- tableActions.add(action);
-
- String actionName = entry.getKey();
- KeyStroke ks = options.getKeyStroke(actionName, null);
- keyStrokesByFullName.put(actionName, ks);
- addToKeyMap(ks, actionName);
- originalValues.put(actionName, ks);
-
- String shortName = action.getName();
- if (shortName.length() > longestName.length()) {
- longestName = shortName;
- }
- }
+ String longestName = keyBindings.getLongestActionName();
Font f = actionTable.getFont();
FontMetrics fm = actionTable.getFontMetrics(f);
@@ -197,7 +138,7 @@ public class KeyBindingsPanel extends JPanel {
private void createPanelComponents() {
setLayout(new BorderLayout(10, 10));
- tableModel = new KeyBindingsTableModel();
+ tableModel = new KeyBindingsTableModel(new ArrayList<>(keyBindings.getUniqueActions()));
actionTable = new GTable(tableModel);
JScrollPane sp = new JScrollPane(actionTable);
@@ -225,6 +166,8 @@ public class KeyBindingsPanel extends JPanel {
add(centerPanel, BorderLayout.CENTER);
add(statusPanel, BorderLayout.SOUTH);
+
+ actionTable.getSelectionModel().addListSelectionListener(new TableSelectionListener());
}
private JPanel createStatusPanel(JPanel keyPanel) {
@@ -264,7 +207,7 @@ public class KeyBindingsPanel extends JPanel {
}
private JPanel createKeyEntryPanel() {
- ksField = new KeyEntryTextField(20, keyStroke -> processKeyStrokeEntry(keyStroke));
+ ksField = new KeyEntryTextField(20, keyStroke -> keyStrokeChanged(keyStroke));
// this is the lower panel that holds the key entry text field
JPanel p = new JPanel(new FlowLayout(FlowLayout.LEFT));
@@ -326,10 +269,9 @@ public class KeyBindingsPanel extends JPanel {
// give Swing a chance to repaint
Swing.runLater(() -> {
- // clear the current user key stroke so that it does not appear as though the
- // user is editing while importing
+ // clear the action to avoid the appearance of editing while restoring
actionTable.clearSelection();
- processKeyBindingsFromOptions(KeyBindingUtils.importKeyBindings());
+ loadKeyBindingsFromImportedOptions(KeyBindingUtils.importKeyBindings());
});
});
@@ -338,7 +280,7 @@ public class KeyBindingsPanel extends JPanel {
exportButton.addActionListener(event -> {
// prompt user to apply changes before exporting
- boolean continueExport = showExportPrompt();
+ boolean continueExport = showApplyPrompt();
if (!continueExport) {
return;
@@ -358,7 +300,7 @@ public class KeyBindingsPanel extends JPanel {
return containerPanel;
}
- private boolean showExportPrompt() {
+ private boolean showApplyPrompt() {
boolean continueOperation = true;
if (unappliedChanges) {
int userChoice = OptionDialog.showYesNoCancelDialog(KeyBindingsPanel.this,
@@ -390,14 +332,11 @@ public class KeyBindingsPanel extends JPanel {
private Map createActionNameToKeyStrokeMap(Options keyBindingOptions) {
Map localActionMap = new HashMap<>();
-
List optionNames = keyBindingOptions.getOptionNames();
-
- for (String element : optionNames) {
- KeyStroke newKeyStroke = keyBindingOptions.getKeyStroke(element, null);
- localActionMap.put(element, newKeyStroke);
+ for (String name : optionNames) {
+ KeyStroke newKeyStroke = keyBindingOptions.getKeyStroke(name, null);
+ localActionMap.put(name, newKeyStroke);
}
-
return localActionMap;
}
@@ -415,52 +354,12 @@ public class KeyBindingsPanel extends JPanel {
}
private void restoreDefaultKeybindings() {
- Iterator iter = keyStrokesByFullName.keySet().iterator();
- while (iter.hasNext()) {
- String actionName = iter.next();
- List actions = actionsByFullName.get(actionName);
- if (actions.isEmpty()) {
- throw new AssertException("No actions defined for " + actionName);
- }
-
- // pick one action, they are all conceptually the same
- DockingActionIf action = actions.get(0);
- KeyStroke currentKeyStroke = keyStrokesByFullName.get(actionName);
- KeyBindingData defaultBinding = action.getDefaultKeyBindingData();
- KeyStroke newKeyStroke =
- (defaultBinding == null) ? null : defaultBinding.getKeyBinding();
-
- updateOptions(actionName, currentKeyStroke, newKeyStroke);
- }
+ keyBindings.restoreOptions();
// let the table know that changes may have been made
tableModel.fireTableDataChanged();
}
- private void addListeners() {
- selectionModel = actionTable.getSelectionModel();
- selectionModel.addListSelectionListener(new TableSelectionListener());
- }
-
- private boolean checkAction(String actionName, KeyStroke keyStroke) {
- String ksName = KeyEntryTextField.parseKeyStroke(keyStroke);
-
- // remove old keystroke for action name
- KeyStroke oldKs = keyStrokesByFullName.get(actionName);
- if (oldKs != null) {
- String oldName = KeyEntryTextField.parseKeyStroke(oldKs);
- if (oldName.equals(ksName)) {
- return false;
- }
- removeFromKeyMap(oldKs, actionName);
- }
- addToKeyMap(keyStroke, actionName);
-
- keyStrokesByFullName.put(actionName, keyStroke);
- changesMade(true);
- return true;
- }
-
// signals that there are unapplied changes
private void changesMade(boolean changes) {
propertyChangeListener.propertyChange(
@@ -469,12 +368,11 @@ public class KeyBindingsPanel extends JPanel {
}
private DockingActionIf getSelectedAction() {
- if (selectionModel.isSelectionEmpty()) {
+ if (actionTable.getSelectedRowCount() == 0) {
return null;
}
int selectedRow = actionTable.getSelectedRow();
- int modelRow = tableFilterPanel.getModelRow(selectedRow);
- return tableActions.get(modelRow);
+ return tableFilterPanel.getRowObject(selectedRow);
}
private String getSelectedActionName() {
@@ -485,62 +383,20 @@ public class KeyBindingsPanel extends JPanel {
return action.getFullName();
}
- private void addToKeyMap(KeyStroke ks, String actionName) {
- if (ks == null) {
- return;
- }
- String ksName = KeyEntryTextField.parseKeyStroke(ks);
- List list = actionNamesByKeyStroke.get(ksName);
- if (list == null) {
- list = new ArrayList<>();
- actionNamesByKeyStroke.put(ksName, list);
- }
- if (!list.contains(actionName)) {
- list.add(actionName);
- }
- }
-
- private void removeFromKeyMap(KeyStroke ks, String actionName) {
- if (ks == null) {
- return;
- }
- String ksName = KeyEntryTextField.parseKeyStroke(ks);
- List list = actionNamesByKeyStroke.get(ksName);
- if (list != null) {
- list.remove(actionName);
- if (list.isEmpty()) {
- actionNamesByKeyStroke.remove(ksName);
- }
- }
- }
-
private void showActionsMappedToKeyStroke(String ksName) {
- List list = actionNamesByKeyStroke.get(ksName);
- if (list == null) {
- return;
- }
- if (list.size() > 0) {
- StringBuffer sb = new StringBuffer();
- sb.append("Actions mapped to key " + ksName + ":\n");
- for (int i = 0; i < list.size(); i++) {
- sb.append(" ");
- sb.append(list.get(i));
- if (i < list.size() - 1) {
- sb.append("\n");
- }
- }
- updateInfoPanel(sb.toString());
- }
- else {
- clearInfoPanel();
+
+ String text = keyBindings.getActionsForKeyStrokeText(ksName);
+ if (StringUtils.isBlank(text)) {
+ text = " ";
}
+ updateCollisionPanel(text);
}
private void clearInfoPanel() {
- updateInfoPanel(" ");
+ updateCollisionPanel(" ");
}
- private void updateInfoPanel(String text) {
+ private void updateCollisionPanel(String text) {
infoPanel.removeAll();
infoPanel.repaint();
collisionLabel = new MultiLineLabel(text);
@@ -550,111 +406,94 @@ public class KeyBindingsPanel extends JPanel {
validate();
}
- private void processKeyBindingsFromOptions(Options keyBindingOptions) {
+ private void loadKeyBindingsFromImportedOptions(Options keyBindingOptions) {
if (keyBindingOptions == null) {
return;
}
- Map keyBindingsMap = createActionNameToKeyStrokeMap(keyBindingOptions);
- if (keyBindingsMap == null) {
+ Map keyStrokesByActionName =
+ createActionNameToKeyStrokeMap(keyBindingOptions);
+ if (keyStrokesByActionName == null) {
return;
}
boolean changes = false;
// add each new key stroke mapping
- Iterator iterator = keyBindingsMap.keySet().iterator();
- while (iterator.hasNext()) {
+ for (String name : keyStrokesByActionName.keySet()) {
- String name = iterator.next();
- KeyStroke keyStroke = keyBindingsMap.get(name);
+ KeyStroke keyStroke = keyStrokesByActionName.get(name);
keyStroke = KeyBindingUtils.validateKeyStroke(keyStroke);
- // prevent non-existing keybindings from being added to Ghidra (this can happen
- // when actions exist in the imported bindings, but have been removed from
- // Ghidra
- if (!keyStrokesByFullName.containsKey(name)) {
+ // prevent non-existing keybindings from being added (this can happen when actions exist
+ // in the imported bindings, but have been removed from the tool
+ if (!keyBindings.containsAction(name)) {
continue;
}
// check to see if the key stroke results in a change and
// record that value
- changes |= processKeyStroke(name, keyStroke);
+ changes |= setActionKeyStroke(name, keyStroke);
}
if (changes) {
changesMade(true);
+ tableModel.fireTableDataChanged();
}
}
/**
* Processes KeyStroke entry from the text field.
*/
- private void processKeyStrokeEntry(KeyStroke ks) {
+ private void keyStrokeChanged(KeyStroke ks) {
clearInfoPanel();
- // An action must be selected
- if (selectionModel.isSelectionEmpty()) {
+ DockingActionIf action = getSelectedAction();
+ if (action == null) {
statusLabel.setText("No action is selected.");
return;
}
- if (ks != null && ReservedKeyBindings.isReservedKeystroke(ks)) {
- statusLabel.setText(KeyEntryTextField.parseKeyStroke(ks) + " is a reserved keystroke");
+ ToolActions toolActions = (ToolActions) tool.getToolActions();
+ String errorMessage = toolActions.validateActionKeyBinding(action, ks);
+ if (errorMessage != null) {
+ statusLabel.setText(errorMessage);
ksField.clearField();
return;
}
String selectedActionName = getSelectedActionName();
if (selectedActionName != null) {
- if (processKeyStroke(selectedActionName, ks)) {
- String keyStrokeText = KeyEntryTextField.parseKeyStroke(ks);
+ if (setActionKeyStroke(selectedActionName, ks)) {
+ String keyStrokeText = KeyBindingUtils.parseKeyStroke(ks);
showActionsMappedToKeyStroke(keyStrokeText);
tableModel.fireTableDataChanged();
+ changesMade(true);
}
}
}
// returns true if the key stroke is a new value
- private boolean processKeyStroke(String actionName, KeyStroke keyStroke) {
- // Clear entry if enter or backspace
- if (keyStroke == null) {
- removeKeystroke(actionName);
- }
- else {
- char keyChar = keyStroke.getKeyChar();
- if (Character.isWhitespace(keyChar) ||
- Character.getType(keyChar) == Character.DIRECTIONALITY_LEFT_TO_RIGHT_OVERRIDE) {
- removeKeystroke(actionName);
- }
- else {
- // check the action to see if is different than the current value
- return checkAction(actionName, keyStroke);
- }
+ private boolean setActionKeyStroke(String actionName, KeyStroke keyStroke) {
+ if (!isValidKeyStroke(keyStroke)) {
+ ksField.setText("");
+ return keyBindings.removeKeyStroke(actionName);
}
- return false;
+ return keyBindings.setActionKeyStroke(actionName, keyStroke);
}
- private void removeKeystroke(String selectedActionName) {
- ksField.setText("");
-
- if (keyStrokesByFullName.containsKey(selectedActionName)) {
- KeyStroke stroke = keyStrokesByFullName.get(selectedActionName);
- if (stroke == null) {
- // nothing to remove; nothing has changed
- return;
- }
-
- removeFromKeyMap(stroke, selectedActionName);
- keyStrokesByFullName.put(selectedActionName, null);
- tableModel.fireTableDataChanged();
- changesMade(true);
+ private boolean isValidKeyStroke(KeyStroke ks) {
+ if (ks == null) {
+ return false;
}
+ char keyChar = ks.getKeyChar();
+ return !Character.isWhitespace(keyChar) &&
+ Character.getType(keyChar) != Character.DIRECTIONALITY_LEFT_TO_RIGHT_OVERRIDE;
}
Map getKeyStrokeMap() {
- return keyStrokesByFullName;
+ return keyBindings.getKeyStrokesByFullActionName();
}
//==================================================================================================
@@ -678,12 +517,12 @@ public class KeyBindingsPanel extends JPanel {
}
helpButton.setEnabled(true);
- KeyStroke ks = keyStrokesByFullName.get(fullActionName);
+ KeyStroke ks = keyBindings.getKeyStroke(fullActionName);
String ksName = "";
clearInfoPanel();
if (ks != null) {
- ksName = KeyEntryTextField.parseKeyStroke(ks);
+ ksName = KeyBindingUtils.parseKeyStroke(ks);
showActionsMappedToKeyStroke(ksName);
}
@@ -693,9 +532,7 @@ public class KeyBindingsPanel extends JPanel {
statusLabel.setPreferredSize(
new Dimension(statusLabel.getPreferredSize().width, STATUS_LABEL_HEIGHT));
- // pick one action, they are all conceptually the same
- List actions = actionsByFullName.get(fullActionName);
- DockingActionIf action = actions.get(0);
+ DockingActionIf action = getSelectedAction();
String description = action.getDescription();
if (description == null || description.trim().isEmpty()) {
description = action.getName();
@@ -709,8 +546,11 @@ public class KeyBindingsPanel extends JPanel {
private final String[] columnNames =
{ "Action Name", "KeyBinding", "Plugin Name" };
- KeyBindingsTableModel() {
+ private List actions;
+
+ KeyBindingsTableModel(List actions) {
super(0);
+ this.actions = actions;
}
@Override
@@ -725,9 +565,9 @@ public class KeyBindingsPanel extends JPanel {
case ACTION_NAME:
return action.getName();
case KEY_BINDING:
- KeyStroke ks = keyStrokesByFullName.get(action.getFullName());
+ KeyStroke ks = keyBindings.getKeyStroke(action.getFullName());
if (ks != null) {
- return KeyEntryTextField.parseKeyStroke(ks);
+ return KeyBindingUtils.parseKeyStroke(ks);
}
return "";
case PLUGIN_NAME:
@@ -738,7 +578,7 @@ public class KeyBindingsPanel extends JPanel {
@Override
public List getModelData() {
- return tableActions;
+ return actions;
}
@Override
@@ -758,7 +598,7 @@ public class KeyBindingsPanel extends JPanel {
@Override
public int getRowCount() {
- return tableActions.size();
+ return actions.size();
}
@Override
diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/mgr/OptionsManager.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/mgr/OptionsManager.java
index 2ddef1377a..d9dbe5906c 100644
--- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/mgr/OptionsManager.java
+++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/mgr/OptionsManager.java
@@ -271,7 +271,7 @@ public class OptionsManager implements OptionsService, OptionsChangeListener {
private KeyBindingsPanel panel;
KeyBindingOptionsEditor() {
- panel = new KeyBindingsPanel(tool, getOptions(DockingToolConstants.KEY_BINDINGS));
+ panel = new KeyBindingsPanel(tool);
}
@Override
diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/project/tool/GhidraToolTemplate.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/project/tool/GhidraToolTemplate.java
index 0e63ccb3e1..58739d858a 100644
--- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/project/tool/GhidraToolTemplate.java
+++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/project/tool/GhidraToolTemplate.java
@@ -95,19 +95,11 @@ public class GhidraToolTemplate implements ToolTemplate {
return iconURL;
}
- /**
- * Returns a hash code value for the object. This method is
- * supported for the benefit of hashtables such as those provided by
- * java.util.Hashtable
.
- */
@Override
public int hashCode() {
return getName().hashCode();
}
- /**
- * Indicates whether some other object is "equal to" this one.
- */
@Override
public boolean equals(Object obj) {
if (obj == null) {
@@ -129,13 +121,6 @@ public class GhidraToolTemplate implements ToolTemplate {
return getName().equals(otherTemplate.getName());
}
- /**
- * Returns a string representation of the object. In general, the
- * toString
method returns a string that
- * "textually represents" this object. The result should
- * be a concise but informative representation that is easy for a
- * person to read.
- */
@Override
public String toString() {
return getName() + " - " + path;
@@ -154,7 +139,7 @@ public class GhidraToolTemplate implements ToolTemplate {
catch (ClassNotFoundException e) {
Msg.warn(this, "Tool supported content class not found: " + className);
}
- catch (Exception exc) {//TODO
+ catch (Exception exc) {
Msg.error(this, "Unexpected Exception: " + exc.getMessage(), exc);
}
}
diff --git a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/ToolScreenShots.java b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/ToolScreenShots.java
index 1165e9de6c..a6fc5d602b 100644
--- a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/ToolScreenShots.java
+++ b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/ToolScreenShots.java
@@ -31,7 +31,6 @@ import docking.DialogComponentProvider;
import docking.StatusBar;
import docking.action.DockingActionIf;
import docking.actions.KeyEntryDialog;
-import docking.actions.ToolActions;
import docking.options.OptionsService;
import docking.tool.ToolConstants;
import docking.widgets.OptionDialog;
@@ -288,10 +287,8 @@ public class ToolScreenShots extends GhidraScreenShotGenerator {
public void testSetKeyBindings() {
tool = env.launchDefaultTool();
- ToolActions toolActions = (ToolActions) getInstanceField("toolActions", tool);
-
DockingActionIf action = getAction(tool, "FunctionPlugin", "Delete Function");
- final KeyEntryDialog keyEntryDialog = new KeyEntryDialog(action, toolActions);
+ final KeyEntryDialog keyEntryDialog = new KeyEntryDialog(tool, action);
runSwing(() -> tool.showDialog(keyEntryDialog), false);
captureDialog();