mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-03 17:59:46 +02:00
GP-4317 - Removing the 'reserved' concept
This commit is contained in:
parent
52e6360d96
commit
e44daf55aa
40 changed files with 1143 additions and 834 deletions
|
@ -17,7 +17,8 @@
|
||||||
//@category Examples.Bundle
|
//@category Examples.Bundle
|
||||||
|
|
||||||
import docking.ActionContext;
|
import docking.ActionContext;
|
||||||
import docking.action.*;
|
import docking.action.DockingAction;
|
||||||
|
import docking.action.ToolBarData;
|
||||||
import ghidra.app.script.GhidraScript;
|
import ghidra.app.script.GhidraScript;
|
||||||
import ghidra.app.services.CodeViewerService;
|
import ghidra.app.services.CodeViewerService;
|
||||||
import ghidra.app.services.ConsoleService;
|
import ghidra.app.services.ConsoleService;
|
||||||
|
@ -40,7 +41,7 @@ public class ActivatorExampleScript extends GhidraScript {
|
||||||
println("The activator will remove the action if this bundle is deactivated,");
|
println("The activator will remove the action if this bundle is deactivated,");
|
||||||
println(" e.g. if this script is modified and the bundle needs to be reloaded.");
|
println(" e.g. if this script is modified and the bundle needs to be reloaded.");
|
||||||
|
|
||||||
DockingAction action = new DockingAction("Added by script!!", null, false) {
|
DockingAction action = new DockingAction("Added by script!!", null) {
|
||||||
@Override
|
@Override
|
||||||
public void actionPerformed(ActionContext context) {
|
public void actionPerformed(ActionContext context) {
|
||||||
ConsoleService console = tool.getService(ConsoleService.class);
|
ConsoleService console = tool.getService(ConsoleService.class);
|
||||||
|
|
|
@ -59,9 +59,12 @@
|
||||||
the tool options. For example, the following keys cannot be changed:</P>
|
the tool options. For example, the following keys cannot be changed:</P>
|
||||||
|
|
||||||
<UL>
|
<UL>
|
||||||
<LI><TT><B>F1, Help, Ctrl-F1, F4</B></TT> (this bindings are reserved and cannot be used
|
<LI>System Action Keybindings - System action default key bindings can be changed, with
|
||||||
when assigning key bindings to actions)</LI>
|
added restrictions: 1) The binding for a System action cannot be used by any other action,
|
||||||
|
2) A key stroke bound to a System action cannot be used by another action until that
|
||||||
|
key stroke is cleared from the System action. The UI will show a message when these
|
||||||
|
restrictions are triggered.
|
||||||
|
</LI>
|
||||||
<LI>Menu Navigation Actions: <TT><B>1-9, Page Up/Down, End, Home</B></TT> (these key
|
<LI>Menu Navigation Actions: <TT><B>1-9, Page Up/Down, End, Home</B></TT> (these key
|
||||||
bindings are usable with a menu or popup menu open and are otherwise available for
|
bindings are usable with a menu or popup menu open and are otherwise available for
|
||||||
assignment to key bindings).</LI>
|
assignment to key bindings).</LI>
|
||||||
|
|
|
@ -21,24 +21,28 @@ import java.awt.Component;
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
|
|
||||||
import docking.*;
|
import docking.*;
|
||||||
|
import docking.actions.KeyBindingUtils;
|
||||||
|
import docking.actions.ToolActions;
|
||||||
import docking.widgets.label.GLabel;
|
import docking.widgets.label.GLabel;
|
||||||
import generic.util.action.ReservedKeyBindings;
|
|
||||||
import ghidra.framework.plugintool.Plugin;
|
import ghidra.framework.plugintool.Plugin;
|
||||||
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
import ghidra.util.HelpLocation;
|
import ghidra.util.HelpLocation;
|
||||||
|
|
||||||
class KeyBindingInputDialog extends DialogComponentProvider implements KeyEntryListener {
|
class KeyBindingInputDialog extends DialogComponentProvider implements KeyEntryListener {
|
||||||
private KeyEntryTextField kbField;
|
private KeyEntryTextField kbField;
|
||||||
private KeyStroke ks;
|
private KeyStroke ks;
|
||||||
private boolean isCancelled;
|
private boolean isCancelled;
|
||||||
|
private Plugin plugin;
|
||||||
|
|
||||||
KeyBindingInputDialog(Component parent, String scriptName, KeyStroke currentKeyStroke,
|
KeyBindingInputDialog(Component parent, String scriptName, KeyStroke currentKeyStroke,
|
||||||
Plugin plugin, HelpLocation help) {
|
Plugin plugin, HelpLocation help) {
|
||||||
super("Assign Script Key Binding", true, true, true, false);
|
super("Assign Script Key Binding", true, true, true, false);
|
||||||
|
this.plugin = plugin;
|
||||||
|
|
||||||
kbField = new KeyEntryTextField(20, this);
|
kbField = new KeyEntryTextField(20, this);
|
||||||
kbField.setName("KEY_BINDING");
|
kbField.setName("KEY_BINDING");
|
||||||
kbField.setText(
|
kbField.setText(
|
||||||
currentKeyStroke == null ? "" : KeyEntryTextField.parseKeyStroke(currentKeyStroke));
|
currentKeyStroke == null ? "" : KeyBindingUtils.parseKeyStroke(currentKeyStroke));
|
||||||
|
|
||||||
JPanel panel = new JPanel(new BorderLayout(10, 10));
|
JPanel panel = new JPanel(new BorderLayout(10, 10));
|
||||||
panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
|
panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
|
||||||
|
@ -55,8 +59,11 @@ class KeyBindingInputDialog extends DialogComponentProvider implements KeyEntryL
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void okCallback() {
|
protected void okCallback() {
|
||||||
if (ks != null && ReservedKeyBindings.isReservedKeystroke(ks)) {
|
PluginTool tool = plugin.getTool();
|
||||||
setStatusText(kbField.getText() + " is a reserved keystroke");
|
ToolActions toolActions = (ToolActions) tool.getToolActions();
|
||||||
|
String errorMessage = toolActions.validateActionKeyBinding(null, ks);
|
||||||
|
if (errorMessage != null) {
|
||||||
|
setStatusText(errorMessage);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -47,7 +47,7 @@ public class ComponentInfoPlugin extends Plugin {
|
||||||
//@formatter:off
|
//@formatter:off
|
||||||
DockingAction action =
|
DockingAction action =
|
||||||
new ActionBuilder("Component Display", getName())
|
new ActionBuilder("Component Display", getName())
|
||||||
.menuPath("Help", "Show Diagnostic Component Information")
|
.menuPath("Help", "Component Diagnostics")
|
||||||
.onAction(e -> showComponentDialog())
|
.onAction(e -> showComponentDialog())
|
||||||
.buildAndInstall(tool);
|
.buildAndInstall(tool);
|
||||||
//@formatter:on
|
//@formatter:on
|
||||||
|
|
|
@ -235,7 +235,7 @@ public class KeyBindingUtilsTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
debug("a");
|
debug("a");
|
||||||
|
|
||||||
// get current options
|
// get current options
|
||||||
ToolOptions toolKeyBindingOptions = (ToolOptions) getInstanceField("options", panel);
|
ToolOptions toolKeyBindingOptions = tool.getOptions(DockingToolConstants.KEY_BINDINGS);
|
||||||
|
|
||||||
debug("b");
|
debug("b");
|
||||||
|
|
||||||
|
@ -289,7 +289,8 @@ public class KeyBindingUtilsTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
|
|
||||||
debug("i");
|
debug("i");
|
||||||
|
|
||||||
ToolOptions newlyLoadedDefaultOptions = (ToolOptions) getInstanceField("options", panel);
|
ToolOptions newlyLoadedDefaultOptions = tool.getOptions(DockingToolConstants.KEY_BINDINGS);
|
||||||
|
|
||||||
assertOptionsMatch(
|
assertOptionsMatch(
|
||||||
"The options from the first tool instance have changed " +
|
"The options from the first tool instance have changed " +
|
||||||
"in the second tool instance even though the testing changes were not applied.",
|
"in the second tool instance even though the testing changes were not applied.",
|
||||||
|
@ -328,7 +329,7 @@ public class KeyBindingUtilsTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
|
|
||||||
setKeyBindingsUpDialog(tool);
|
setKeyBindingsUpDialog(tool);
|
||||||
|
|
||||||
newlyLoadedDefaultOptions = (ToolOptions) getInstanceField("options", panel);
|
newlyLoadedDefaultOptions = tool.getOptions(DockingToolConstants.KEY_BINDINGS);
|
||||||
assertOptionsDontMatch(
|
assertOptionsDontMatch(
|
||||||
"The options are the same after making changes, applying, closing and reloading.",
|
"The options are the same after making changes, applying, closing and reloading.",
|
||||||
originalOptions, newlyLoadedDefaultOptions);
|
originalOptions, newlyLoadedDefaultOptions);
|
||||||
|
@ -364,7 +365,7 @@ public class KeyBindingUtilsTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
assertEquals(newBinding, restoredBinding);
|
assertEquals(newBinding, restoredBinding);
|
||||||
|
|
||||||
setKeyBindingsUpDialog(tool);
|
setKeyBindingsUpDialog(tool);
|
||||||
ToolOptions options = (ToolOptions) getInstanceField("options", panel);
|
ToolOptions options = tool.getOptions(DockingToolConstants.KEY_BINDINGS);
|
||||||
KeyStroke optionBinding = options.getKeyStroke(action.getFullName(), null);
|
KeyStroke optionBinding = options.getKeyStroke(action.getFullName(), null);
|
||||||
assertEquals(appliedBinding, optionBinding);
|
assertEquals(appliedBinding, optionBinding);
|
||||||
|
|
||||||
|
@ -457,7 +458,7 @@ public class KeyBindingUtilsTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setSelectionPath(final GTree tree, final TreePath path) throws Exception {
|
private void setSelectionPath(final GTree tree, final TreePath path) throws Exception {
|
||||||
SwingUtilities.invokeAndWait(() -> tree.setSelectionPath(path));
|
runSwing(() -> tree.setSelectionPath(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
private GTreeNode getGTreeNode(GTreeNode parent, String nodeName) throws Exception {
|
private GTreeNode getGTreeNode(GTreeNode parent, String nodeName) throws Exception {
|
||||||
|
@ -520,7 +521,7 @@ public class KeyBindingUtilsTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
if (actionName.equals(model.getValueAt(i, 0)) &&
|
if (actionName.equals(model.getValueAt(i, 0)) &&
|
||||||
owner.equals(model.getValueAt(i, 2))) {
|
owner.equals(model.getValueAt(i, 2))) {
|
||||||
final int idx = i;
|
final int idx = i;
|
||||||
SwingUtilities.invokeAndWait(() -> {
|
runSwing(() -> {
|
||||||
table.setRowSelectionInterval(idx, idx);
|
table.setRowSelectionInterval(idx, idx);
|
||||||
Rectangle rect = table.getCellRect(idx, idx, true);
|
Rectangle rect = table.getCellRect(idx, idx, true);
|
||||||
table.scrollRectToVisible(rect);
|
table.scrollRectToVisible(rect);
|
||||||
|
|
|
@ -20,6 +20,7 @@ import static org.junit.Assert.*;
|
||||||
import java.awt.Rectangle;
|
import java.awt.Rectangle;
|
||||||
import java.awt.event.InputEvent;
|
import java.awt.event.InputEvent;
|
||||||
import java.awt.event.KeyEvent;
|
import java.awt.event.KeyEvent;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
|
@ -28,16 +29,17 @@ import javax.swing.table.*;
|
||||||
import org.junit.*;
|
import org.junit.*;
|
||||||
|
|
||||||
import docking.DockingWindowManager;
|
import docking.DockingWindowManager;
|
||||||
import docking.KeyEntryTextField;
|
import docking.action.*;
|
||||||
import docking.action.DockingActionIf;
|
import docking.actions.KeyBindingUtils;
|
||||||
import docking.tool.util.DockingToolConstants;
|
import docking.actions.ToolActions;
|
||||||
import docking.widgets.MultiLineLabel;
|
import docking.widgets.MultiLineLabel;
|
||||||
import generic.test.TestUtils;
|
import generic.test.TestUtils;
|
||||||
|
import generic.util.action.SystemKeyBindings;
|
||||||
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
|
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
|
||||||
|
import ghidra.app.plugin.core.navigation.GoToAddressLabelPlugin;
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
||||||
import ghidra.test.TestEnv;
|
import ghidra.test.TestEnv;
|
||||||
import ghidra.util.Msg;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for key bindings option panel
|
* Tests for key bindings option panel
|
||||||
|
@ -62,6 +64,7 @@ public class KeyBindingsTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
tool = env.getTool();
|
tool = env.getTool();
|
||||||
|
|
||||||
tool.addPlugin(CodeBrowserPlugin.class.getName());
|
tool.addPlugin(CodeBrowserPlugin.class.getName());
|
||||||
|
tool.addPlugin(GoToAddressLabelPlugin.class.getName());
|
||||||
|
|
||||||
env.showTool();
|
env.showTool();
|
||||||
|
|
||||||
|
@ -123,14 +126,6 @@ public class KeyBindingsTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean ignoreAction(DockingActionIf action) {
|
|
||||||
if (!action.getKeyBindingType().isManaged()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return action.getFullName().contains("Table Data");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEditKeyBinding() throws Exception {
|
public void testEditKeyBinding() throws Exception {
|
||||||
// find action that has a keystroke assigned
|
// find action that has a keystroke assigned
|
||||||
|
@ -139,7 +134,7 @@ public class KeyBindingsTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
|
|
||||||
selectRowForAction(action);
|
selectRowForAction(action);
|
||||||
triggerText(keyField, "z");
|
triggerText(keyField, "z");
|
||||||
assertEquals("Z", keyField.getText());
|
assertKeyFieldText("Z");
|
||||||
|
|
||||||
apply();
|
apply();
|
||||||
assertEquals(KeyStroke.getKeyStroke(KeyEvent.VK_Z, 0), getKeyStroke(action));
|
assertEquals(KeyStroke.getKeyStroke(KeyEvent.VK_Z, 0), getKeyStroke(action));
|
||||||
|
@ -157,24 +152,19 @@ public class KeyBindingsTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
triggerText(keyField, "z");
|
triggerText(keyField, "z");
|
||||||
assertTrue(statusPane.getText().indexOf("No action is selected.") != -1);
|
assertMessage("No action is selected.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSetKeyBinding() throws Exception {
|
public void testSetKeyBinding() throws Exception {
|
||||||
// set a key binding on an action that does not have a key binding
|
// set a key binding on an action that does not have a key binding
|
||||||
|
|
||||||
selectRowForAction(action1);
|
selectRowForAction(action1);
|
||||||
triggerActionKey(keyField, InputEvent.CTRL_DOWN_MASK, KeyEvent.VK_X);
|
triggerActionKey(keyField, InputEvent.CTRL_DOWN_MASK, KeyEvent.VK_X);
|
||||||
assertEquals(
|
KeyStroke ks = KeyStroke.getKeyStroke(KeyEvent.VK_X, InputEvent.CTRL_DOWN_MASK);
|
||||||
KeyEntryTextField.parseKeyStroke(
|
assertKeyFieldText(KeyBindingUtils.parseKeyStroke(ks));
|
||||||
KeyStroke.getKeyStroke(KeyEvent.VK_X, InputEvent.CTRL_DOWN_MASK)),
|
|
||||||
keyField.getText());
|
|
||||||
|
|
||||||
apply();
|
apply();
|
||||||
assertEquals(KeyStroke.getKeyStroke(KeyEvent.VK_X, InputEvent.CTRL_DOWN_MASK),
|
assertEquals(ks, getKeyStroke(action1));
|
||||||
getKeyStroke(action1));
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -182,57 +172,34 @@ public class KeyBindingsTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
|
|
||||||
selectRowForAction(action1);
|
selectRowForAction(action1);
|
||||||
triggerText(keyField, "x");
|
triggerText(keyField, "x");
|
||||||
assertEquals("X", keyField.getText());
|
assertKeyFieldText("X");
|
||||||
|
|
||||||
apply();
|
apply();
|
||||||
assertEquals(KeyStroke.getKeyStroke(KeyEvent.VK_X, 0), getKeyStroke(action1));
|
assertEquals(KeyStroke.getKeyStroke(KeyEvent.VK_X, 0), getKeyStroke(action1));
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSetKeyBindingNotAllowed() throws Exception {
|
|
||||||
|
|
||||||
selectRowForAction(action1);
|
|
||||||
triggerActionKey(keyField, 0, KeyEvent.VK_F1);
|
|
||||||
// F1 is the help key and cannot be used
|
|
||||||
assertEquals("", keyField.getText());
|
|
||||||
|
|
||||||
triggerActionKey(keyField, 0, KeyEvent.VK_HELP);
|
|
||||||
assertEquals("", keyField.getText());
|
|
||||||
|
|
||||||
triggerActionKey(keyField, 0, KeyEvent.VK_SHIFT);
|
|
||||||
assertEquals("", keyField.getText());
|
|
||||||
|
|
||||||
triggerActionKey(keyField, 0, KeyEvent.VK_ENTER);
|
|
||||||
assertEquals("", keyField.getText());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSetKeyBinding3() throws Exception {
|
public void testSetKeyBinding3() throws Exception {
|
||||||
|
|
||||||
selectRowForAction(action1);
|
selectRowForAction(action1);
|
||||||
triggerActionKey(keyField, InputEvent.CTRL_DOWN_MASK, KeyEvent.VK_HOME);
|
typeKeyStroke(InputEvent.CTRL_DOWN_MASK, KeyEvent.VK_HOME);
|
||||||
assertEquals(
|
KeyStroke ks = KeyStroke.getKeyStroke(KeyEvent.VK_HOME, InputEvent.CTRL_DOWN_MASK);
|
||||||
KeyEntryTextField.parseKeyStroke(
|
assertKeyFieldText(ks);
|
||||||
KeyStroke.getKeyStroke(KeyEvent.VK_HOME, InputEvent.CTRL_DOWN_MASK)),
|
|
||||||
keyField.getText());
|
|
||||||
|
|
||||||
apply();
|
apply();
|
||||||
assertEquals(KeyStroke.getKeyStroke(KeyEvent.VK_HOME, InputEvent.CTRL_DOWN_MASK),
|
assertEquals(ks, getKeyStroke(action1));
|
||||||
getKeyStroke(action1));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSetKeyBinding4() throws Exception {
|
public void testSetKeyBinding4() throws Exception {
|
||||||
|
|
||||||
selectRowForAction(action1);
|
selectRowForAction(action1);
|
||||||
triggerActionKey(keyField, 0, KeyEvent.VK_PAGE_UP);
|
typeKeyStroke(KeyEvent.VK_PAGE_UP);
|
||||||
assertEquals(
|
KeyStroke ks = KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, 0);
|
||||||
KeyEntryTextField.parseKeyStroke(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, 0)),
|
assertKeyFieldText(ks);
|
||||||
keyField.getText());
|
|
||||||
|
|
||||||
apply();
|
apply();
|
||||||
assertEquals(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, 0), getKeyStroke(action1));
|
assertEquals(ks, getKeyStroke(action1));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -259,13 +226,11 @@ public class KeyBindingsTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
|
|
||||||
// set the new binding that uses the 'Alt' key
|
// set the new binding that uses the 'Alt' key
|
||||||
selectRowForAction(action1);
|
selectRowForAction(action1);
|
||||||
triggerActionKey(keyField, InputEvent.ALT_DOWN_MASK, keyCode);
|
typeKeyStroke(InputEvent.ALT_DOWN_MASK, keyCode);
|
||||||
String keyStrokeString = KeyEntryTextField.parseKeyStroke(
|
KeyStroke ks = KeyStroke.getKeyStroke(keyCode, InputEvent.ALT_DOWN_MASK);
|
||||||
KeyStroke.getKeyStroke(keyCode, InputEvent.ALT_DOWN_MASK));
|
assertKeyFieldText(ks);
|
||||||
assertEquals(keyStrokeString, keyField.getText());
|
|
||||||
apply();
|
apply();
|
||||||
assertEquals(KeyStroke.getKeyStroke(keyCode, InputEvent.ALT_DOWN_MASK),
|
assertEquals(ks, getKeyStroke(action1));
|
||||||
getKeyStroke(action1));
|
|
||||||
|
|
||||||
// verify the additional binding for 'Alt Graph'
|
// verify the additional binding for 'Alt Graph'
|
||||||
action =
|
action =
|
||||||
|
@ -278,8 +243,8 @@ public class KeyBindingsTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
|
|
||||||
selectRowForAction(action1);
|
selectRowForAction(action1);
|
||||||
|
|
||||||
triggerActionKey(keyField, 0, KeyEvent.VK_ENTER);
|
typeKeyStroke(KeyEvent.VK_ENTER);
|
||||||
assertEquals("", keyField.getText());
|
assertNoKeyStrokeText();
|
||||||
|
|
||||||
apply();
|
apply();
|
||||||
assertNull(getKeyStroke(action1));
|
assertNull(getKeyStroke(action1));
|
||||||
|
@ -289,8 +254,8 @@ public class KeyBindingsTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
public void testClearKeyBinding2() throws Exception {
|
public void testClearKeyBinding2() throws Exception {
|
||||||
|
|
||||||
selectRowForAction(action1);
|
selectRowForAction(action1);
|
||||||
triggerText(keyField, "\b");
|
typeBackspace();
|
||||||
assertEquals("", keyField.getText());
|
assertNoKeyStrokeText();
|
||||||
|
|
||||||
apply();
|
apply();
|
||||||
assertNull(getKeyStroke(action1));
|
assertNull(getKeyStroke(action1));
|
||||||
|
@ -302,31 +267,36 @@ public class KeyBindingsTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
// verify that a list of collisions show up
|
// verify that a list of collisions show up
|
||||||
|
|
||||||
selectRowForAction(action1);
|
selectRowForAction(action1);
|
||||||
triggerActionKey(keyField, 0, KeyEvent.VK_OPEN_BRACKET);
|
typeKeyStroke(KeyEvent.VK_OPEN_BRACKET);
|
||||||
apply();
|
apply();
|
||||||
|
|
||||||
// set same binding on a different action, which will trigger the collisions list
|
// set same binding on a different action, which will trigger the collisions list
|
||||||
selectRowForAction(action2);
|
selectRowForAction(action2);
|
||||||
triggerActionKey(keyField, 0, KeyEvent.VK_OPEN_BRACKET);
|
typeKeyStroke(KeyEvent.VK_OPEN_BRACKET);
|
||||||
|
|
||||||
MultiLineLabel label = (MultiLineLabel) findComponentByName(panel, "CollisionLabel");
|
MultiLineLabel label = (MultiLineLabel) findComponentByName(panel, "CollisionLabel");
|
||||||
|
|
||||||
String msg = label.getLabel();
|
String msg = label.getLabel();
|
||||||
String[] lines = msg.split("\n");
|
String[] lines = msg.split("\n");
|
||||||
assertEquals(3, lines.length);
|
assertEquals(3, lines.length);
|
||||||
assertTrue(lines[1].contains(action1.getName()));
|
|
||||||
assertTrue(lines[2].contains(action2.getName()));
|
boolean success = msg.contains(action1.getName()) && msg.contains(action2.getName());
|
||||||
|
|
||||||
|
assertTrue("In-use action message incorrect.\n\tIt should contain these 2 actions:\n\t\t" +
|
||||||
|
action1.getName() + "\n\t\t" + action2.getName() + ".\nActual message:\n" +
|
||||||
|
msg + "\n", success);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSetReservedKeybinding() throws Exception {
|
public void testSetReservedKeybinding() throws Exception {
|
||||||
// try to set a reserved keybinding
|
// try to set a reserved keybinding
|
||||||
KeyStroke reservedKeystroke = KeyStroke.getKeyStroke(KeyEvent.VK_F4, 0);
|
KeyStroke reservedKeystroke = SystemKeyBindings.UPDATE_KEY_BINDINGS_KEY; // F4
|
||||||
|
|
||||||
selectRowForAction(action1);
|
selectRowForAction(action1);
|
||||||
triggerActionKey(keyField, 0, reservedKeystroke.getKeyCode());
|
typeKeyStroke(reservedKeystroke);
|
||||||
|
|
||||||
assertEquals("", keyField.getText());
|
assertNoKeyStrokeText();
|
||||||
|
assertMessage("F4 in use by System action 'Set KeyBinding'");
|
||||||
|
|
||||||
apply();
|
apply();
|
||||||
assertEquals(null, getKeyStroke(action1));
|
assertEquals(null, getKeyStroke(action1));
|
||||||
|
@ -336,8 +306,8 @@ public class KeyBindingsTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
selectRowForAction(action1);
|
selectRowForAction(action1);
|
||||||
|
|
||||||
KeyStroke validKeyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_X, InputEvent.CTRL_DOWN_MASK);
|
KeyStroke validKeyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_X, InputEvent.CTRL_DOWN_MASK);
|
||||||
triggerActionKey(keyField, InputEvent.CTRL_DOWN_MASK, validKeyStroke.getKeyCode());
|
typeKeyStroke(validKeyStroke);
|
||||||
assertEquals(KeyEntryTextField.parseKeyStroke(validKeyStroke), keyField.getText());
|
assertKeyFieldText(validKeyStroke);
|
||||||
|
|
||||||
apply();
|
apply();
|
||||||
assertEquals(validKeyStroke, getKeyStroke(action1));
|
assertEquals(validKeyStroke, getKeyStroke(action1));
|
||||||
|
@ -345,22 +315,172 @@ public class KeyBindingsTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
// try again to set a reserved binding
|
// try again to set a reserved binding
|
||||||
setUpDialog();
|
setUpDialog();
|
||||||
selectRowForAction(action1);
|
selectRowForAction(action1);
|
||||||
|
|
||||||
assertEquals(validKeyStroke, getKeyStroke(action1));
|
assertEquals(validKeyStroke, getKeyStroke(action1));
|
||||||
|
|
||||||
String originalText = keyField.getText();
|
typeKeyStroke(reservedKeystroke.getKeyCode());
|
||||||
triggerActionKey(keyField, 0, reservedKeystroke.getKeyCode());
|
assertNoKeyStrokeText();
|
||||||
|
assertMessage("F4 in use by System action 'Set KeyBinding'");
|
||||||
assertEquals(originalText, keyField.getText());
|
|
||||||
|
|
||||||
apply();
|
apply();
|
||||||
assertEquals(validKeyStroke, getKeyStroke(action1));
|
assertEquals(validKeyStroke, getKeyStroke(action1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSetKeyBindingOnSystemAction() throws Exception {
|
||||||
|
//
|
||||||
|
// Test that users can change the keybinding for a System action. The new binding cannot
|
||||||
|
// be in use by any other action.
|
||||||
|
//
|
||||||
|
DockingActionIf goToAction = getAction(tool, "Go To Address/Label"); // arbitrary plugin action
|
||||||
|
KeyStroke goToKs = goToAction.getKeyBinding();
|
||||||
|
assertNotNull(goToKs);
|
||||||
|
|
||||||
|
DockingActionIf systemAction = getAction("Show Context Menu"); // arbitrary system action
|
||||||
|
KeyStroke systemKs = systemAction.getKeyBinding();
|
||||||
|
assertNotNull(systemKs);
|
||||||
|
selectRowForAction(systemAction);
|
||||||
|
|
||||||
|
typeKeyStroke(goToKs);
|
||||||
|
assertNoKeyStrokeText();
|
||||||
|
assertMessage("System action cannot be set to in-use key stroke");
|
||||||
|
|
||||||
|
apply();
|
||||||
|
assertEquals(systemKs, getKeyStroke(systemAction)); // unchanged
|
||||||
|
|
||||||
|
// clear the in-use binding and then try again
|
||||||
|
clearKeyBinding(goToKs);
|
||||||
|
|
||||||
|
setUpDialog();
|
||||||
|
selectRowForAction(systemAction);
|
||||||
|
|
||||||
|
typeKeyStroke(goToKs);
|
||||||
|
assertKeyFieldText("G");
|
||||||
|
|
||||||
|
apply();
|
||||||
|
assertEquals(goToKs, getKeyStroke(systemAction));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSetKeybindingUsingSystemDefaultBinding_InUse() throws Exception {
|
||||||
|
//
|
||||||
|
// Test that users can change the keybinding for a non-System action to use a pre-defined
|
||||||
|
// System key stroke only if the binding is not in-use by a System action.
|
||||||
|
//
|
||||||
|
// This test will clear the system key binding in the UI by using the backspace key.
|
||||||
|
// Note: The 'Apply' button must be pressed before the system key stroke can be reused.
|
||||||
|
//
|
||||||
|
|
||||||
|
DockingActionIf systemAction = getAction("Show Context Menu"); // arbitrary system action
|
||||||
|
KeyStroke systemKs = systemAction.getKeyBinding();
|
||||||
|
String systemKsText = KeyBindingUtils.parseKeyStroke(systemKs);
|
||||||
|
assertEquals(SystemKeyBindings.CONTEXT_MENU_KEY1, systemKs);
|
||||||
|
assertNotNull(systemKs);
|
||||||
|
|
||||||
|
DockingActionIf goToAction = getAction(tool, "Go To Address/Label"); // arbitrary plugin action
|
||||||
|
KeyStroke goToKs = goToAction.getKeyBinding();
|
||||||
|
assertNotNull(goToKs);
|
||||||
|
|
||||||
|
setUpDialog();
|
||||||
|
|
||||||
|
selectRowForAction(action1);
|
||||||
|
typeKeyStroke(systemKs);
|
||||||
|
assertNoKeyStrokeText();
|
||||||
|
assertMessage(systemKsText + " in use by System action 'Show Context Menu'");
|
||||||
|
|
||||||
|
selectRowForAction(systemAction);
|
||||||
|
typeBackspace();
|
||||||
|
apply();
|
||||||
|
assertEquals(null, getKeyStroke(systemAction));
|
||||||
|
|
||||||
|
selectRowForAction(action1);
|
||||||
|
typeKeyStroke(systemKs);
|
||||||
|
assertKeyFieldText(systemKsText);
|
||||||
|
assertNoErrorMessage();
|
||||||
|
}
|
||||||
|
|
||||||
//==================================================================================================
|
//==================================================================================================
|
||||||
// Private Methods
|
// Private Methods
|
||||||
//==================================================================================================
|
//==================================================================================================
|
||||||
|
|
||||||
|
private boolean ignoreAction(DockingActionIf action) {
|
||||||
|
if (!action.getKeyBindingType().isManaged()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return action.getFullName().contains("Table Data");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertNoKeyStrokeText() {
|
||||||
|
assertEquals("", keyField.getText());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertKeyFieldText(KeyStroke ks) {
|
||||||
|
assertKeyFieldText(KeyBindingUtils.parseKeyStroke(ks));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertKeyFieldText(String s) {
|
||||||
|
assertEquals(s, runSwing(() -> keyField.getText()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertNoErrorMessage() {
|
||||||
|
assertMessage("");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void typeBackspace() {
|
||||||
|
triggerBackspaceKey(keyField);
|
||||||
|
waitForSwing();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void typeKeyStroke(KeyStroke ks) {
|
||||||
|
triggerKey(keyField, ks);
|
||||||
|
waitForSwing();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void typeKeyStroke(int keyCode) {
|
||||||
|
typeKeyStroke(0, keyCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void typeKeyStroke(int modifiers, int keyCode) {
|
||||||
|
triggerKey(keyField, modifiers, keyCode, KeyEvent.CHAR_UNDEFINED);
|
||||||
|
waitForSwing();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearKeyBinding(KeyStroke ks) {
|
||||||
|
|
||||||
|
ToolActions toolActions = (ToolActions) tool.getToolActions();
|
||||||
|
Action action = toolActions.getAction(ks);
|
||||||
|
if (action instanceof MultipleKeyAction multiAction) {
|
||||||
|
List<DockingActionIf> actions = multiAction.getActions();
|
||||||
|
for (DockingActionIf dockingAction : actions) {
|
||||||
|
runSwing(() -> dockingAction.setKeyBindingData(null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (action instanceof SystemKeyBindingAction systemAction) {
|
||||||
|
DockingActionIf dockingAction = systemAction.getAction();
|
||||||
|
runSwing(() -> dockingAction.setKeyBindingData(null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private DockingActionIf getAction(String name) {
|
||||||
|
|
||||||
|
Set<DockingActionIf> actions = tool.getAllActions();
|
||||||
|
for (DockingActionIf action : actions) {
|
||||||
|
if (action.getName().equals(name)) {
|
||||||
|
return action;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fail("Unable to find System action '%s'".formatted(name));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertMessage(String text) {
|
||||||
|
String kbStatusMessage = runSwing(panel::getStatusText);
|
||||||
|
if (!kbStatusMessage.contains(text)) {
|
||||||
|
fail("Expected message: " + text + ". Found message: " + kbStatusMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void apply() {
|
private void apply() {
|
||||||
runSwing(() -> panel.apply());
|
runSwing(() -> panel.apply());
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
@ -374,7 +494,7 @@ public class KeyBindingsTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
Set<DockingActionIf> list = tool.getAllActions();
|
Set<DockingActionIf> list = tool.getAllActions();
|
||||||
for (DockingActionIf action : list) {
|
for (DockingActionIf action : list) {
|
||||||
KeyStroke ks = action.getKeyBinding();
|
KeyStroke ks = action.getKeyBinding();
|
||||||
if (ignoreAction(action) && ks != null &&
|
if (!ignoreAction(action) && ks != null &&
|
||||||
ks != KeyStroke.getKeyStroke(KeyEvent.VK_Z, 0)) {
|
ks != KeyStroke.getKeyStroke(KeyEvent.VK_Z, 0)) {
|
||||||
return action;
|
return action;
|
||||||
}
|
}
|
||||||
|
@ -390,7 +510,7 @@ public class KeyBindingsTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
if (actionName.equals(model.getValueAt(i, 0))) {
|
if (actionName.equals(model.getValueAt(i, 0))) {
|
||||||
if (ks != null) {
|
if (ks != null) {
|
||||||
String ksStr = (String) model.getValueAt(i, 1);
|
String ksStr = (String) model.getValueAt(i, 1);
|
||||||
return ksStr.equals(KeyEntryTextField.parseKeyStroke(ks));
|
return ksStr.equals(KeyBindingUtils.parseKeyStroke(ks));
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -398,24 +518,22 @@ public class KeyBindingsTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void selectRowForAction(DockingActionIf action) throws Exception {
|
private void selectRowForAction(DockingActionIf action) {
|
||||||
String actionName = action.getName();
|
String actionName = action.getName();
|
||||||
|
|
||||||
Msg.debug(this, "Keybinding Action: " + action.getFullName());
|
|
||||||
for (int i = 0; i < model.getRowCount(); i++) {
|
for (int i = 0; i < model.getRowCount(); i++) {
|
||||||
if (actionName.equals(model.getValueAt(i, 0))) {
|
if (actionName.equals(model.getValueAt(i, 0))) {
|
||||||
final int idx = i;
|
int idx = i;
|
||||||
|
|
||||||
Msg.debug(this, "\tselection row for action: " + i);
|
|
||||||
runSwing(() -> {
|
runSwing(() -> {
|
||||||
table.setRowSelectionInterval(idx, idx);
|
table.setRowSelectionInterval(idx, idx);
|
||||||
Rectangle rect = table.getCellRect(idx, idx, true);
|
Rectangle rect = table.getCellRect(idx, idx, true);
|
||||||
table.scrollRectToVisible(rect);
|
table.scrollRectToVisible(rect);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
waitForSwing();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
waitForSwing();
|
fail("Could not find action to select: " + action);
|
||||||
}
|
}
|
||||||
|
|
||||||
private KeyStroke getKeyStroke(DockingActionIf action) {
|
private KeyStroke getKeyStroke(DockingActionIf action) {
|
||||||
|
@ -423,8 +541,15 @@ public class KeyBindingsTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setUpDialog() throws Exception {
|
private void setUpDialog() throws Exception {
|
||||||
|
|
||||||
|
if (panel != null) {
|
||||||
runSwing(() -> {
|
runSwing(() -> {
|
||||||
panel = new KeyBindingsPanel(tool, tool.getOptions(DockingToolConstants.KEY_BINDINGS));
|
dialog.setVisible(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
runSwing(() -> {
|
||||||
|
panel = new KeyBindingsPanel(tool);
|
||||||
panel.setOptionsPropertyChangeListener(evt -> {
|
panel.setOptionsPropertyChangeListener(evt -> {
|
||||||
// stub
|
// stub
|
||||||
});
|
});
|
||||||
|
@ -439,6 +564,7 @@ public class KeyBindingsTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
keyField = (JTextField) getInstanceField("ksField", panel);
|
keyField = (JTextField) getInstanceField("ksField", panel);
|
||||||
statusPane = findComponent(panel, JTextPane.class);
|
statusPane = findComponent(panel, JTextPane.class);
|
||||||
model = table.getModel();
|
model = table.getModel();
|
||||||
|
waitForSwing();
|
||||||
}
|
}
|
||||||
|
|
||||||
// find 2 actions that do not have key bindings so that we can add and change the values
|
// find 2 actions that do not have key bindings so that we can add and change the values
|
||||||
|
|
|
@ -38,6 +38,7 @@ import docking.action.DockingActionIf;
|
||||||
import docking.actions.KeyBindingUtils;
|
import docking.actions.KeyBindingUtils;
|
||||||
import docking.options.editor.*;
|
import docking.options.editor.*;
|
||||||
import docking.tool.ToolConstants;
|
import docking.tool.ToolConstants;
|
||||||
|
import docking.tool.util.DockingToolConstants;
|
||||||
import docking.widgets.MultiLineLabel;
|
import docking.widgets.MultiLineLabel;
|
||||||
import docking.widgets.OptionDialog;
|
import docking.widgets.OptionDialog;
|
||||||
import docking.widgets.filechooser.GhidraFileChooser;
|
import docking.widgets.filechooser.GhidraFileChooser;
|
||||||
|
@ -51,6 +52,7 @@ import ghidra.app.plugin.core.console.ConsolePlugin;
|
||||||
import ghidra.app.util.viewer.options.OptionsGui;
|
import ghidra.app.util.viewer.options.OptionsGui;
|
||||||
import ghidra.app.util.viewer.options.ScreenElement;
|
import ghidra.app.util.viewer.options.ScreenElement;
|
||||||
import ghidra.framework.main.ConsoleTextPane;
|
import ghidra.framework.main.ConsoleTextPane;
|
||||||
|
import ghidra.framework.main.FrontEndTool;
|
||||||
import ghidra.framework.options.*;
|
import ghidra.framework.options.*;
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
import ghidra.framework.plugintool.dialog.KeyBindingsPanel;
|
import ghidra.framework.plugintool.dialog.KeyBindingsPanel;
|
||||||
|
@ -422,47 +424,48 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
String actionName = "Clear Cut";
|
String actionName = "Clear Cut";
|
||||||
String pluginName = "DataTypeManagerPlugin";
|
String pluginName = "DataTypeManagerPlugin";
|
||||||
KeyStroke defaultKeyStroke = getKeyBinding(actionName);
|
KeyStroke defaultKeyStroke = getKeyBinding(actionName);
|
||||||
assertOptionsKeyStroke(actionName, pluginName, defaultKeyStroke);
|
assertOptionsKeyStroke(tool, actionName, pluginName, defaultKeyStroke);
|
||||||
|
|
||||||
int keyCode = KeyEvent.VK_Q;
|
int keyCode = KeyEvent.VK_Q;
|
||||||
int modifiers = InputEvent.CTRL_DOWN_MASK | InputEvent.ALT_DOWN_MASK;
|
int modifiers = InputEvent.CTRL_DOWN_MASK | InputEvent.ALT_DOWN_MASK;
|
||||||
KeyStroke newKeyStroke = setKeyBinding(actionName, modifiers, keyCode, 'Q');
|
KeyStroke newKeyStroke = setKeyBinding(actionName, modifiers, keyCode, 'Q');
|
||||||
|
|
||||||
apply();
|
apply();
|
||||||
assertOptionsKeyStroke(actionName, pluginName, newKeyStroke);
|
assertOptionsKeyStroke(tool, actionName, pluginName, newKeyStroke);
|
||||||
|
|
||||||
restoreDefaults();
|
restoreDefaults();
|
||||||
|
|
||||||
KeyStroke currentBinding = getKeyBinding(actionName);
|
KeyStroke currentBinding = getKeyBinding(actionName);
|
||||||
assertEquals("Key binding not restored after a call to restore defautls", defaultKeyStroke,
|
assertEquals("Key binding not restored after a call to restore defautls", defaultKeyStroke,
|
||||||
currentBinding);
|
currentBinding);
|
||||||
assertOptionsKeyStroke(actionName, pluginName, defaultKeyStroke);
|
assertOptionsKeyStroke(tool, actionName, pluginName, defaultKeyStroke);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRestoreDefaultsForFrontEndKeybindings() throws Exception {
|
public void testRestoreDefaultsForFrontEndKeybindings() throws Exception {
|
||||||
runSwing(() -> dialog.close());
|
runSwing(() -> dialog.close());
|
||||||
|
|
||||||
setUpDialog(env.getFrontEndTool());
|
FrontEndTool frontEndTool = env.getFrontEndTool();
|
||||||
|
setUpDialog(frontEndTool);
|
||||||
|
|
||||||
String actionName = "Archive Project";
|
String actionName = "Archive Project";
|
||||||
String pluginName = "ArchivePlugin";
|
String pluginName = "ArchivePlugin";
|
||||||
KeyStroke defaultKeyStroke = getKeyBinding(actionName);
|
KeyStroke defaultKeyStroke = getKeyBinding(actionName);
|
||||||
assertOptionsKeyStroke(actionName, pluginName, defaultKeyStroke);
|
assertOptionsKeyStroke(frontEndTool, actionName, pluginName, defaultKeyStroke);
|
||||||
|
|
||||||
int keyCode = KeyEvent.VK_Q;
|
int keyCode = KeyEvent.VK_Q;
|
||||||
int modifiers = InputEvent.CTRL_DOWN_MASK | InputEvent.ALT_DOWN_MASK;
|
int modifiers = InputEvent.CTRL_DOWN_MASK | InputEvent.ALT_DOWN_MASK;
|
||||||
KeyStroke newKeyStroke = setKeyBinding(actionName, modifiers, keyCode, 'Q');
|
KeyStroke newKeyStroke = setKeyBinding(actionName, modifiers, keyCode, 'Q');
|
||||||
|
|
||||||
apply();
|
apply();
|
||||||
assertOptionsKeyStroke(actionName, pluginName, newKeyStroke);
|
assertOptionsKeyStroke(frontEndTool, actionName, pluginName, newKeyStroke);
|
||||||
|
|
||||||
restoreDefaults();
|
restoreDefaults();
|
||||||
|
|
||||||
KeyStroke currentBinding = getKeyBinding(actionName);
|
KeyStroke currentBinding = getKeyBinding(actionName);
|
||||||
assertEquals("Key binding not restored after a call to restore defautls", defaultKeyStroke,
|
assertEquals("Key binding not restored after a call to restore defautls", defaultKeyStroke,
|
||||||
currentBinding);
|
currentBinding);
|
||||||
assertOptionsKeyStroke(actionName, pluginName, defaultKeyStroke);
|
assertOptionsKeyStroke(frontEndTool, actionName, pluginName, defaultKeyStroke);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -763,12 +766,9 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
return KeyBindingUtils.parseKeyStroke(keyBindingColumnValue);
|
return KeyBindingUtils.parseKeyStroke(keyBindingColumnValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertOptionsKeyStroke(String actionName, String pluginName, KeyStroke value)
|
private void assertOptionsKeyStroke(PluginTool pluginTool, String actionName, String pluginName,
|
||||||
throws Exception {
|
KeyStroke value) throws Exception {
|
||||||
OptionsEditor editor = seleNodeWithCustomEditor("Key Bindings");
|
Options options = pluginTool.getOptions(DockingToolConstants.KEY_BINDINGS);
|
||||||
KeyBindingsPanel panel = (KeyBindingsPanel) getInstanceField("panel", editor);
|
|
||||||
|
|
||||||
Options options = (Options) getInstanceField("options", panel);
|
|
||||||
KeyStroke optionsKeyStroke =
|
KeyStroke optionsKeyStroke =
|
||||||
options.getKeyStroke(actionName + " (" + pluginName + ")", null);
|
options.getKeyStroke(actionName + " (" + pluginName + ")", null);
|
||||||
assertEquals("The options keystroke does not match the value in keybinding options table",
|
assertEquals("The options keystroke does not match the value in keybinding options table",
|
||||||
|
@ -784,6 +784,7 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
|
|
||||||
JTextField textField = (JTextField) getInstanceField("ksField", panel);
|
JTextField textField = (JTextField) getInstanceField("ksField", panel);
|
||||||
triggerKey(textField, modifiers, keyCode, keyChar);
|
triggerKey(textField, modifiers, keyCode, keyChar);
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
KeyStroke expectedKeyStroke = KeyStroke.getKeyStroke(keyCode, modifiers, false);
|
KeyStroke expectedKeyStroke = KeyStroke.getKeyStroke(keyCode, modifiers, false);
|
||||||
KeyStroke currentBinding = getKeyBinding(actionName);
|
KeyStroke currentBinding = getKeyBinding(actionName);
|
||||||
|
|
|
@ -30,7 +30,7 @@ import docking.actions.KeyBindingUtils;
|
||||||
*/
|
*/
|
||||||
public abstract class DockingKeyBindingAction extends AbstractAction {
|
public abstract class DockingKeyBindingAction extends AbstractAction {
|
||||||
|
|
||||||
private DockingActionIf dockingAction;
|
protected DockingActionIf dockingAction;
|
||||||
|
|
||||||
protected final KeyStroke keyStroke;
|
protected final KeyStroke keyStroke;
|
||||||
protected final Tool tool;
|
protected final Tool tool;
|
||||||
|
@ -54,7 +54,7 @@ public abstract class DockingKeyBindingAction extends AbstractAction {
|
||||||
|
|
||||||
public abstract KeyBindingPrecedence getKeyBindingPrecedence();
|
public abstract KeyBindingPrecedence getKeyBindingPrecedence();
|
||||||
|
|
||||||
public boolean isReservedKeybindingPrecedence() {
|
public boolean isSystemKeybindingPrecedence() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -127,8 +127,8 @@ public class KeyBindingOverrideKeyEventDispatcher implements KeyEventDispatcher
|
||||||
return false; // let the normal event flow continue
|
return false; // let the normal event flow continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// *Special*, reserved key bindings--these can always be processed
|
// *Special*, System key bindings--these can always be processed and are a higher priority
|
||||||
if (processReservedKeyActionsPrecedence(action, event)) {
|
if (processSystemActionPrecedence(action, event)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -245,6 +245,16 @@ public class KeyBindingOverrideKeyEventDispatcher implements KeyEventDispatcher
|
||||||
return true; // default case; allow it through
|
return true; // default case; allow it through
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isSettingKeyBindings(KeyEvent event) {
|
||||||
|
Component destination = event.getComponent();
|
||||||
|
if (destination == null) {
|
||||||
|
Component focusOwner = focusProvider.getFocusOwner();
|
||||||
|
destination = focusOwner;
|
||||||
|
}
|
||||||
|
|
||||||
|
return destination instanceof KeyEntryTextField;
|
||||||
|
}
|
||||||
|
|
||||||
private boolean willBeHandledByTextComponent(KeyEvent event) {
|
private boolean willBeHandledByTextComponent(KeyEvent event) {
|
||||||
|
|
||||||
Component destination = event.getComponent();
|
Component destination = event.getComponent();
|
||||||
|
@ -301,10 +311,16 @@ public class KeyBindingOverrideKeyEventDispatcher implements KeyEventDispatcher
|
||||||
throw new AssertException("New precedence added to KeyBindingPrecedence?");
|
throw new AssertException("New precedence added to KeyBindingPrecedence?");
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean processReservedKeyActionsPrecedence(DockingKeyBindingAction action,
|
private boolean processSystemActionPrecedence(DockingKeyBindingAction action,
|
||||||
KeyEvent event) {
|
KeyEvent event) {
|
||||||
|
|
||||||
if (!action.isReservedKeybindingPrecedence()) {
|
if (isSettingKeyBindings(event)) {
|
||||||
|
// This means the user is setting keybindings. Do not process System actions during
|
||||||
|
// this operation so that the user can assign those keybindings.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!action.isSystemKeybindingPrecedence()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@ import java.awt.event.KeyEvent;
|
||||||
public enum KeyBindingPrecedence {
|
public enum KeyBindingPrecedence {
|
||||||
|
|
||||||
/** Actions at this level will be processed before all others, including Java components'. */
|
/** Actions at this level will be processed before all others, including Java components'. */
|
||||||
ReservedActionsLevel,
|
SystemActionsLevel,
|
||||||
|
|
||||||
/** Actions with this precedence will be processed before key listener on Java components. */
|
/** Actions with this precedence will be processed before key listener on Java components. */
|
||||||
KeyListenerLevel,
|
KeyListenerLevel,
|
||||||
|
|
|
@ -57,18 +57,7 @@ public class KeyEntryTextField extends JTextField {
|
||||||
*/
|
*/
|
||||||
public void setKeyStroke(KeyStroke ks) {
|
public void setKeyStroke(KeyStroke ks) {
|
||||||
processEntry(ks);
|
processEntry(ks);
|
||||||
setText(parseKeyStroke(ks));
|
setText(KeyBindingUtils.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 KeyBindingUtils.parseKeyStroke(ks);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clearField() {
|
public void clearField() {
|
||||||
|
@ -114,23 +103,14 @@ public class KeyEntryTextField extends JTextField {
|
||||||
@Override
|
@Override
|
||||||
public void keyPressed(KeyEvent e) {
|
public void keyPressed(KeyEvent e) {
|
||||||
int keyCode = e.getKeyCode();
|
int keyCode = e.getKeyCode();
|
||||||
if (isHelpKey(keyCode)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
KeyStroke keyStroke = null;
|
KeyStroke keyStroke = null;
|
||||||
if (!isClearKey(keyCode) && !isModifiersOnly(e)) {
|
if (!isClearKey(keyCode) && !isModifiersOnly(e)) {
|
||||||
keyStroke = KeyStroke.getKeyStroke(keyCode, e.getModifiersEx());
|
keyStroke = KeyStroke.getKeyStroke(keyCode, e.getModifiersEx());
|
||||||
}
|
}
|
||||||
|
|
||||||
processEntry(keyStroke);
|
processEntry(keyStroke);
|
||||||
e.consume();
|
e.consume();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isHelpKey(int keyCode) {
|
|
||||||
return keyCode == KeyEvent.VK_F1 || keyCode == KeyEvent.VK_HELP;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isClearKey(int keyCode) {
|
private boolean isClearKey(int keyCode) {
|
||||||
return keyCode == KeyEvent.VK_BACK_SPACE || keyCode == KeyEvent.VK_ENTER;
|
return keyCode == KeyEvent.VK_BACK_SPACE || keyCode == KeyEvent.VK_ENTER;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,156 @@
|
||||||
|
/* ###
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
|
||||||
|
import javax.swing.KeyStroke;
|
||||||
|
|
||||||
|
import docking.*;
|
||||||
|
import ghidra.util.HelpLocation;
|
||||||
|
import ghidra.util.SystemUtilities;
|
||||||
|
import help.HelpService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A base system action used for actions that show help information.
|
||||||
|
*/
|
||||||
|
public abstract class AbstractHelpAction extends DockingAction {
|
||||||
|
|
||||||
|
public AbstractHelpAction(String name, KeyStroke keyStroke, boolean isPrimary) {
|
||||||
|
super(name, DockingWindowManager.DOCKING_WINDOWS_OWNER, isPrimary);
|
||||||
|
|
||||||
|
// Only the primary action will appear in the tool' key binding settings UI. The primary
|
||||||
|
// action can be managed by the users. The secondary action is not managed at this time.
|
||||||
|
if (isPrimary) {
|
||||||
|
setKeyBindingData(new KeyBindingData(keyStroke));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
createSystemKeyBinding(keyStroke);
|
||||||
|
}
|
||||||
|
|
||||||
|
setEnabled(true);
|
||||||
|
|
||||||
|
// Help actions don't have help
|
||||||
|
DockingWindowManager.getHelpService().excludeFromHelp(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract boolean isInfo();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnabledForContext(ActionContext context) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionContext context) {
|
||||||
|
DockingActionIf mouseOverAction = DockingWindowManager.getMouseOverAction();
|
||||||
|
if (mouseOverAction != null) {
|
||||||
|
showHelp(mouseOverAction);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object mouseOverObject = DockingWindowManager.getMouseOverObject();
|
||||||
|
Object helpObject = getFirstAvailableObjectThatHasHelp(mouseOverObject);
|
||||||
|
if (helpObject != null) {
|
||||||
|
showHelp(helpObject);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// some components are special in that they have help registered just for them
|
||||||
|
Object eventSource = context.getSourceObject();
|
||||||
|
helpObject = getFirstAvailableObjectThatHasHelp(eventSource);
|
||||||
|
if (helpObject != null) {
|
||||||
|
showHelp(helpObject);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// dialogs help is handled differently than core Ghidra components
|
||||||
|
DialogComponentProvider dialogProvider = findDialogComponentProvider();
|
||||||
|
if (dialogProvider != null) {
|
||||||
|
showHelp(dialogProvider.getComponent());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle our 'normal' CompentProviders...just use the focused provider
|
||||||
|
DockingWindowManager windowManager = DockingWindowManager.getActiveInstance();
|
||||||
|
ComponentPlaceholder info = windowManager.getFocusedComponent();
|
||||||
|
if (info != null) {
|
||||||
|
ComponentProvider componentProvider = info.getProvider();
|
||||||
|
showHelp(componentProvider);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showHelp(Object helpObject) {
|
||||||
|
|
||||||
|
SystemUtilities.runSwingLater(() -> {
|
||||||
|
DockingWindowManager windowManager = DockingWindowManager.getActiveInstance();
|
||||||
|
Component component = windowManager.getActiveComponent();
|
||||||
|
DockingWindowManager.getHelpService().showHelp(helpObject, isInfo(), component);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private DialogComponentProvider findDialogComponentProvider() {
|
||||||
|
KeyboardFocusManager keyboardFocusManager =
|
||||||
|
KeyboardFocusManager.getCurrentKeyboardFocusManager();
|
||||||
|
Window activeWindow = keyboardFocusManager.getActiveWindow();
|
||||||
|
if (activeWindow instanceof DockingDialog) {
|
||||||
|
DockingDialog dockingDialog = (DockingDialog) activeWindow;
|
||||||
|
return dockingDialog.getDialogComponent();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object getFirstAvailableObjectThatHasHelp(Object startingHelpObject) {
|
||||||
|
if (startingHelpObject == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// First see if help exists for the given component directly...
|
||||||
|
HelpService helpService = DockingWindowManager.getHelpService();
|
||||||
|
HelpLocation helpLocation = helpService.getHelpLocation(startingHelpObject);
|
||||||
|
if (helpLocation != null) {
|
||||||
|
return startingHelpObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second, with no help registered for the starting component, start looking for a suitable
|
||||||
|
// help proxy.
|
||||||
|
//
|
||||||
|
// For Components, we can walk their containment hierarchy to find a potential help object
|
||||||
|
if (!(startingHelpObject instanceof Component)) {
|
||||||
|
// not a Component; don't know how to find a better help object
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return getFirstAvailableComponentThatHasHelp((Component) startingHelpObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Component getFirstAvailableComponentThatHasHelp(Component component) {
|
||||||
|
HelpService helpService = DockingWindowManager.getHelpService();
|
||||||
|
HelpLocation helpLocation = helpService.getHelpLocation(component);
|
||||||
|
if (helpLocation != null) {
|
||||||
|
return component;
|
||||||
|
}
|
||||||
|
|
||||||
|
Container parent = component.getParent();
|
||||||
|
if (parent == null) {
|
||||||
|
// nothing else to check
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return getFirstAvailableComponentThatHasHelp(parent);
|
||||||
|
}
|
||||||
|
}
|
|
@ -29,14 +29,14 @@ import org.apache.commons.lang3.StringUtils;
|
||||||
import docking.ActionContext;
|
import docking.ActionContext;
|
||||||
import docking.DockingWindowManager;
|
import docking.DockingWindowManager;
|
||||||
import generic.theme.GColor;
|
import generic.theme.GColor;
|
||||||
import generic.util.action.ReservedKeyBindings;
|
import generic.util.action.SystemKeyBindings;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
|
|
||||||
public class ComponentThemeInspectorAction extends DockingAction {
|
public class ComponentThemeInspectorAction extends DockingAction {
|
||||||
|
|
||||||
public ComponentThemeInspectorAction() {
|
public ComponentThemeInspectorAction() {
|
||||||
super("Component Theme Inspector", DockingWindowManager.DOCKING_WINDOWS_OWNER, false);
|
super("Component Theme Inspector", DockingWindowManager.DOCKING_WINDOWS_OWNER, true);
|
||||||
createReservedKeyBinding(ReservedKeyBindings.COMPONENT_THEME_INFO_KEY);
|
createSystemKeyBinding(SystemKeyBindings.COMPONENT_THEME_INFO_KEY);
|
||||||
|
|
||||||
// System action; no help needed
|
// System action; no help needed
|
||||||
DockingWindowManager.getHelpService().excludeFromHelp(this);
|
DockingWindowManager.getHelpService().excludeFromHelp(this);
|
||||||
|
@ -99,9 +99,6 @@ public class ComponentThemeInspectorAction extends DockingAction {
|
||||||
next = new Entry(component);
|
next = new Entry(component);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entry != null) {
|
|
||||||
entry.parent = next;
|
|
||||||
}
|
|
||||||
entry = next;
|
entry = next;
|
||||||
|
|
||||||
tree.add(entry);
|
tree.add(entry);
|
||||||
|
@ -199,7 +196,6 @@ public class ComponentThemeInspectorAction extends DockingAction {
|
||||||
|
|
||||||
private class Entry {
|
private class Entry {
|
||||||
|
|
||||||
private Entry parent;
|
|
||||||
protected Component component;
|
protected Component component;
|
||||||
protected Point mouseLocation;
|
protected Point mouseLocation;
|
||||||
|
|
||||||
|
|
|
@ -372,8 +372,8 @@ public abstract class DockingAction implements DockingActionIf {
|
||||||
precedence = kbData.getKeyBindingPrecedence();
|
precedence = kbData.getKeyBindingPrecedence();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (precedence == KeyBindingPrecedence.ReservedActionsLevel) {
|
if (precedence == KeyBindingPrecedence.SystemActionsLevel) {
|
||||||
return true; // reserved actions are special
|
return true; // system actions are special
|
||||||
}
|
}
|
||||||
|
|
||||||
// log a trace message instead of throwing an exception, as to not break any legacy code
|
// log a trace message instead of throwing an exception, as to not break any legacy code
|
||||||
|
@ -453,8 +453,8 @@ public abstract class DockingAction implements DockingActionIf {
|
||||||
* other actions are prevented from using the same KeyStroke as a reserved keybinding.
|
* other actions are prevented from using the same KeyStroke as a reserved keybinding.
|
||||||
* @param keyStroke the keystroke to be used for the keybinding
|
* @param keyStroke the keystroke to be used for the keybinding
|
||||||
*/
|
*/
|
||||||
void createReservedKeyBinding(KeyStroke keyStroke) {
|
void createSystemKeyBinding(KeyStroke keyStroke) {
|
||||||
KeyBindingData data = KeyBindingData.createReservedKeyBindingData(keyStroke);
|
KeyBindingData data = KeyBindingData.createSystemKeyBindingData(keyStroke);
|
||||||
setKeyBindingData(data);
|
setKeyBindingData(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -53,7 +53,7 @@ public class GlobalFocusTraversalAction extends DockingAction {
|
||||||
DockingWindowManager.DOCKING_WINDOWS_OWNER);
|
DockingWindowManager.DOCKING_WINDOWS_OWNER);
|
||||||
|
|
||||||
this.forward = forward;
|
this.forward = forward;
|
||||||
createReservedKeyBinding(keybinding);
|
createSystemKeyBinding(keybinding);
|
||||||
setEnabled(isGlobalFocusTraversalEnabled());
|
setEnabled(isGlobalFocusTraversalEnabled());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,131 +15,16 @@
|
||||||
*/
|
*/
|
||||||
package docking.action;
|
package docking.action;
|
||||||
|
|
||||||
import java.awt.*;
|
|
||||||
|
|
||||||
import javax.swing.KeyStroke;
|
import javax.swing.KeyStroke;
|
||||||
|
|
||||||
import docking.*;
|
public class HelpAction extends AbstractHelpAction {
|
||||||
import ghidra.util.HelpLocation;
|
|
||||||
import ghidra.util.SystemUtilities;
|
|
||||||
import help.HelpService;
|
|
||||||
|
|
||||||
public class HelpAction extends DockingAction {
|
public HelpAction(KeyStroke keyStroke, boolean isPrimary) {
|
||||||
|
super("Context Help", keyStroke, isPrimary);
|
||||||
private boolean infoOnly;
|
|
||||||
|
|
||||||
public HelpAction(boolean infoOnly, KeyStroke keybinding) {
|
|
||||||
super(infoOnly ? "HelpInfo" : "Help", DockingWindowManager.DOCKING_WINDOWS_OWNER, false);
|
|
||||||
createReservedKeyBinding(keybinding);
|
|
||||||
setEnabled(true);
|
|
||||||
this.infoOnly = infoOnly;
|
|
||||||
|
|
||||||
// Help actions don't have help
|
|
||||||
DockingWindowManager.getHelpService().excludeFromHelp(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isEnabledForContext(ActionContext context) {
|
public boolean isInfo() {
|
||||||
return true;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void actionPerformed(ActionContext context) {
|
|
||||||
DockingActionIf mouseOverAction = DockingWindowManager.getMouseOverAction();
|
|
||||||
if (mouseOverAction != null) {
|
|
||||||
showHelp(mouseOverAction);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Object mouseOverObject = DockingWindowManager.getMouseOverObject();
|
|
||||||
Object helpObject = getFirstAvailableObjectThatHasHelp(mouseOverObject);
|
|
||||||
if (helpObject != null) {
|
|
||||||
showHelp(helpObject);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// some components are special in that they have help registered just for them
|
|
||||||
Object eventSource = context.getSourceObject();
|
|
||||||
helpObject = getFirstAvailableObjectThatHasHelp(eventSource);
|
|
||||||
if (helpObject != null) {
|
|
||||||
showHelp(helpObject);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// dialogs help is handled differently than core Ghidra components
|
|
||||||
DialogComponentProvider dialogProvider = findDialogComponentProvider();
|
|
||||||
if (dialogProvider != null) {
|
|
||||||
showHelp(dialogProvider.getComponent());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle our 'normal' CompentProviders...just use the focused provider
|
|
||||||
DockingWindowManager windowManager = DockingWindowManager.getActiveInstance();
|
|
||||||
ComponentPlaceholder info = windowManager.getFocusedComponent();
|
|
||||||
if (info != null) {
|
|
||||||
ComponentProvider componentProvider = info.getProvider();
|
|
||||||
showHelp(componentProvider);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showHelp(final Object helpObject) {
|
|
||||||
|
|
||||||
SystemUtilities.runSwingLater(() -> {
|
|
||||||
DockingWindowManager windowManager = DockingWindowManager.getActiveInstance();
|
|
||||||
Component component = windowManager.getActiveComponent();
|
|
||||||
DockingWindowManager.getHelpService().showHelp(helpObject, infoOnly, component);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private DialogComponentProvider findDialogComponentProvider() {
|
|
||||||
KeyboardFocusManager keyboardFocusManager =
|
|
||||||
KeyboardFocusManager.getCurrentKeyboardFocusManager();
|
|
||||||
Window activeWindow = keyboardFocusManager.getActiveWindow();
|
|
||||||
if (activeWindow instanceof DockingDialog) {
|
|
||||||
DockingDialog dockingDialog = (DockingDialog) activeWindow;
|
|
||||||
return dockingDialog.getDialogComponent();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Object getFirstAvailableObjectThatHasHelp(Object startingHelpObject) {
|
|
||||||
if (startingHelpObject == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// First see if help exists for the given component directly...
|
|
||||||
HelpService helpService = DockingWindowManager.getHelpService();
|
|
||||||
HelpLocation helpLocation = helpService.getHelpLocation(startingHelpObject);
|
|
||||||
if (helpLocation != null) {
|
|
||||||
return startingHelpObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Second, with no help registered for the starting component, start looking for a suitable
|
|
||||||
// help proxy.
|
|
||||||
//
|
|
||||||
// For Components, we can walk their containment hierarchy to find a potential help object
|
|
||||||
if (!(startingHelpObject instanceof Component)) {
|
|
||||||
// not a Component; don't know how to find a better help object
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return getFirstAvailableComponentThatHasHelp((Component) startingHelpObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Component getFirstAvailableComponentThatHasHelp(Component component) {
|
|
||||||
HelpService helpService = DockingWindowManager.getHelpService();
|
|
||||||
HelpLocation helpLocation = helpService.getHelpLocation(component);
|
|
||||||
if (helpLocation != null) {
|
|
||||||
return component;
|
|
||||||
}
|
|
||||||
|
|
||||||
Container parent = component.getParent();
|
|
||||||
if (parent == null) {
|
|
||||||
// nothing else to check
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return getFirstAvailableComponentThatHasHelp(parent);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,21 +17,14 @@ package docking.action;
|
||||||
|
|
||||||
import javax.swing.KeyStroke;
|
import javax.swing.KeyStroke;
|
||||||
|
|
||||||
import docking.*;
|
public class HelpInfoAction extends AbstractHelpAction {
|
||||||
|
|
||||||
class ReservedKeyBindingAction extends DockingKeyBindingAction {
|
public HelpInfoAction(KeyStroke keybinding) {
|
||||||
|
super("Help Info", keybinding, true);
|
||||||
ReservedKeyBindingAction(Tool tool, DockingActionIf action, KeyStroke keyStroke) {
|
|
||||||
super(tool, action, keyStroke);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isReservedKeybindingPrecedence() {
|
public boolean isInfo() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public KeyBindingPrecedence getKeyBindingPrecedence() {
|
|
||||||
return KeyBindingPrecedence.ReservedActionsLevel;
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -63,9 +63,9 @@ public class KeyBindingData {
|
||||||
}
|
}
|
||||||
|
|
||||||
public KeyBindingData(KeyStroke keyStroke, KeyBindingPrecedence precedence) {
|
public KeyBindingData(KeyStroke keyStroke, KeyBindingPrecedence precedence) {
|
||||||
if (precedence == KeyBindingPrecedence.ReservedActionsLevel) {
|
if (precedence == KeyBindingPrecedence.SystemActionsLevel) {
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
"Can't set precedence to Reserved KeyBindingPrecedence");
|
"Can't set precedence to System KeyBindingPrecedence");
|
||||||
}
|
}
|
||||||
this.keyStroke = keyStroke;
|
this.keyStroke = keyStroke;
|
||||||
this.keyBindingPrecedence = precedence;
|
this.keyBindingPrecedence = precedence;
|
||||||
|
@ -93,9 +93,9 @@ public class KeyBindingData {
|
||||||
keyBindingPrecedence + "]";
|
keyBindingPrecedence + "]";
|
||||||
}
|
}
|
||||||
|
|
||||||
static KeyBindingData createReservedKeyBindingData(KeyStroke keyStroke) {
|
static KeyBindingData createSystemKeyBindingData(KeyStroke keyStroke) {
|
||||||
KeyBindingData keyBindingData = new KeyBindingData(keyStroke);
|
KeyBindingData keyBindingData = new KeyBindingData(keyStroke);
|
||||||
keyBindingData.keyBindingPrecedence = KeyBindingPrecedence.ReservedActionsLevel;
|
keyBindingData.keyBindingPrecedence = KeyBindingPrecedence.SystemActionsLevel;
|
||||||
return keyBindingData;
|
return keyBindingData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,7 +105,7 @@ public class KeyBindingData {
|
||||||
* @param newKeyBindingData the data to validate
|
* @param newKeyBindingData the data to validate
|
||||||
* @return the potentially changed data
|
* @return the potentially changed data
|
||||||
*/
|
*/
|
||||||
public static KeyBindingData validateKeyBindingData(KeyBindingData newKeyBindingData) {
|
static KeyBindingData validateKeyBindingData(KeyBindingData newKeyBindingData) {
|
||||||
if (newKeyBindingData == null) {
|
if (newKeyBindingData == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -117,8 +117,8 @@ public class KeyBindingData {
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyBindingPrecedence precedence = newKeyBindingData.getKeyBindingPrecedence();
|
KeyBindingPrecedence precedence = newKeyBindingData.getKeyBindingPrecedence();
|
||||||
if (precedence == KeyBindingPrecedence.ReservedActionsLevel) {
|
if (precedence == KeyBindingPrecedence.SystemActionsLevel) {
|
||||||
return createReservedKeyBindingData(KeyBindingUtils.validateKeyStroke(keyBinding));
|
return createSystemKeyBindingData(KeyBindingUtils.validateKeyStroke(keyBinding));
|
||||||
}
|
}
|
||||||
return new KeyBindingData(KeyBindingUtils.validateKeyStroke(keyBinding), precedence);
|
return new KeyBindingData(KeyBindingUtils.validateKeyStroke(keyBinding), precedence);
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,8 @@ import javax.swing.Action;
|
||||||
import javax.swing.KeyStroke;
|
import javax.swing.KeyStroke;
|
||||||
|
|
||||||
import docking.*;
|
import docking.*;
|
||||||
import generic.util.action.ReservedKeyBindings;
|
import docking.actions.KeyBindingUtils;
|
||||||
|
import ghidra.util.Msg;
|
||||||
import ghidra.util.exception.AssertException;
|
import ghidra.util.exception.AssertException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -36,14 +37,14 @@ import ghidra.util.exception.AssertException;
|
||||||
public class KeyBindingsManager implements PropertyChangeListener {
|
public class KeyBindingsManager implements PropertyChangeListener {
|
||||||
|
|
||||||
// this map exists to update the MultiKeyBindingAction when the key binding changes
|
// this map exists to update the MultiKeyBindingAction when the key binding changes
|
||||||
private Map<DockingActionIf, ComponentProvider> actionToProviderMap;
|
private Map<DockingActionIf, ComponentProvider> actionToProviderMap = new HashMap<>();
|
||||||
private Map<KeyStroke, DockingKeyBindingAction> dockingKeyMap;
|
private Map<KeyStroke, DockingKeyBindingAction> dockingKeyMap = new HashMap<>();
|
||||||
|
private Map<DockingActionIf, SystemKeyBindingAction> dockingActionToSystemActionMap =
|
||||||
|
new HashMap<>();
|
||||||
private Tool tool;
|
private Tool tool;
|
||||||
|
|
||||||
public KeyBindingsManager(Tool tool) {
|
public KeyBindingsManager(Tool tool) {
|
||||||
this.tool = tool;
|
this.tool = tool;
|
||||||
dockingKeyMap = new HashMap<>();
|
|
||||||
actionToProviderMap = new HashMap<>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addAction(ComponentProvider optionalProvider, DockingActionIf action) {
|
public void addAction(ComponentProvider optionalProvider, DockingActionIf action) {
|
||||||
|
@ -59,14 +60,17 @@ public class KeyBindingsManager implements PropertyChangeListener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addReservedAction(DockingActionIf action) {
|
public void addSystemAction(DockingActionIf action) {
|
||||||
KeyStroke keyBinding = action.getKeyBinding();
|
KeyStroke keyStroke = action.getKeyBinding();
|
||||||
Objects.requireNonNull(keyBinding);
|
Objects.requireNonNull(keyStroke);
|
||||||
addReservedKeyBinding(action, keyBinding);
|
DockingKeyBindingAction existingAction = dockingKeyMap.get(keyStroke);
|
||||||
|
if (existingAction != null) {
|
||||||
|
throw new AssertException("Attempting to add more than one reserved " +
|
||||||
|
"action to a given keystroke: " + keyStroke);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addReservedAction(DockingActionIf action, KeyStroke ks) {
|
addSystemKeyBinding(action, keyStroke);
|
||||||
addReservedKeyBinding(action, ks);
|
action.addPropertyChangeListener(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeAction(DockingActionIf action) {
|
public void removeAction(DockingActionIf action) {
|
||||||
|
@ -78,17 +82,54 @@ public class KeyBindingsManager implements PropertyChangeListener {
|
||||||
private void addKeyBinding(ComponentProvider provider, DockingActionIf action,
|
private void addKeyBinding(ComponentProvider provider, DockingActionIf action,
|
||||||
KeyStroke keyStroke) {
|
KeyStroke keyStroke) {
|
||||||
|
|
||||||
if (ReservedKeyBindings.isReservedKeystroke(keyStroke)) {
|
String errorMessage = validateActionKeyBinding(action, keyStroke);
|
||||||
throw new AssertException("Cannot assign action to a reserved keystroke. " +
|
if (errorMessage != null) {
|
||||||
"Action: " + action.getName() + " - Keystroke: " + keyStroke);
|
// Getting here should not be possible from the UI, but may happen if a developer sets
|
||||||
|
// an incorrect keybinding
|
||||||
|
Msg.error(this, errorMessage);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// map standard keybinding to action
|
// map standard keystroke to action
|
||||||
doAddKeyBinding(provider, action, keyStroke);
|
doAddKeyBinding(provider, action, keyStroke);
|
||||||
|
|
||||||
|
// map workaround keystroke to action
|
||||||
fixupAltGraphKeyStrokeMapping(provider, action, keyStroke);
|
fixupAltGraphKeyStrokeMapping(provider, action, keyStroke);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String validateActionKeyBinding(DockingActionIf dockingAction, KeyStroke ks) {
|
||||||
|
|
||||||
|
if (ks == null) {
|
||||||
|
return null; // clearing the key stroke
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// 1) Handle case with given key stroke already in use by a system action
|
||||||
|
//
|
||||||
|
Action existingAction = dockingKeyMap.get(ks);
|
||||||
|
if (existingAction instanceof SystemKeyBindingAction systemAction) {
|
||||||
|
|
||||||
|
DockingActionIf systemDockingAction = systemAction.getAction();
|
||||||
|
if (dockingAction == systemDockingAction) {
|
||||||
|
return null; // same key stroke; not sure if this can happen
|
||||||
|
}
|
||||||
|
|
||||||
|
String ksString = KeyBindingUtils.parseKeyStroke(ks);
|
||||||
|
return ksString + " in use by System action '" + systemDockingAction.getName() + "'";
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// 2) Handle the case where a system action key stroke is being set to something that is
|
||||||
|
// already in-use by some other action
|
||||||
|
//
|
||||||
|
SystemKeyBindingAction systemAction = dockingActionToSystemActionMap.get(dockingAction);
|
||||||
|
if (systemAction != null && existingAction != null) {
|
||||||
|
return "System action cannot be set to in-use key stroke";
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private void fixupAltGraphKeyStrokeMapping(ComponentProvider provider, DockingActionIf action,
|
private void fixupAltGraphKeyStrokeMapping(ComponentProvider provider, DockingActionIf action,
|
||||||
KeyStroke keyStroke) {
|
KeyStroke keyStroke) {
|
||||||
|
|
||||||
|
@ -116,56 +157,65 @@ public class KeyBindingsManager implements PropertyChangeListener {
|
||||||
KeyStroke mappingKeyStroke, KeyStroke actionKeyStroke) {
|
KeyStroke mappingKeyStroke, KeyStroke actionKeyStroke) {
|
||||||
|
|
||||||
DockingKeyBindingAction existingAction = dockingKeyMap.get(mappingKeyStroke);
|
DockingKeyBindingAction existingAction = dockingKeyMap.get(mappingKeyStroke);
|
||||||
if (existingAction == null) {
|
if (existingAction instanceof MultipleKeyAction) {
|
||||||
dockingKeyMap.put(mappingKeyStroke,
|
MultipleKeyAction multipleKeyction = (MultipleKeyAction) existingAction;
|
||||||
new MultipleKeyAction(tool, provider, action, actionKeyStroke));
|
multipleKeyction.addAction(provider, action);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(existingAction instanceof MultipleKeyAction)) {
|
if (existingAction instanceof SystemKeyBindingAction) {
|
||||||
return; // reserved binding; nothing to do
|
// This should not happen due to protections in the UI
|
||||||
|
Msg.error(this, "Attempted to use the same keybinding for an existing System action: " +
|
||||||
|
existingAction + ". Keystroke: " + mappingKeyStroke);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
MultipleKeyAction multipleKeyction = (MultipleKeyAction) existingAction;
|
SystemKeyBindingAction systemAction = dockingActionToSystemActionMap.get(action);
|
||||||
multipleKeyction.addAction(provider, action);
|
if (systemAction != null) {
|
||||||
|
// the user has updated the binding for a System action; re-install it
|
||||||
|
registerSystemKeyBinding(action, mappingKeyStroke);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addReservedKeyBinding(DockingActionIf action, KeyStroke keyStroke) {
|
// assume existingAction == null
|
||||||
DockingKeyBindingAction existingAction = dockingKeyMap.get(keyStroke);
|
dockingKeyMap.put(mappingKeyStroke,
|
||||||
if (existingAction != null) {
|
new MultipleKeyAction(tool, provider, action, actionKeyStroke));
|
||||||
throw new AssertException("Attempting to add more than one reserved " +
|
|
||||||
"action to a given keystroke: " + keyStroke);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyBindingData binding = KeyBindingData.createReservedKeyBindingData(keyStroke);
|
private void addSystemKeyBinding(DockingActionIf action, KeyStroke keyStroke) {
|
||||||
|
KeyBindingData binding = KeyBindingData.createSystemKeyBindingData(keyStroke);
|
||||||
action.setKeyBindingData(binding);
|
action.setKeyBindingData(binding);
|
||||||
dockingKeyMap.put(keyStroke, new ReservedKeyBindingAction(tool, action, keyStroke));
|
registerSystemKeyBinding(action, keyStroke);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void registerSystemKeyBinding(DockingActionIf action, KeyStroke keyStroke) {
|
||||||
|
SystemKeyBindingAction systemAction = new SystemKeyBindingAction(tool, action, keyStroke);
|
||||||
|
dockingKeyMap.put(keyStroke, systemAction);
|
||||||
|
dockingActionToSystemActionMap.put(action, systemAction);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove the keystroke binding from the root pane's input map
|
|
||||||
* using keystroke specified instead of that specified by the action
|
|
||||||
*/
|
|
||||||
private void removeKeyBinding(KeyStroke keyStroke, DockingActionIf action) {
|
private void removeKeyBinding(KeyStroke keyStroke, DockingActionIf action) {
|
||||||
if (keyStroke == null) {
|
if (keyStroke == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ReservedKeyBindings.isReservedKeystroke(keyStroke)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
DockingKeyBindingAction existingAction = dockingKeyMap.get(keyStroke);
|
DockingKeyBindingAction existingAction = dockingKeyMap.get(keyStroke);
|
||||||
if (existingAction == null) {
|
if (existingAction == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (existingAction instanceof SystemKeyBindingAction) {
|
||||||
|
dockingKeyMap.remove(keyStroke);
|
||||||
|
}
|
||||||
|
else if (existingAction instanceof MultipleKeyAction) {
|
||||||
|
|
||||||
MultipleKeyAction mkAction = (MultipleKeyAction) existingAction;
|
MultipleKeyAction mkAction = (MultipleKeyAction) existingAction;
|
||||||
mkAction.removeAction(action);
|
mkAction.removeAction(action);
|
||||||
if (mkAction.isEmpty()) {
|
if (mkAction.isEmpty()) {
|
||||||
dockingKeyMap.remove(keyStroke);
|
dockingKeyMap.remove(keyStroke);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void propertyChange(PropertyChangeEvent evt) {
|
public void propertyChange(PropertyChangeEvent evt) {
|
||||||
|
@ -196,8 +246,13 @@ public class KeyBindingsManager implements PropertyChangeListener {
|
||||||
return dockingKeyMap.get(keyStroke);
|
return dockingKeyMap.get(keyStroke);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Set<DockingActionIf> getSystemActions() {
|
||||||
|
return new HashSet<>(dockingActionToSystemActionMap.keySet());
|
||||||
|
}
|
||||||
|
|
||||||
public void dispose() {
|
public void dispose() {
|
||||||
dockingKeyMap.clear();
|
dockingKeyMap.clear();
|
||||||
actionToProviderMap.clear();
|
actionToProviderMap.clear();
|
||||||
|
dockingActionToSystemActionMap.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -142,8 +142,8 @@ public class MultipleKeyAction extends DockingKeyBindingAction {
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean ignoreActionWhileMenuShowing() {
|
private boolean ignoreActionWhileMenuShowing() {
|
||||||
if (getKeyBindingPrecedence() == KeyBindingPrecedence.ReservedActionsLevel) {
|
if (getKeyBindingPrecedence() == KeyBindingPrecedence.SystemActionsLevel) {
|
||||||
return false; // allow reserved bindings through "no matter what!"
|
return false; // allow system bindings through "no matter what!"
|
||||||
}
|
}
|
||||||
|
|
||||||
MenuSelectionManager menuManager = MenuSelectionManager.defaultManager();
|
MenuSelectionManager menuManager = MenuSelectionManager.defaultManager();
|
||||||
|
@ -238,8 +238,8 @@ public class MultipleKeyAction extends DockingKeyBindingAction {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isReservedKeybindingPrecedence() {
|
public boolean isSystemKeybindingPrecedence() {
|
||||||
return false; // MultipleKeyActions can never be reserved
|
return false; // MultipleKeyActions can never be 'system'
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -35,7 +35,7 @@ public class NextPreviousWindowAction extends DockingAction {
|
||||||
DockingWindowManager.DOCKING_WINDOWS_OWNER);
|
DockingWindowManager.DOCKING_WINDOWS_OWNER);
|
||||||
|
|
||||||
this.forward = forward;
|
this.forward = forward;
|
||||||
createReservedKeyBinding(keybinding);
|
createSystemKeyBinding(keybinding);
|
||||||
setEnabled(true);
|
setEnabled(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,10 +29,19 @@ import ghidra.util.Swing;
|
||||||
*/
|
*/
|
||||||
public class ShowContextMenuAction extends DockingAction {
|
public class ShowContextMenuAction extends DockingAction {
|
||||||
|
|
||||||
public ShowContextMenuAction(KeyStroke keyStroke) {
|
public ShowContextMenuAction(KeyStroke keyStroke, boolean isPrimary) {
|
||||||
super("Show Context Menu", DockingWindowManager.DOCKING_WINDOWS_OWNER);
|
super(isPrimary ? "Show Context Menu" : "Show Context Menu Alternate",
|
||||||
|
DockingWindowManager.DOCKING_WINDOWS_OWNER, isPrimary);
|
||||||
|
|
||||||
|
// Only the primary action will appear in the tool' key binding settings UI. The primary
|
||||||
|
// action can be managed by the users. The secondary action is not managed at this time.
|
||||||
|
if (isPrimary) {
|
||||||
setKeyBindingData(new KeyBindingData(keyStroke));
|
setKeyBindingData(new KeyBindingData(keyStroke));
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
createSystemKeyBinding(keyStroke);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void actionPerformed(ActionContext context) {
|
public void actionPerformed(ActionContext context) {
|
||||||
|
|
|
@ -24,14 +24,14 @@ import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
import docking.ActionContext;
|
import docking.ActionContext;
|
||||||
import docking.DockingWindowManager;
|
import docking.DockingWindowManager;
|
||||||
import generic.util.action.ReservedKeyBindings;
|
import generic.util.action.SystemKeyBindings;
|
||||||
|
|
||||||
public class ShowFocusCycleAction extends DockingAction {
|
public class ShowFocusCycleAction extends DockingAction {
|
||||||
static final Logger log = LogManager.getLogger(ShowFocusCycleAction.class);
|
static final Logger log = LogManager.getLogger(ShowFocusCycleAction.class);
|
||||||
|
|
||||||
public ShowFocusCycleAction() {
|
public ShowFocusCycleAction() {
|
||||||
super("Show Focus Cycle", DockingWindowManager.DOCKING_WINDOWS_OWNER, false);
|
super("Show Focus Cycle", DockingWindowManager.DOCKING_WINDOWS_OWNER);
|
||||||
createReservedKeyBinding(ReservedKeyBindings.FOCUS_CYCLE_INFO_KEY);
|
createSystemKeyBinding(SystemKeyBindings.FOCUS_CYCLE_INFO_KEY);
|
||||||
setEnabled(true);
|
setEnabled(true);
|
||||||
|
|
||||||
// System action; no help needed
|
// System action; no help needed
|
||||||
|
|
|
@ -25,14 +25,14 @@ import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
import docking.*;
|
import docking.*;
|
||||||
import generic.util.action.ReservedKeyBindings;
|
import generic.util.action.SystemKeyBindings;
|
||||||
|
|
||||||
public class ShowFocusInfoAction extends DockingAction {
|
public class ShowFocusInfoAction extends DockingAction {
|
||||||
static final Logger log = LogManager.getLogger(ShowFocusInfoAction.class);
|
static final Logger log = LogManager.getLogger(ShowFocusInfoAction.class);
|
||||||
|
|
||||||
public ShowFocusInfoAction() {
|
public ShowFocusInfoAction() {
|
||||||
super("Show Focus Info", DockingWindowManager.DOCKING_WINDOWS_OWNER, false);
|
super("Show Focus Info", DockingWindowManager.DOCKING_WINDOWS_OWNER);
|
||||||
createReservedKeyBinding(ReservedKeyBindings.FOCUS_INFO_KEY);
|
createSystemKeyBinding(SystemKeyBindings.FOCUS_INFO_KEY);
|
||||||
setEnabled(true);
|
setEnabled(true);
|
||||||
|
|
||||||
// System action; no help needed
|
// System action; no help needed
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
/* ###
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
import javax.swing.KeyStroke;
|
||||||
|
|
||||||
|
import docking.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An {@link DockingKeyBindingAction} to signal that the given {@link DockingAction} gets priority
|
||||||
|
* over all other non-system actions in the system.
|
||||||
|
*/
|
||||||
|
public class SystemKeyBindingAction extends DockingKeyBindingAction {
|
||||||
|
|
||||||
|
SystemKeyBindingAction(Tool tool, DockingActionIf action, KeyStroke keyStroke) {
|
||||||
|
super(tool, action, keyStroke);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DockingActionIf getAction() {
|
||||||
|
return dockingAction;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSystemKeybindingPrecedence() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public KeyBindingPrecedence getKeyBindingPrecedence() {
|
||||||
|
return KeyBindingPrecedence.SystemActionsLevel;
|
||||||
|
}
|
||||||
|
}
|
|
@ -87,7 +87,7 @@ public interface DockingToolActions {
|
||||||
public Set<DockingActionIf> getActions(String owner);
|
public Set<DockingActionIf> getActions(String owner);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns all actions known to the tool
|
* Returns all actions known to the tool.
|
||||||
* @return the actions
|
* @return the actions
|
||||||
*/
|
*/
|
||||||
public Set<DockingActionIf> getAllActions();
|
public Set<DockingActionIf> getAllActions();
|
||||||
|
@ -103,4 +103,5 @@ public interface DockingToolActions {
|
||||||
* @param placeholder the placeholder containing information related to the action it represents
|
* @param placeholder the placeholder containing information related to the action it represents
|
||||||
*/
|
*/
|
||||||
public void registerSharedActionPlaceholder(SharedDockingActionPlaceholder placeholder);
|
public void registerSharedActionPlaceholder(SharedDockingActionPlaceholder placeholder);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
* <p>
|
||||||
|
* 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<String, List<DockingActionIf>> actionsByFullName;
|
||||||
|
private Map<String, List<String>> actionNamesByKeyStroke = new HashMap<>();
|
||||||
|
private Map<String, KeyStroke> keyStrokesByFullName = new HashMap<>();
|
||||||
|
private List<DockingActionIf> uniqueActions = new ArrayList<>();
|
||||||
|
|
||||||
|
// to know what has been changed
|
||||||
|
private Map<String, KeyStroke> 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<DockingActionIf> getUniqueActions() {
|
||||||
|
return Collections.unmodifiableList(uniqueActions);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, KeyStroke> 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<String> 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<DockingActionIf> 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<Entry<String, List<DockingActionIf>>> entries = actionsByFullName.entrySet();
|
||||||
|
for (Entry<String, List<DockingActionIf>> entry : entries) {
|
||||||
|
List<DockingActionIf> 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<String> 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<String> 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<String> 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<DockingActionIf> actions = actionsByFullName.get(fullActionName);
|
||||||
|
for (DockingActionIf action : actions) {
|
||||||
|
action.setUnvalidatedKeyBindingData(new KeyBindingData(newKeyStroke));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init() {
|
||||||
|
|
||||||
|
actionsByFullName = KeyBindingUtils.getAllActionsByFullName(tool);
|
||||||
|
Set<Entry<String, List<DockingActionIf>>> entries = actionsByFullName.entrySet();
|
||||||
|
for (Entry<String, List<DockingActionIf>> entry : entries) {
|
||||||
|
|
||||||
|
// pick one action, they are all conceptually the same
|
||||||
|
List<DockingActionIf> 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<String> list = actionNamesByKeyStroke.get(ksName);
|
||||||
|
if (list == null) {
|
||||||
|
list = new ArrayList<>();
|
||||||
|
actionNamesByKeyStroke.put(ksName, list);
|
||||||
|
}
|
||||||
|
if (!list.contains(actionName)) {
|
||||||
|
list.add(actionName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -16,42 +16,43 @@
|
||||||
package docking.actions;
|
package docking.actions;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.util.*;
|
import java.util.Objects;
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import javax.swing.text.*;
|
import javax.swing.text.*;
|
||||||
|
|
||||||
import docking.DialogComponentProvider;
|
import docking.*;
|
||||||
import docking.KeyEntryTextField;
|
import docking.action.DockingActionIf;
|
||||||
import docking.action.*;
|
import docking.action.KeyBindingData;
|
||||||
import docking.tool.ToolConstants;
|
import docking.tool.ToolConstants;
|
||||||
import docking.widgets.label.GIconLabel;
|
import docking.widgets.label.GIconLabel;
|
||||||
import generic.theme.GThemeDefaults.Colors.Messages;
|
import generic.theme.GThemeDefaults.Colors.Messages;
|
||||||
import generic.util.action.ReservedKeyBindings;
|
|
||||||
import ghidra.util.HelpLocation;
|
import ghidra.util.HelpLocation;
|
||||||
import resources.Icons;
|
import resources.Icons;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dialog to set the key binding on an action; it is popped up when the F4 key
|
* Dialog to set the key binding on an action. It is triggered by the F4 key.
|
||||||
* is hit.
|
|
||||||
*/
|
*/
|
||||||
public class KeyEntryDialog extends DialogComponentProvider {
|
public class KeyEntryDialog extends DialogComponentProvider {
|
||||||
|
|
||||||
|
private KeyBindings keyBindings;
|
||||||
private ToolActions toolActions;
|
private ToolActions toolActions;
|
||||||
private DockingActionIf action;
|
private DockingActionIf action;
|
||||||
|
|
||||||
private JPanel defaultPanel;
|
private JPanel defaultPanel;
|
||||||
private KeyEntryTextField keyEntryField;
|
private KeyEntryTextField keyEntryField;
|
||||||
private JTextPane collisionPane;
|
private JTextPane collisionPane;
|
||||||
private StyledDocument doc;
|
private StyledDocument doc;
|
||||||
private SimpleAttributeSet tabAttrSet;
|
|
||||||
private SimpleAttributeSet textAttrSet;
|
private SimpleAttributeSet textAttrSet;
|
||||||
private Color bgColor;
|
private Color bgColor;
|
||||||
|
|
||||||
public KeyEntryDialog(DockingActionIf action, ToolActions actions) {
|
public KeyEntryDialog(Tool tool, DockingActionIf action) {
|
||||||
super("Set Key Binding for " + action.getName(), true);
|
super("Set Key Binding for " + action.getName(), true);
|
||||||
this.action = action;
|
this.action = action;
|
||||||
this.toolActions = actions;
|
this.toolActions = (ToolActions) tool.getToolActions();
|
||||||
|
|
||||||
|
this.keyBindings = new KeyBindings(tool);
|
||||||
|
|
||||||
setUpAttributes();
|
setUpAttributes();
|
||||||
createPanel();
|
createPanel();
|
||||||
KeyStroke keyBinding = action.getKeyBinding();
|
KeyStroke keyBinding = action.getKeyBinding();
|
||||||
|
@ -150,21 +151,22 @@ public class KeyEntryDialog extends DialogComponentProvider {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void okCallback() {
|
protected void okCallback() {
|
||||||
KeyStroke newKeyStroke = keyEntryField.getKeyStroke();
|
KeyStroke newKs = keyEntryField.getKeyStroke();
|
||||||
if (newKeyStroke != null && ReservedKeyBindings.isReservedKeystroke(newKeyStroke)) {
|
String errorMessage = toolActions.validateActionKeyBinding(action, newKs);
|
||||||
setStatusText(keyEntryField.getText() + " is a reserved keystroke");
|
if (errorMessage != null) {
|
||||||
|
setStatusText(errorMessage);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
clearStatusText();
|
clearStatusText();
|
||||||
|
|
||||||
KeyStroke existingKeyStroke = action.getKeyBinding();
|
KeyStroke existingKeyStroke = action.getKeyBinding();
|
||||||
if (Objects.equals(existingKeyStroke, newKeyStroke)) {
|
if (Objects.equals(existingKeyStroke, newKs)) {
|
||||||
setStatusText("Key binding unchanged");
|
setStatusText("Key binding unchanged");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
action.setUnvalidatedKeyBindingData(new KeyBindingData(newKeyStroke));
|
action.setUnvalidatedKeyBindingData(new KeyBindingData(newKs));
|
||||||
|
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
|
@ -174,10 +176,6 @@ public class KeyEntryDialog extends DialogComponentProvider {
|
||||||
textAttrSet.addAttribute(StyleConstants.FontFamily, "Tahoma");
|
textAttrSet.addAttribute(StyleConstants.FontFamily, "Tahoma");
|
||||||
textAttrSet.addAttribute(StyleConstants.FontSize, Integer.valueOf(11));
|
textAttrSet.addAttribute(StyleConstants.FontSize, Integer.valueOf(11));
|
||||||
textAttrSet.addAttribute(StyleConstants.Foreground, Messages.NORMAL);
|
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) {
|
private void updateCollisionPane(KeyStroke ks) {
|
||||||
|
@ -193,26 +191,10 @@ public class KeyEntryDialog extends DialogComponentProvider {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<DockingActionIf> 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 ksName = KeyBindingUtils.parseKeyStroke(ks);
|
||||||
|
String text = keyBindings.getActionsForKeyStrokeText(ksName);
|
||||||
try {
|
try {
|
||||||
doc.insertString(0, "Actions mapped to " + ksName + "\n\n", textAttrSet);
|
doc.insertString(0, text, 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);
|
|
||||||
}
|
|
||||||
collisionPane.setCaretPosition(0);
|
collisionPane.setCaretPosition(0);
|
||||||
}
|
}
|
||||||
catch (BadLocationException e) {
|
catch (BadLocationException e) {
|
||||||
|
@ -220,37 +202,4 @@ public class KeyEntryDialog extends DialogComponentProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<DockingActionIf> getManagedActionsForKeyStroke(KeyStroke keyStroke) {
|
|
||||||
MultipleKeyAction multiAction = getMultipleKeyAction(keyStroke);
|
|
||||||
if (multiAction == null) {
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<DockingActionIf> list = multiAction.getActions();
|
|
||||||
Map<String, DockingActionIf> 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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,19 +17,22 @@ package docking.actions;
|
||||||
|
|
||||||
import java.awt.Component;
|
import java.awt.Component;
|
||||||
|
|
||||||
import docking.ActionContext;
|
import javax.swing.KeyStroke;
|
||||||
import docking.DockingWindowManager;
|
|
||||||
|
import docking.*;
|
||||||
import docking.action.*;
|
import docking.action.*;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
|
|
||||||
public class KeyBindingAction extends DockingAction {
|
public class SetKeyBindingAction extends DockingAction {
|
||||||
|
|
||||||
public static String NAME = "Set KeyBinding";
|
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);
|
super(NAME, DockingWindowManager.DOCKING_WINDOWS_OWNER);
|
||||||
this.toolActions = toolActions;
|
this.tool = tool;
|
||||||
|
|
||||||
|
setKeyBindingData(new KeyBindingData(keyStroke));
|
||||||
|
|
||||||
// Help actions don't have help
|
// Help actions don't have help
|
||||||
DockingWindowManager.getHelpService().excludeFromHelp(this);
|
DockingWindowManager.getHelpService().excludeFromHelp(this);
|
||||||
|
@ -56,7 +59,7 @@ public class KeyBindingAction extends DockingAction {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyEntryDialog d = new KeyEntryDialog(action, toolActions);
|
KeyEntryDialog d = new KeyEntryDialog(tool, action);
|
||||||
DockingWindowManager.showDialog(d);
|
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
|
// It is not key binding managed, which means that it may be a shared key binding
|
||||||
String actionName = dockingAction.getName();
|
String actionName = dockingAction.getName();
|
||||||
|
ToolActions toolActions = (ToolActions) tool.getToolActions();
|
||||||
DockingActionIf sharedAction = toolActions.getSharedStubKeyBindingAction(actionName);
|
DockingActionIf sharedAction = toolActions.getSharedStubKeyBindingAction(actionName);
|
||||||
if (sharedAction != null) {
|
if (sharedAction != null) {
|
||||||
return sharedAction;
|
return sharedAction;
|
|
@ -15,6 +15,8 @@
|
||||||
*/
|
*/
|
||||||
package docking.actions;
|
package docking.actions;
|
||||||
|
|
||||||
|
import static generic.util.action.SystemKeyBindings.*;
|
||||||
|
|
||||||
import java.beans.PropertyChangeEvent;
|
import java.beans.PropertyChangeEvent;
|
||||||
import java.beans.PropertyChangeListener;
|
import java.beans.PropertyChangeListener;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
@ -32,7 +34,6 @@ import org.apache.commons.collections4.map.LazyMap;
|
||||||
import docking.*;
|
import docking.*;
|
||||||
import docking.action.*;
|
import docking.action.*;
|
||||||
import docking.tool.util.DockingToolConstants;
|
import docking.tool.util.DockingToolConstants;
|
||||||
import generic.util.action.ReservedKeyBindings;
|
|
||||||
import ghidra.framework.options.*;
|
import ghidra.framework.options.*;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
import ghidra.util.exception.AssertException;
|
import ghidra.util.exception.AssertException;
|
||||||
|
@ -60,7 +61,7 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener {
|
||||||
private Map<String, SharedStubKeyBindingAction> sharedActionMap = new HashMap<>();
|
private Map<String, SharedStubKeyBindingAction> sharedActionMap = new HashMap<>();
|
||||||
|
|
||||||
private ToolOptions keyBindingOptions;
|
private ToolOptions keyBindingOptions;
|
||||||
private Tool dockingTool;
|
private Tool tool;
|
||||||
private KeyBindingsManager keyBindingsManager;
|
private KeyBindingsManager keyBindingsManager;
|
||||||
private OptionsChangeListener optionChangeListener = (options, optionName, oldValue,
|
private OptionsChangeListener optionChangeListener = (options, optionName, oldValue,
|
||||||
newValue) -> updateKeyBindingsFromOptions(options, optionName, (KeyStroke) newValue);
|
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
|
* @param actionToGuiHelper the class that takes actions and maps them to GUI widgets
|
||||||
*/
|
*/
|
||||||
public ToolActions(Tool tool, ActionToGuiHelper actionToGuiHelper) {
|
public ToolActions(Tool tool, ActionToGuiHelper actionToGuiHelper) {
|
||||||
this.dockingTool = tool;
|
this.tool = tool;
|
||||||
this.actionGuiHelper = actionToGuiHelper;
|
this.actionGuiHelper = actionToGuiHelper;
|
||||||
this.keyBindingsManager = new KeyBindingsManager(tool);
|
this.keyBindingsManager = new KeyBindingsManager(tool);
|
||||||
this.keyBindingOptions = tool.getOptions(DockingToolConstants.KEY_BINDINGS);
|
this.keyBindingOptions = tool.getOptions(DockingToolConstants.KEY_BINDINGS);
|
||||||
this.keyBindingOptions.addOptionsChangeListener(optionChangeListener);
|
this.keyBindingOptions.addOptionsChangeListener(optionChangeListener);
|
||||||
|
|
||||||
createReservedKeyBindings();
|
createSystemActions();
|
||||||
SharedActionRegistry.installSharedActions(tool, this);
|
SharedActionRegistry.installSharedActions(tool, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createReservedKeyBindings() {
|
private void createSystemActions() {
|
||||||
KeyBindingAction keyBindingAction = new KeyBindingAction(this);
|
|
||||||
keyBindingsManager.addReservedAction(keyBindingAction,
|
|
||||||
ReservedKeyBindings.UPDATE_KEY_BINDINGS_KEY);
|
|
||||||
|
|
||||||
keyBindingsManager.addReservedAction(new HelpAction(false, ReservedKeyBindings.HELP_KEY1));
|
addSystemAction(new SetKeyBindingAction(tool, UPDATE_KEY_BINDINGS_KEY));
|
||||||
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));
|
|
||||||
|
|
||||||
keyBindingsManager.addReservedAction(
|
addSystemAction(new HelpAction(HELP_KEY1, false));
|
||||||
new NextPreviousWindowAction(ReservedKeyBindings.FOCUS_NEXT_WINDOW_KEY, true));
|
addSystemAction(new HelpAction(HELP_KEY2, true));
|
||||||
keyBindingsManager.addReservedAction(
|
addSystemAction(new HelpInfoAction(HELP_INFO_KEY));
|
||||||
new NextPreviousWindowAction(ReservedKeyBindings.FOCUS_PREVIOUS_WINDOW_KEY, false));
|
addSystemAction(new ShowContextMenuAction(CONTEXT_MENU_KEY1, true));
|
||||||
|
addSystemAction(new ShowContextMenuAction(CONTEXT_MENU_KEY2, false));
|
||||||
|
|
||||||
keyBindingsManager.addReservedAction(
|
addSystemAction(new NextPreviousWindowAction(FOCUS_NEXT_WINDOW_KEY, true));
|
||||||
new GlobalFocusTraversalAction(ReservedKeyBindings.FOCUS_NEXT_COMPONENT_KEY, true));
|
addSystemAction(new NextPreviousWindowAction(FOCUS_PREVIOUS_WINDOW_KEY, false));
|
||||||
keyBindingsManager.addReservedAction(
|
|
||||||
new GlobalFocusTraversalAction(ReservedKeyBindings.FOCUS_PREVIOUS_COMPONENT_KEY,
|
addSystemAction(new GlobalFocusTraversalAction(FOCUS_NEXT_COMPONENT_KEY, true));
|
||||||
false));
|
addSystemAction(new GlobalFocusTraversalAction(FOCUS_PREVIOUS_COMPONENT_KEY, false));
|
||||||
|
|
||||||
// helpful debugging actions
|
// helpful debugging actions
|
||||||
keyBindingsManager.addReservedAction(new ShowFocusInfoAction());
|
addSystemAction(new ShowFocusInfoAction());
|
||||||
keyBindingsManager.addReservedAction(new ShowFocusCycleAction());
|
addSystemAction(new ShowFocusCycleAction());
|
||||||
keyBindingsManager.addReservedAction(new ComponentThemeInspectorAction());
|
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() {
|
public void dispose() {
|
||||||
|
@ -170,7 +175,6 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadKeyBindingFromOptions(DockingActionIf action, KeyStroke ks) {
|
private void loadKeyBindingFromOptions(DockingActionIf action, KeyStroke ks) {
|
||||||
|
|
||||||
String description = "Keybinding for " + action.getFullName();
|
String description = "Keybinding for " + action.getFullName();
|
||||||
keyBindingOptions.registerOption(action.getFullName(), OptionType.KEYSTROKE_TYPE, ks, null,
|
keyBindingOptions.registerOption(action.getFullName(), OptionType.KEYSTROKE_TYPE, ks, null,
|
||||||
description);
|
description);
|
||||||
|
@ -288,6 +292,8 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener {
|
||||||
|
|
||||||
result.addAll(sharedActionMap.values());
|
result.addAll(sharedActionMap.values());
|
||||||
|
|
||||||
|
result.addAll(keyBindingsManager.getSystemActions());
|
||||||
|
|
||||||
return result;
|
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.
|
* otherwise the options will be removed because they are noted as not being used.
|
||||||
*/
|
*/
|
||||||
public synchronized void restoreKeyBindings() {
|
public synchronized void restoreKeyBindings() {
|
||||||
keyBindingOptions = dockingTool.getOptions(DockingToolConstants.KEY_BINDINGS);
|
keyBindingOptions = tool.getOptions(DockingToolConstants.KEY_BINDINGS);
|
||||||
|
|
||||||
Iterator<DockingActionIf> it = getKeyBindingActionsIterator();
|
Iterator<DockingActionIf> it = getKeyBindingActionsIterator();
|
||||||
for (DockingActionIf action : CollectionUtils.asIterable(it)) {
|
for (DockingActionIf action : CollectionUtils.asIterable(it)) {
|
||||||
|
@ -406,8 +412,7 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
DockingActionIf action = (DockingActionIf) evt.getSource();
|
DockingActionIf action = (DockingActionIf) evt.getSource();
|
||||||
if (!action.getKeyBindingType()
|
if (!action.getKeyBindingType().isManaged()) {
|
||||||
.isManaged()) {
|
|
||||||
// this reads unusually, but we need to notify the tool to rebuild its 'Window' menu
|
// 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
|
// in the case that this action is one of the tool's special actions
|
||||||
keyBindingsChanged();
|
keyBindingsChanged();
|
||||||
|
@ -429,7 +434,7 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener {
|
||||||
|
|
||||||
// triggered by a user-initiated action; called by propertyChange()
|
// triggered by a user-initiated action; called by propertyChange()
|
||||||
private void keyBindingsChanged() {
|
private void keyBindingsChanged() {
|
||||||
dockingTool.setConfigChanged(true);
|
tool.setConfigChanged(true);
|
||||||
actionGuiHelper.keyBindingsChanged();
|
actionGuiHelper.keyBindingsChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -447,6 +452,17 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener {
|
||||||
return null;
|
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) {
|
public Action getAction(KeyStroke ks) {
|
||||||
return keyBindingsManager.getDockingKeyAction(ks);
|
return keyBindingsManager.getDockingKeyAction(ks);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -54,6 +54,9 @@
|
||||||
<logger name="ghidra.framework.project.DefaultProject" level="WARN"/>
|
<logger name="ghidra.framework.project.DefaultProject" level="WARN"/>
|
||||||
<logger name="ghidra.framework.project.DefaultProjectManager" level="INFO"/>
|
<logger name="ghidra.framework.project.DefaultProjectManager" level="INFO"/>
|
||||||
|
|
||||||
|
<!-- Ignore warnings about missing content classes in test env -->
|
||||||
|
<logger name="ghidra.framework.project.tool.GhidraToolTemplate" level="ERROR"/>
|
||||||
|
|
||||||
<logger name="functioncalls" level="DEBUG" />
|
<logger name="functioncalls" level="DEBUG" />
|
||||||
<logger name="generic.random" level="WARN"/>
|
<logger name="generic.random" level="WARN"/>
|
||||||
<logger name="ghidra.app.plugin.core.progmgr.ProgramManagerPlugin" level="WARN"/>
|
<logger name="ghidra.app.plugin.core.progmgr.ProgramManagerPlugin" level="WARN"/>
|
||||||
|
|
|
@ -53,6 +53,9 @@
|
||||||
<logger name="ghidra.framework.project.DefaultProject" level="WARN"/>
|
<logger name="ghidra.framework.project.DefaultProject" level="WARN"/>
|
||||||
<logger name="ghidra.framework.project.DefaultProjectManager" level="INFO"/>
|
<logger name="ghidra.framework.project.DefaultProjectManager" level="INFO"/>
|
||||||
|
|
||||||
|
<!-- Ignore warnings about missing content classes in test env -->
|
||||||
|
<logger name="ghidra.framework.project.tool.GhidraToolTemplate" level="ERROR"/>
|
||||||
|
|
||||||
<logger name="functioncalls" level="DEBUG" />
|
<logger name="functioncalls" level="DEBUG" />
|
||||||
<logger name="generic.random" level="WARN"/>
|
<logger name="generic.random" level="WARN"/>
|
||||||
<logger name="ghidra.app.plugin.core.progmgr.ProgramManagerPlugin" level="WARN"/>
|
<logger name="ghidra.app.plugin.core.progmgr.ProgramManagerPlugin" level="WARN"/>
|
||||||
|
|
|
@ -660,12 +660,26 @@ public class AbstractGuiTest extends AbstractGenericTest {
|
||||||
* @param s the supplier
|
* @param s the supplier
|
||||||
* @return the value returned by the supplier
|
* @return the value returned by the supplier
|
||||||
*/
|
*/
|
||||||
public static <T> T runSwing(Supplier<T> s) {
|
public static <T> T getSwing(Supplier<T> s) {
|
||||||
AtomicReference<T> ref = new AtomicReference<>();
|
AtomicReference<T> ref = new AtomicReference<>();
|
||||||
runSwing(() -> ref.set(s.get()));
|
runSwing(() -> ref.set(s.get()));
|
||||||
return ref.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> T runSwing(Supplier<T> s) {
|
||||||
|
return getSwing(s);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run the given code snippet on the Swing thread and wait for it to finish
|
* Run the given code snippet on the Swing thread and wait for it to finish
|
||||||
* @param r the runnable code snippet
|
* @param r the runnable code snippet
|
||||||
|
|
|
@ -1260,7 +1260,6 @@ public class DefaultProjectData implements ProjectData {
|
||||||
public void close() {
|
public void close() {
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
if (!closed) {
|
if (!closed) {
|
||||||
Msg.debug(this, "Closing ProjectData: " + projectDir);
|
|
||||||
closed = true;
|
closed = true;
|
||||||
}
|
}
|
||||||
if (inUseCount != 0) {
|
if (inUseCount != 0) {
|
||||||
|
@ -1301,8 +1300,6 @@ public class DefaultProjectData implements ProjectData {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Msg.debug(this, "Disposing ProjectData: " + projectDir);
|
|
||||||
|
|
||||||
closed = true;
|
closed = true;
|
||||||
disposed = true;
|
disposed = true;
|
||||||
|
|
||||||
|
|
|
@ -20,30 +20,28 @@ import java.beans.PropertyChangeEvent;
|
||||||
import java.beans.PropertyChangeListener;
|
import java.beans.PropertyChangeListener;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map.Entry;
|
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import javax.swing.event.ListSelectionEvent;
|
import javax.swing.event.ListSelectionEvent;
|
||||||
import javax.swing.event.ListSelectionListener;
|
import javax.swing.event.ListSelectionListener;
|
||||||
import javax.swing.table.TableColumn;
|
import javax.swing.table.TableColumn;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
import docking.DockingUtils;
|
import docking.DockingUtils;
|
||||||
import docking.KeyEntryTextField;
|
import docking.KeyEntryTextField;
|
||||||
import docking.action.DockingActionIf;
|
import docking.action.DockingActionIf;
|
||||||
import docking.action.KeyBindingData;
|
import docking.actions.*;
|
||||||
import docking.actions.KeyBindingUtils;
|
|
||||||
import docking.tool.util.DockingToolConstants;
|
import docking.tool.util.DockingToolConstants;
|
||||||
import docking.widgets.*;
|
import docking.widgets.*;
|
||||||
import docking.widgets.label.GIconLabel;
|
import docking.widgets.label.GIconLabel;
|
||||||
import docking.widgets.table.*;
|
import docking.widgets.table.*;
|
||||||
import generic.theme.Gui;
|
import generic.theme.Gui;
|
||||||
import generic.util.action.ReservedKeyBindings;
|
|
||||||
import ghidra.framework.options.Options;
|
import ghidra.framework.options.Options;
|
||||||
import ghidra.framework.options.ToolOptions;
|
import ghidra.framework.options.ToolOptions;
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
import ghidra.util.HTMLUtilities;
|
import ghidra.util.HTMLUtilities;
|
||||||
import ghidra.util.Swing;
|
import ghidra.util.Swing;
|
||||||
import ghidra.util.exception.AssertException;
|
|
||||||
import ghidra.util.layout.PairLayout;
|
import ghidra.util.layout.PairLayout;
|
||||||
import ghidra.util.layout.VerticalLayout;
|
import ghidra.util.layout.VerticalLayout;
|
||||||
import help.Help;
|
import help.Help;
|
||||||
|
@ -68,31 +66,25 @@ public class KeyBindingsPanel extends JPanel {
|
||||||
private JPanel infoPanel;
|
private JPanel infoPanel;
|
||||||
private MultiLineLabel collisionLabel;
|
private MultiLineLabel collisionLabel;
|
||||||
private KeyBindingsTableModel tableModel;
|
private KeyBindingsTableModel tableModel;
|
||||||
private ListSelectionModel selectionModel;
|
|
||||||
private Options options;
|
|
||||||
|
|
||||||
private Map<String, List<DockingActionIf>> actionsByFullName;
|
|
||||||
private Map<String, List<String>> actionNamesByKeyStroke = new HashMap<>();
|
|
||||||
private Map<String, KeyStroke> keyStrokesByFullName = new HashMap<>();
|
|
||||||
private Map<String, KeyStroke> originalValues = new HashMap<>(); // to know what has been changed
|
|
||||||
private List<DockingActionIf> tableActions = new ArrayList<>();
|
|
||||||
|
|
||||||
private KeyEntryTextField ksField;
|
private KeyEntryTextField ksField;
|
||||||
|
private GTableFilterPanel<DockingActionIf> tableFilterPanel;
|
||||||
|
private EmptyBorderButton helpButton;
|
||||||
|
|
||||||
|
private KeyBindings keyBindings;
|
||||||
private boolean unappliedChanges;
|
private boolean unappliedChanges;
|
||||||
|
|
||||||
private PluginTool tool;
|
private PluginTool tool;
|
||||||
private boolean firingTableDataChanged;
|
private boolean firingTableDataChanged;
|
||||||
private PropertyChangeListener propertyChangeListener;
|
private PropertyChangeListener propertyChangeListener;
|
||||||
private GTableFilterPanel<DockingActionIf> tableFilterPanel;
|
|
||||||
private EmptyBorderButton helpButton;
|
|
||||||
|
|
||||||
public KeyBindingsPanel(PluginTool tool, Options options) {
|
public KeyBindingsPanel(PluginTool tool) {
|
||||||
this.tool = tool;
|
this.tool = tool;
|
||||||
this.options = options;
|
|
||||||
|
this.keyBindings = new KeyBindings(tool);
|
||||||
|
|
||||||
createPanelComponents();
|
createPanelComponents();
|
||||||
createActionMap();
|
|
||||||
addListeners();
|
initializeTableWidth();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setOptionsPropertyChangeListener(PropertyChangeListener listener) {
|
public void setOptionsPropertyChangeListener(PropertyChangeListener listener) {
|
||||||
|
@ -105,83 +97,32 @@ public class KeyBindingsPanel extends JPanel {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void apply() {
|
public void apply() {
|
||||||
Iterator<String> iter = keyStrokesByFullName.keySet().iterator();
|
keyBindings.applyChanges();
|
||||||
while (iter.hasNext()) {
|
|
||||||
String actionName = iter.next();
|
|
||||||
KeyStroke currentKeyStroke = keyStrokesByFullName.get(actionName);
|
|
||||||
KeyStroke originalKeyStroke = originalValues.get(actionName);
|
|
||||||
updateOptions(actionName, originalKeyStroke, currentKeyStroke);
|
|
||||||
}
|
|
||||||
|
|
||||||
changesMade(false);
|
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<DockingActionIf> actions = actionsByFullName.get(fullActionName);
|
|
||||||
for (DockingActionIf action : actions) {
|
|
||||||
action.setUnvalidatedKeyBindingData(new KeyBindingData(newKeyStroke));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void cancel() {
|
public void cancel() {
|
||||||
Iterator<String> iter = originalValues.keySet().iterator();
|
keyBindings.cancelChanges();
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tableModel.fireTableDataChanged();
|
tableModel.fireTableDataChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void reload() {
|
public void reload() {
|
||||||
Swing.runLater(() -> {
|
Swing.runLater(() -> {
|
||||||
// clear the current user key stroke so that it does not appear as though the
|
// clear the action to avoid the appearance of editing while restoring
|
||||||
// user is editing while restoring
|
|
||||||
actionTable.clearSelection();
|
actionTable.clearSelection();
|
||||||
|
|
||||||
restoreDefaultKeybindings();
|
restoreDefaultKeybindings();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createActionMap() {
|
public String getStatusText() {
|
||||||
|
return statusLabel.getText();
|
||||||
String longestName = "";
|
|
||||||
|
|
||||||
actionsByFullName = KeyBindingUtils.getAllActionsByFullName(tool);
|
|
||||||
Set<Entry<String, List<DockingActionIf>>> entries = actionsByFullName.entrySet();
|
|
||||||
for (Entry<String, List<DockingActionIf>> entry : entries) {
|
|
||||||
|
|
||||||
// pick one action, they are all conceptually the same
|
|
||||||
List<DockingActionIf> 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void initializeTableWidth() {
|
||||||
|
|
||||||
|
String longestName = keyBindings.getLongestActionName();
|
||||||
|
|
||||||
Font f = actionTable.getFont();
|
Font f = actionTable.getFont();
|
||||||
FontMetrics fm = actionTable.getFontMetrics(f);
|
FontMetrics fm = actionTable.getFontMetrics(f);
|
||||||
int maxWidth = 0;
|
int maxWidth = 0;
|
||||||
|
@ -197,7 +138,7 @@ public class KeyBindingsPanel extends JPanel {
|
||||||
private void createPanelComponents() {
|
private void createPanelComponents() {
|
||||||
setLayout(new BorderLayout(10, 10));
|
setLayout(new BorderLayout(10, 10));
|
||||||
|
|
||||||
tableModel = new KeyBindingsTableModel();
|
tableModel = new KeyBindingsTableModel(new ArrayList<>(keyBindings.getUniqueActions()));
|
||||||
actionTable = new GTable(tableModel);
|
actionTable = new GTable(tableModel);
|
||||||
|
|
||||||
JScrollPane sp = new JScrollPane(actionTable);
|
JScrollPane sp = new JScrollPane(actionTable);
|
||||||
|
@ -225,6 +166,8 @@ public class KeyBindingsPanel extends JPanel {
|
||||||
|
|
||||||
add(centerPanel, BorderLayout.CENTER);
|
add(centerPanel, BorderLayout.CENTER);
|
||||||
add(statusPanel, BorderLayout.SOUTH);
|
add(statusPanel, BorderLayout.SOUTH);
|
||||||
|
|
||||||
|
actionTable.getSelectionModel().addListSelectionListener(new TableSelectionListener());
|
||||||
}
|
}
|
||||||
|
|
||||||
private JPanel createStatusPanel(JPanel keyPanel) {
|
private JPanel createStatusPanel(JPanel keyPanel) {
|
||||||
|
@ -264,7 +207,7 @@ public class KeyBindingsPanel extends JPanel {
|
||||||
}
|
}
|
||||||
|
|
||||||
private JPanel createKeyEntryPanel() {
|
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
|
// this is the lower panel that holds the key entry text field
|
||||||
JPanel p = new JPanel(new FlowLayout(FlowLayout.LEFT));
|
JPanel p = new JPanel(new FlowLayout(FlowLayout.LEFT));
|
||||||
|
@ -326,10 +269,9 @@ public class KeyBindingsPanel extends JPanel {
|
||||||
|
|
||||||
// give Swing a chance to repaint
|
// give Swing a chance to repaint
|
||||||
Swing.runLater(() -> {
|
Swing.runLater(() -> {
|
||||||
// clear the current user key stroke so that it does not appear as though the
|
// clear the action to avoid the appearance of editing while restoring
|
||||||
// user is editing while importing
|
|
||||||
actionTable.clearSelection();
|
actionTable.clearSelection();
|
||||||
processKeyBindingsFromOptions(KeyBindingUtils.importKeyBindings());
|
loadKeyBindingsFromImportedOptions(KeyBindingUtils.importKeyBindings());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -338,7 +280,7 @@ public class KeyBindingsPanel extends JPanel {
|
||||||
exportButton.addActionListener(event -> {
|
exportButton.addActionListener(event -> {
|
||||||
|
|
||||||
// prompt user to apply changes before exporting
|
// prompt user to apply changes before exporting
|
||||||
boolean continueExport = showExportPrompt();
|
boolean continueExport = showApplyPrompt();
|
||||||
|
|
||||||
if (!continueExport) {
|
if (!continueExport) {
|
||||||
return;
|
return;
|
||||||
|
@ -358,7 +300,7 @@ public class KeyBindingsPanel extends JPanel {
|
||||||
return containerPanel;
|
return containerPanel;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean showExportPrompt() {
|
private boolean showApplyPrompt() {
|
||||||
boolean continueOperation = true;
|
boolean continueOperation = true;
|
||||||
if (unappliedChanges) {
|
if (unappliedChanges) {
|
||||||
int userChoice = OptionDialog.showYesNoCancelDialog(KeyBindingsPanel.this,
|
int userChoice = OptionDialog.showYesNoCancelDialog(KeyBindingsPanel.this,
|
||||||
|
@ -390,14 +332,11 @@ public class KeyBindingsPanel extends JPanel {
|
||||||
private Map<String, KeyStroke> createActionNameToKeyStrokeMap(Options keyBindingOptions) {
|
private Map<String, KeyStroke> createActionNameToKeyStrokeMap(Options keyBindingOptions) {
|
||||||
|
|
||||||
Map<String, KeyStroke> localActionMap = new HashMap<>();
|
Map<String, KeyStroke> localActionMap = new HashMap<>();
|
||||||
|
|
||||||
List<String> optionNames = keyBindingOptions.getOptionNames();
|
List<String> optionNames = keyBindingOptions.getOptionNames();
|
||||||
|
for (String name : optionNames) {
|
||||||
for (String element : optionNames) {
|
KeyStroke newKeyStroke = keyBindingOptions.getKeyStroke(name, null);
|
||||||
KeyStroke newKeyStroke = keyBindingOptions.getKeyStroke(element, null);
|
localActionMap.put(name, newKeyStroke);
|
||||||
localActionMap.put(element, newKeyStroke);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return localActionMap;
|
return localActionMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -415,52 +354,12 @@ public class KeyBindingsPanel extends JPanel {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void restoreDefaultKeybindings() {
|
private void restoreDefaultKeybindings() {
|
||||||
Iterator<String> iter = keyStrokesByFullName.keySet().iterator();
|
keyBindings.restoreOptions();
|
||||||
while (iter.hasNext()) {
|
|
||||||
String actionName = iter.next();
|
|
||||||
List<DockingActionIf> 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
// let the table know that changes may have been made
|
// let the table know that changes may have been made
|
||||||
tableModel.fireTableDataChanged();
|
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
|
// signals that there are unapplied changes
|
||||||
private void changesMade(boolean changes) {
|
private void changesMade(boolean changes) {
|
||||||
propertyChangeListener.propertyChange(
|
propertyChangeListener.propertyChange(
|
||||||
|
@ -469,12 +368,11 @@ public class KeyBindingsPanel extends JPanel {
|
||||||
}
|
}
|
||||||
|
|
||||||
private DockingActionIf getSelectedAction() {
|
private DockingActionIf getSelectedAction() {
|
||||||
if (selectionModel.isSelectionEmpty()) {
|
if (actionTable.getSelectedRowCount() == 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
int selectedRow = actionTable.getSelectedRow();
|
int selectedRow = actionTable.getSelectedRow();
|
||||||
int modelRow = tableFilterPanel.getModelRow(selectedRow);
|
return tableFilterPanel.getRowObject(selectedRow);
|
||||||
return tableActions.get(modelRow);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getSelectedActionName() {
|
private String getSelectedActionName() {
|
||||||
|
@ -485,62 +383,20 @@ public class KeyBindingsPanel extends JPanel {
|
||||||
return action.getFullName();
|
return action.getFullName();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addToKeyMap(KeyStroke ks, String actionName) {
|
|
||||||
if (ks == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
String ksName = KeyEntryTextField.parseKeyStroke(ks);
|
|
||||||
List<String> 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<String> list = actionNamesByKeyStroke.get(ksName);
|
|
||||||
if (list != null) {
|
|
||||||
list.remove(actionName);
|
|
||||||
if (list.isEmpty()) {
|
|
||||||
actionNamesByKeyStroke.remove(ksName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showActionsMappedToKeyStroke(String ksName) {
|
private void showActionsMappedToKeyStroke(String ksName) {
|
||||||
List<String> list = actionNamesByKeyStroke.get(ksName);
|
|
||||||
if (list == null) {
|
String text = keyBindings.getActionsForKeyStrokeText(ksName);
|
||||||
return;
|
if (StringUtils.isBlank(text)) {
|
||||||
}
|
text = " ";
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
updateCollisionPanel(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void clearInfoPanel() {
|
private void clearInfoPanel() {
|
||||||
updateInfoPanel(" ");
|
updateCollisionPanel(" ");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateInfoPanel(String text) {
|
private void updateCollisionPanel(String text) {
|
||||||
infoPanel.removeAll();
|
infoPanel.removeAll();
|
||||||
infoPanel.repaint();
|
infoPanel.repaint();
|
||||||
collisionLabel = new MultiLineLabel(text);
|
collisionLabel = new MultiLineLabel(text);
|
||||||
|
@ -550,111 +406,94 @@ public class KeyBindingsPanel extends JPanel {
|
||||||
validate();
|
validate();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processKeyBindingsFromOptions(Options keyBindingOptions) {
|
private void loadKeyBindingsFromImportedOptions(Options keyBindingOptions) {
|
||||||
if (keyBindingOptions == null) {
|
if (keyBindingOptions == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, KeyStroke> keyBindingsMap = createActionNameToKeyStrokeMap(keyBindingOptions);
|
Map<String, KeyStroke> keyStrokesByActionName =
|
||||||
if (keyBindingsMap == null) {
|
createActionNameToKeyStrokeMap(keyBindingOptions);
|
||||||
|
if (keyStrokesByActionName == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean changes = false;
|
boolean changes = false;
|
||||||
|
|
||||||
// add each new key stroke mapping
|
// add each new key stroke mapping
|
||||||
Iterator<String> iterator = keyBindingsMap.keySet().iterator();
|
for (String name : keyStrokesByActionName.keySet()) {
|
||||||
while (iterator.hasNext()) {
|
|
||||||
|
|
||||||
String name = iterator.next();
|
KeyStroke keyStroke = keyStrokesByActionName.get(name);
|
||||||
KeyStroke keyStroke = keyBindingsMap.get(name);
|
|
||||||
keyStroke = KeyBindingUtils.validateKeyStroke(keyStroke);
|
keyStroke = KeyBindingUtils.validateKeyStroke(keyStroke);
|
||||||
|
|
||||||
// prevent non-existing keybindings from being added to Ghidra (this can happen
|
// prevent non-existing keybindings from being added (this can happen when actions exist
|
||||||
// when actions exist in the imported bindings, but have been removed from
|
// in the imported bindings, but have been removed from the tool
|
||||||
// Ghidra
|
if (!keyBindings.containsAction(name)) {
|
||||||
if (!keyStrokesByFullName.containsKey(name)) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// check to see if the key stroke results in a change and
|
// check to see if the key stroke results in a change and
|
||||||
// record that value
|
// record that value
|
||||||
changes |= processKeyStroke(name, keyStroke);
|
changes |= setActionKeyStroke(name, keyStroke);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (changes) {
|
if (changes) {
|
||||||
changesMade(true);
|
changesMade(true);
|
||||||
|
tableModel.fireTableDataChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Processes KeyStroke entry from the text field.
|
* Processes KeyStroke entry from the text field.
|
||||||
*/
|
*/
|
||||||
private void processKeyStrokeEntry(KeyStroke ks) {
|
private void keyStrokeChanged(KeyStroke ks) {
|
||||||
clearInfoPanel();
|
clearInfoPanel();
|
||||||
|
|
||||||
// An action must be selected
|
DockingActionIf action = getSelectedAction();
|
||||||
if (selectionModel.isSelectionEmpty()) {
|
if (action == null) {
|
||||||
statusLabel.setText("No action is selected.");
|
statusLabel.setText("No action is selected.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ks != null && ReservedKeyBindings.isReservedKeystroke(ks)) {
|
ToolActions toolActions = (ToolActions) tool.getToolActions();
|
||||||
statusLabel.setText(KeyEntryTextField.parseKeyStroke(ks) + " is a reserved keystroke");
|
String errorMessage = toolActions.validateActionKeyBinding(action, ks);
|
||||||
|
if (errorMessage != null) {
|
||||||
|
statusLabel.setText(errorMessage);
|
||||||
ksField.clearField();
|
ksField.clearField();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String selectedActionName = getSelectedActionName();
|
String selectedActionName = getSelectedActionName();
|
||||||
if (selectedActionName != null) {
|
if (selectedActionName != null) {
|
||||||
if (processKeyStroke(selectedActionName, ks)) {
|
if (setActionKeyStroke(selectedActionName, ks)) {
|
||||||
String keyStrokeText = KeyEntryTextField.parseKeyStroke(ks);
|
String keyStrokeText = KeyBindingUtils.parseKeyStroke(ks);
|
||||||
showActionsMappedToKeyStroke(keyStrokeText);
|
showActionsMappedToKeyStroke(keyStrokeText);
|
||||||
tableModel.fireTableDataChanged();
|
tableModel.fireTableDataChanged();
|
||||||
|
changesMade(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns true if the key stroke is a new value
|
// returns true if the key stroke is a new value
|
||||||
private boolean processKeyStroke(String actionName, KeyStroke keyStroke) {
|
private boolean setActionKeyStroke(String actionName, KeyStroke keyStroke) {
|
||||||
// Clear entry if enter or backspace
|
if (!isValidKeyStroke(keyStroke)) {
|
||||||
if (keyStroke == null) {
|
ksField.setText("");
|
||||||
removeKeystroke(actionName);
|
return keyBindings.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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return keyBindings.setActionKeyStroke(actionName, keyStroke);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isValidKeyStroke(KeyStroke ks) {
|
||||||
|
if (ks == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
char keyChar = ks.getKeyChar();
|
||||||
private void removeKeystroke(String selectedActionName) {
|
return !Character.isWhitespace(keyChar) &&
|
||||||
ksField.setText("");
|
Character.getType(keyChar) != Character.DIRECTIONALITY_LEFT_TO_RIGHT_OVERRIDE;
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, KeyStroke> getKeyStrokeMap() {
|
Map<String, KeyStroke> getKeyStrokeMap() {
|
||||||
return keyStrokesByFullName;
|
return keyBindings.getKeyStrokesByFullActionName();
|
||||||
}
|
}
|
||||||
|
|
||||||
//==================================================================================================
|
//==================================================================================================
|
||||||
|
@ -678,12 +517,12 @@ public class KeyBindingsPanel extends JPanel {
|
||||||
}
|
}
|
||||||
|
|
||||||
helpButton.setEnabled(true);
|
helpButton.setEnabled(true);
|
||||||
KeyStroke ks = keyStrokesByFullName.get(fullActionName);
|
KeyStroke ks = keyBindings.getKeyStroke(fullActionName);
|
||||||
String ksName = "";
|
String ksName = "";
|
||||||
clearInfoPanel();
|
clearInfoPanel();
|
||||||
|
|
||||||
if (ks != null) {
|
if (ks != null) {
|
||||||
ksName = KeyEntryTextField.parseKeyStroke(ks);
|
ksName = KeyBindingUtils.parseKeyStroke(ks);
|
||||||
showActionsMappedToKeyStroke(ksName);
|
showActionsMappedToKeyStroke(ksName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -693,9 +532,7 @@ public class KeyBindingsPanel extends JPanel {
|
||||||
statusLabel.setPreferredSize(
|
statusLabel.setPreferredSize(
|
||||||
new Dimension(statusLabel.getPreferredSize().width, STATUS_LABEL_HEIGHT));
|
new Dimension(statusLabel.getPreferredSize().width, STATUS_LABEL_HEIGHT));
|
||||||
|
|
||||||
// pick one action, they are all conceptually the same
|
DockingActionIf action = getSelectedAction();
|
||||||
List<DockingActionIf> actions = actionsByFullName.get(fullActionName);
|
|
||||||
DockingActionIf action = actions.get(0);
|
|
||||||
String description = action.getDescription();
|
String description = action.getDescription();
|
||||||
if (description == null || description.trim().isEmpty()) {
|
if (description == null || description.trim().isEmpty()) {
|
||||||
description = action.getName();
|
description = action.getName();
|
||||||
|
@ -709,8 +546,11 @@ public class KeyBindingsPanel extends JPanel {
|
||||||
private final String[] columnNames =
|
private final String[] columnNames =
|
||||||
{ "Action Name", "KeyBinding", "Plugin Name" };
|
{ "Action Name", "KeyBinding", "Plugin Name" };
|
||||||
|
|
||||||
KeyBindingsTableModel() {
|
private List<DockingActionIf> actions;
|
||||||
|
|
||||||
|
KeyBindingsTableModel(List<DockingActionIf> actions) {
|
||||||
super(0);
|
super(0);
|
||||||
|
this.actions = actions;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -725,9 +565,9 @@ public class KeyBindingsPanel extends JPanel {
|
||||||
case ACTION_NAME:
|
case ACTION_NAME:
|
||||||
return action.getName();
|
return action.getName();
|
||||||
case KEY_BINDING:
|
case KEY_BINDING:
|
||||||
KeyStroke ks = keyStrokesByFullName.get(action.getFullName());
|
KeyStroke ks = keyBindings.getKeyStroke(action.getFullName());
|
||||||
if (ks != null) {
|
if (ks != null) {
|
||||||
return KeyEntryTextField.parseKeyStroke(ks);
|
return KeyBindingUtils.parseKeyStroke(ks);
|
||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
case PLUGIN_NAME:
|
case PLUGIN_NAME:
|
||||||
|
@ -738,7 +578,7 @@ public class KeyBindingsPanel extends JPanel {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<DockingActionIf> getModelData() {
|
public List<DockingActionIf> getModelData() {
|
||||||
return tableActions;
|
return actions;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -758,7 +598,7 @@ public class KeyBindingsPanel extends JPanel {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getRowCount() {
|
public int getRowCount() {
|
||||||
return tableActions.size();
|
return actions.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -271,7 +271,7 @@ public class OptionsManager implements OptionsService, OptionsChangeListener {
|
||||||
private KeyBindingsPanel panel;
|
private KeyBindingsPanel panel;
|
||||||
|
|
||||||
KeyBindingOptionsEditor() {
|
KeyBindingOptionsEditor() {
|
||||||
panel = new KeyBindingsPanel(tool, getOptions(DockingToolConstants.KEY_BINDINGS));
|
panel = new KeyBindingsPanel(tool);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -95,19 +95,11 @@ public class GhidraToolTemplate implements ToolTemplate {
|
||||||
return iconURL;
|
return iconURL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a hash code value for the object. This method is
|
|
||||||
* supported for the benefit of hashtables such as those provided by
|
|
||||||
* <code>java.util.Hashtable</code>.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return getName().hashCode();
|
return getName().hashCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates whether some other object is "equal to" this one.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object obj) {
|
public boolean equals(Object obj) {
|
||||||
if (obj == null) {
|
if (obj == null) {
|
||||||
|
@ -129,13 +121,6 @@ public class GhidraToolTemplate implements ToolTemplate {
|
||||||
return getName().equals(otherTemplate.getName());
|
return getName().equals(otherTemplate.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a string representation of the object. In general, the
|
|
||||||
* <code>toString</code> 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
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return getName() + " - " + path;
|
return getName() + " - " + path;
|
||||||
|
@ -154,7 +139,7 @@ public class GhidraToolTemplate implements ToolTemplate {
|
||||||
catch (ClassNotFoundException e) {
|
catch (ClassNotFoundException e) {
|
||||||
Msg.warn(this, "Tool supported content class not found: " + className);
|
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);
|
Msg.error(this, "Unexpected Exception: " + exc.getMessage(), exc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,6 @@ import docking.DialogComponentProvider;
|
||||||
import docking.StatusBar;
|
import docking.StatusBar;
|
||||||
import docking.action.DockingActionIf;
|
import docking.action.DockingActionIf;
|
||||||
import docking.actions.KeyEntryDialog;
|
import docking.actions.KeyEntryDialog;
|
||||||
import docking.actions.ToolActions;
|
|
||||||
import docking.options.OptionsService;
|
import docking.options.OptionsService;
|
||||||
import docking.tool.ToolConstants;
|
import docking.tool.ToolConstants;
|
||||||
import docking.widgets.OptionDialog;
|
import docking.widgets.OptionDialog;
|
||||||
|
@ -288,10 +287,8 @@ public class ToolScreenShots extends GhidraScreenShotGenerator {
|
||||||
public void testSetKeyBindings() {
|
public void testSetKeyBindings() {
|
||||||
|
|
||||||
tool = env.launchDefaultTool();
|
tool = env.launchDefaultTool();
|
||||||
ToolActions toolActions = (ToolActions) getInstanceField("toolActions", tool);
|
|
||||||
|
|
||||||
DockingActionIf action = getAction(tool, "FunctionPlugin", "Delete Function");
|
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);
|
runSwing(() -> tool.showDialog(keyEntryDialog), false);
|
||||||
captureDialog();
|
captureDialog();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue