mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-03 17:59:46 +02:00
GP-4436 - Mouse Bindings
This commit is contained in:
parent
eca5195dea
commit
8aeebf919a
61 changed files with 3136 additions and 919 deletions
|
@ -21,6 +21,7 @@ import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import javax.swing.Icon;
|
import javax.swing.Icon;
|
||||||
|
import javax.swing.KeyStroke;
|
||||||
|
|
||||||
import docking.ActionContext;
|
import docking.ActionContext;
|
||||||
import docking.action.*;
|
import docking.action.*;
|
||||||
|
@ -39,6 +40,7 @@ import ghidra.app.services.NavigationHistoryService;
|
||||||
import ghidra.app.util.HelpTopics;
|
import ghidra.app.util.HelpTopics;
|
||||||
import ghidra.app.util.viewer.field.BrowserCodeUnitFormat;
|
import ghidra.app.util.viewer.field.BrowserCodeUnitFormat;
|
||||||
import ghidra.framework.model.DomainFile;
|
import ghidra.framework.model.DomainFile;
|
||||||
|
import ghidra.framework.options.ActionTrigger;
|
||||||
import ghidra.framework.plugintool.*;
|
import ghidra.framework.plugintool.*;
|
||||||
import ghidra.framework.plugintool.util.PluginStatus;
|
import ghidra.framework.plugintool.util.PluginStatus;
|
||||||
import ghidra.program.model.address.Address;
|
import ghidra.program.model.address.Address;
|
||||||
|
@ -46,6 +48,7 @@ import ghidra.program.model.listing.*;
|
||||||
import ghidra.program.model.symbol.Symbol;
|
import ghidra.program.model.symbol.Symbol;
|
||||||
import ghidra.program.model.symbol.SymbolTable;
|
import ghidra.program.model.symbol.SymbolTable;
|
||||||
import ghidra.util.HelpLocation;
|
import ghidra.util.HelpLocation;
|
||||||
|
import gui.event.MouseBinding;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <CODE>NextPrevAddressPlugin</CODE> allows the user to go back and forth in
|
* <CODE>NextPrevAddressPlugin</CODE> allows the user to go back and forth in
|
||||||
|
@ -83,7 +86,7 @@ public class NextPrevAddressPlugin extends Plugin {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new instance of the plugin
|
* Creates a new instance of the plugin
|
||||||
*
|
*
|
||||||
* @param tool the tool
|
* @param tool the tool
|
||||||
*/
|
*/
|
||||||
public NextPrevAddressPlugin(PluginTool tool) {
|
public NextPrevAddressPlugin(PluginTool tool) {
|
||||||
|
@ -119,7 +122,7 @@ public class NextPrevAddressPlugin extends Plugin {
|
||||||
|
|
||||||
//==================================================================================================
|
//==================================================================================================
|
||||||
// Private Methods
|
// Private Methods
|
||||||
//==================================================================================================
|
//==================================================================================================
|
||||||
|
|
||||||
private List<DockingActionIf> getPreviousActions(Navigatable navigatable) {
|
private List<DockingActionIf> getPreviousActions(Navigatable navigatable) {
|
||||||
Program lastProgram = null;
|
Program lastProgram = null;
|
||||||
|
@ -297,6 +300,9 @@ public class NextPrevAddressPlugin extends Plugin {
|
||||||
|
|
||||||
private class NextPreviousAction extends MultiActionDockingAction {
|
private class NextPreviousAction extends MultiActionDockingAction {
|
||||||
|
|
||||||
|
private static final int MOUSE_BUTTON_4 = 4;
|
||||||
|
private static final int MOUSE_BUTTON_5 = 5;
|
||||||
|
|
||||||
private final boolean isNext;
|
private final boolean isNext;
|
||||||
|
|
||||||
NextPreviousAction(String name, String owner, boolean isNext) {
|
NextPreviousAction(String name, String owner, boolean isNext) {
|
||||||
|
@ -306,8 +312,15 @@ public class NextPrevAddressPlugin extends Plugin {
|
||||||
setToolBarData(new ToolBarData(isNext ? NEXT_ICON : PREVIOUS_ICON,
|
setToolBarData(new ToolBarData(isNext ? NEXT_ICON : PREVIOUS_ICON,
|
||||||
ToolConstants.TOOLBAR_GROUP_TWO));
|
ToolConstants.TOOLBAR_GROUP_TWO));
|
||||||
setHelpLocation(new HelpLocation(HelpTopics.NAVIGATION, name));
|
setHelpLocation(new HelpLocation(HelpTopics.NAVIGATION, name));
|
||||||
int keycode = isNext ? KeyEvent.VK_RIGHT : KeyEvent.VK_LEFT;
|
|
||||||
setKeyBindingData(new KeyBindingData(keycode, InputEvent.ALT_DOWN_MASK));
|
int keyCode = isNext ? KeyEvent.VK_RIGHT : KeyEvent.VK_LEFT;
|
||||||
|
KeyStroke keyStroke = KeyStroke.getKeyStroke(keyCode, InputEvent.ALT_DOWN_MASK);
|
||||||
|
|
||||||
|
int mouseButton = isNext ? MOUSE_BUTTON_5 : MOUSE_BUTTON_4;
|
||||||
|
MouseBinding mouseBinding = new MouseBinding(mouseButton);
|
||||||
|
|
||||||
|
setKeyBindingData(new KeyBindingData(new ActionTrigger(keyStroke, mouseBinding)));
|
||||||
|
|
||||||
setDescription(isNext ? "Go to next location" : "Go to previous location");
|
setDescription(isNext ? "Go to next location" : "Go to previous location");
|
||||||
addToWindowWhen(NavigatableActionContext.class);
|
addToWindowWhen(NavigatableActionContext.class);
|
||||||
}
|
}
|
||||||
|
|
|
@ -345,7 +345,8 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
|
||||||
plugin.getTool().setStatusInfo("User cancelled keybinding.");
|
plugin.getTool().setStatusInfo("User cancelled keybinding.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
action.setKeyBindingData(new KeyBindingData(dialog.getKeyStroke()));
|
KeyStroke newKs = dialog.getKeyStroke();
|
||||||
|
action.setKeyBindingData(newKs == null ? null : new KeyBindingData(newKs));
|
||||||
scriptTable.repaint();
|
scriptTable.repaint();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -90,6 +90,7 @@ class KeyBindingInputDialog extends DialogComponentProvider implements KeyEntryL
|
||||||
}
|
}
|
||||||
|
|
||||||
void setKeyStroke(KeyStroke ks) {
|
void setKeyStroke(KeyStroke ks) {
|
||||||
|
this.ks = ks;
|
||||||
kbField.setKeyStroke(ks);
|
kbField.setKeyStroke(ks);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,10 +85,13 @@ class ScriptAction extends DockingAction {
|
||||||
}
|
}
|
||||||
|
|
||||||
private KeyBindingData checkForFallbackKeybindingCondition(KeyBindingData keyBindingData) {
|
private KeyBindingData checkForFallbackKeybindingCondition(KeyBindingData keyBindingData) {
|
||||||
KeyStroke newKeyStroke = keyBindingData.getKeyBinding();
|
|
||||||
if (newKeyStroke != null) {
|
if (keyBindingData != null) {
|
||||||
// we have a valid value; the current keybinding data is what we want
|
KeyStroke newKeyStroke = keyBindingData.getKeyBinding();
|
||||||
return keyBindingData;
|
if (newKeyStroke != null) {
|
||||||
|
// we have a valid value; the current keybinding data is what we want
|
||||||
|
return keyBindingData;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// check to see if we have a fallback value
|
// check to see if we have a fallback value
|
||||||
|
@ -106,7 +109,10 @@ class ScriptAction extends DockingAction {
|
||||||
private void updateUserDefinedKeybindingStatus(KeyBindingData keyBindingData) {
|
private void updateUserDefinedKeybindingStatus(KeyBindingData keyBindingData) {
|
||||||
// we have a user defined keybinding if the keystroke for the action differs from
|
// we have a user defined keybinding if the keystroke for the action differs from
|
||||||
// that which is defined in the metadata of the script
|
// that which is defined in the metadata of the script
|
||||||
KeyStroke actionKeyStroke = keyBindingData.getKeyBinding();
|
KeyStroke actionKeyStroke = null;
|
||||||
|
if (keyBindingData != null) {
|
||||||
|
actionKeyStroke = keyBindingData.getKeyBinding();
|
||||||
|
}
|
||||||
ScriptInfo info = infoManager.getExistingScriptInfo(script);
|
ScriptInfo info = infoManager.getExistingScriptInfo(script);
|
||||||
KeyStroke metadataKeyBinding = info.getKeyBinding();
|
KeyStroke metadataKeyBinding = info.getKeyBinding();
|
||||||
isUserDefinedKeyBinding = !SystemUtilities.isEqual(actionKeyStroke, metadataKeyBinding);
|
isUserDefinedKeyBinding = !SystemUtilities.isEqual(actionKeyStroke, metadataKeyBinding);
|
||||||
|
@ -128,8 +134,9 @@ class ScriptAction extends DockingAction {
|
||||||
ScriptInfo info = infoManager.getScriptInfo(script);
|
ScriptInfo info = infoManager.getScriptInfo(script);
|
||||||
KeyStroke stroke = info.getKeyBinding();
|
KeyStroke stroke = info.getKeyBinding();
|
||||||
if (!isUserDefinedKeyBinding) {
|
if (!isUserDefinedKeyBinding) {
|
||||||
setKeyBindingData(new KeyBindingData(stroke));
|
setKeyBindingData(stroke == null ? null : new KeyBindingData(stroke));
|
||||||
}
|
}
|
||||||
|
|
||||||
Icon icon = info.getToolBarImage(false);
|
Icon icon = info.getToolBarImage(false);
|
||||||
if (icon != null) {
|
if (icon != null) {
|
||||||
ToolBarData data = getToolBarData();
|
ToolBarData data = getToolBarData();
|
||||||
|
|
|
@ -588,8 +588,8 @@ public abstract class GhidraScript extends FlatProgramAPI {
|
||||||
if (isRunningHeadless()) {
|
if (isRunningHeadless()) {
|
||||||
// only change client authenticator in headless mode
|
// only change client authenticator in headless mode
|
||||||
try {
|
try {
|
||||||
HeadlessClientAuthenticator
|
HeadlessClientAuthenticator.installHeadlessClientAuthenticator(
|
||||||
.installHeadlessClientAuthenticator(ClientUtil.getUserName(), null, false);
|
ClientUtil.getUserName(), null, false);
|
||||||
}
|
}
|
||||||
catch (IOException e) {
|
catch (IOException e) {
|
||||||
throw new RuntimeException("Unexpected Exception", e);
|
throw new RuntimeException("Unexpected Exception", e);
|
||||||
|
@ -1316,6 +1316,7 @@ public abstract class GhidraScript extends FlatProgramAPI {
|
||||||
case FILE_TYPE:
|
case FILE_TYPE:
|
||||||
case FONT_TYPE:
|
case FONT_TYPE:
|
||||||
case KEYSTROKE_TYPE:
|
case KEYSTROKE_TYPE:
|
||||||
|
case ACTION_TRIGGER:
|
||||||
// do nothing; don't allow user to set these options (doesn't make any sense)
|
// do nothing; don't allow user to set these options (doesn't make any sense)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -2333,22 +2334,22 @@ public abstract class GhidraScript extends FlatProgramAPI {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prompts for multiple values at the same time. To use this method, you must first
|
* Prompts for multiple values at the same time. To use this method, you must first
|
||||||
* create a {@link GhidraValuesMap} and define the values that will be supplied by this method.
|
* create a {@link GhidraValuesMap} and define the values that will be supplied by this method.
|
||||||
* In the GUI environment, this will result in a single dialog with an entry for each value
|
* In the GUI environment, this will result in a single dialog with an entry for each value
|
||||||
* defined in the values map. This method returns a GhidraValuesMap with the values supplied by
|
* defined in the values map. This method returns a GhidraValuesMap with the values supplied by
|
||||||
* the user in GUI mode or command line arguments in headless mode. If the user cancels the
|
* the user in GUI mode or command line arguments in headless mode. If the user cancels the
|
||||||
* dialog, a cancelled exception will be thrown, and unless it is explicity caught by the
|
* dialog, a cancelled exception will be thrown, and unless it is explicity caught by the
|
||||||
* script, will terminate the script. Also, if the values map has a {@link ValuesMapValidator},
|
* script, will terminate the script. Also, if the values map has a {@link ValuesMapValidator},
|
||||||
* the values will be validated when the user presses the "OK" button and will only exit the
|
* the values will be validated when the user presses the "OK" button and will only exit the
|
||||||
* dialog if the validate check passes. Otherwise, the validator should have reported an error
|
* dialog if the validate check passes. Otherwise, the validator should have reported an error
|
||||||
* message in the dialog and the dialog will remain visible.
|
* message in the dialog and the dialog will remain visible.
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* Regardless of environment -- if script arguments have been set, this method will use the
|
* Regardless of environment -- if script arguments have been set, this method will use the
|
||||||
* next arguments in the array and advance the array index until all values in the values map
|
* next arguments in the array and advance the array index until all values in the values map
|
||||||
* have been satisfied and so the next call to an ask method will get the next argument after
|
* have been satisfied and so the next call to an ask method will get the next argument after
|
||||||
* those consumed by this call.
|
* those consumed by this call.
|
||||||
*
|
*
|
||||||
* @param title the title of the dialog if in GUI mode
|
* @param title the title of the dialog if in GUI mode
|
||||||
* @param optionalMessage an optional message that is displayed in the dialog, just above the
|
* @param optionalMessage an optional message that is displayed in the dialog, just above the
|
||||||
* list of name/value pairs
|
* list of name/value pairs
|
||||||
|
@ -2616,7 +2617,7 @@ public abstract class GhidraScript extends FlatProgramAPI {
|
||||||
* (in headless mode or when using .properties file)
|
* (in headless mode or when using .properties file)
|
||||||
* @param message the message to display next to the input field (in GUI mode) or the
|
* @param message the message to display next to the input field (in GUI mode) or the
|
||||||
* second part of the variable name (in headless mode or when using .properties file)
|
* second part of the variable name (in headless mode or when using .properties file)
|
||||||
* @param defaultValue the optional default address as a String - if null is passed or an invalid
|
* @param defaultValue the optional default address as a String - if null is passed or an invalid
|
||||||
* address is given no default will be shown in dialog
|
* address is given no default will be shown in dialog
|
||||||
* @return the user-specified Address value
|
* @return the user-specified Address value
|
||||||
* @throws CancelledException if the user hit the 'cancel' button in GUI mode
|
* @throws CancelledException if the user hit the 'cancel' button in GUI mode
|
||||||
|
@ -2759,14 +2760,14 @@ public abstract class GhidraScript extends FlatProgramAPI {
|
||||||
*
|
*
|
||||||
* @param title the title of the pop-up dialog (in GUI mode) or the variable name (in
|
* @param title the title of the pop-up dialog (in GUI mode) or the variable name (in
|
||||||
* headless mode)
|
* headless mode)
|
||||||
* @return the user-selected Program with this script as the consumer if a program was
|
* @return the user-selected Program with this script as the consumer if a program was
|
||||||
* selected. Null is returned if a program is not selected. NOTE: It is very important that
|
* selected. Null is returned if a program is not selected. NOTE: It is very important that
|
||||||
* the program instance returned by this method ALWAYS be properly released when no longer
|
* the program instance returned by this method ALWAYS be properly released when no longer
|
||||||
* needed. The script which invoked this method must be
|
* needed. The script which invoked this method must be
|
||||||
* specified as the consumer upon release (i.e., {@code program.release(this) } - failure to
|
* specified as the consumer upon release (i.e., {@code program.release(this) } - failure to
|
||||||
* properly release the program may result in improper project disposal. If the program was
|
* properly release the program may result in improper project disposal. If the program was
|
||||||
* opened by the tool, the tool will be a second consumer responsible for its own release.
|
* opened by the tool, the tool will be a second consumer responsible for its own release.
|
||||||
* @throws VersionException if the Program is out-of-date from the version of Ghidra and an
|
* @throws VersionException if the Program is out-of-date from the version of Ghidra and an
|
||||||
* upgrade was not been performed. In non-headless mode, the user will have already been
|
* upgrade was not been performed. In non-headless mode, the user will have already been
|
||||||
* notified via a popup dialog.
|
* notified via a popup dialog.
|
||||||
* @throws IOException if there is an error accessing the Program's DomainObject
|
* @throws IOException if there is an error accessing the Program's DomainObject
|
||||||
|
@ -2781,7 +2782,7 @@ public abstract class GhidraScript extends FlatProgramAPI {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a Program, using the title parameter for guidance with the option to upgrade
|
* Returns a Program, using the title parameter for guidance with the option to upgrade
|
||||||
* if needed. The actual behavior of the method depends on your environment, which can be
|
* if needed. The actual behavior of the method depends on your environment, which can be
|
||||||
* GUI or headless. You can control whether or not the program is allowed to upgrade via
|
* GUI or headless. You can control whether or not the program is allowed to upgrade via
|
||||||
* the {@code upgradeIfNeeded} parameter.
|
* the {@code upgradeIfNeeded} parameter.
|
||||||
* <br>
|
* <br>
|
||||||
|
@ -2811,14 +2812,14 @@ public abstract class GhidraScript extends FlatProgramAPI {
|
||||||
* @param upgradeIfNeeded if true, program will be upgraded if needed and possible. If false,
|
* @param upgradeIfNeeded if true, program will be upgraded if needed and possible. If false,
|
||||||
* the program will only be upgraded after first prompting the user. In headless mode, it will
|
* the program will only be upgraded after first prompting the user. In headless mode, it will
|
||||||
* attempt to upgrade only if the parameter is true.
|
* attempt to upgrade only if the parameter is true.
|
||||||
* @return the user-selected Program with this script as the consumer if a program was
|
* @return the user-selected Program with this script as the consumer if a program was
|
||||||
* selected. Null is returned if a program is not selected. NOTE: It is very important that
|
* selected. Null is returned if a program is not selected. NOTE: It is very important that
|
||||||
* the program instance returned by this method ALWAYS be properly released when no longer
|
* the program instance returned by this method ALWAYS be properly released when no longer
|
||||||
* needed. The script which invoked this method must be
|
* needed. The script which invoked this method must be
|
||||||
* specified as the consumer upon release (i.e., {@code program.release(this) } - failure to
|
* specified as the consumer upon release (i.e., {@code program.release(this) } - failure to
|
||||||
* properly release the program may result in improper project disposal. If the program was
|
* properly release the program may result in improper project disposal. If the program was
|
||||||
* opened by the tool, the tool will be a second consumer responsible for its own release.
|
* opened by the tool, the tool will be a second consumer responsible for its own release.
|
||||||
* @throws VersionException if the Program is out-of-date from the version of GHIDRA and an
|
* @throws VersionException if the Program is out-of-date from the version of GHIDRA and an
|
||||||
* upgrade was not been performed. In non-headless mode, the user will have already been
|
* upgrade was not been performed. In non-headless mode, the user will have already been
|
||||||
* notified via a popup dialog.
|
* notified via a popup dialog.
|
||||||
* @throws IOException if there is an error accessing the Program's DomainObject
|
* @throws IOException if there is an error accessing the Program's DomainObject
|
||||||
|
@ -3139,13 +3140,13 @@ public abstract class GhidraScript extends FlatProgramAPI {
|
||||||
* only be used in headed mode.
|
* only be used in headed mode.
|
||||||
* <p>
|
* <p>
|
||||||
* In the GUI environment, this method displays a password popup dialog that prompts the user
|
* In the GUI environment, this method displays a password popup dialog that prompts the user
|
||||||
* for a password. There is no pre-population of the input. If the user cancels the dialog, it
|
* for a password. There is no pre-population of the input. If the user cancels the dialog, it
|
||||||
* is immediately disposed, and any input to that dialog is cleared from memory. If the user
|
* is immediately disposed, and any input to that dialog is cleared from memory. If the user
|
||||||
* completes the dialog, then the password is returned in a wrapped buffer. The buffer can be
|
* completes the dialog, then the password is returned in a wrapped buffer. The buffer can be
|
||||||
* cleared by calling {@link Password#close()}; however, it is meant to be used in a
|
* cleared by calling {@link Password#close()}; however, it is meant to be used in a
|
||||||
* {@code try-with-resources} block. The pattern does not guarantee protection of the password,
|
* {@code try-with-resources} block. The pattern does not guarantee protection of the password,
|
||||||
* but it will help you avoid some typical pitfalls:
|
* but it will help you avoid some typical pitfalls:
|
||||||
*
|
*
|
||||||
* <pre>
|
* <pre>
|
||||||
* String user = askString("Login", "Username:");
|
* String user = askString("Login", "Username:");
|
||||||
* Project project;
|
* Project project;
|
||||||
|
@ -3153,12 +3154,12 @@ public abstract class GhidraScript extends FlatProgramAPI {
|
||||||
* project = doLoginAndOpenProject(user, password.getPasswordChars());
|
* project = doLoginAndOpenProject(user, password.getPasswordChars());
|
||||||
* }
|
* }
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
* The buffer will be zero-filled upon leaving the {@code try-with-resources} block. If, in the
|
* The buffer will be zero-filled upon leaving the {@code try-with-resources} block. If, in the
|
||||||
* sample, the {@code doLoginAndOpenProject} method or any part of its implementation needs to
|
* sample, the {@code doLoginAndOpenProject} method or any part of its implementation needs to
|
||||||
* retain the password, it must make a copy. It is then the implementation's responsibility to
|
* retain the password, it must make a copy. It is then the implementation's responsibility to
|
||||||
* protect its copy.
|
* protect its copy.
|
||||||
*
|
*
|
||||||
* @param title the title of the dialog
|
* @param title the title of the dialog
|
||||||
* @param prompt the prompt to the left of the input field, or null to display "Password:"
|
* @param prompt the prompt to the left of the input field, or null to display "Password:"
|
||||||
* @return the password
|
* @return the password
|
||||||
|
@ -3639,10 +3640,10 @@ public abstract class GhidraScript extends FlatProgramAPI {
|
||||||
* null is returned. For more control over the import process, {@link AutoImporter} may be
|
* null is returned. For more control over the import process, {@link AutoImporter} may be
|
||||||
* directly called.
|
* directly called.
|
||||||
* <p>
|
* <p>
|
||||||
* NOTE: The returned {@link Program} is not automatically saved into the current project.
|
* NOTE: The returned {@link Program} is not automatically saved into the current project.
|
||||||
* <p>
|
* <p>
|
||||||
* NOTE: It is the responsibility of the script that calls this method to release the returned
|
* NOTE: It is the responsibility of the script that calls this method to release the returned
|
||||||
* {@link Program} with {@link DomainObject#release(Object consumer)} when it is no longer
|
* {@link Program} with {@link DomainObject#release(Object consumer)} when it is no longer
|
||||||
* needed, where <code>consumer</code> is <code>this</code>.
|
* needed, where <code>consumer</code> is <code>this</code>.
|
||||||
*
|
*
|
||||||
* @param file the file to import
|
* @param file the file to import
|
||||||
|
@ -3662,11 +3663,11 @@ public abstract class GhidraScript extends FlatProgramAPI {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Imports the specified file as raw binary. For more control over the import process,
|
* Imports the specified file as raw binary. For more control over the import process,
|
||||||
* {@link AutoImporter} may be directly called.
|
* {@link AutoImporter} may be directly called.
|
||||||
* <p>
|
* <p>
|
||||||
* NOTE: It is the responsibility of the script that calls this method to release the returned
|
* NOTE: It is the responsibility of the script that calls this method to release the returned
|
||||||
* {@link Program} with {@link DomainObject#release(Object consumer)} when it is no longer
|
* {@link Program} with {@link DomainObject#release(Object consumer)} when it is no longer
|
||||||
* needed, where <code>consumer</code> is <code>this</code>.
|
* needed, where <code>consumer</code> is <code>this</code>.
|
||||||
*
|
*
|
||||||
* @param file the file to import
|
* @param file the file to import
|
||||||
|
|
|
@ -18,13 +18,13 @@ package ghidra.app.script;
|
||||||
import org.apache.logging.log4j.message.Message;
|
import org.apache.logging.log4j.message.Message;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A simple {@link Message} implementation that allows us to use the filtering capability
|
* A simple {@link Message} implementation that allows us to use the filtering capability
|
||||||
* of log4j. This class has a formatted and unformatted message. log4j writes the the formatted
|
* of log4j. This class has a formatted and unformatted message. log4j writes the the formatted
|
||||||
* message out. Our formatted message is the original message given to us. We use the
|
* message out. Our formatted message is the original message given to us. We use the
|
||||||
* unformatted message, in conjunction with a regex filter to allow for filtering such that
|
* unformatted message, in conjunction with a regex filter to allow for filtering such that
|
||||||
* the script log file only has script messages.
|
* the script log file only has script messages.
|
||||||
*
|
*
|
||||||
* <P>See logj4-appender-rolling-file-scripts.xml
|
* <P>See log4j-appender-rolling-file-scripts.xml
|
||||||
*/
|
*/
|
||||||
public class ScriptMessage implements Message {
|
public class ScriptMessage implements Message {
|
||||||
|
|
||||||
|
|
|
@ -166,7 +166,7 @@ class PropertiesXmlMgr {
|
||||||
strMap.add(addr, str);
|
strMap.add(addr, str);
|
||||||
}
|
}
|
||||||
else if ("bookmarks".equals(type)) {
|
else if ("bookmarks".equals(type)) {
|
||||||
// Must retain for backward compatibility with old Ver-1 Note bookmarks which
|
// Must retain for backward compatibility with old Ver-1 Note bookmarks which
|
||||||
// were saved as simple properties
|
// were saved as simple properties
|
||||||
BookmarkManager bmMgr = program.getBookmarkManager();
|
BookmarkManager bmMgr = program.getBookmarkManager();
|
||||||
if (!overwrite) {
|
if (!overwrite) {
|
||||||
|
@ -254,8 +254,7 @@ class PropertiesXmlMgr {
|
||||||
list.setDate(name, new Date(value));
|
list.setDate(name, new Date(value));
|
||||||
}
|
}
|
||||||
else if ("color".equals(type)) {
|
else if ("color".equals(type)) {
|
||||||
Color color =
|
Color color = ColorUtils.getColor(XmlUtilities.parseInt(element.getAttribute("VALUE")));
|
||||||
ColorUtils.getColor(XmlUtilities.parseInt(element.getAttribute("VALUE")));
|
|
||||||
list.setColor(name, color);
|
list.setColor(name, color);
|
||||||
}
|
}
|
||||||
else if ("file".equals(type)) {
|
else if ("file".equals(type)) {
|
||||||
|
@ -280,7 +279,19 @@ class PropertiesXmlMgr {
|
||||||
String xmlString = XmlUtilities.unEscapeElementEntities(escapedXML);
|
String xmlString = XmlUtilities.unEscapeElementEntities(escapedXML);
|
||||||
KeyStroke keyStroke =
|
KeyStroke keyStroke =
|
||||||
(KeyStroke) OptionType.KEYSTROKE_TYPE.convertStringToObject(xmlString);
|
(KeyStroke) OptionType.KEYSTROKE_TYPE.convertStringToObject(xmlString);
|
||||||
list.setKeyStroke(name, keyStroke);
|
|
||||||
|
ActionTrigger trigger = null;
|
||||||
|
if (keyStroke != null) {
|
||||||
|
trigger = new ActionTrigger(keyStroke);
|
||||||
|
}
|
||||||
|
list.setActionTrigger(name, trigger);
|
||||||
|
}
|
||||||
|
else if ("actionTrigger".equals(type)) {
|
||||||
|
String escapedXML = element.getAttribute("VALUE");
|
||||||
|
String xmlString = XmlUtilities.unEscapeElementEntities(escapedXML);
|
||||||
|
ActionTrigger actionTrigger =
|
||||||
|
(ActionTrigger) OptionType.ACTION_TRIGGER.convertStringToObject(xmlString);
|
||||||
|
list.setActionTrigger(name, actionTrigger);
|
||||||
}
|
}
|
||||||
else if ("custom".equals(type)) {
|
else if ("custom".equals(type)) {
|
||||||
String escapedXML = element.getAttribute("VALUE");
|
String escapedXML = element.getAttribute("VALUE");
|
||||||
|
@ -401,9 +412,15 @@ class PropertiesXmlMgr {
|
||||||
attrs.addAttribute("VALUE", XmlUtilities.escapeElementEntities(xmlString));
|
attrs.addAttribute("VALUE", XmlUtilities.escapeElementEntities(xmlString));
|
||||||
break;
|
break;
|
||||||
case KEYSTROKE_TYPE:
|
case KEYSTROKE_TYPE:
|
||||||
attrs.addAttribute("TYPE", "keyStroke");
|
attrs.addAttribute("TYPE", "actionTrigger");
|
||||||
KeyStroke keyStroke = propList.getKeyStroke(name, null);
|
ActionTrigger trigger = propList.getActionTrigger(name, null);
|
||||||
xmlString = OptionType.KEYSTROKE_TYPE.convertObjectToString(keyStroke);
|
xmlString = OptionType.ACTION_TRIGGER.convertObjectToString(trigger);
|
||||||
|
attrs.addAttribute("VALUE", XmlUtilities.escapeElementEntities(xmlString));
|
||||||
|
break;
|
||||||
|
case ACTION_TRIGGER:
|
||||||
|
attrs.addAttribute("TYPE", "actionTrigger");
|
||||||
|
ActionTrigger actionTrigger = propList.getActionTrigger(name, null);
|
||||||
|
xmlString = OptionType.ACTION_TRIGGER.convertObjectToString(actionTrigger);
|
||||||
attrs.addAttribute("VALUE", XmlUtilities.escapeElementEntities(xmlString));
|
attrs.addAttribute("VALUE", XmlUtilities.escapeElementEntities(xmlString));
|
||||||
break;
|
break;
|
||||||
case CUSTOM_TYPE:
|
case CUSTOM_TYPE:
|
||||||
|
|
|
@ -30,6 +30,7 @@ import docking.actions.KeyEntryDialog;
|
||||||
import docking.actions.ToolActions;
|
import docking.actions.ToolActions;
|
||||||
import docking.tool.util.DockingToolConstants;
|
import docking.tool.util.DockingToolConstants;
|
||||||
import generic.theme.GIcon;
|
import generic.theme.GIcon;
|
||||||
|
import ghidra.framework.options.ActionTrigger;
|
||||||
import ghidra.framework.options.ToolOptions;
|
import ghidra.framework.options.ToolOptions;
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
||||||
|
@ -443,7 +444,8 @@ public class ComponentProviderActionsTest extends AbstractGhidraHeadedIntegratio
|
||||||
ToolOptions keyOptions = tool.getOptions(DockingToolConstants.KEY_BINDINGS);
|
ToolOptions keyOptions = tool.getOptions(DockingToolConstants.KEY_BINDINGS);
|
||||||
|
|
||||||
// shared option name/format: "Provider Name (Shared)" - the shared action's owner is the Tool
|
// shared option name/format: "Provider Name (Shared)" - the shared action's owner is the Tool
|
||||||
runSwing(() -> keyOptions.setKeyStroke(provider.getName() + " (Shared)", newKs));
|
runSwing(() -> keyOptions.setActionTrigger(provider.getName() + " (Shared)",
|
||||||
|
new ActionTrigger(newKs)));
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -491,7 +493,11 @@ public class ComponentProviderActionsTest extends AbstractGhidraHeadedIntegratio
|
||||||
|
|
||||||
// Option name: the action name with the 'Shared' owner
|
// Option name: the action name with the 'Shared' owner
|
||||||
String fullName = provider.getName() + " (Shared)";
|
String fullName = provider.getName() + " (Shared)";
|
||||||
KeyStroke optionsKs = runSwing(() -> options.getKeyStroke(fullName, null));
|
ActionTrigger actionTrigger = runSwing(() -> options.getActionTrigger(fullName, null));
|
||||||
|
KeyStroke optionsKs = null;
|
||||||
|
if (actionTrigger != null) {
|
||||||
|
optionsKs = actionTrigger.getKeyStroke();
|
||||||
|
}
|
||||||
assertEquals("Key stroke in options does not match expected key stroke", expectedKs,
|
assertEquals("Key stroke in options does not match expected key stroke", expectedKs,
|
||||||
optionsKs);
|
optionsKs);
|
||||||
}
|
}
|
||||||
|
|
|
@ -807,6 +807,7 @@ public class CommentsPluginTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
assertNotNull(button);
|
assertNotNull(button);
|
||||||
pressButton(button, false);
|
pressButton(button, false);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
waitForBusyTool(tool);
|
||||||
}
|
}
|
||||||
|
|
||||||
private CommentsDialog editComment(Address a) {
|
private CommentsDialog editComment(Address a) {
|
||||||
|
|
|
@ -36,6 +36,7 @@ import docking.tool.util.DockingToolConstants;
|
||||||
import docking.widgets.table.TableSortState;
|
import docking.widgets.table.TableSortState;
|
||||||
import ghidra.app.nav.Navigatable;
|
import ghidra.app.nav.Navigatable;
|
||||||
import ghidra.app.nav.TestDummyNavigatable;
|
import ghidra.app.nav.TestDummyNavigatable;
|
||||||
|
import ghidra.framework.options.ActionTrigger;
|
||||||
import ghidra.framework.options.ToolOptions;
|
import ghidra.framework.options.ToolOptions;
|
||||||
import ghidra.framework.plugintool.DummyPluginTool;
|
import ghidra.framework.plugintool.DummyPluginTool;
|
||||||
import ghidra.program.database.ProgramBuilder;
|
import ghidra.program.database.ProgramBuilder;
|
||||||
|
@ -466,7 +467,7 @@ public class TableChooserDialogTest extends AbstractGhidraHeadedIntegrationTest
|
||||||
ToolOptions keyOptions = tool.getOptions(DockingToolConstants.KEY_BINDINGS);
|
ToolOptions keyOptions = tool.getOptions(DockingToolConstants.KEY_BINDINGS);
|
||||||
|
|
||||||
String name = action.getName() + " (" + action.getOwner() + ")";
|
String name = action.getName() + " (" + action.getOwner() + ")";
|
||||||
runSwing(() -> keyOptions.setKeyStroke(name, newKs));
|
runSwing(() -> keyOptions.setActionTrigger(name, new ActionTrigger(newKs)));
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
|
||||||
KeyStroke actual = action.getKeyBinding();
|
KeyStroke actual = action.getKeyBinding();
|
||||||
|
|
|
@ -346,9 +346,13 @@ public class ToolPluginOptionsTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
|
|
||||||
private String clearKeyBinding(Options options) {
|
private String clearKeyBinding(Options options) {
|
||||||
String keyBindingName = "Go To Next Function (CodeBrowserPlugin)";
|
String keyBindingName = "Go To Next Function (CodeBrowserPlugin)";
|
||||||
KeyStroke ks = options.getKeyStroke(keyBindingName, null);
|
ActionTrigger actionTrigger = options.getActionTrigger(keyBindingName, null);
|
||||||
|
assertNotNull(actionTrigger);
|
||||||
|
|
||||||
|
KeyStroke ks = actionTrigger.getKeyStroke();
|
||||||
assertNotNull(ks);
|
assertNotNull(ks);
|
||||||
options.setKeyStroke(keyBindingName, null);
|
|
||||||
|
options.setActionTrigger(keyBindingName, null);
|
||||||
return keyBindingName;
|
return keyBindingName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -378,7 +382,12 @@ public class ToolPluginOptionsTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void verifyKeyBindingIsStillCleared(Options options, String optionName) {
|
private void verifyKeyBindingIsStillCleared(Options options, String optionName) {
|
||||||
KeyStroke ksValue = options.getKeyStroke(optionName, null);
|
ActionTrigger actionTrigger = options.getActionTrigger(optionName, null);
|
||||||
|
if (actionTrigger == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyStroke ksValue = actionTrigger.getKeyStroke();
|
||||||
assertNull(ksValue);
|
assertNull(ksValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -48,8 +48,7 @@ import ghidra.app.plugin.core.memory.MemoryMapPlugin;
|
||||||
import ghidra.app.plugin.core.navigation.GoToAddressLabelPlugin;
|
import ghidra.app.plugin.core.navigation.GoToAddressLabelPlugin;
|
||||||
import ghidra.app.plugin.core.navigation.NavigationHistoryPlugin;
|
import ghidra.app.plugin.core.navigation.NavigationHistoryPlugin;
|
||||||
import ghidra.framework.model.ToolServices;
|
import ghidra.framework.model.ToolServices;
|
||||||
import ghidra.framework.options.Options;
|
import ghidra.framework.options.*;
|
||||||
import ghidra.framework.options.ToolOptions;
|
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
import ghidra.framework.plugintool.mgr.OptionsManager;
|
import ghidra.framework.plugintool.mgr.OptionsManager;
|
||||||
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
||||||
|
@ -194,12 +193,11 @@ public class KeyBindingUtilsTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
debug("d");
|
debug("d");
|
||||||
|
|
||||||
// now repeat the above test with changing some values before writing out
|
// now repeat the above test with changing some values before writing out
|
||||||
invokeInstanceMethod("putObject", defaultKeyBindings,
|
defaultKeyBindings.putObject("TestAction1 (Owner1)",
|
||||||
new Class[] { String.class, Object.class },
|
new ActionTrigger(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0)));
|
||||||
new Object[] { "TestAction1 (Owner1)", KeyStroke.getKeyStroke(65, 0) });
|
|
||||||
invokeInstanceMethod("putObject", defaultKeyBindings,
|
defaultKeyBindings.putObject("TestAction2 (Owner 2)",
|
||||||
new Class[] { String.class, Object.class },
|
new ActionTrigger(KeyStroke.getKeyStroke(KeyEvent.VK_B, 0)));
|
||||||
new Object[] { "TestAction2 (Owner 2)", KeyStroke.getKeyStroke(66, 0) });
|
|
||||||
|
|
||||||
debug("e");
|
debug("e");
|
||||||
|
|
||||||
|
@ -366,8 +364,9 @@ public class KeyBindingUtilsTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
|
|
||||||
setKeyBindingsUpDialog(tool);
|
setKeyBindingsUpDialog(tool);
|
||||||
ToolOptions options = tool.getOptions(DockingToolConstants.KEY_BINDINGS);
|
ToolOptions options = tool.getOptions(DockingToolConstants.KEY_BINDINGS);
|
||||||
KeyStroke optionBinding = options.getKeyStroke(action.getFullName(), null);
|
ActionTrigger actionTrigger = options.getActionTrigger(action.getFullName(), null);
|
||||||
assertEquals(appliedBinding, optionBinding);
|
KeyStroke optionKeyStroke = actionTrigger.getKeyStroke();
|
||||||
|
assertEquals(appliedBinding, optionKeyStroke);
|
||||||
|
|
||||||
closeAllWindows();
|
closeAllWindows();
|
||||||
}
|
}
|
||||||
|
@ -429,7 +428,8 @@ public class KeyBindingUtilsTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
// setup our test variables
|
// setup our test variables
|
||||||
panel = (KeyBindingsPanel) getEditorPanel(keyBindingsNode, optionsDialog);
|
panel = (KeyBindingsPanel) getEditorPanel(keyBindingsNode, optionsDialog);
|
||||||
table = findComponent(panel, JTable.class);
|
table = findComponent(panel, JTable.class);
|
||||||
keyField = (JTextField) getInstanceField("ksField", panel);
|
Object actionBindingPanel = getInstanceField("actionBindingPanel", panel);
|
||||||
|
keyField = (JTextField) getInstanceField("keyEntryField", actionBindingPanel);
|
||||||
model = table.getModel();
|
model = table.getModel();
|
||||||
|
|
||||||
debug("ff");
|
debug("ff");
|
||||||
|
@ -518,8 +518,7 @@ public class KeyBindingUtilsTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
String owner = action.getOwnerDescription();
|
String owner = action.getOwnerDescription();
|
||||||
|
|
||||||
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)) && owner.equals(model.getValueAt(i, 2))) {
|
||||||
owner.equals(model.getValueAt(i, 2))) {
|
|
||||||
final int idx = i;
|
final int idx = i;
|
||||||
runSwing(() -> {
|
runSwing(() -> {
|
||||||
table.setRowSelectionInterval(idx, idx);
|
table.setRowSelectionInterval(idx, idx);
|
||||||
|
@ -627,7 +626,12 @@ public class KeyBindingUtilsTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
for (String name : propertyNames) {
|
for (String name : propertyNames) {
|
||||||
|
|
||||||
boolean match = panelKeyStrokeMap.containsKey(name);
|
boolean match = panelKeyStrokeMap.containsKey(name);
|
||||||
KeyStroke optionsKs = oldOptions.getKeyStroke(name, null);
|
ActionTrigger actionTrigger = oldOptions.getActionTrigger(name, null);
|
||||||
|
KeyStroke optionsKs = null;
|
||||||
|
if (actionTrigger != null) {
|
||||||
|
optionsKs = actionTrigger.getKeyStroke();
|
||||||
|
}
|
||||||
|
|
||||||
KeyStroke panelKs = panelKeyStrokeMap.get(name);
|
KeyStroke panelKs = panelKeyStrokeMap.get(name);
|
||||||
|
|
||||||
// if the value is null, then it would not have been placed into the options map
|
// if the value is null, then it would not have been placed into the options map
|
||||||
|
|
|
@ -94,10 +94,8 @@ public class KeyBindingsTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
// look for the info panel
|
// look for the info panel
|
||||||
MultiLineLabel label = findComponent(panel, MultiLineLabel.class);
|
MultiLineLabel label = findComponent(panel, MultiLineLabel.class);
|
||||||
String str = "To add or change a key binding, select an action\n" +
|
String str = "To add or change a key binding, select an action\n" +
|
||||||
"and type any key combination\n" +
|
"and type any key combination\n" + " \n" +
|
||||||
" \n" +
|
"To remove a key binding, select an action and\n" + "press <Enter> or <Backspace>";
|
||||||
"To remove a key binding, select an action and\n" +
|
|
||||||
"press <Enter> or <Backspace>";
|
|
||||||
|
|
||||||
assertEquals(str, label.getLabel());
|
assertEquals(str, label.getLabel());
|
||||||
|
|
||||||
|
@ -215,9 +213,8 @@ public class KeyBindingsTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
// verify that no action is mapped to the new binding
|
// verify that no action is mapped to the new binding
|
||||||
int keyCode = KeyEvent.VK_0;
|
int keyCode = KeyEvent.VK_0;
|
||||||
int modifiers = InputEvent.ALT_DOWN_MASK | InputEvent.ALT_GRAPH_DOWN_MASK;
|
int modifiers = InputEvent.ALT_DOWN_MASK | InputEvent.ALT_GRAPH_DOWN_MASK;
|
||||||
KeyEvent keyEvent =
|
KeyEvent keyEvent = new KeyEvent(dialog, KeyEvent.KEY_PRESSED, System.currentTimeMillis(),
|
||||||
new KeyEvent(dialog, KeyEvent.KEY_PRESSED, System.currentTimeMillis(), modifiers,
|
modifiers, keyCode, KeyEvent.CHAR_UNDEFINED);
|
||||||
keyCode, KeyEvent.CHAR_UNDEFINED);
|
|
||||||
KeyStroke keyStroke = KeyStroke.getKeyStrokeForEvent(keyEvent);
|
KeyStroke keyStroke = KeyStroke.getKeyStrokeForEvent(keyEvent);
|
||||||
DockingWindowManager dwm = DockingWindowManager.getActiveInstance();
|
DockingWindowManager dwm = DockingWindowManager.getActiveInstance();
|
||||||
Action action =
|
Action action =
|
||||||
|
@ -233,8 +230,7 @@ public class KeyBindingsTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
assertEquals(ks, getKeyStroke(action1));
|
assertEquals(ks, getKeyStroke(action1));
|
||||||
|
|
||||||
// verify the additional binding for 'Alt Graph'
|
// verify the additional binding for 'Alt Graph'
|
||||||
action =
|
action = (Action) TestUtils.invokeInstanceMethod("getActionForKeyStroke", dwm, keyStroke);
|
||||||
(Action) TestUtils.invokeInstanceMethod("getActionForKeyStroke", dwm, keyStroke);
|
|
||||||
assertNotNull(action);
|
assertNotNull(action);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -283,8 +279,8 @@ public class KeyBindingsTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
boolean success = msg.contains(action1.getName()) && msg.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" +
|
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" +
|
action1.getName() + "\n\t\t" + action2.getName() + ".\nActual message:\n" + msg + "\n",
|
||||||
msg + "\n", success);
|
success);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -560,8 +556,7 @@ public class KeyBindingsTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
dialog.setVisible(true);
|
dialog.setVisible(true);
|
||||||
});
|
});
|
||||||
table = findComponent(panel, JTable.class);
|
table = findComponent(panel, JTable.class);
|
||||||
keyField = findComponent(panel, JTextField.class);
|
keyField = (JTextField) findComponentByName(panel, "Key Entry Text Field");
|
||||||
keyField = (JTextField) getInstanceField("ksField", panel);
|
|
||||||
statusPane = findComponent(panel, JTextPane.class);
|
statusPane = findComponent(panel, JTextPane.class);
|
||||||
model = table.getModel();
|
model = table.getModel();
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
|
|
@ -24,6 +24,8 @@ import java.beans.PropertyEditor;
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import javax.swing.text.JTextComponent;
|
import javax.swing.text.JTextComponent;
|
||||||
|
@ -60,6 +62,7 @@ import ghidra.framework.preferences.Preferences;
|
||||||
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
||||||
import ghidra.test.TestEnv;
|
import ghidra.test.TestEnv;
|
||||||
import ghidra.util.ColorUtils;
|
import ghidra.util.ColorUtils;
|
||||||
|
import gui.event.MouseBinding;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for the options dialog.
|
* Tests for the options dialog.
|
||||||
|
@ -420,25 +423,154 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRestoreDefaultsForKeybindings() throws Exception {
|
public void testKeybindings_SetMouseBounding_NoDefaultBindings() throws Exception {
|
||||||
String actionName = "Clear Cut";
|
|
||||||
String pluginName = "DataTypeManagerPlugin";
|
|
||||||
KeyStroke defaultKeyStroke = getKeyBinding(actionName);
|
|
||||||
assertOptionsKeyStroke(tool, actionName, pluginName, defaultKeyStroke);
|
|
||||||
|
|
||||||
int keyCode = KeyEvent.VK_Q;
|
String actionName = "Clear Color";
|
||||||
int modifiers = InputEvent.CTRL_DOWN_MASK | InputEvent.ALT_DOWN_MASK;
|
String actionOwner = "ColorizingPlugin";
|
||||||
KeyStroke newKeyStroke = setKeyBinding(actionName, modifiers, keyCode, 'Q');
|
|
||||||
|
KeyStroke defaultKeyStroke = getKeyBindingFromTable(actionName, actionOwner);
|
||||||
|
assertNull(defaultKeyStroke);
|
||||||
|
|
||||||
|
MouseBinding defaultMouseBinding = getMouseBindingFromTable(actionName, actionOwner);
|
||||||
|
assertNull(defaultMouseBinding);
|
||||||
|
|
||||||
|
int button = 1;
|
||||||
|
int modifiers = 0;
|
||||||
|
MouseBinding newMouseBinding = setMouseBinding(actionName, actionOwner, modifiers, button);
|
||||||
|
|
||||||
apply();
|
apply();
|
||||||
assertOptionsKeyStroke(tool, actionName, pluginName, newKeyStroke);
|
assertOptionsMouseBinding(tool, actionName, actionOwner, newMouseBinding);
|
||||||
|
|
||||||
restoreDefaults();
|
restoreDefaults();
|
||||||
|
|
||||||
KeyStroke currentBinding = getKeyBinding(actionName);
|
MouseBinding currentMouseBinding = getMouseBindingFromTable(actionName, actionOwner);
|
||||||
|
|
||||||
|
assertEquals("Mouse binding not restored after a call to restore defautls",
|
||||||
|
defaultMouseBinding, currentMouseBinding);
|
||||||
|
assertOptionsMouseBinding(tool, actionName, actionOwner, defaultMouseBinding);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testKeybindings_SetMouseBoundingAndKeyBinding_NoDefaultBindings() throws Exception {
|
||||||
|
|
||||||
|
String actionName = "Clear Color";
|
||||||
|
String actionOwner = "ColorizingPlugin";
|
||||||
|
|
||||||
|
KeyStroke defaultKeyStroke = getKeyBindingFromTable(actionName, actionOwner);
|
||||||
|
assertNull(defaultKeyStroke);
|
||||||
|
|
||||||
|
MouseBinding defaultMouseBinding = getMouseBindingFromTable(actionName, actionOwner);
|
||||||
|
assertNull(defaultMouseBinding);
|
||||||
|
|
||||||
|
int button = 1;
|
||||||
|
int modifiers = 0;
|
||||||
|
MouseBinding newMouseBinding = setMouseBinding(actionName, actionOwner, modifiers, button);
|
||||||
|
|
||||||
|
int keyCode = KeyEvent.VK_Q;
|
||||||
|
modifiers = InputEvent.CTRL_DOWN_MASK | InputEvent.ALT_DOWN_MASK;
|
||||||
|
KeyStroke newKeyStroke = setKeyBinding(actionName, actionOwner, modifiers, keyCode, 'Q');
|
||||||
|
|
||||||
|
apply();
|
||||||
|
assertOptionsMouseBinding(tool, actionName, actionOwner, newMouseBinding);
|
||||||
|
assertOptionsKeyStroke(tool, actionName, actionOwner, newKeyStroke);
|
||||||
|
|
||||||
|
restoreDefaults();
|
||||||
|
|
||||||
|
MouseBinding currentMouseBinding = getMouseBindingFromTable(actionName, actionOwner);
|
||||||
|
|
||||||
|
assertEquals("Mouse binding not restored after a call to restore defautls",
|
||||||
|
defaultMouseBinding, currentMouseBinding);
|
||||||
|
assertOptionsMouseBinding(tool, actionName, actionOwner, defaultMouseBinding);
|
||||||
|
assertOptionsKeyStroke(tool, actionName, actionOwner, defaultKeyStroke);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testKeybindings_SetMouseBoundingAndKeyBinding_ClearKeyBinding() throws Exception {
|
||||||
|
|
||||||
|
String actionName = "Clear Color";
|
||||||
|
String actionOwner = "ColorizingPlugin";
|
||||||
|
|
||||||
|
KeyStroke defaultKeyStroke = getKeyBindingFromTable(actionName, actionOwner);
|
||||||
|
assertNull(defaultKeyStroke);
|
||||||
|
|
||||||
|
MouseBinding defaultMouseBinding = getMouseBindingFromTable(actionName, actionOwner);
|
||||||
|
assertNull(defaultMouseBinding);
|
||||||
|
|
||||||
|
int keyCode = KeyEvent.VK_Q;
|
||||||
|
int modifiers = InputEvent.CTRL_DOWN_MASK | InputEvent.ALT_DOWN_MASK;
|
||||||
|
KeyStroke newKeyStroke = setKeyBinding(actionName, actionOwner, modifiers, keyCode, 'Q');
|
||||||
|
|
||||||
|
int button = 1;
|
||||||
|
modifiers = 0;
|
||||||
|
MouseBinding newMouseBinding = setMouseBinding(actionName, actionOwner, modifiers, button);
|
||||||
|
|
||||||
|
apply();
|
||||||
|
assertOptionsMouseBinding(tool, actionName, actionOwner, newMouseBinding);
|
||||||
|
assertOptionsKeyStroke(tool, actionName, actionOwner, newKeyStroke);
|
||||||
|
|
||||||
|
clearKeyBinding(actionName, actionOwner);
|
||||||
|
apply();
|
||||||
|
assertOptionsMouseBinding(tool, actionName, actionOwner, newMouseBinding); // unchanged
|
||||||
|
|
||||||
|
restoreDefaults();
|
||||||
|
|
||||||
|
MouseBinding currentMouseBinding = getMouseBindingFromTable(actionName, actionOwner);
|
||||||
|
|
||||||
|
assertEquals("Mouse binding not restored after a call to restore defautls",
|
||||||
|
defaultMouseBinding, currentMouseBinding);
|
||||||
|
assertOptionsMouseBinding(tool, actionName, actionOwner, defaultMouseBinding);
|
||||||
|
assertOptionsKeyStroke(tool, actionName, actionOwner, defaultKeyStroke);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testKeybindings_SetMouseBounding_DefaultKeyBinding() throws Exception {
|
||||||
|
|
||||||
|
String actionName = "Clear Cut";
|
||||||
|
String actionOwner = "DataTypeManagerPlugin";
|
||||||
|
KeyStroke defaultKeyStroke = getKeyBindingFromTable(actionName, actionOwner);
|
||||||
|
assertNotNull(defaultKeyStroke);
|
||||||
|
|
||||||
|
MouseBinding defaultMouseBinding = getMouseBindingFromTable(actionName, actionOwner);
|
||||||
|
assertNull(defaultMouseBinding);
|
||||||
|
|
||||||
|
int button = 1;
|
||||||
|
int modifiers = 0;
|
||||||
|
MouseBinding newMouseBinding = setMouseBinding(actionName, actionOwner, modifiers, button);
|
||||||
|
|
||||||
|
apply();
|
||||||
|
assertOptionsMouseBinding(tool, actionName, actionOwner, newMouseBinding);
|
||||||
|
assertOptionsKeyStroke(tool, actionName, actionOwner, defaultKeyStroke);
|
||||||
|
|
||||||
|
restoreDefaults();
|
||||||
|
|
||||||
|
MouseBinding currentMouseBinding = getMouseBindingFromTable(actionName, actionOwner);
|
||||||
|
|
||||||
|
assertEquals("Mouse binding not restored after a call to restore defautls",
|
||||||
|
defaultMouseBinding, currentMouseBinding);
|
||||||
|
assertOptionsMouseBinding(tool, actionName, actionOwner, defaultMouseBinding);
|
||||||
|
assertOptionsKeyStroke(tool, actionName, actionOwner, defaultKeyStroke);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRestoreDefaultsForKeybindings() throws Exception {
|
||||||
|
String actionName = "Clear Cut";
|
||||||
|
String actionOwner = "DataTypeManagerPlugin";
|
||||||
|
KeyStroke defaultKeyStroke = getKeyBindingFromTable(actionName, actionOwner);
|
||||||
|
assertOptionsKeyStroke(tool, actionName, actionOwner, defaultKeyStroke);
|
||||||
|
|
||||||
|
int keyCode = KeyEvent.VK_Q;
|
||||||
|
int modifiers = InputEvent.CTRL_DOWN_MASK | InputEvent.ALT_DOWN_MASK;
|
||||||
|
KeyStroke newKeyStroke = setKeyBinding(actionName, actionOwner, modifiers, keyCode, 'Q');
|
||||||
|
|
||||||
|
apply();
|
||||||
|
assertOptionsKeyStroke(tool, actionName, actionOwner, newKeyStroke);
|
||||||
|
|
||||||
|
restoreDefaults();
|
||||||
|
|
||||||
|
KeyStroke currentBinding = getKeyBindingFromTable(actionName, actionOwner);
|
||||||
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(tool, actionName, pluginName, defaultKeyStroke);
|
assertOptionsKeyStroke(tool, actionName, actionOwner, defaultKeyStroke);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -449,23 +581,23 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
setUpDialog(frontEndTool);
|
setUpDialog(frontEndTool);
|
||||||
|
|
||||||
String actionName = "Archive Project";
|
String actionName = "Archive Project";
|
||||||
String pluginName = "ArchivePlugin";
|
String actionOwner = "ArchivePlugin";
|
||||||
KeyStroke defaultKeyStroke = getKeyBinding(actionName);
|
KeyStroke defaultKeyStroke = getKeyBindingFromTable(actionName, actionOwner);
|
||||||
assertOptionsKeyStroke(frontEndTool, actionName, pluginName, defaultKeyStroke);
|
assertOptionsKeyStroke(frontEndTool, actionName, actionOwner, 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, actionOwner, modifiers, keyCode, 'Q');
|
||||||
|
|
||||||
apply();
|
apply();
|
||||||
assertOptionsKeyStroke(frontEndTool, actionName, pluginName, newKeyStroke);
|
assertOptionsKeyStroke(frontEndTool, actionName, actionOwner, newKeyStroke);
|
||||||
|
|
||||||
restoreDefaults();
|
restoreDefaults();
|
||||||
|
|
||||||
KeyStroke currentBinding = getKeyBinding(actionName);
|
KeyStroke currentBinding = getKeyBindingFromTable(actionName, actionOwner);
|
||||||
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(frontEndTool, actionName, pluginName, defaultKeyStroke);
|
assertOptionsKeyStroke(frontEndTool, actionName, actionOwner, defaultKeyStroke);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -745,11 +877,13 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
// Inner Classes
|
// Inner Classes
|
||||||
//=================================================================================================
|
//=================================================================================================
|
||||||
|
|
||||||
private KeyStroke getKeyBinding(String actionName) throws Exception {
|
private MouseBinding getMouseBindingFromTable(String actionName, String actionOwner)
|
||||||
|
throws Exception {
|
||||||
|
|
||||||
OptionsEditor editor = seleNodeWithCustomEditor("Key Bindings");
|
OptionsEditor editor = seleNodeWithCustomEditor("Key Bindings");
|
||||||
KeyBindingsPanel panel = (KeyBindingsPanel) getInstanceField("panel", editor);
|
KeyBindingsPanel panel = (KeyBindingsPanel) getInstanceField("panel", editor);
|
||||||
|
|
||||||
int row = selectRowForAction(panel, actionName);
|
int row = selectRowForAction(panel, actionName, actionOwner);
|
||||||
|
|
||||||
JTable table = (JTable) getInstanceField("actionTable", panel);
|
JTable table = (JTable) getInstanceField("actionTable", panel);
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
|
@ -763,36 +897,150 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
if (StringUtils.isBlank(keyBindingColumnValue)) {
|
if (StringUtils.isBlank(keyBindingColumnValue)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String mouseBinding = keyBindingColumnValue;
|
||||||
|
Pattern p = Pattern.compile(".*\\((.*)\\)");
|
||||||
|
Matcher matcher = p.matcher(keyBindingColumnValue);
|
||||||
|
if (matcher.matches()) {
|
||||||
|
mouseBinding = matcher.group(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return MouseBinding.getMouseBinding(mouseBinding);
|
||||||
|
}
|
||||||
|
|
||||||
|
private KeyStroke getKeyBindingFromTable(String actionName, String actionOwner)
|
||||||
|
throws Exception {
|
||||||
|
OptionsEditor editor = seleNodeWithCustomEditor("Key Bindings");
|
||||||
|
KeyBindingsPanel panel = (KeyBindingsPanel) getInstanceField("panel", editor);
|
||||||
|
|
||||||
|
int row = selectRowForAction(panel, actionName, actionOwner);
|
||||||
|
|
||||||
|
JTable table = (JTable) getInstanceField("actionTable", panel);
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
RowObjectFilterModel<DockingActionIf> model =
|
||||||
|
(RowObjectFilterModel<DockingActionIf>) table.getModel();
|
||||||
|
|
||||||
|
DockingActionIf rowValue = model.getModelData().get(row);
|
||||||
|
|
||||||
|
String keyBindingColumnValue =
|
||||||
|
(String) model.getColumnValueForRow(rowValue, 1 /* key binding column */);
|
||||||
|
if (StringUtils.isBlank(keyBindingColumnValue)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
int index = keyBindingColumnValue.indexOf("(");
|
||||||
|
if (index != -1) {
|
||||||
|
int endIndex = keyBindingColumnValue.indexOf(")");
|
||||||
|
if (endIndex != -1) {
|
||||||
|
keyBindingColumnValue = keyBindingColumnValue.substring(0, index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return KeyBindingUtils.parseKeyStroke(keyBindingColumnValue);
|
return KeyBindingUtils.parseKeyStroke(keyBindingColumnValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void assertOptionsMouseBinding(PluginTool pluginTool, String actionName,
|
||||||
|
String pluginName, MouseBinding value) {
|
||||||
|
Options options = pluginTool.getOptions(DockingToolConstants.KEY_BINDINGS);
|
||||||
|
ActionTrigger actionTrigger =
|
||||||
|
options.getActionTrigger(actionName + " (" + pluginName + ")", null);
|
||||||
|
if (actionTrigger == null) {
|
||||||
|
assertNull("The options mouse binding does not match the value in the options table",
|
||||||
|
value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseBinding mouseBinding = actionTrigger.getMouseBinding();
|
||||||
|
assertEquals("The options mouse binding does not match the value in the options table",
|
||||||
|
value, mouseBinding);
|
||||||
|
}
|
||||||
|
|
||||||
private void assertOptionsKeyStroke(PluginTool pluginTool, String actionName, String pluginName,
|
private void assertOptionsKeyStroke(PluginTool pluginTool, String actionName, String pluginName,
|
||||||
KeyStroke value) throws Exception {
|
KeyStroke value) throws Exception {
|
||||||
Options options = pluginTool.getOptions(DockingToolConstants.KEY_BINDINGS);
|
Options options = pluginTool.getOptions(DockingToolConstants.KEY_BINDINGS);
|
||||||
KeyStroke optionsKeyStroke =
|
ActionTrigger actionTrigger =
|
||||||
options.getKeyStroke(actionName + " (" + pluginName + ")", null);
|
options.getActionTrigger(actionName + " (" + pluginName + ")", null);
|
||||||
assertEquals("The options keystroke does not match the value in keybinding options table",
|
if (actionTrigger == null) {
|
||||||
value, optionsKeyStroke);
|
assertNull("The options keystroke does not match the value in the options table",
|
||||||
|
value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyStroke keyStroke = actionTrigger.getKeyStroke();
|
||||||
|
assertEquals("The options keystroke does not match the value in the options table", value,
|
||||||
|
keyStroke);
|
||||||
}
|
}
|
||||||
|
|
||||||
private KeyStroke setKeyBinding(String actionName, int modifiers, int keyCode, char keyChar)
|
private MouseBinding setMouseBinding(String actionName, String actionOwner, int modifiers,
|
||||||
throws Exception {
|
int button) throws Exception {
|
||||||
|
|
||||||
OptionsEditor editor = seleNodeWithCustomEditor("Key Bindings");
|
OptionsEditor editor = seleNodeWithCustomEditor("Key Bindings");
|
||||||
final KeyBindingsPanel panel = (KeyBindingsPanel) getInstanceField("panel", editor);
|
KeyBindingsPanel panel = (KeyBindingsPanel) getInstanceField("panel", editor);
|
||||||
|
|
||||||
selectRowForAction(panel, actionName);
|
selectRowForAction(panel, actionName, actionOwner);
|
||||||
|
|
||||||
|
setToggleButtonSelected(panel, "Enter Mouse Binding", true);
|
||||||
|
|
||||||
|
JPanel actionBindingPanel = (JPanel) getInstanceField("actionBindingPanel", panel);
|
||||||
|
JTextField textField = (JTextField) getInstanceField("mouseEntryField", actionBindingPanel);
|
||||||
|
|
||||||
|
clickMouse(textField, button, 5, 5, 1, modifiers);
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
MouseBinding expectedMouseBinding = new MouseBinding(button, modifiers);
|
||||||
|
|
||||||
|
waitForSwing();
|
||||||
|
waitForSwing();
|
||||||
|
waitForSwing();
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
MouseBinding currentMouseBinding = getMouseBindingFromTable(actionName, actionOwner);
|
||||||
|
assertEquals("Did not properly set mouse binding", expectedMouseBinding,
|
||||||
|
currentMouseBinding);
|
||||||
|
return currentMouseBinding;
|
||||||
|
}
|
||||||
|
|
||||||
|
private KeyStroke setKeyBinding(String actionName, String actionOwner, int modifiers,
|
||||||
|
int keyCode, char keyChar) throws Exception {
|
||||||
|
OptionsEditor editor = seleNodeWithCustomEditor("Key Bindings");
|
||||||
|
KeyBindingsPanel panel = (KeyBindingsPanel) getInstanceField("panel", editor);
|
||||||
|
|
||||||
|
selectRowForAction(panel, actionName, actionOwner);
|
||||||
|
|
||||||
|
setToggleButtonSelected(panel, "Enter Mouse Binding", false);
|
||||||
|
|
||||||
|
JPanel actionBindingPanel = (JPanel) getInstanceField("actionBindingPanel", panel);
|
||||||
|
JTextField textField = (JTextField) getInstanceField("keyEntryField", actionBindingPanel);
|
||||||
|
|
||||||
JTextField textField = (JTextField) getInstanceField("ksField", panel);
|
|
||||||
triggerKey(textField, modifiers, keyCode, keyChar);
|
triggerKey(textField, modifiers, keyCode, keyChar);
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
|
|
||||||
KeyStroke expectedKeyStroke = KeyStroke.getKeyStroke(keyCode, modifiers, false);
|
KeyStroke expectedKeyStroke = KeyStroke.getKeyStroke(keyCode, modifiers, false);
|
||||||
KeyStroke currentBinding = getKeyBinding(actionName);
|
KeyStroke currentBinding = getKeyBindingFromTable(actionName, actionOwner);
|
||||||
assertEquals("Did not properly set new keybinding", expectedKeyStroke, currentBinding);
|
assertEquals("Did not properly set new keybinding", expectedKeyStroke, currentBinding);
|
||||||
return currentBinding;
|
return currentBinding;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int selectRowForAction(KeyBindingsPanel panel, String actionName) {
|
private void clearKeyBinding(String actionName, String actionOwner) throws Exception {
|
||||||
|
|
||||||
|
OptionsEditor editor = seleNodeWithCustomEditor("Key Bindings");
|
||||||
|
KeyBindingsPanel panel = (KeyBindingsPanel) getInstanceField("panel", editor);
|
||||||
|
|
||||||
|
selectRowForAction(panel, actionName, actionOwner);
|
||||||
|
|
||||||
|
setToggleButtonSelected(panel, "Enter Mouse Binding", false);
|
||||||
|
|
||||||
|
JPanel actionBindingPanel = (JPanel) getInstanceField("actionBindingPanel", panel);
|
||||||
|
JTextField textField = (JTextField) getInstanceField("keyEntryField", actionBindingPanel);
|
||||||
|
|
||||||
|
triggerBackspaceKey(textField);
|
||||||
|
waitForSwing();
|
||||||
|
|
||||||
|
KeyStroke currentBinding = getKeyBindingFromTable(actionName, actionOwner);
|
||||||
|
assertNull(currentBinding);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int selectRowForAction(KeyBindingsPanel panel, String actionName, String actionOwner) {
|
||||||
final JTable table = (JTable) getInstanceField("actionTable", panel);
|
final JTable table = (JTable) getInstanceField("actionTable", panel);
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
final RowObjectFilterModel<DockingActionIf> model =
|
final RowObjectFilterModel<DockingActionIf> model =
|
||||||
|
@ -806,15 +1054,25 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
String rowActionName =
|
String rowActionName =
|
||||||
(String) model.getColumnValueForRow(rowData, 0 /* action name column */);
|
(String) model.getColumnValueForRow(rowData, 0 /* action name column */);
|
||||||
if (rowActionName.equals(actionName)) {
|
if (rowActionName.equals(actionName)) {
|
||||||
actionRow = i;
|
|
||||||
break;
|
String rowActionOwner =
|
||||||
|
(String) model.getColumnValueForRow(rowData, 2 /* owner column */);
|
||||||
|
if (rowActionOwner.equals(actionOwner)) {
|
||||||
|
actionRow = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assertTrue("Could not find row for action: " + actionName, actionRow != -1);
|
assertTrue("Could not find row for action: " + actionName + " (" + actionOwner + ")",
|
||||||
|
actionRow != -1);
|
||||||
|
|
||||||
final int row = actionRow;
|
int row = actionRow;
|
||||||
runSwing(() -> table.setRowSelectionInterval(row, row));
|
runSwing(() -> {
|
||||||
|
table.setRowSelectionInterval(row, row);
|
||||||
|
Rectangle cellRectangle = table.getCellRect(row, row, true);
|
||||||
|
table.scrollRectToVisible(cellRectangle);
|
||||||
|
});
|
||||||
|
|
||||||
return row;
|
return row;
|
||||||
}
|
}
|
||||||
|
@ -1039,17 +1297,21 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
showOptionsDialog(pluginTool);
|
showOptionsDialog(pluginTool);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showOptionsDialog(PluginTool pluginTool) throws Exception {
|
private void editOptions(PluginTool pluginTool) {
|
||||||
// TODO change to getAction("Edit Options")
|
|
||||||
Set<DockingActionIf> list = pluginTool.getAllActions();
|
Set<DockingActionIf> list = pluginTool.getAllActions();
|
||||||
for (DockingActionIf action : list) {
|
for (DockingActionIf action : list) {
|
||||||
if (action.getName().equals("Edit Options")) {
|
if (action.getName().equals("Edit Options")) {
|
||||||
performAction(action, false);
|
performAction(action, false);
|
||||||
break;
|
waitForSwing();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fail("Unable to find action 'Edit Options'");
|
||||||
|
}
|
||||||
|
|
||||||
waitForSwing();
|
private void showOptionsDialog(PluginTool pluginTool) throws Exception {
|
||||||
|
|
||||||
|
editOptions(pluginTool);
|
||||||
dialog = waitForDialogComponent(OptionsDialog.class);
|
dialog = waitForDialogComponent(OptionsDialog.class);
|
||||||
optionsPanel = (OptionsPanel) getInstanceField("panel", dialog);
|
optionsPanel = (OptionsPanel) getInstanceField("panel", dialog);
|
||||||
Container pane = dialog.getComponent();
|
Container pane = dialog.getComponent();
|
||||||
|
|
|
@ -19,6 +19,8 @@ import static org.junit.Assert.*;
|
||||||
|
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
import java.awt.Font;
|
import java.awt.Font;
|
||||||
|
import java.awt.event.InputEvent;
|
||||||
|
import java.awt.event.KeyEvent;
|
||||||
import java.beans.PropertyChangeListener;
|
import java.beans.PropertyChangeListener;
|
||||||
import java.beans.PropertyEditorSupport;
|
import java.beans.PropertyEditorSupport;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
@ -30,7 +32,6 @@ import javax.swing.KeyStroke;
|
||||||
import org.junit.*;
|
import org.junit.*;
|
||||||
|
|
||||||
import docking.test.AbstractDockingTest;
|
import docking.test.AbstractDockingTest;
|
||||||
import generic.theme.GColor;
|
|
||||||
import generic.theme.GThemeDefaults.Colors.Palette;
|
import generic.theme.GThemeDefaults.Colors.Palette;
|
||||||
import generic.theme.ThemeManager;
|
import generic.theme.ThemeManager;
|
||||||
import ghidra.framework.options.*;
|
import ghidra.framework.options.*;
|
||||||
|
@ -39,20 +40,19 @@ import ghidra.program.database.ProgramBuilder;
|
||||||
import ghidra.program.database.ProgramDB;
|
import ghidra.program.database.ProgramDB;
|
||||||
import ghidra.util.HelpLocation;
|
import ghidra.util.HelpLocation;
|
||||||
import ghidra.util.exception.InvalidInputException;
|
import ghidra.util.exception.InvalidInputException;
|
||||||
|
import gui.event.MouseBinding;
|
||||||
|
|
||||||
public class OptionsDBTest extends AbstractDockingTest {
|
public class OptionsDBTest extends AbstractDockingTest {
|
||||||
|
|
||||||
private OptionsDB options;
|
private OptionsDB options;
|
||||||
private ProgramBuilder builder;
|
private ProgramBuilder builder;
|
||||||
private int txID;
|
private int txID;
|
||||||
private GColor testColor;
|
|
||||||
|
|
||||||
public enum fruit {
|
public enum fruit {
|
||||||
Apple, Pear, Orange
|
Apple, Pear, Orange
|
||||||
}
|
}
|
||||||
|
|
||||||
public OptionsDBTest() {
|
public OptionsDBTest() {
|
||||||
super();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
|
@ -62,7 +62,6 @@ public class OptionsDBTest extends AbstractDockingTest {
|
||||||
txID = program.startTransaction("Test");
|
txID = program.startTransaction("Test");
|
||||||
options = new OptionsDB(program);
|
options = new OptionsDB(program);
|
||||||
ThemeManager.getInstance().setColor("color.test", Palette.MAGENTA);
|
ThemeManager.getInstance().setColor("color.test", Palette.MAGENTA);
|
||||||
testColor = new GColor("color.test");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void saveAndRestoreOptions() {
|
private void saveAndRestoreOptions() {
|
||||||
|
@ -223,10 +222,38 @@ public class OptionsDBTest extends AbstractDockingTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSaveKeyStrokeOption() {
|
public void testSaveKeyStrokeOption() {
|
||||||
options.setKeyStroke("Foo", KeyStroke.getKeyStroke('a', 0));
|
options.setKeyStroke("Foo", KeyStroke.getKeyStroke(KeyEvent.VK_A, 0));
|
||||||
saveAndRestoreOptions();
|
saveAndRestoreOptions();
|
||||||
assertEquals(KeyStroke.getKeyStroke('a', 0),
|
KeyStroke savedKs = options.getKeyStroke("Foo", null);
|
||||||
options.getKeyStroke("Foo", KeyStroke.getKeyStroke('b', 0)));
|
assertEquals(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0), savedKs);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSaveActionTrigger_KeyStroke() {
|
||||||
|
KeyStroke ks = KeyStroke.getKeyStroke('a', 0);
|
||||||
|
ActionTrigger trigger = new ActionTrigger(ks);
|
||||||
|
options.setActionTrigger("Foo", trigger);
|
||||||
|
saveAndRestoreOptions();
|
||||||
|
assertEquals(trigger, options.getActionTrigger("Foo", null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSaveActionTrigger_MouseBinding() {
|
||||||
|
MouseBinding mb = new MouseBinding(1, InputEvent.CTRL_DOWN_MASK);
|
||||||
|
ActionTrigger trigger = new ActionTrigger(mb);
|
||||||
|
options.setActionTrigger("Foo", trigger);
|
||||||
|
saveAndRestoreOptions();
|
||||||
|
assertEquals(trigger, options.getActionTrigger("Foo", null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSaveActionTrigger_KeyStrokeAndMouseBinding() {
|
||||||
|
KeyStroke ks = KeyStroke.getKeyStroke(KeyEvent.VK_A, 0);
|
||||||
|
MouseBinding mb = new MouseBinding(1, InputEvent.CTRL_DOWN_MASK);
|
||||||
|
ActionTrigger trigger = new ActionTrigger(ks, mb);
|
||||||
|
options.setActionTrigger("Foo", trigger);
|
||||||
|
saveAndRestoreOptions();
|
||||||
|
assertEquals(trigger, options.getActionTrigger("Foo", null));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -678,7 +705,7 @@ public class OptionsDBTest extends AbstractDockingTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class MyPropertyEditor extends PropertyEditorSupport {
|
public static class MyPropertyEditor extends PropertyEditorSupport {
|
||||||
|
//
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,8 @@ import static org.junit.Assert.*;
|
||||||
|
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
import java.awt.Font;
|
import java.awt.Font;
|
||||||
|
import java.awt.event.InputEvent;
|
||||||
|
import java.awt.event.KeyEvent;
|
||||||
import java.beans.PropertyChangeListener;
|
import java.beans.PropertyChangeListener;
|
||||||
import java.beans.PropertyEditorSupport;
|
import java.beans.PropertyEditorSupport;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
@ -35,6 +37,7 @@ import generic.theme.GThemeDefaults.Colors.Palette;
|
||||||
import ghidra.util.HelpLocation;
|
import ghidra.util.HelpLocation;
|
||||||
import ghidra.util.bean.opteditor.OptionsVetoException;
|
import ghidra.util.bean.opteditor.OptionsVetoException;
|
||||||
import ghidra.util.exception.InvalidInputException;
|
import ghidra.util.exception.InvalidInputException;
|
||||||
|
import gui.event.MouseBinding;
|
||||||
|
|
||||||
public class OptionsTest extends AbstractGuiTest {
|
public class OptionsTest extends AbstractGuiTest {
|
||||||
|
|
||||||
|
@ -161,10 +164,37 @@ public class OptionsTest extends AbstractGuiTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSaveKeyStrokeOption() {
|
public void testSaveKeyStrokeOption() {
|
||||||
options.setKeyStroke("Foo", KeyStroke.getKeyStroke('a', 0));
|
options.setKeyStroke("Foo", KeyStroke.getKeyStroke(KeyEvent.VK_A, 0));
|
||||||
saveAndRestoreOptions();
|
saveAndRestoreOptions();
|
||||||
assertEquals(KeyStroke.getKeyStroke('a', 0),
|
assertEquals(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0), options.getKeyStroke("Foo", null));
|
||||||
options.getKeyStroke("Foo", KeyStroke.getKeyStroke('b', 0)));
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSaveActionTrigger_KeyStroke() {
|
||||||
|
KeyStroke ks = KeyStroke.getKeyStroke(KeyEvent.VK_A, 0);
|
||||||
|
ActionTrigger trigger = new ActionTrigger(ks);
|
||||||
|
options.setActionTrigger("Foo", trigger);
|
||||||
|
saveAndRestoreOptions();
|
||||||
|
assertEquals(trigger, options.getActionTrigger("Foo", null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSaveActionTrigger_MouseBinding() {
|
||||||
|
MouseBinding mb = new MouseBinding(1, InputEvent.CTRL_DOWN_MASK);
|
||||||
|
ActionTrigger trigger = new ActionTrigger(mb);
|
||||||
|
options.setActionTrigger("Foo", trigger);
|
||||||
|
saveAndRestoreOptions();
|
||||||
|
assertEquals(trigger, options.getActionTrigger("Foo", null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSaveActionTrigger_KeyStrokeAndMouseBinding() {
|
||||||
|
KeyStroke ks = KeyStroke.getKeyStroke(KeyEvent.VK_A, 0);
|
||||||
|
MouseBinding mb = new MouseBinding(1, InputEvent.CTRL_DOWN_MASK);
|
||||||
|
ActionTrigger trigger = new ActionTrigger(ks, mb);
|
||||||
|
options.setActionTrigger("Foo", trigger);
|
||||||
|
saveAndRestoreOptions();
|
||||||
|
assertEquals(trigger, options.getActionTrigger("Foo", null));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -20,11 +20,7 @@ import java.awt.Font;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
import javax.swing.KeyStroke;
|
import ghidra.framework.options.*;
|
||||||
|
|
||||||
import ghidra.framework.options.CustomOption;
|
|
||||||
import ghidra.framework.options.OptionType;
|
|
||||||
import ghidra.framework.options.Options;
|
|
||||||
import ghidra.program.model.data.ISF.IsfObject;
|
import ghidra.program.model.data.ISF.IsfObject;
|
||||||
import ghidra.util.exception.AssertException;
|
import ghidra.util.exception.AssertException;
|
||||||
|
|
||||||
|
@ -39,88 +35,94 @@ public class ExtProperty implements IsfObject {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.value = value;
|
this.value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ExtProperty(String name, Options propList) {
|
public ExtProperty(String name, Options propList) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
OptionType optionType = propList.getType(name);
|
OptionType optionType = propList.getType(name);
|
||||||
switch (optionType) {
|
switch (optionType) {
|
||||||
case INT_TYPE:
|
case INT_TYPE:
|
||||||
type = "int";
|
type = "int";
|
||||||
value = Integer.toString(propList.getInt(name, 0));
|
value = Integer.toString(propList.getInt(name, 0));
|
||||||
break;
|
break;
|
||||||
case LONG_TYPE:
|
case LONG_TYPE:
|
||||||
type = "long";
|
type = "long";
|
||||||
value = Long.toString(propList.getLong(name, 0));
|
value = Long.toString(propList.getLong(name, 0));
|
||||||
break;
|
break;
|
||||||
case STRING_TYPE:
|
case STRING_TYPE:
|
||||||
type = "string";
|
type = "string";
|
||||||
value = propList.getString(name, "");
|
value = propList.getString(name, "");
|
||||||
break;
|
break;
|
||||||
case BOOLEAN_TYPE:
|
case BOOLEAN_TYPE:
|
||||||
type = "bool";
|
type = "bool";
|
||||||
value = Boolean.toString(propList.getBoolean(name, true));
|
value = Boolean.toString(propList.getBoolean(name, true));
|
||||||
break;
|
break;
|
||||||
case DOUBLE_TYPE:
|
case DOUBLE_TYPE:
|
||||||
type = "double";
|
type = "double";
|
||||||
value = Double.toString(propList.getDouble(name, 0));
|
value = Double.toString(propList.getDouble(name, 0));
|
||||||
break;
|
break;
|
||||||
case FLOAT_TYPE:
|
case FLOAT_TYPE:
|
||||||
type = "float";
|
type = "float";
|
||||||
value = Float.toString(propList.getFloat(name, 0f));
|
value = Float.toString(propList.getFloat(name, 0f));
|
||||||
break;
|
break;
|
||||||
case DATE_TYPE:
|
case DATE_TYPE:
|
||||||
type = "date";
|
type = "date";
|
||||||
Date date = propList.getDate(name, (Date) null);
|
Date date = propList.getDate(name, (Date) null);
|
||||||
long time = date == null ? 0 : date.getTime();
|
long time = date == null ? 0 : date.getTime();
|
||||||
value = Long.toHexString(time);
|
value = Long.toHexString(time);
|
||||||
break;
|
break;
|
||||||
case COLOR_TYPE:
|
case COLOR_TYPE:
|
||||||
type = "color";
|
type = "color";
|
||||||
Color color = propList.getColor(name, null);
|
Color color = propList.getColor(name, null);
|
||||||
int rgb = color.getRGB();
|
int rgb = color.getRGB();
|
||||||
value = Integer.toHexString(rgb);
|
value = Integer.toHexString(rgb);
|
||||||
break;
|
break;
|
||||||
case ENUM_TYPE:
|
case ENUM_TYPE:
|
||||||
type = "enum";
|
type = "enum";
|
||||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||||
Enum enuum = propList.getEnum(name, null);
|
Enum enuum = propList.getEnum(name, null);
|
||||||
String enumString = OptionType.ENUM_TYPE.convertObjectToString(enuum);
|
String enumString = OptionType.ENUM_TYPE.convertObjectToString(enuum);
|
||||||
value = escapeElementEntities(enumString);
|
value = escapeElementEntities(enumString);
|
||||||
break;
|
break;
|
||||||
case FILE_TYPE:
|
case FILE_TYPE:
|
||||||
type = "file";
|
type = "file";
|
||||||
File file = propList.getFile(name, null);
|
File file = propList.getFile(name, null);
|
||||||
String path = file.getAbsolutePath();
|
String path = file.getAbsolutePath();
|
||||||
value = path;
|
value = path;
|
||||||
break;
|
break;
|
||||||
case FONT_TYPE:
|
case FONT_TYPE:
|
||||||
type = "font";
|
type = "font";
|
||||||
Font font = propList.getFont(name, null);
|
Font font = propList.getFont(name, null);
|
||||||
enumString = OptionType.FONT_TYPE.convertObjectToString(font);
|
enumString = OptionType.FONT_TYPE.convertObjectToString(font);
|
||||||
value = escapeElementEntities(enumString);
|
value = escapeElementEntities(enumString);
|
||||||
break;
|
break;
|
||||||
case KEYSTROKE_TYPE:
|
case KEYSTROKE_TYPE:
|
||||||
type = "keyStroke";
|
type = "actionTrigger";
|
||||||
KeyStroke keyStroke = propList.getKeyStroke(name, null);
|
ActionTrigger trigger = propList.getActionTrigger(name, null);
|
||||||
enumString = OptionType.KEYSTROKE_TYPE.convertObjectToString(keyStroke);
|
enumString = OptionType.ACTION_TRIGGER.convertObjectToString(trigger);
|
||||||
value = escapeElementEntities(enumString);
|
value = escapeElementEntities(enumString);
|
||||||
break;
|
break;
|
||||||
case CUSTOM_TYPE:
|
case ACTION_TRIGGER:
|
||||||
type = "custom";
|
type = "actionTrigger";
|
||||||
CustomOption custom = propList.getCustomOption(name, null);
|
ActionTrigger actionTrigger = propList.getActionTrigger(name, null);
|
||||||
enumString = OptionType.CUSTOM_TYPE.convertObjectToString(custom);
|
enumString = OptionType.ACTION_TRIGGER.convertObjectToString(actionTrigger);
|
||||||
value = escapeElementEntities(enumString);
|
value = escapeElementEntities(enumString);
|
||||||
break;
|
break;
|
||||||
case BYTE_ARRAY_TYPE:
|
case CUSTOM_TYPE:
|
||||||
type = "bytes";
|
type = "custom";
|
||||||
byte[] bytes = propList.getByteArray(name, null);
|
CustomOption custom = propList.getCustomOption(name, null);
|
||||||
enumString = OptionType.BYTE_ARRAY_TYPE.convertObjectToString(bytes);
|
enumString = OptionType.CUSTOM_TYPE.convertObjectToString(custom);
|
||||||
value = escapeElementEntities(enumString);
|
value = escapeElementEntities(enumString);
|
||||||
break;
|
break;
|
||||||
case NO_TYPE:
|
case BYTE_ARRAY_TYPE:
|
||||||
break;
|
type = "bytes";
|
||||||
default:
|
byte[] bytes = propList.getByteArray(name, null);
|
||||||
throw new AssertException();
|
enumString = OptionType.BYTE_ARRAY_TYPE.convertObjectToString(bytes);
|
||||||
|
value = escapeElementEntities(enumString);
|
||||||
|
break;
|
||||||
|
case NO_TYPE:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new AssertException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,11 +131,11 @@ public class ExtProperty implements IsfObject {
|
||||||
private static final String APOSTROPHE = "'";
|
private static final String APOSTROPHE = "'";
|
||||||
private static final String QUOTE = """;
|
private static final String QUOTE = """;
|
||||||
private static final String AMPERSAND = "&";
|
private static final String AMPERSAND = "&";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts any special or reserved characters in the specified SARIF string
|
* Converts any special or reserved characters in the specified SARIF string
|
||||||
* into the equivalent Unicode encoding.
|
* into the equivalent Unicode encoding.
|
||||||
*
|
*
|
||||||
* @param sarif the SARIF string
|
* @param sarif the SARIF string
|
||||||
* @return the encoded SARIF string
|
* @return the encoded SARIF string
|
||||||
*/
|
*/
|
||||||
|
@ -143,7 +145,8 @@ public class ExtProperty implements IsfObject {
|
||||||
int codePoint = sarif.codePointAt(offset);
|
int codePoint = sarif.codePointAt(offset);
|
||||||
offset += Character.charCount(codePoint);
|
offset += Character.charCount(codePoint);
|
||||||
|
|
||||||
if ((codePoint < ' ') && (codePoint != 0x09) && (codePoint != 0x0A) && (codePoint != 0x0D)) {
|
if ((codePoint < ' ') && (codePoint != 0x09) && (codePoint != 0x0A) &&
|
||||||
|
(codePoint != 0x0D)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (codePoint >= 0x7F) {
|
if (codePoint >= 0x7F) {
|
||||||
|
|
|
@ -15,42 +15,23 @@
|
||||||
*/
|
*/
|
||||||
package sarif.managers;
|
package sarif.managers;
|
||||||
|
|
||||||
import java.awt.Color;
|
import java.awt.*;
|
||||||
import java.awt.Font;
|
|
||||||
import java.awt.Point;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.Date;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.StringTokenizer;
|
|
||||||
|
|
||||||
import javax.swing.KeyStroke;
|
import javax.swing.KeyStroke;
|
||||||
|
|
||||||
import com.google.gson.JsonArray;
|
import com.google.gson.JsonArray;
|
||||||
|
|
||||||
import ghidra.app.util.importer.MessageLog;
|
import ghidra.app.util.importer.MessageLog;
|
||||||
import ghidra.framework.options.CustomOption;
|
import ghidra.framework.options.*;
|
||||||
import ghidra.framework.options.OptionType;
|
|
||||||
import ghidra.framework.options.Options;
|
|
||||||
import ghidra.program.model.address.Address;
|
import ghidra.program.model.address.Address;
|
||||||
import ghidra.program.model.address.AddressSetView;
|
import ghidra.program.model.address.AddressSetView;
|
||||||
import ghidra.program.model.listing.Bookmark;
|
import ghidra.program.model.listing.*;
|
||||||
import ghidra.program.model.listing.BookmarkManager;
|
import ghidra.program.model.util.*;
|
||||||
import ghidra.program.model.listing.BookmarkType;
|
import ghidra.util.*;
|
||||||
import ghidra.program.model.listing.Program;
|
|
||||||
import ghidra.program.model.util.IntPropertyMap;
|
|
||||||
import ghidra.program.model.util.LongPropertyMap;
|
|
||||||
import ghidra.program.model.util.ObjectPropertyMap;
|
|
||||||
import ghidra.program.model.util.PropertyMap;
|
|
||||||
import ghidra.program.model.util.PropertyMapManager;
|
|
||||||
import ghidra.program.model.util.StringPropertyMap;
|
|
||||||
import ghidra.program.model.util.VoidPropertyMap;
|
|
||||||
import ghidra.util.ColorUtils;
|
|
||||||
import ghidra.util.SaveableColor;
|
|
||||||
import ghidra.util.SaveablePoint;
|
|
||||||
import ghidra.util.exception.CancelledException;
|
import ghidra.util.exception.CancelledException;
|
||||||
import ghidra.util.exception.DuplicateNameException;
|
import ghidra.util.exception.DuplicateNameException;
|
||||||
import ghidra.util.task.TaskLauncher;
|
import ghidra.util.task.TaskLauncher;
|
||||||
|
@ -79,8 +60,8 @@ public class PropertiesSarifMgr extends SarifMgr {
|
||||||
////////////////////////////
|
////////////////////////////
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean read(Map<String, Object> result, SarifProgramOptions options, TaskMonitor monitor)
|
public boolean read(Map<String, Object> result, SarifProgramOptions options,
|
||||||
throws CancelledException {
|
TaskMonitor monitor) throws CancelledException {
|
||||||
processProperty(result, options == null || options.isOverwritePropertyConflicts());
|
processProperty(result, options == null || options.isOverwritePropertyConflicts());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -92,18 +73,20 @@ public class PropertiesSarifMgr extends SarifMgr {
|
||||||
Address addr = getLocation(result);
|
Address addr = getLocation(result);
|
||||||
if (addr != null) {
|
if (addr != null) {
|
||||||
processPropertyMapEntry(addr, name, result, overwrite);
|
processPropertyMapEntry(addr, name, result, overwrite);
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
processPropertyListEntry(name, result, overwrite);
|
processPropertyListEntry(name, result, overwrite);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
}
|
||||||
|
catch (Exception e) {
|
||||||
log.appendException(e);
|
log.appendException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private void processPropertyMapEntry(Address addr, String name, Map<String, Object> result, boolean overwrite)
|
private void processPropertyMapEntry(Address addr, String name, Map<String, Object> result,
|
||||||
throws DuplicateNameException {
|
boolean overwrite) throws DuplicateNameException {
|
||||||
|
|
||||||
String type = (String) result.get("type");
|
String type = (String) result.get("type");
|
||||||
if (type != null) {
|
if (type != null) {
|
||||||
|
@ -128,44 +111,51 @@ public class PropertiesSarifMgr extends SarifMgr {
|
||||||
voidMap = propMapMgr.createVoidPropertyMap(name);
|
voidMap = propMapMgr.createVoidPropertyMap(name);
|
||||||
}
|
}
|
||||||
voidMap.add(addr);
|
voidMap.add(addr);
|
||||||
} else if ("int".equals(type)) {
|
}
|
||||||
|
else if ("int".equals(type)) {
|
||||||
int value = Integer.parseInt(val, 16);
|
int value = Integer.parseInt(val, 16);
|
||||||
IntPropertyMap intMap = propMapMgr.getIntPropertyMap(name);
|
IntPropertyMap intMap = propMapMgr.getIntPropertyMap(name);
|
||||||
if (intMap == null) {
|
if (intMap == null) {
|
||||||
intMap = propMapMgr.createIntPropertyMap(name);
|
intMap = propMapMgr.createIntPropertyMap(name);
|
||||||
}
|
}
|
||||||
intMap.add(addr, value);
|
intMap.add(addr, value);
|
||||||
} else if ("long".equals(type)) {
|
}
|
||||||
|
else if ("long".equals(type)) {
|
||||||
long value = Long.parseLong(val, 16);
|
long value = Long.parseLong(val, 16);
|
||||||
LongPropertyMap longMap = propMapMgr.getLongPropertyMap(name);
|
LongPropertyMap longMap = propMapMgr.getLongPropertyMap(name);
|
||||||
if (longMap == null) {
|
if (longMap == null) {
|
||||||
longMap = propMapMgr.createLongPropertyMap(name);
|
longMap = propMapMgr.createLongPropertyMap(name);
|
||||||
}
|
}
|
||||||
longMap.add(addr, value);
|
longMap.add(addr, value);
|
||||||
} else if ("string".equals(type)) {
|
}
|
||||||
|
else if ("string".equals(type)) {
|
||||||
String str = val;
|
String str = val;
|
||||||
StringPropertyMap strMap = propMapMgr.getStringPropertyMap(name);
|
StringPropertyMap strMap = propMapMgr.getStringPropertyMap(name);
|
||||||
if (strMap == null) {
|
if (strMap == null) {
|
||||||
strMap = propMapMgr.createStringPropertyMap(name);
|
strMap = propMapMgr.createStringPropertyMap(name);
|
||||||
}
|
}
|
||||||
strMap.add(addr, str);
|
strMap.add(addr, str);
|
||||||
} else if ("color".equals(type)) {
|
}
|
||||||
ObjectPropertyMap<SaveableColor> objMap = (ObjectPropertyMap<SaveableColor>) propMapMgr
|
else if ("color".equals(type)) {
|
||||||
.getObjectPropertyMap(name);
|
ObjectPropertyMap<SaveableColor> objMap =
|
||||||
|
(ObjectPropertyMap<SaveableColor>) propMapMgr.getObjectPropertyMap(name);
|
||||||
if (objMap == null) {
|
if (objMap == null) {
|
||||||
objMap = propMapMgr.createObjectPropertyMap(name, SaveableColor.class);
|
objMap = propMapMgr.createObjectPropertyMap(name, SaveableColor.class);
|
||||||
}
|
}
|
||||||
objMap.add(addr, new SaveableColor(Color.decode(val)));
|
objMap.add(addr, new SaveableColor(Color.decode(val)));
|
||||||
} else if ("point".equals(type)) {
|
}
|
||||||
|
else if ("point".equals(type)) {
|
||||||
String xstr = val.substring(val.indexOf("[x="), val.indexOf(","));
|
String xstr = val.substring(val.indexOf("[x="), val.indexOf(","));
|
||||||
String ystr = val.substring(val.indexOf("y="), val.indexOf("]"));
|
String ystr = val.substring(val.indexOf("y="), val.indexOf("]"));
|
||||||
ObjectPropertyMap<SaveablePoint> objMap = (ObjectPropertyMap<SaveablePoint>) propMapMgr
|
ObjectPropertyMap<SaveablePoint> objMap =
|
||||||
.getObjectPropertyMap(name);
|
(ObjectPropertyMap<SaveablePoint>) propMapMgr.getObjectPropertyMap(name);
|
||||||
if (objMap == null) {
|
if (objMap == null) {
|
||||||
objMap = propMapMgr.createObjectPropertyMap(name, SaveablePoint.class);
|
objMap = propMapMgr.createObjectPropertyMap(name, SaveablePoint.class);
|
||||||
}
|
}
|
||||||
objMap.add(addr, new SaveablePoint(new Point(Integer.parseInt(xstr), Integer.parseInt(ystr))));
|
objMap.add(addr,
|
||||||
} else if ("bookmarks".equals(type)) {
|
new SaveablePoint(new Point(Integer.parseInt(xstr), Integer.parseInt(ystr))));
|
||||||
|
}
|
||||||
|
else if ("bookmarks".equals(type)) {
|
||||||
// Must retain for backward compatibility with old Ver-1 Note bookmarks which
|
// Must retain for backward compatibility with old Ver-1 Note bookmarks which
|
||||||
// were saved as simple properties
|
// were saved as simple properties
|
||||||
BookmarkManager bmMgr = program.getBookmarkManager();
|
BookmarkManager bmMgr = program.getBookmarkManager();
|
||||||
|
@ -177,7 +167,8 @@ public class PropertiesSarifMgr extends SarifMgr {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bmMgr.setBookmark(addr, BookmarkType.NOTE, name, val);
|
bmMgr.setBookmark(addr, BookmarkType.NOTE, name, val);
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
log.appendMsg("Unsupported PROPERTY usage");
|
log.appendMsg("Unsupported PROPERTY usage");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -202,13 +193,14 @@ public class PropertiesSarifMgr extends SarifMgr {
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private void processPropertyListEntry(String pathname, Map<String, Object> result, boolean overwrite)
|
private void processPropertyListEntry(String pathname, Map<String, Object> result,
|
||||||
throws Exception {
|
boolean overwrite) throws Exception {
|
||||||
|
|
||||||
String listName = getPropertyList(pathname);
|
String listName = getPropertyList(pathname);
|
||||||
String name = getPropertyName(pathname);
|
String name = getPropertyName(pathname);
|
||||||
if (listName == null || name == null) {
|
if (listName == null || name == null) {
|
||||||
log.appendMsg("Property NAME attribute must contain both category prefix and property name");
|
log.appendMsg(
|
||||||
|
"Property NAME attribute must contain both category prefix and property name");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Options list = program.getOptions(listName);
|
Options list = program.getOptions(listName);
|
||||||
|
@ -223,48 +215,76 @@ public class PropertiesSarifMgr extends SarifMgr {
|
||||||
Object val = result.get("value");
|
Object val = result.get("value");
|
||||||
if (type == null || "void".equals(type)) {
|
if (type == null || "void".equals(type)) {
|
||||||
log.appendMsg("Unsupported PROPERTY usage");
|
log.appendMsg("Unsupported PROPERTY usage");
|
||||||
} else if ("int".equals(type)) {
|
}
|
||||||
|
else if ("int".equals(type)) {
|
||||||
list.setInt(name, Integer.parseInt((String) val, 16));
|
list.setInt(name, Integer.parseInt((String) val, 16));
|
||||||
} else if ("long".equals(type)) {
|
}
|
||||||
|
else if ("long".equals(type)) {
|
||||||
list.setLong(name, Long.parseLong((String) val, 16));
|
list.setLong(name, Long.parseLong((String) val, 16));
|
||||||
} else if ("double".equals(type)) {
|
}
|
||||||
|
else if ("double".equals(type)) {
|
||||||
list.setDouble(name, Double.parseDouble((String) val));
|
list.setDouble(name, Double.parseDouble((String) val));
|
||||||
} else if ("float".equals(type)) {
|
}
|
||||||
|
else if ("float".equals(type)) {
|
||||||
list.setFloat(name, Float.parseFloat((String) val));
|
list.setFloat(name, Float.parseFloat((String) val));
|
||||||
} else if ("bool".equals(type)) {
|
}
|
||||||
|
else if ("bool".equals(type)) {
|
||||||
list.setBoolean(name, Boolean.parseBoolean((String) val));
|
list.setBoolean(name, Boolean.parseBoolean((String) val));
|
||||||
} else if ("string".equals(type)) {
|
}
|
||||||
|
else if ("string".equals(type)) {
|
||||||
list.setString(name, (String) val);
|
list.setString(name, (String) val);
|
||||||
} else if ("date".equals(type)) {
|
}
|
||||||
|
else if ("date".equals(type)) {
|
||||||
list.setDate(name, new Date(Long.parseLong((String) val, 16)));
|
list.setDate(name, new Date(Long.parseLong((String) val, 16)));
|
||||||
} else if ("color".equals(type)) {
|
}
|
||||||
|
else if ("color".equals(type)) {
|
||||||
Color color = ColorUtils.getColor((Integer) val);
|
Color color = ColorUtils.getColor((Integer) val);
|
||||||
list.setColor(name, color);
|
list.setColor(name, color);
|
||||||
} else if ("file".equals(type)) {
|
}
|
||||||
|
else if ("file".equals(type)) {
|
||||||
File file = new File((String) val);
|
File file = new File((String) val);
|
||||||
list.setFile(name, file);
|
list.setFile(name, file);
|
||||||
} else if ("enum".equals(type)) {
|
}
|
||||||
|
else if ("enum".equals(type)) {
|
||||||
String sarifString = unEscapeElementEntities((String) val);
|
String sarifString = unEscapeElementEntities((String) val);
|
||||||
@SuppressWarnings("rawtypes")
|
@SuppressWarnings("rawtypes")
|
||||||
Enum enuum = (Enum) OptionType.ENUM_TYPE.convertStringToObject(sarifString);
|
Enum enuum = (Enum) OptionType.ENUM_TYPE.convertStringToObject(sarifString);
|
||||||
list.setEnum(name, enuum);
|
list.setEnum(name, enuum);
|
||||||
} else if ("font".equals(type)) {
|
}
|
||||||
|
else if ("font".equals(type)) {
|
||||||
String sarifString = unEscapeElementEntities((String) val);
|
String sarifString = unEscapeElementEntities((String) val);
|
||||||
Font font = (Font) OptionType.FONT_TYPE.convertStringToObject(sarifString);
|
Font font = (Font) OptionType.FONT_TYPE.convertStringToObject(sarifString);
|
||||||
list.setFont(name, font);
|
list.setFont(name, font);
|
||||||
} else if ("keyStroke".equals(type)) {
|
}
|
||||||
|
else if ("keyStroke".equals(type)) {
|
||||||
String sarifString = unEscapeElementEntities((String) val);
|
String sarifString = unEscapeElementEntities((String) val);
|
||||||
KeyStroke keyStroke = (KeyStroke) OptionType.KEYSTROKE_TYPE.convertStringToObject(sarifString);
|
KeyStroke keyStroke =
|
||||||
list.setKeyStroke(name, keyStroke);
|
(KeyStroke) OptionType.KEYSTROKE_TYPE.convertStringToObject(sarifString);
|
||||||
} else if ("custom".equals(type)) {
|
|
||||||
|
ActionTrigger trigger = null;
|
||||||
|
if (keyStroke != null) {
|
||||||
|
trigger = new ActionTrigger(keyStroke);
|
||||||
|
}
|
||||||
|
list.setActionTrigger(name, trigger);
|
||||||
|
}
|
||||||
|
else if ("actionTrigger".equals(type)) {
|
||||||
String sarifString = unEscapeElementEntities((String) val);
|
String sarifString = unEscapeElementEntities((String) val);
|
||||||
CustomOption custom = (CustomOption) OptionType.CUSTOM_TYPE.convertStringToObject(sarifString);
|
ActionTrigger actionTrigger =
|
||||||
|
(ActionTrigger) OptionType.ACTION_TRIGGER.convertStringToObject(sarifString);
|
||||||
|
list.setActionTrigger(name, actionTrigger);
|
||||||
|
}
|
||||||
|
else if ("custom".equals(type)) {
|
||||||
|
String sarifString = unEscapeElementEntities((String) val);
|
||||||
|
CustomOption custom =
|
||||||
|
(CustomOption) OptionType.CUSTOM_TYPE.convertStringToObject(sarifString);
|
||||||
list.setCustomOption(name, custom);
|
list.setCustomOption(name, custom);
|
||||||
} else if ("bytes".equals(type)) {
|
}
|
||||||
|
else if ("bytes".equals(type)) {
|
||||||
String sarifString = unEscapeElementEntities((String) val);
|
String sarifString = unEscapeElementEntities((String) val);
|
||||||
byte[] bytes = (byte[]) OptionType.BYTE_ARRAY_TYPE.convertStringToObject(sarifString);
|
byte[] bytes = (byte[]) OptionType.BYTE_ARRAY_TYPE.convertStringToObject(sarifString);
|
||||||
list.setByteArray(name, bytes);
|
list.setByteArray(name, bytes);
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
log.appendMsg("Unsupported PROPERTY usage");
|
log.appendMsg("Unsupported PROPERTY usage");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -273,7 +293,8 @@ public class PropertiesSarifMgr extends SarifMgr {
|
||||||
// SARIF WRITE CURRENT DTD //
|
// SARIF WRITE CURRENT DTD //
|
||||||
/////////////////////////////
|
/////////////////////////////
|
||||||
|
|
||||||
void write(JsonArray results, AddressSetView set, TaskMonitor monitor) throws IOException, CancelledException {
|
void write(JsonArray results, AddressSetView set, TaskMonitor monitor)
|
||||||
|
throws IOException, CancelledException {
|
||||||
monitor.setMessage("Writing PROPERTIES ...");
|
monitor.setMessage("Writing PROPERTIES ...");
|
||||||
|
|
||||||
List<String> request = program.getOptionsNames();
|
List<String> request = program.getOptionsNames();
|
||||||
|
@ -290,13 +311,14 @@ public class PropertiesSarifMgr extends SarifMgr {
|
||||||
writeAsSARIF(program, set, mapRequest, results);
|
writeAsSARIF(program, set, mapRequest, results);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void writeAsSARIF(Program program, List<String> request, JsonArray results) throws IOException {
|
public static void writeAsSARIF(Program program, List<String> request, JsonArray results)
|
||||||
|
throws IOException {
|
||||||
SarifPropertyListWriter writer = new SarifPropertyListWriter(program, request, null);
|
SarifPropertyListWriter writer = new SarifPropertyListWriter(program, request, null);
|
||||||
new TaskLauncher(new SarifWriterTask(SUBKEY, writer, results), null);
|
new TaskLauncher(new SarifWriterTask(SUBKEY, writer, results), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void writeAsSARIF(Program program, AddressSetView set, List<PropertyMap<?>> request,
|
public static void writeAsSARIF(Program program, AddressSetView set,
|
||||||
JsonArray results) throws IOException {
|
List<PropertyMap<?>> request, JsonArray results) throws IOException {
|
||||||
SarifPropertyMapWriter writer = new SarifPropertyMapWriter(request, program, set, null);
|
SarifPropertyMapWriter writer = new SarifPropertyMapWriter(request, program, set, null);
|
||||||
new TaskLauncher(new SarifWriterTask(SUBKEY, writer, results), null);
|
new TaskLauncher(new SarifWriterTask(SUBKEY, writer, results), null);
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,8 +19,6 @@ import java.awt.Component;
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
import javax.swing.KeyStroke;
|
|
||||||
|
|
||||||
import org.jdom.Document;
|
import org.jdom.Document;
|
||||||
import org.jdom.output.XMLOutputter;
|
import org.jdom.output.XMLOutputter;
|
||||||
|
|
||||||
|
@ -51,7 +49,6 @@ import ghidra.framework.options.*;
|
||||||
import ghidra.framework.plugintool.*;
|
import ghidra.framework.plugintool.*;
|
||||||
import ghidra.framework.plugintool.util.PluginException;
|
import ghidra.framework.plugintool.util.PluginException;
|
||||||
import ghidra.framework.plugintool.util.PluginStatus;
|
import ghidra.framework.plugintool.util.PluginStatus;
|
||||||
import ghidra.framework.project.tool.GhidraTool;
|
|
||||||
import ghidra.program.model.address.*;
|
import ghidra.program.model.address.*;
|
||||||
import ghidra.program.model.listing.*;
|
import ghidra.program.model.listing.*;
|
||||||
import ghidra.program.model.symbol.Reference;
|
import ghidra.program.model.symbol.Reference;
|
||||||
|
@ -144,8 +141,7 @@ public class VTSubToolManager implements VTControllerListener, OptionsChangeList
|
||||||
toolTemplate = ToolUtils.readToolTemplate(toolFileName);
|
toolTemplate = ToolUtils.readToolTemplate(toolFileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
PluginTool newTool =
|
PluginTool newTool = toolTemplate.createTool(controller.getTool().getProject());
|
||||||
(GhidraTool) toolTemplate.createTool(controller.getTool().getProject());
|
|
||||||
try {
|
try {
|
||||||
VersionTrackingSubordinatePluginX pluginX =
|
VersionTrackingSubordinatePluginX pluginX =
|
||||||
new VersionTrackingSubordinatePluginX(newTool, isSourceTool);
|
new VersionTrackingSubordinatePluginX(newTool, isSourceTool);
|
||||||
|
@ -190,33 +186,38 @@ public class VTSubToolManager implements VTControllerListener, OptionsChangeList
|
||||||
if (processingOptions) {
|
if (processingOptions) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!(newValue instanceof ActionTrigger)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
processingOptions = true;
|
processingOptions = true;
|
||||||
try {
|
try {
|
||||||
if (!(newValue instanceof KeyStroke)) {
|
updateActionTrigger(options, optionName, (ActionTrigger) newValue);
|
||||||
return;
|
|
||||||
}
|
|
||||||
KeyStroke keyStroke = (KeyStroke) newValue;
|
|
||||||
if (sourceTool != null) {
|
|
||||||
Options sourceOptions = sourceTool.getOptions(ToolConstants.KEY_BINDINGS);
|
|
||||||
if (sourceOptions != options) {
|
|
||||||
sourceOptions.setKeyStroke(optionName, keyStroke);
|
|
||||||
sourceTool.refreshKeybindings();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (destinationTool != null) {
|
|
||||||
Options destinationOptions = destinationTool.getOptions(ToolConstants.KEY_BINDINGS);
|
|
||||||
if (destinationOptions != options) {
|
|
||||||
destinationOptions.setKeyStroke(optionName, keyStroke);
|
|
||||||
destinationTool.refreshKeybindings();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
processingOptions = false;
|
processingOptions = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateActionTrigger(ToolOptions options, String optionName,
|
||||||
|
ActionTrigger trigger) {
|
||||||
|
if (sourceTool != null) {
|
||||||
|
Options sourceOptions = sourceTool.getOptions(DockingToolConstants.KEY_BINDINGS);
|
||||||
|
if (sourceOptions != options) {
|
||||||
|
sourceOptions.setActionTrigger(optionName, trigger);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (destinationTool != null) {
|
||||||
|
Options destinationOptions =
|
||||||
|
destinationTool.getOptions(DockingToolConstants.KEY_BINDINGS);
|
||||||
|
if (destinationOptions != options) {
|
||||||
|
destinationOptions.setActionTrigger(optionName, trigger);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void createMatchActions(final PluginTool newTool) {
|
private void createMatchActions(final PluginTool newTool) {
|
||||||
newTool.setMenuGroup(new String[] { VTPlugin.MATCH_POPUP_MENU_NAME }, "1", "1");
|
newTool.setMenuGroup(new String[] { VTPlugin.MATCH_POPUP_MENU_NAME }, "1", "1");
|
||||||
newTool.setMenuGroup(new String[] { VTPlugin.MARKUP_POPUP_MENU_NAME }, "1", "2");
|
newTool.setMenuGroup(new String[] { VTPlugin.MARKUP_POPUP_MENU_NAME }, "1", "2");
|
||||||
|
|
|
@ -0,0 +1,123 @@
|
||||||
|
/* ###
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
import java.awt.BorderLayout;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
|
||||||
|
import docking.widgets.checkbox.GCheckBox;
|
||||||
|
import gui.event.MouseBinding;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A panel that displays inputs for key strokes and mouse bindings.
|
||||||
|
*/
|
||||||
|
public class ActionBindingPanel extends JPanel {
|
||||||
|
|
||||||
|
private static final String DISABLED_HINT = "Select an action";
|
||||||
|
|
||||||
|
private KeyEntryTextField keyEntryField;
|
||||||
|
private JCheckBox useMouseBindingCheckBox;
|
||||||
|
private MouseEntryTextField mouseEntryField;
|
||||||
|
private JPanel textFieldPanel;
|
||||||
|
|
||||||
|
private DockingActionInputBindingListener listener;
|
||||||
|
|
||||||
|
public ActionBindingPanel(DockingActionInputBindingListener listener) {
|
||||||
|
|
||||||
|
this.listener = Objects.requireNonNull(listener);
|
||||||
|
build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void build() {
|
||||||
|
|
||||||
|
setLayout(new BoxLayout(this, BoxLayout.LINE_AXIS));
|
||||||
|
|
||||||
|
textFieldPanel = new JPanel(new BorderLayout());
|
||||||
|
|
||||||
|
keyEntryField = new KeyEntryTextField(20, ks -> listener.keyStrokeChanged(ks));
|
||||||
|
keyEntryField.setDisabledHint(DISABLED_HINT);
|
||||||
|
keyEntryField.setEnabled(false); // enabled on action selection
|
||||||
|
mouseEntryField = new MouseEntryTextField(20, mb -> listener.mouseBindingChanged(mb));
|
||||||
|
mouseEntryField.setDisabledHint(DISABLED_HINT);
|
||||||
|
mouseEntryField.setEnabled(false); // enabled on action selection
|
||||||
|
|
||||||
|
textFieldPanel.add(keyEntryField, BorderLayout.NORTH);
|
||||||
|
|
||||||
|
String checkBoxText = "Enter Mouse Binding";
|
||||||
|
useMouseBindingCheckBox = new GCheckBox(checkBoxText);
|
||||||
|
useMouseBindingCheckBox
|
||||||
|
.setToolTipText("When checked, the text field accepts mouse buttons");
|
||||||
|
useMouseBindingCheckBox.setName(checkBoxText);
|
||||||
|
useMouseBindingCheckBox.addItemListener(e -> updateTextField());
|
||||||
|
|
||||||
|
add(textFieldPanel);
|
||||||
|
add(Box.createHorizontalStrut(5));
|
||||||
|
add(useMouseBindingCheckBox);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateTextField() {
|
||||||
|
|
||||||
|
if (useMouseBindingCheckBox.isSelected()) {
|
||||||
|
textFieldPanel.remove(keyEntryField);
|
||||||
|
textFieldPanel.add(mouseEntryField, BorderLayout.NORTH);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
textFieldPanel.remove(mouseEntryField);
|
||||||
|
textFieldPanel.add(keyEntryField, BorderLayout.NORTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
validate();
|
||||||
|
repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setKeyBindingData(KeyStroke ks, MouseBinding mb) {
|
||||||
|
|
||||||
|
keyEntryField.setKeyStroke(ks);
|
||||||
|
mouseEntryField.setMouseBinding(mb);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setEnabled(boolean enabled) {
|
||||||
|
keyEntryField.clearField();
|
||||||
|
mouseEntryField.clearField();
|
||||||
|
|
||||||
|
keyEntryField.setEnabled(enabled);
|
||||||
|
mouseEntryField.setEnabled(enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearKeyStroke() {
|
||||||
|
keyEntryField.clearField();
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeyStroke getKeyStroke() {
|
||||||
|
return keyEntryField.getKeyStroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
public MouseBinding getMouseBinding() {
|
||||||
|
return mouseEntryField.getMouseBinding();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearMouseBinding() {
|
||||||
|
mouseEntryField.clearField();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isMouseBinding() {
|
||||||
|
return useMouseBindingCheckBox.isSelected();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -47,13 +47,14 @@ public class ActionToGuiMapper {
|
||||||
popupActionManager = new PopupActionManager(winMgr, menuGroupMap);
|
popupActionManager = new PopupActionManager(winMgr, menuGroupMap);
|
||||||
|
|
||||||
DockingWindowsContextSensitiveHelpListener.install();
|
DockingWindowsContextSensitiveHelpListener.install();
|
||||||
|
MouseBindingMouseEventDispatcher.install();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register a specific Help content location for a component.
|
* Register a specific Help content location for a component.
|
||||||
* The DocWinListener will be notified with the help location if the specified
|
* The DocWinListener will be notified with the help location if the specified
|
||||||
* component 'c' has focus and the help key is pressed.
|
* component 'c' has focus and the help key is pressed.
|
||||||
*
|
*
|
||||||
* @param c component
|
* @param c component
|
||||||
* @param helpLocation the help location
|
* @param helpLocation the help location
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -230,6 +230,7 @@ public abstract class ComponentProvider implements HelpDescriptor, ActionContext
|
||||||
if (!isVisible()) {
|
if (!isVisible()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
dockingTool.toFront();
|
dockingTool.toFront();
|
||||||
if (defaultFocusComponent != null) {
|
if (defaultFocusComponent != null) {
|
||||||
DockingWindowManager.requestFocus(defaultFocusComponent);
|
DockingWindowManager.requestFocus(defaultFocusComponent);
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
/* ###
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
import javax.swing.KeyStroke;
|
||||||
|
|
||||||
|
import gui.event.MouseBinding;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple listener interface to notify clients of changes to key strokes and mouse bindings.
|
||||||
|
*/
|
||||||
|
public interface DockingActionInputBindingListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the key stroke is changed.
|
||||||
|
* @param ks the key stroke.
|
||||||
|
*/
|
||||||
|
public void keyStrokeChanged(KeyStroke ks);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the mouse binding is changed.
|
||||||
|
* @param mb the mouse binding.
|
||||||
|
*/
|
||||||
|
public void mouseBindingChanged(MouseBinding mb);
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
/* ###
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
|
|
||||||
|
import docking.action.DockingActionIf;
|
||||||
|
import docking.action.ToggleDockingActionIf;
|
||||||
|
import ghidra.util.Msg;
|
||||||
|
import ghidra.util.Swing;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple class to handle executing the given action. This class will generate the action context
|
||||||
|
* as needed and validate the context before executing the action.
|
||||||
|
*/
|
||||||
|
public class DockingActionPerformer {
|
||||||
|
|
||||||
|
private DockingActionPerformer() {
|
||||||
|
// static only
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes the given action later on the Swing thread.
|
||||||
|
* @param action the action.
|
||||||
|
* @param event the event that triggered the action.
|
||||||
|
*/
|
||||||
|
public static void perform(DockingActionIf action, ActionEvent event) {
|
||||||
|
perform(action, event, DockingWindowManager.getActiveInstance());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes the given action later on the Swing thread.
|
||||||
|
* @param action the action.
|
||||||
|
* @param event the event that triggered the action.
|
||||||
|
* @param windowManager the window manager containing the action being processed.
|
||||||
|
*/
|
||||||
|
public static void perform(DockingActionIf action, ActionEvent event,
|
||||||
|
DockingWindowManager windowManager) {
|
||||||
|
|
||||||
|
if (windowManager == null) {
|
||||||
|
// not sure if this can happen
|
||||||
|
Msg.error(DockingActionPerformer.class,
|
||||||
|
"No window manager found; unable to execute action: " + action.getFullName());
|
||||||
|
}
|
||||||
|
|
||||||
|
DockingWindowManager.clearMouseOverHelp();
|
||||||
|
ActionContext context = windowManager.createActionContext(action);
|
||||||
|
|
||||||
|
context.setSourceObject(event.getSource());
|
||||||
|
context.setEventClickModifiers(event.getModifiers());
|
||||||
|
|
||||||
|
// this gives the UI some time to repaint before executing the action
|
||||||
|
Swing.runLater(() -> {
|
||||||
|
windowManager.setStatusText("");
|
||||||
|
if (action.isValidContext(context) && action.isEnabledForContext(context)) {
|
||||||
|
if (action instanceof ToggleDockingActionIf) {
|
||||||
|
ToggleDockingActionIf toggleAction = ((ToggleDockingActionIf) action);
|
||||||
|
toggleAction.setSelected(!toggleAction.isSelected());
|
||||||
|
}
|
||||||
|
action.actionPerformed(context);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -30,10 +30,9 @@ import docking.actions.KeyBindingUtils;
|
||||||
*/
|
*/
|
||||||
public abstract class DockingKeyBindingAction extends AbstractAction {
|
public abstract class DockingKeyBindingAction extends AbstractAction {
|
||||||
|
|
||||||
|
protected Tool tool;
|
||||||
protected DockingActionIf dockingAction;
|
protected DockingActionIf dockingAction;
|
||||||
|
protected KeyStroke keyStroke;
|
||||||
protected final KeyStroke keyStroke;
|
|
||||||
protected final Tool tool;
|
|
||||||
|
|
||||||
public DockingKeyBindingAction(Tool tool, DockingActionIf action, KeyStroke keyStroke) {
|
public DockingKeyBindingAction(Tool tool, DockingActionIf action, KeyStroke keyStroke) {
|
||||||
super(KeyBindingUtils.parseKeyStroke(keyStroke));
|
super(KeyBindingUtils.parseKeyStroke(keyStroke));
|
||||||
|
@ -42,14 +41,9 @@ public abstract class DockingKeyBindingAction extends AbstractAction {
|
||||||
this.keyStroke = keyStroke;
|
this.keyStroke = keyStroke;
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyStroke getKeyStroke() {
|
|
||||||
return keyStroke;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isEnabled() {
|
public boolean isEnabled() {
|
||||||
// always enable; this is a reserved binding and cannot be disabled
|
return true; // always enable; this is a internal action that cannot be disabled
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract KeyBindingPrecedence getKeyBindingPrecedence();
|
public abstract KeyBindingPrecedence getKeyBindingPrecedence();
|
||||||
|
|
|
@ -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 docking;
|
||||||
|
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import javax.swing.AbstractAction;
|
||||||
|
|
||||||
|
import docking.action.DockingActionIf;
|
||||||
|
import gui.event.MouseBinding;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class for using actions associated with mouse bindings. This class is meant to only by used by
|
||||||
|
* internal Ghidra mouse event processing.
|
||||||
|
*/
|
||||||
|
public class DockingMouseBindingAction extends AbstractAction {
|
||||||
|
|
||||||
|
private DockingActionIf dockingAction;
|
||||||
|
private MouseBinding mouseBinding;
|
||||||
|
|
||||||
|
public DockingMouseBindingAction(DockingActionIf action, MouseBinding mouseBinding) {
|
||||||
|
this.dockingAction = Objects.requireNonNull(action);
|
||||||
|
this.mouseBinding = Objects.requireNonNull(mouseBinding);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFullActionName() {
|
||||||
|
return dockingAction.getFullName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return true; // always enable; this is a internal action that cannot be disabled
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
DockingActionPerformer.perform(dockingAction, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return getFullActionName() + " (" + mouseBinding + ")";
|
||||||
|
}
|
||||||
|
}
|
|
@ -29,8 +29,7 @@ import javax.swing.*;
|
||||||
import org.apache.commons.collections4.map.LazyMap;
|
import org.apache.commons.collections4.map.LazyMap;
|
||||||
import org.jdom.Element;
|
import org.jdom.Element;
|
||||||
|
|
||||||
import docking.action.ActionContextProvider;
|
import docking.action.*;
|
||||||
import docking.action.DockingActionIf;
|
|
||||||
import docking.actions.*;
|
import docking.actions.*;
|
||||||
import docking.widgets.PasswordDialog;
|
import docking.widgets.PasswordDialog;
|
||||||
import generic.util.WindowUtilities;
|
import generic.util.WindowUtilities;
|
||||||
|
@ -41,6 +40,7 @@ import ghidra.util.*;
|
||||||
import ghidra.util.datastruct.*;
|
import ghidra.util.datastruct.*;
|
||||||
import ghidra.util.exception.AssertException;
|
import ghidra.util.exception.AssertException;
|
||||||
import ghidra.util.task.SwingUpdateManager;
|
import ghidra.util.task.SwingUpdateManager;
|
||||||
|
import gui.event.MouseBinding;
|
||||||
import help.Help;
|
import help.Help;
|
||||||
import help.HelpService;
|
import help.HelpService;
|
||||||
import util.CollectionUtils;
|
import util.CollectionUtils;
|
||||||
|
@ -707,7 +707,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the local actions installed on the given provider
|
* Get the local actions installed on the given provider
|
||||||
*
|
*
|
||||||
* @param provider the provider
|
* @param provider the provider
|
||||||
* @return an iterator over the actions
|
* @return an iterator over the actions
|
||||||
*/
|
*/
|
||||||
|
@ -754,7 +754,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
|
||||||
* Returns any action that is bound to the given keystroke for the tool associated with this
|
* Returns any action that is bound to the given keystroke for the tool associated with this
|
||||||
* DockingWindowManager instance.
|
* DockingWindowManager instance.
|
||||||
*
|
*
|
||||||
* @param keyStroke The keystroke to check for key bindings.
|
* @param keyStroke The keystroke to check for a bound action.
|
||||||
* @return The action that is bound to the keystroke, or null of there is no binding for the
|
* @return The action that is bound to the keystroke, or null of there is no binding for the
|
||||||
* given keystroke.
|
* given keystroke.
|
||||||
*/
|
*/
|
||||||
|
@ -768,6 +768,24 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns any action that is bound to the given mouse binding for the tool associated with this
|
||||||
|
* DockingWindowManager instance.
|
||||||
|
*
|
||||||
|
* @param mouseBinding The mouse binding to check for a bound action.
|
||||||
|
* @return The action associated with the mouse binding , or null of there is no binding for the
|
||||||
|
* given keystroke.
|
||||||
|
*/
|
||||||
|
Action getActionForMouseBinding(MouseBinding mouseBinding) {
|
||||||
|
DockingToolActions toolActions = tool.getToolActions();
|
||||||
|
if (toolActions instanceof ToolActions) {
|
||||||
|
// Using a cast here; it didn't make sense to include this 'getAction' on the
|
||||||
|
// DockingToolActions
|
||||||
|
return ((ToolActions) toolActions).getAction(mouseBinding);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
//==================================================================================================
|
//==================================================================================================
|
||||||
// End Package-level Methods
|
// End Package-level Methods
|
||||||
//==================================================================================================
|
//==================================================================================================
|
||||||
|
@ -1189,8 +1207,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
tool.getToolActions()
|
tool.getToolActions().removeActions(DOCKING_WINDOWS_OWNER);
|
||||||
.removeActions(DOCKING_WINDOWS_OWNER);
|
|
||||||
|
|
||||||
Map<String, List<ComponentPlaceholder>> permanentMap =
|
Map<String, List<ComponentPlaceholder>> permanentMap =
|
||||||
LazyMap.lazyMap(new HashMap<>(), menuName -> new ArrayList<>());
|
LazyMap.lazyMap(new HashMap<>(), menuName -> new ArrayList<>());
|
||||||
|
@ -1206,12 +1223,10 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
|
||||||
|
|
||||||
String subMenuName = provider.getWindowSubMenuName();
|
String subMenuName = provider.getWindowSubMenuName();
|
||||||
if (provider.isTransient() && !provider.isSnapshot()) {
|
if (provider.isTransient() && !provider.isSnapshot()) {
|
||||||
transientMap.get(subMenuName)
|
transientMap.get(subMenuName).add(placeholder);
|
||||||
.add(placeholder);
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
permanentMap.get(subMenuName)
|
permanentMap.get(subMenuName).add(placeholder);
|
||||||
.add(placeholder);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
promoteSingleMenuGroups(permanentMap);
|
promoteSingleMenuGroups(permanentMap);
|
||||||
|
@ -1225,8 +1240,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isWindowMenuShowing() {
|
private boolean isWindowMenuShowing() {
|
||||||
MenuElement[] selectedPath = MenuSelectionManager.defaultManager()
|
MenuElement[] selectedPath = MenuSelectionManager.defaultManager().getSelectedPath();
|
||||||
.getSelectedPath();
|
|
||||||
if (selectedPath == null || selectedPath.length == 0) {
|
if (selectedPath == null || selectedPath.length == 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -1282,8 +1296,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
|
||||||
|
|
||||||
List<ComponentPlaceholder> list = lazyMap.get(key);
|
List<ComponentPlaceholder> list = lazyMap.get(key);
|
||||||
if (list.size() == 1) {
|
if (list.size() == 1) {
|
||||||
lazyMap.get(null /*submenu name*/)
|
lazyMap.get(null /*submenu name*/).add(list.get(0));
|
||||||
.add(list.get(0));
|
|
||||||
lazyMap.remove(key);
|
lazyMap.remove(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1422,10 +1435,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
|
||||||
for (Entry<ComponentProvider, ComponentPlaceholder> entry : entrySet) {
|
for (Entry<ComponentProvider, ComponentPlaceholder> entry : entrySet) {
|
||||||
ComponentProvider provider = entry.getKey();
|
ComponentProvider provider = entry.getKey();
|
||||||
ComponentPlaceholder placeholder = entry.getValue();
|
ComponentPlaceholder placeholder = entry.getValue();
|
||||||
if (provider.getOwner()
|
if (provider.getOwner().equals(focusOwner) && provider.getName().equals(focusName)) {
|
||||||
.equals(focusOwner) &&
|
|
||||||
provider.getName()
|
|
||||||
.equals(focusName)) {
|
|
||||||
focusReplacement = placeholder;
|
focusReplacement = placeholder;
|
||||||
break; // found one!
|
break; // found one!
|
||||||
}
|
}
|
||||||
|
@ -1502,7 +1512,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clears the docking window manager's notion of the active provider. This is used
|
* Clears the docking window manager's notion of the active provider. This is used
|
||||||
* when a component that is not contained within a dockable component gets focus
|
* when a component that is not contained within a dockable component gets focus
|
||||||
* (e.g., JTabbedPanes for stacked components).
|
* (e.g., JTabbedPanes for stacked components).
|
||||||
*/
|
*/
|
||||||
private void deactivateFocusedComponent() {
|
private void deactivateFocusedComponent() {
|
||||||
|
@ -1552,8 +1562,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
|
||||||
@Override
|
@Override
|
||||||
public void propertyChange(PropertyChangeEvent evt) {
|
public void propertyChange(PropertyChangeEvent evt) {
|
||||||
|
|
||||||
Window win = KeyboardFocusManager.getCurrentKeyboardFocusManager()
|
Window win = KeyboardFocusManager.getCurrentKeyboardFocusManager().getActiveWindow();
|
||||||
.getActiveWindow();
|
|
||||||
if (!isMyWindow(win)) {
|
if (!isMyWindow(win)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1693,8 +1702,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
|
||||||
toolPreferencesElement.getChildren(PreferenceState.PREFERENCE_STATE_NAME);
|
toolPreferencesElement.getChildren(PreferenceState.PREFERENCE_STATE_NAME);
|
||||||
for (Object name : children) {
|
for (Object name : children) {
|
||||||
Element preferencesElement = (Element) name;
|
Element preferencesElement = (Element) name;
|
||||||
preferenceStateMap.put(preferencesElement.getAttribute("NAME")
|
preferenceStateMap.put(preferencesElement.getAttribute("NAME").getValue(),
|
||||||
.getValue(),
|
|
||||||
new PreferenceState(preferencesElement));
|
new PreferenceState(preferencesElement));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1954,7 +1962,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Note: Which window should be the parent of the dialog when the user does not specify?
|
Note: Which window should be the parent of the dialog when the user does not specify?
|
||||||
|
|
||||||
Some use cases; a dialog is shown from:
|
Some use cases; a dialog is shown from:
|
||||||
1) A toolbar action
|
1) A toolbar action
|
||||||
2) A component provider's code
|
2) A component provider's code
|
||||||
|
@ -1962,7 +1970,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
|
||||||
4) A background thread
|
4) A background thread
|
||||||
5) The help window
|
5) The help window
|
||||||
6) A modal password dialog appears over the splash screen
|
6) A modal password dialog appears over the splash screen
|
||||||
|
|
||||||
It seems like the parent should be the active window for 1-2.
|
It seems like the parent should be the active window for 1-2.
|
||||||
Case 3 should probably use the window of the dialog provider.
|
Case 3 should probably use the window of the dialog provider.
|
||||||
Case 4 should probably use the main tool frame, since the user may be
|
Case 4 should probably use the main tool frame, since the user may be
|
||||||
|
@ -1970,12 +1978,12 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
|
||||||
active window, we can default to the tool's frame.
|
active window, we can default to the tool's frame.
|
||||||
Case 5 should use the help window.
|
Case 5 should use the help window.
|
||||||
Case 6 should use the splash screen as the parent.
|
Case 6 should use the splash screen as the parent.
|
||||||
|
|
||||||
We have not yet solidified how we should parent. This documentation is meant to
|
We have not yet solidified how we should parent. This documentation is meant to
|
||||||
move us towards clarity as we find Use Cases that don't make sense. (Once we
|
move us towards clarity as we find Use Cases that don't make sense. (Once we
|
||||||
finalize our understanding, we should update the javadoc to list exactly where
|
finalize our understanding, we should update the javadoc to list exactly where
|
||||||
the given Dialog Component will be shown.)
|
the given Dialog Component will be shown.)
|
||||||
|
|
||||||
Use Case
|
Use Case
|
||||||
A -The user presses an action on a toolbar from a window on screen 1, while the
|
A -The user presses an action on a toolbar from a window on screen 1, while the
|
||||||
main tool frame is on screen 2. We want the popup window to appear on screen
|
main tool frame is on screen 2. We want the popup window to appear on screen
|
||||||
|
@ -1994,12 +2002,12 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
|
||||||
E -A long-running API shows a non-modal progress dialog. This API then shows a
|
E -A long-running API shows a non-modal progress dialog. This API then shows a
|
||||||
results dialog which is also non-modal. We do not want to parent the new dialog
|
results dialog which is also non-modal. We do not want to parent the new dialog
|
||||||
to the original dialog, since it is a progress dialog that will go away.
|
to the original dialog, since it is a progress dialog that will go away.
|
||||||
|
|
||||||
|
|
||||||
For now, the easiest mental model to use is to always prefer the active non-transient
|
For now, the easiest mental model to use is to always prefer the active non-transient
|
||||||
window so that a dialog will appear in the user's view. If we find a case where this is
|
window so that a dialog will appear in the user's view. If we find a case where this is
|
||||||
not desired, then document it here.
|
not desired, then document it here.
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
DockingWindowManager dwm = getActiveInstance();
|
DockingWindowManager dwm = getActiveInstance();
|
||||||
|
@ -2183,8 +2191,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
|
||||||
|
|
||||||
setStatusText(text);
|
setStatusText(text);
|
||||||
if (beep) {
|
if (beep) {
|
||||||
Toolkit.getDefaultToolkit()
|
Toolkit.getDefaultToolkit().beep();
|
||||||
.beep();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2201,8 +2208,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
|
||||||
* A convenience method to make an attention-grabbing noise to the user
|
* A convenience method to make an attention-grabbing noise to the user
|
||||||
*/
|
*/
|
||||||
public static void beep() {
|
public static void beep() {
|
||||||
Toolkit.getDefaultToolkit()
|
Toolkit.getDefaultToolkit().beep();
|
||||||
.beep();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -2274,8 +2280,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
|
||||||
if (includeMain) {
|
if (includeMain) {
|
||||||
winList.add(root.getMainWindow());
|
winList.add(root.getMainWindow());
|
||||||
}
|
}
|
||||||
Iterator<DetachedWindowNode> it = root.getDetachedWindows()
|
Iterator<DetachedWindowNode> it = root.getDetachedWindows().iterator();
|
||||||
.iterator();
|
|
||||||
while (it.hasNext()) {
|
while (it.hasNext()) {
|
||||||
DetachedWindowNode node = it.next();
|
DetachedWindowNode node = it.next();
|
||||||
Window win = node.getWindow();
|
Window win = node.getWindow();
|
||||||
|
@ -2450,8 +2455,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
|
||||||
defaultContextProviderMap.entrySet();
|
defaultContextProviderMap.entrySet();
|
||||||
|
|
||||||
for (Entry<Class<? extends ActionContext>, ActionContextProvider> entry : entrySet) {
|
for (Entry<Class<? extends ActionContext>, ActionContextProvider> entry : entrySet) {
|
||||||
contextMap.put(entry.getKey(), entry.getValue()
|
contextMap.put(entry.getKey(), entry.getValue().getActionContext(null));
|
||||||
.getActionContext(null));
|
|
||||||
}
|
}
|
||||||
return contextMap;
|
return contextMap;
|
||||||
}
|
}
|
||||||
|
@ -2472,7 +2476,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Some actions work on a non-active, default component provider. See if this action
|
// Some actions work on a non-active, default component provider. See if this action
|
||||||
// supports that.
|
// supports that.
|
||||||
if (action.supportsDefaultContext()) {
|
if (action.supportsDefaultContext()) {
|
||||||
context = getDefaultContext(action.getContextClass());
|
context = getDefaultContext(action.getContextClass());
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
/* ###
|
/* ###
|
||||||
* IP: GHIDRA
|
* IP: GHIDRA
|
||||||
* REVIEWED: YES
|
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -19,12 +18,8 @@ package docking;
|
||||||
import javax.swing.KeyStroke;
|
import javax.swing.KeyStroke;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface used to notify listener when a keystroke was entered in the
|
* Interface used to notify listener when a keystroke has changed.
|
||||||
* KeyEntryPanel.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
public interface KeyEntryListener {
|
public interface KeyEntryListener {
|
||||||
|
|
||||||
public void processEntry(KeyStroke keyStroke);
|
public void processEntry(KeyStroke keyStroke);
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,16 +17,20 @@ package docking;
|
||||||
|
|
||||||
import java.awt.event.KeyEvent;
|
import java.awt.event.KeyEvent;
|
||||||
import java.awt.event.KeyListener;
|
import java.awt.event.KeyListener;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
import javax.swing.JTextField;
|
|
||||||
import javax.swing.KeyStroke;
|
import javax.swing.KeyStroke;
|
||||||
|
|
||||||
import docking.actions.KeyBindingUtils;
|
import docking.actions.KeyBindingUtils;
|
||||||
|
import docking.widgets.textfield.HintTextField;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Text field captures key strokes and notifies a listener to process the key entry.
|
* Text field captures key strokes and notifies a listener to process the key entry.
|
||||||
*/
|
*/
|
||||||
public class KeyEntryTextField extends JTextField {
|
public class KeyEntryTextField extends HintTextField {
|
||||||
|
|
||||||
|
private static final String HINT = "Type a key";
|
||||||
|
private String disabledHint = HINT;
|
||||||
|
|
||||||
private KeyEntryListener listener;
|
private KeyEntryListener listener;
|
||||||
private String ksName;
|
private String ksName;
|
||||||
|
@ -38,11 +42,28 @@ public class KeyEntryTextField extends JTextField {
|
||||||
* @param listener listener that is notified when the a key is pressed
|
* @param listener listener that is notified when the a key is pressed
|
||||||
*/
|
*/
|
||||||
public KeyEntryTextField(int columns, KeyEntryListener listener) {
|
public KeyEntryTextField(int columns, KeyEntryListener listener) {
|
||||||
super(columns);
|
super(HINT);
|
||||||
|
setName("Key Entry Text Field");
|
||||||
|
getAccessibleContext().setAccessibleName(getName());
|
||||||
|
setColumns(columns);
|
||||||
this.listener = listener;
|
this.listener = listener;
|
||||||
addKeyListener(new MyKeyListener());
|
addKeyListener(new MyKeyListener());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setEnabled(boolean enabled) {
|
||||||
|
setHint(enabled ? HINT : disabledHint);
|
||||||
|
super.setEnabled(enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the hint text that will be displayed when this field is disabled
|
||||||
|
* @param disabledHint the hint text
|
||||||
|
*/
|
||||||
|
public void setDisabledHint(String disabledHint) {
|
||||||
|
this.disabledHint = Objects.requireNonNull(disabledHint);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the current key stroke
|
* Get the current key stroke
|
||||||
* @return the key stroke
|
* @return the key stroke
|
||||||
|
@ -56,7 +77,7 @@ public class KeyEntryTextField extends JTextField {
|
||||||
* @param ks the new key stroke
|
* @param ks the new key stroke
|
||||||
*/
|
*/
|
||||||
public void setKeyStroke(KeyStroke ks) {
|
public void setKeyStroke(KeyStroke ks) {
|
||||||
processEntry(ks);
|
processKeyStroke(ks, false);
|
||||||
setText(KeyBindingUtils.parseKeyStroke(ks));
|
setText(KeyBindingUtils.parseKeyStroke(ks));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,7 +87,7 @@ public class KeyEntryTextField extends JTextField {
|
||||||
currentKeyStroke = null;
|
currentKeyStroke = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processEntry(KeyStroke ks) {
|
private void processKeyStroke(KeyStroke ks, boolean notify) {
|
||||||
ksName = null;
|
ksName = null;
|
||||||
|
|
||||||
currentKeyStroke = ks;
|
currentKeyStroke = ks;
|
||||||
|
@ -79,7 +100,10 @@ public class KeyEntryTextField extends JTextField {
|
||||||
ksName = KeyBindingUtils.parseKeyStroke(ks);
|
ksName = KeyBindingUtils.parseKeyStroke(ks);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
listener.processEntry(ks);
|
|
||||||
|
if (notify) {
|
||||||
|
listener.processEntry(ks);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class MyKeyListener implements KeyListener {
|
private class MyKeyListener implements KeyListener {
|
||||||
|
@ -107,7 +131,7 @@ public class KeyEntryTextField extends JTextField {
|
||||||
if (!isClearKey(keyCode) && !isModifiersOnly(e)) {
|
if (!isClearKey(keyCode) && !isModifiersOnly(e)) {
|
||||||
keyStroke = KeyStroke.getKeyStroke(keyCode, e.getModifiersEx());
|
keyStroke = KeyStroke.getKeyStroke(keyCode, e.getModifiersEx());
|
||||||
}
|
}
|
||||||
processEntry(keyStroke);
|
processKeyStroke(keyStroke, true);
|
||||||
e.consume();
|
e.consume();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,9 +18,7 @@ package docking;
|
||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
|
|
||||||
import docking.action.DockingActionIf;
|
import docking.action.DockingActionIf;
|
||||||
import docking.action.ToggleDockingActionIf;
|
|
||||||
import docking.menu.MenuHandler;
|
import docking.menu.MenuHandler;
|
||||||
import ghidra.util.Swing;
|
|
||||||
|
|
||||||
public class MenuBarMenuHandler extends MenuHandler {
|
public class MenuBarMenuHandler extends MenuHandler {
|
||||||
|
|
||||||
|
@ -41,24 +39,7 @@ public class MenuBarMenuHandler extends MenuHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void processMenuAction(final DockingActionIf action, final ActionEvent event) {
|
public void processMenuAction(DockingActionIf action, ActionEvent event) {
|
||||||
|
DockingActionPerformer.perform(action, event, windowManager);
|
||||||
DockingWindowManager.clearMouseOverHelp();
|
|
||||||
|
|
||||||
ActionContext context = windowManager.createActionContext(action);
|
|
||||||
|
|
||||||
context.setSourceObject(event.getSource());
|
|
||||||
|
|
||||||
// this gives the UI some time to repaint before executing the action
|
|
||||||
Swing.runLater(() -> {
|
|
||||||
windowManager.setStatusText("");
|
|
||||||
if (action.isValidContext(context) && action.isEnabledForContext(context)) {
|
|
||||||
if (action instanceof ToggleDockingActionIf) {
|
|
||||||
ToggleDockingActionIf toggleAction = ((ToggleDockingActionIf) action);
|
|
||||||
toggleAction.setSelected(!toggleAction.isSelected());
|
|
||||||
}
|
|
||||||
action.actionPerformed(context);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,185 @@
|
||||||
|
/* ###
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.*;
|
||||||
|
|
||||||
|
import org.apache.logging.log4j.message.ParameterizedMessage;
|
||||||
|
|
||||||
|
import ghidra.util.Msg;
|
||||||
|
import gui.event.MouseBinding;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows Ghidra to give preference to its mouse event processing over the default Java mouse event
|
||||||
|
* processing. This class allows us to assign mouse bindings to actions.
|
||||||
|
* <p>
|
||||||
|
* {@link #install()} must be called in order to install this <code>Singleton</code> into Java's
|
||||||
|
* mouse event processing system.
|
||||||
|
*
|
||||||
|
* @see KeyBindingOverrideKeyEventDispatcher
|
||||||
|
*/
|
||||||
|
public class MouseBindingMouseEventDispatcher {
|
||||||
|
|
||||||
|
private static MouseBindingMouseEventDispatcher instance;
|
||||||
|
|
||||||
|
static synchronized void install() {
|
||||||
|
if (instance == null) {
|
||||||
|
instance = new MouseBindingMouseEventDispatcher();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the current focus owner. This allows for dependency injection.
|
||||||
|
*/
|
||||||
|
private FocusOwnerProvider focusProvider = new DefaultFocusOwnerProvider();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We use this action as a signal that we intend to process a mouse binding and that no other
|
||||||
|
* Java component should try to handle it.
|
||||||
|
* <p>
|
||||||
|
* This action is one that is triggered by a mouse pressed, but will be processed on a
|
||||||
|
* mouse released. We do this to ensure that we consume all related mouse events (press and
|
||||||
|
* release) and to be consistent with the {@link KeyBindingOverrideKeyEventDispatcher}.
|
||||||
|
*/
|
||||||
|
private PendingActionInfo inProgressAction;
|
||||||
|
|
||||||
|
private MouseBindingMouseEventDispatcher() {
|
||||||
|
// Note: see the documentation on addAWTEventListener() for limitations of using this
|
||||||
|
// listener mechanism
|
||||||
|
Toolkit toolkit = Toolkit.getDefaultToolkit();
|
||||||
|
AWTEventListener listener = new AWTEventListener() {
|
||||||
|
@Override
|
||||||
|
public void eventDispatched(AWTEvent event) {
|
||||||
|
process((MouseEvent) event);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
toolkit.addAWTEventListener(listener, AWTEvent.MOUSE_EVENT_MASK);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void process(MouseEvent e) {
|
||||||
|
|
||||||
|
int id = e.getID();
|
||||||
|
if (id == MouseEvent.MOUSE_ENTERED || id == MouseEvent.MOUSE_EXITED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// always let the application finish processing key events that it started
|
||||||
|
if (actionInProgress(e)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseBinding mouseBinding = MouseBinding.getMouseBinding(e);
|
||||||
|
DockingMouseBindingAction action = getDockingKeyBindingActionForEvent(mouseBinding);
|
||||||
|
Msg.trace(this,
|
||||||
|
new ParameterizedMessage("Mouse binding to action: {} to {}", mouseBinding, action));
|
||||||
|
if (action == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
inProgressAction = new PendingActionInfo(action, mouseBinding);
|
||||||
|
e.consume();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to clear the flag that signals we are in the middle of processing a Ghidra action.
|
||||||
|
*/
|
||||||
|
private boolean actionInProgress(MouseEvent e) {
|
||||||
|
|
||||||
|
if (inProgressAction == null) {
|
||||||
|
Msg.trace(this, "No mouse binding action in progress");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: mouse buttons can be simultaneously clicked. This means that the order of pressed
|
||||||
|
// and released events may arrive intermixed. To handle this correctly, we allow the
|
||||||
|
// MouseBinding to check for the matching release event.
|
||||||
|
MouseBinding mouseBinding = inProgressAction.mouseBinding();
|
||||||
|
boolean isMatching = mouseBinding.isMatchingRelease(e);
|
||||||
|
Msg.trace(this,
|
||||||
|
new ParameterizedMessage(
|
||||||
|
"Is release event for in-progress mouse binding action? {} for {}", isMatching,
|
||||||
|
inProgressAction.action()));
|
||||||
|
if (isMatching) {
|
||||||
|
DockingMouseBindingAction action = inProgressAction.action();
|
||||||
|
inProgressAction = null;
|
||||||
|
|
||||||
|
String command = null;
|
||||||
|
Object source = e.getSource();
|
||||||
|
long when = e.getWhen();
|
||||||
|
int modifiers = e.getModifiersEx();
|
||||||
|
|
||||||
|
action.actionPerformed(
|
||||||
|
new ActionEvent(source, ActionEvent.ACTION_PERFORMED, command, when, modifiers));
|
||||||
|
}
|
||||||
|
|
||||||
|
e.consume();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private DockingMouseBindingAction getDockingKeyBindingActionForEvent(
|
||||||
|
MouseBinding mouseBinding) {
|
||||||
|
DockingWindowManager activeManager = getActiveDockingWindowManager();
|
||||||
|
if (activeManager == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
DockingMouseBindingAction bindingAction =
|
||||||
|
(DockingMouseBindingAction) activeManager.getActionForMouseBinding(mouseBinding);
|
||||||
|
return bindingAction;
|
||||||
|
}
|
||||||
|
|
||||||
|
private DockingWindowManager getActiveDockingWindowManager() {
|
||||||
|
// we need an active window to process events
|
||||||
|
Window activeWindow = focusProvider.getActiveWindow();
|
||||||
|
if (activeWindow == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
DockingWindowManager activeManager = DockingWindowManager.getActiveInstance();
|
||||||
|
if (activeManager == null) {
|
||||||
|
// this can happen if clients use DockingWindows Look and Feel settings or
|
||||||
|
// DockingWindows widgets without using the DockingWindows system (like in tests or
|
||||||
|
// in stand-alone, non-Ghidra apps).
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
DockingWindowManager managingInstance = getDockingWindowManagerForWindow(activeWindow);
|
||||||
|
if (managingInstance != null) {
|
||||||
|
return managingInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is a case where the current window is unaffiliated with a DockingWindowManager,
|
||||||
|
// like a JavaHelp window
|
||||||
|
return activeManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DockingWindowManager getDockingWindowManagerForWindow(Window activeWindow) {
|
||||||
|
DockingWindowManager manager = DockingWindowManager.getInstance(activeWindow);
|
||||||
|
if (manager != null) {
|
||||||
|
return manager;
|
||||||
|
}
|
||||||
|
if (activeWindow instanceof DockingDialog) {
|
||||||
|
DockingDialog dockingDialog = (DockingDialog) activeWindow;
|
||||||
|
return dockingDialog.getOwningWindowManager();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private record PendingActionInfo(DockingMouseBindingAction action, MouseBinding mouseBinding) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,137 @@
|
||||||
|
/* ###
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
import java.awt.event.*;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import docking.widgets.textfield.HintTextField;
|
||||||
|
import gui.event.MouseBinding;
|
||||||
|
|
||||||
|
public class MouseEntryTextField extends HintTextField {
|
||||||
|
|
||||||
|
private static final String HINT = "Press a mouse button";
|
||||||
|
private String disabledHint = HINT;
|
||||||
|
|
||||||
|
private MouseBinding mouseBinding;
|
||||||
|
private Consumer<MouseBinding> listener;
|
||||||
|
|
||||||
|
public MouseEntryTextField(int columns, Consumer<MouseBinding> listener) {
|
||||||
|
super(HINT);
|
||||||
|
setColumns(columns);
|
||||||
|
setName("Mouse Entry Text Field");
|
||||||
|
getAccessibleContext().setAccessibleName(getName());
|
||||||
|
this.listener = Objects.requireNonNull(listener);
|
||||||
|
|
||||||
|
addMouseListener(new MyMouseListener());
|
||||||
|
addKeyListener(new MyKeyListener());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setEnabled(boolean enabled) {
|
||||||
|
setHint(enabled ? HINT : disabledHint);
|
||||||
|
super.setEnabled(enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the hint text that will be displayed when this field is disabled
|
||||||
|
* @param disabledHint the hint text
|
||||||
|
*/
|
||||||
|
public void setDisabledHint(String disabledHint) {
|
||||||
|
this.disabledHint = Objects.requireNonNull(disabledHint);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MouseBinding getMouseBinding() {
|
||||||
|
return mouseBinding;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMouseBinding(MouseBinding mb) {
|
||||||
|
processMouseBinding(mb, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearField() {
|
||||||
|
processMouseBinding(null, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processMouseBinding(MouseBinding mb, boolean notify) {
|
||||||
|
|
||||||
|
this.mouseBinding = mb;
|
||||||
|
if (mouseBinding == null) {
|
||||||
|
setText("");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setText(mouseBinding.getDisplayText());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (notify) {
|
||||||
|
listener.accept(mb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MyMouseListener extends MouseAdapter {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mousePressed(MouseEvent e) {
|
||||||
|
if (!MouseEntryTextField.this.isEnabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int modifiersEx = e.getModifiersEx();
|
||||||
|
int button = e.getButton();
|
||||||
|
processMouseBinding(new MouseBinding(button, modifiersEx), true);
|
||||||
|
e.consume();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseReleased(MouseEvent e) {
|
||||||
|
e.consume();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseClicked(MouseEvent e) {
|
||||||
|
e.consume();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MyKeyListener implements KeyListener {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void keyTyped(KeyEvent e) {
|
||||||
|
e.consume();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void keyReleased(KeyEvent e) {
|
||||||
|
e.consume();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void keyPressed(KeyEvent e) {
|
||||||
|
int keyCode = e.getKeyCode();
|
||||||
|
if (isClearKey(keyCode)) {
|
||||||
|
processMouseBinding(null, true);
|
||||||
|
}
|
||||||
|
e.consume();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isClearKey(int keyCode) {
|
||||||
|
return keyCode == KeyEvent.VK_BACK_SPACE || keyCode == KeyEvent.VK_ENTER;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -30,6 +30,7 @@ import ghidra.util.*;
|
||||||
import ghidra.util.datastruct.WeakDataStructureFactory;
|
import ghidra.util.datastruct.WeakDataStructureFactory;
|
||||||
import ghidra.util.datastruct.WeakSet;
|
import ghidra.util.datastruct.WeakSet;
|
||||||
import ghidra.util.exception.AssertException;
|
import ghidra.util.exception.AssertException;
|
||||||
|
import gui.event.MouseBinding;
|
||||||
import resources.ResourceManager;
|
import resources.ResourceManager;
|
||||||
import utilities.util.reflection.ReflectionUtilities;
|
import utilities.util.reflection.ReflectionUtilities;
|
||||||
|
|
||||||
|
@ -323,6 +324,10 @@ public abstract class DockingAction implements DockingActionIf {
|
||||||
return menuItem;
|
return menuItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private MouseBinding getMouseBinding() {
|
||||||
|
return keyBindingData == null ? null : keyBindingData.getMouseBinding();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public KeyBindingType getKeyBindingType() {
|
public KeyBindingType getKeyBindingType() {
|
||||||
return keyBindingType;
|
return keyBindingType;
|
||||||
|
@ -383,6 +388,10 @@ public abstract class DockingAction implements DockingActionIf {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setUnvalidatedKeyBindingData(KeyBindingData newKeyBindingData) {
|
public void setUnvalidatedKeyBindingData(KeyBindingData newKeyBindingData) {
|
||||||
|
if (Objects.equals(keyBindingData, newKeyBindingData)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
KeyBindingData oldData = keyBindingData;
|
KeyBindingData oldData = keyBindingData;
|
||||||
keyBindingData = newKeyBindingData;
|
keyBindingData = newKeyBindingData;
|
||||||
firePropertyChanged(KEYBINDING_DATA_PROPERTY, oldData, keyBindingData);
|
firePropertyChanged(KEYBINDING_DATA_PROPERTY, oldData, keyBindingData);
|
||||||
|
@ -492,8 +501,8 @@ public abstract class DockingAction implements DockingActionIf {
|
||||||
|
|
||||||
// menu path
|
// menu path
|
||||||
if (menuBarData != null) {
|
if (menuBarData != null) {
|
||||||
buffer.append(" MENU PATH: ")
|
buffer.append(" MENU PATH: ").append(
|
||||||
.append(menuBarData.getMenuPathAsString());
|
menuBarData.getMenuPathAsString());
|
||||||
buffer.append('\n');
|
buffer.append('\n');
|
||||||
buffer.append(" MENU GROUP: ").append(menuBarData.getMenuGroup());
|
buffer.append(" MENU GROUP: ").append(menuBarData.getMenuGroup());
|
||||||
buffer.append('\n');
|
buffer.append('\n');
|
||||||
|
@ -519,8 +528,8 @@ public abstract class DockingAction implements DockingActionIf {
|
||||||
|
|
||||||
// popup menu path
|
// popup menu path
|
||||||
if (popupMenuData != null) {
|
if (popupMenuData != null) {
|
||||||
buffer.append(" POPUP PATH: ")
|
buffer.append(" POPUP PATH: ").append(
|
||||||
.append(popupMenuData.getMenuPathAsString());
|
popupMenuData.getMenuPathAsString());
|
||||||
buffer.append('\n');
|
buffer.append('\n');
|
||||||
buffer.append(" POPUP GROUP: ").append(popupMenuData.getMenuGroup());
|
buffer.append(" POPUP GROUP: ").append(popupMenuData.getMenuGroup());
|
||||||
buffer.append('\n');
|
buffer.append('\n');
|
||||||
|
@ -569,10 +578,15 @@ public abstract class DockingAction implements DockingActionIf {
|
||||||
|
|
||||||
KeyStroke keyStroke = getKeyBinding();
|
KeyStroke keyStroke = getKeyBinding();
|
||||||
if (keyStroke != null) {
|
if (keyStroke != null) {
|
||||||
buffer.append(" KEYBINDING: ").append(keyStroke.toString());
|
buffer.append(" KEYBINDING: ").append(keyStroke);
|
||||||
buffer.append('\n');
|
buffer.append('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MouseBinding mouseBinding = getMouseBinding();
|
||||||
|
if (mouseBinding != null) {
|
||||||
|
buffer.append(" MOUSE BINDING: ").append(mouseBinding);
|
||||||
|
}
|
||||||
|
|
||||||
String inception = getInceptionInformation();
|
String inception = getInceptionInformation();
|
||||||
if (inception != null) {
|
if (inception != null) {
|
||||||
buffer.append("\n \n");
|
buffer.append("\n \n");
|
||||||
|
|
|
@ -15,21 +15,27 @@
|
||||||
*/
|
*/
|
||||||
package docking.action;
|
package docking.action;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
import javax.swing.KeyStroke;
|
import javax.swing.KeyStroke;
|
||||||
|
|
||||||
import docking.KeyBindingPrecedence;
|
import docking.KeyBindingPrecedence;
|
||||||
import docking.actions.KeyBindingUtils;
|
import docking.actions.KeyBindingUtils;
|
||||||
|
import ghidra.framework.options.ActionTrigger;
|
||||||
|
import gui.event.MouseBinding;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An object that contains a key stroke and the precedence for when that key stroke should be used.
|
* A class for storing an action's key stroke, mouse binding or both.
|
||||||
*
|
* <p>
|
||||||
* <p>Note: this class creates key strokes that work on key {@code pressed}. This effectively
|
* Note: this class creates key strokes that work on key {@code pressed}. This effectively
|
||||||
* normalizes all client key bindings to work on the same type of key stroke (pressed, typed or
|
* normalizes all client key bindings to work on the same type of key stroke (pressed, typed or
|
||||||
* released).
|
* released).
|
||||||
*/
|
*/
|
||||||
public class KeyBindingData {
|
public class KeyBindingData {
|
||||||
private KeyStroke keyStroke;
|
private KeyStroke keyStroke;
|
||||||
private KeyBindingPrecedence keyBindingPrecedence;
|
private KeyBindingPrecedence keyBindingPrecedence = KeyBindingPrecedence.DefaultLevel;
|
||||||
|
|
||||||
|
private MouseBinding mouseBinding;
|
||||||
|
|
||||||
public KeyBindingData(KeyStroke keyStroke) {
|
public KeyBindingData(KeyStroke keyStroke) {
|
||||||
this(keyStroke, KeyBindingPrecedence.DefaultLevel);
|
this(keyStroke, KeyBindingPrecedence.DefaultLevel);
|
||||||
|
@ -43,17 +49,44 @@ public class KeyBindingData {
|
||||||
this(KeyStroke.getKeyStroke(keyCode, modifiers));
|
this(KeyStroke.getKeyStroke(keyCode, modifiers));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance of this class that uses a mouse binding instead of a key stroke.
|
||||||
|
* @param mouseBinding the mouse binding.
|
||||||
|
*/
|
||||||
|
public KeyBindingData(MouseBinding mouseBinding) {
|
||||||
|
this.mouseBinding = Objects.requireNonNull(mouseBinding);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a key stroke from the given text. See
|
* Creates a key stroke from the given text. See
|
||||||
* {@link KeyBindingUtils#parseKeyStroke(KeyStroke)}. The key stroke created for this class
|
* {@link KeyBindingUtils#parseKeyStroke(KeyStroke)}. The key stroke created for this class
|
||||||
* will always be a key {@code pressed} key stroke.
|
* will always be a key {@code pressed} key stroke.
|
||||||
*
|
*
|
||||||
* @param keyStrokeString the key stroke string to parse
|
* @param keyStrokeString the key stroke string to parse
|
||||||
*/
|
*/
|
||||||
public KeyBindingData(String keyStrokeString) {
|
public KeyBindingData(String keyStrokeString) {
|
||||||
this(parseKeyStrokeString(keyStrokeString));
|
this(parseKeyStrokeString(keyStrokeString));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a key binding data with the given action trigger.
|
||||||
|
* @param actionTrigger the trigger; may not be null
|
||||||
|
*/
|
||||||
|
public KeyBindingData(ActionTrigger actionTrigger) {
|
||||||
|
Objects.requireNonNull(actionTrigger);
|
||||||
|
this.keyStroke = actionTrigger.getKeyStroke();
|
||||||
|
this.mouseBinding = actionTrigger.getMouseBinding();
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeyBindingData(KeyStroke keyStroke, KeyBindingPrecedence precedence) {
|
||||||
|
if (precedence == KeyBindingPrecedence.SystemActionsLevel) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Can't set precedence to System KeyBindingPrecedence");
|
||||||
|
}
|
||||||
|
this.keyStroke = Objects.requireNonNull(keyStroke);
|
||||||
|
this.keyBindingPrecedence = Objects.requireNonNull(precedence);
|
||||||
|
}
|
||||||
|
|
||||||
private static KeyStroke parseKeyStrokeString(String keyStrokeString) {
|
private static KeyStroke parseKeyStrokeString(String keyStrokeString) {
|
||||||
KeyStroke keyStroke = KeyBindingUtils.parseKeyStroke(keyStrokeString);
|
KeyStroke keyStroke = KeyBindingUtils.parseKeyStroke(keyStrokeString);
|
||||||
if (keyStroke == null) {
|
if (keyStroke == null) {
|
||||||
|
@ -62,13 +95,33 @@ public class KeyBindingData {
|
||||||
return keyStroke;
|
return keyStroke;
|
||||||
}
|
}
|
||||||
|
|
||||||
public KeyBindingData(KeyStroke keyStroke, KeyBindingPrecedence precedence) {
|
/**
|
||||||
if (precedence == KeyBindingPrecedence.SystemActionsLevel) {
|
* Returns a key binding data object that matches the given trigger. If the existing key
|
||||||
throw new IllegalArgumentException(
|
* binding object already matches the new trigger, then the existing key binding data is
|
||||||
"Can't set precedence to System KeyBindingPrecedence");
|
* returned. If the new trigger is null, the null will be returned.
|
||||||
|
*
|
||||||
|
* @param kbData the existing key binding data; my be null
|
||||||
|
* @param newTrigger the new action trigger; may be null
|
||||||
|
* @return a key binding data based on the new action trigger; may be null
|
||||||
|
*/
|
||||||
|
public static KeyBindingData update(KeyBindingData kbData, ActionTrigger newTrigger) {
|
||||||
|
if (kbData == null) {
|
||||||
|
if (newTrigger == null) {
|
||||||
|
return null; // no change
|
||||||
|
}
|
||||||
|
return new KeyBindingData(newTrigger); // trigger added
|
||||||
}
|
}
|
||||||
this.keyStroke = keyStroke;
|
|
||||||
this.keyBindingPrecedence = precedence;
|
if (newTrigger == null) {
|
||||||
|
return null; // trigger has been cleared
|
||||||
|
}
|
||||||
|
|
||||||
|
ActionTrigger existingTrigger = kbData.getActionTrigger();
|
||||||
|
if (existingTrigger.equals(newTrigger)) {
|
||||||
|
return kbData;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new KeyBindingData(newTrigger);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -87,10 +140,56 @@ public class KeyBindingData {
|
||||||
return keyBindingPrecedence;
|
return keyBindingPrecedence;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the mouse binding assigned to this key binding data.
|
||||||
|
* @return the mouse binding; may be null
|
||||||
|
*/
|
||||||
|
public MouseBinding getMouseBinding() {
|
||||||
|
return mouseBinding;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new action trigger with the values of this class
|
||||||
|
* @return the action trigger
|
||||||
|
*/
|
||||||
|
public ActionTrigger getActionTrigger() {
|
||||||
|
return new ActionTrigger(keyStroke, mouseBinding);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return getClass().getSimpleName() + "[KeyStroke=" + keyStroke + ", precedence=" +
|
return getClass().getSimpleName() + "[KeyStroke=" + keyStroke + ", precedence=" +
|
||||||
keyBindingPrecedence + "]";
|
keyBindingPrecedence + ", MouseBinding=" + mouseBinding + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(keyBindingPrecedence, keyStroke, mouseBinding);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (obj == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (getClass() != obj.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyBindingData other = (KeyBindingData) obj;
|
||||||
|
if (keyBindingPrecedence != other.keyBindingPrecedence) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!Objects.equals(keyStroke, other.keyStroke)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!Objects.equals(mouseBinding, other.mouseBinding)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static KeyBindingData createSystemKeyBindingData(KeyStroke keyStroke) {
|
static KeyBindingData createSystemKeyBindingData(KeyStroke keyStroke) {
|
||||||
|
@ -118,8 +217,15 @@ public class KeyBindingData {
|
||||||
|
|
||||||
KeyBindingPrecedence precedence = newKeyBindingData.getKeyBindingPrecedence();
|
KeyBindingPrecedence precedence = newKeyBindingData.getKeyBindingPrecedence();
|
||||||
if (precedence == KeyBindingPrecedence.SystemActionsLevel) {
|
if (precedence == KeyBindingPrecedence.SystemActionsLevel) {
|
||||||
return createSystemKeyBindingData(KeyBindingUtils.validateKeyStroke(keyBinding));
|
KeyBindingData kbd =
|
||||||
|
createSystemKeyBindingData(KeyBindingUtils.validateKeyStroke(keyBinding));
|
||||||
|
kbd.mouseBinding = newKeyBindingData.mouseBinding;
|
||||||
|
return kbd;
|
||||||
}
|
}
|
||||||
return new KeyBindingData(KeyBindingUtils.validateKeyStroke(keyBinding), precedence);
|
|
||||||
|
KeyBindingData kbd =
|
||||||
|
new KeyBindingData(KeyBindingUtils.validateKeyStroke(keyBinding), precedence);
|
||||||
|
kbd.mouseBinding = newKeyBindingData.mouseBinding;
|
||||||
|
return kbd;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,11 +27,12 @@ import docking.*;
|
||||||
import docking.actions.KeyBindingUtils;
|
import docking.actions.KeyBindingUtils;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
import ghidra.util.exception.AssertException;
|
import ghidra.util.exception.AssertException;
|
||||||
|
import gui.event.MouseBinding;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A class that organizes system key bindings by mapping them to assigned {@link DockingActionIf}s.
|
* A class that organizes system key bindings by mapping them to assigned {@link DockingActionIf}s.
|
||||||
*
|
*
|
||||||
* <p>This class understands reserved system key bindings. For non-reserved key bindings, this
|
* <p>This class understands reserved system key bindings. For non-reserved key bindings, this
|
||||||
* class knows how to map a single key binding to multiple actions.
|
* class knows how to map a single key binding to multiple actions.
|
||||||
*/
|
*/
|
||||||
public class KeyBindingsManager implements PropertyChangeListener {
|
public class KeyBindingsManager implements PropertyChangeListener {
|
||||||
|
@ -39,8 +40,9 @@ 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 = new HashMap<>();
|
private Map<DockingActionIf, ComponentProvider> actionToProviderMap = new HashMap<>();
|
||||||
private Map<KeyStroke, DockingKeyBindingAction> dockingKeyMap = new HashMap<>();
|
private Map<KeyStroke, DockingKeyBindingAction> dockingKeyMap = new HashMap<>();
|
||||||
private Map<DockingActionIf, SystemKeyBindingAction> dockingActionToSystemActionMap =
|
private Map<MouseBinding, DockingMouseBindingAction> dockingMouseMap = new HashMap<>();
|
||||||
new HashMap<>();
|
private Map<String, DockingActionIf> systemActionsByFullName = new HashMap<>();
|
||||||
|
|
||||||
private Tool tool;
|
private Tool tool;
|
||||||
|
|
||||||
public KeyBindingsManager(Tool tool) {
|
public KeyBindingsManager(Tool tool) {
|
||||||
|
@ -53,11 +55,20 @@ public class KeyBindingsManager implements PropertyChangeListener {
|
||||||
actionToProviderMap.put(action, optionalProvider);
|
actionToProviderMap.put(action, optionalProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyStroke keyBinding = action.getKeyBinding();
|
KeyBindingData kbData = action.getKeyBindingData();
|
||||||
|
if (kbData == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyStroke keyBinding = kbData.getKeyBinding();
|
||||||
if (keyBinding != null) {
|
if (keyBinding != null) {
|
||||||
addKeyBinding(optionalProvider, action, keyBinding);
|
addKeyBinding(optionalProvider, action, keyBinding);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MouseBinding mouseBinding = kbData.getMouseBinding();
|
||||||
|
if (mouseBinding != null) {
|
||||||
|
doAddMouseBinding(action, mouseBinding);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addSystemAction(DockingActionIf action) {
|
public void addSystemAction(DockingActionIf action) {
|
||||||
|
@ -90,7 +101,7 @@ public class KeyBindingsManager implements PropertyChangeListener {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// map standard keystroke to action
|
// map standard keystroke to action
|
||||||
doAddKeyBinding(provider, action, keyStroke);
|
doAddKeyBinding(provider, action, keyStroke);
|
||||||
|
|
||||||
// map workaround keystroke to action
|
// map workaround keystroke to action
|
||||||
|
@ -103,7 +114,7 @@ public class KeyBindingsManager implements PropertyChangeListener {
|
||||||
return null; // clearing the key stroke
|
return null; // clearing the key stroke
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// 1) Handle case with given key stroke already in use by a system action
|
// 1) Handle case with given key stroke already in use by a system action
|
||||||
//
|
//
|
||||||
Action existingAction = dockingKeyMap.get(ks);
|
Action existingAction = dockingKeyMap.get(ks);
|
||||||
|
@ -118,12 +129,16 @@ public class KeyBindingsManager implements PropertyChangeListener {
|
||||||
return ksString + " in use by System action '" + systemDockingAction.getName() + "'";
|
return ksString + " in use by System action '" + systemDockingAction.getName() + "'";
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
if (dockingAction == null) {
|
||||||
// 2) Handle the case where a system action key stroke is being set to something that is
|
return null; // the client is only checking the keystroke and not any associated action
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// 2) Handle the case where a system action key stroke is being set to something that is
|
||||||
// already in-use by some other action
|
// already in-use by some other action
|
||||||
//
|
//
|
||||||
SystemKeyBindingAction systemAction = dockingActionToSystemActionMap.get(dockingAction);
|
boolean hasSystemAction = systemActionsByFullName.containsKey(dockingAction.getFullName());
|
||||||
if (systemAction != null && existingAction != null) {
|
if (hasSystemAction && existingAction != null) {
|
||||||
return "System action cannot be set to in-use key stroke";
|
return "System action cannot be set to in-use key stroke";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,12 +148,12 @@ public class KeyBindingsManager implements PropertyChangeListener {
|
||||||
private void fixupAltGraphKeyStrokeMapping(ComponentProvider provider, DockingActionIf action,
|
private void fixupAltGraphKeyStrokeMapping(ComponentProvider provider, DockingActionIf action,
|
||||||
KeyStroke keyStroke) {
|
KeyStroke keyStroke) {
|
||||||
|
|
||||||
// special case
|
// special case
|
||||||
int modifiers = keyStroke.getModifiers();
|
int modifiers = keyStroke.getModifiers();
|
||||||
if ((modifiers & InputEvent.ALT_DOWN_MASK) == InputEvent.ALT_DOWN_MASK) {
|
if ((modifiers & InputEvent.ALT_DOWN_MASK) == InputEvent.ALT_DOWN_MASK) {
|
||||||
//
|
//
|
||||||
// Also register the 'Alt' binding with the 'Alt Graph' mask. This fixes the but
|
// Also register the 'Alt' binding with the 'Alt Graph' mask. This fixes the but
|
||||||
// on Windows (https://bugs.openjdk.java.net/browse/JDK-8194873)
|
// on Windows (https://bugs.openjdk.java.net/browse/JDK-8194873)
|
||||||
// that have different key codes for the left and right Alt keys.
|
// that have different key codes for the left and right Alt keys.
|
||||||
//
|
//
|
||||||
modifiers |= InputEvent.ALT_GRAPH_DOWN_MASK;
|
modifiers |= InputEvent.ALT_GRAPH_DOWN_MASK;
|
||||||
|
@ -170,8 +185,7 @@ public class KeyBindingsManager implements PropertyChangeListener {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
SystemKeyBindingAction systemAction = dockingActionToSystemActionMap.get(action);
|
if (systemActionsByFullName.containsKey(action.getFullName())) {
|
||||||
if (systemAction != null) {
|
|
||||||
// the user has updated the binding for a System action; re-install it
|
// the user has updated the binding for a System action; re-install it
|
||||||
registerSystemKeyBinding(action, mappingKeyStroke);
|
registerSystemKeyBinding(action, mappingKeyStroke);
|
||||||
return;
|
return;
|
||||||
|
@ -182,6 +196,23 @@ public class KeyBindingsManager implements PropertyChangeListener {
|
||||||
new MultipleKeyAction(tool, provider, action, actionKeyStroke));
|
new MultipleKeyAction(tool, provider, action, actionKeyStroke));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void doAddMouseBinding(DockingActionIf action, MouseBinding mouseBinding) {
|
||||||
|
|
||||||
|
DockingMouseBindingAction mouseBindingAction = dockingMouseMap.get(mouseBinding);
|
||||||
|
if (mouseBindingAction != null) {
|
||||||
|
String existingName = mouseBindingAction.getFullActionName();
|
||||||
|
String message = """
|
||||||
|
Attempted to use the same mouse binding for multiple actions. \
|
||||||
|
Multiple mouse bindings are not supported. Binding: %s \
|
||||||
|
New action: %s; existing action: %s
|
||||||
|
""".formatted(mouseBinding, action.getFullName(), existingName);
|
||||||
|
Msg.error(this, message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dockingMouseMap.put(mouseBinding, new DockingMouseBindingAction(action, mouseBinding));
|
||||||
|
}
|
||||||
|
|
||||||
private void addSystemKeyBinding(DockingActionIf action, KeyStroke keyStroke) {
|
private void addSystemKeyBinding(DockingActionIf action, KeyStroke keyStroke) {
|
||||||
KeyBindingData binding = KeyBindingData.createSystemKeyBindingData(keyStroke);
|
KeyBindingData binding = KeyBindingData.createSystemKeyBindingData(keyStroke);
|
||||||
action.setKeyBindingData(binding);
|
action.setKeyBindingData(binding);
|
||||||
|
@ -191,7 +222,7 @@ public class KeyBindingsManager implements PropertyChangeListener {
|
||||||
private void registerSystemKeyBinding(DockingActionIf action, KeyStroke keyStroke) {
|
private void registerSystemKeyBinding(DockingActionIf action, KeyStroke keyStroke) {
|
||||||
SystemKeyBindingAction systemAction = new SystemKeyBindingAction(tool, action, keyStroke);
|
SystemKeyBindingAction systemAction = new SystemKeyBindingAction(tool, action, keyStroke);
|
||||||
dockingKeyMap.put(keyStroke, systemAction);
|
dockingKeyMap.put(keyStroke, systemAction);
|
||||||
dockingActionToSystemActionMap.put(action, systemAction);
|
systemActionsByFullName.put(action.getFullName(), action);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void removeKeyBinding(KeyStroke keyStroke, DockingActionIf action) {
|
private void removeKeyBinding(KeyStroke keyStroke, DockingActionIf action) {
|
||||||
|
@ -242,17 +273,30 @@ public class KeyBindingsManager implements PropertyChangeListener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Action getDockingKeyAction(KeyStroke keyStroke) {
|
public Action getDockingAction(KeyStroke keyStroke) {
|
||||||
return dockingKeyMap.get(keyStroke);
|
return dockingKeyMap.get(keyStroke);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Action getDockingAction(MouseBinding mouseBinding) {
|
||||||
|
return dockingMouseMap.get(mouseBinding);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSystemAction(DockingActionIf action) {
|
||||||
|
return systemActionsByFullName.containsKey(action.getFullName());
|
||||||
|
}
|
||||||
|
|
||||||
|
public DockingActionIf getSystemAction(String fullName) {
|
||||||
|
return systemActionsByFullName.get(fullName);
|
||||||
|
}
|
||||||
|
|
||||||
public Set<DockingActionIf> getSystemActions() {
|
public Set<DockingActionIf> getSystemActions() {
|
||||||
return new HashSet<>(dockingActionToSystemActionMap.keySet());
|
return new HashSet<>(systemActionsByFullName.values());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void dispose() {
|
public void dispose() {
|
||||||
dockingKeyMap.clear();
|
dockingKeyMap.clear();
|
||||||
|
dockingMouseMap.clear();
|
||||||
actionToProviderMap.clear();
|
actionToProviderMap.clear();
|
||||||
dockingActionToSystemActionMap.clear();
|
systemActionsByFullName.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,13 +46,12 @@ import ghidra.util.filechooser.GhidraFileFilter;
|
||||||
import ghidra.util.xml.GenericXMLOutputter;
|
import ghidra.util.xml.GenericXMLOutputter;
|
||||||
import ghidra.util.xml.XmlUtilities;
|
import ghidra.util.xml.XmlUtilities;
|
||||||
import util.CollectionUtils;
|
import util.CollectionUtils;
|
||||||
import utilities.util.reflection.ReflectionUtilities;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A class to provide utilities for system key bindings, such as importing and
|
* A class to provide utilities for system key bindings, such as importing and
|
||||||
* exporting key binding configurations.
|
* exporting key binding configurations.
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* @since Tracker Id 329
|
* @since Tracker Id 329
|
||||||
*/
|
*/
|
||||||
public class KeyBindingUtils {
|
public class KeyBindingUtils {
|
||||||
|
@ -100,7 +99,7 @@ public class KeyBindingUtils {
|
||||||
* <p>
|
* <p>
|
||||||
* If there is a problem reading the data then the user will be shown an
|
* If there is a problem reading the data then the user will be shown an
|
||||||
* error dialog.
|
* error dialog.
|
||||||
*
|
*
|
||||||
* @param inputStream the input stream from which to read options
|
* @param inputStream the input stream from which to read options
|
||||||
* @return An options object that is composed of key binding names and their
|
* @return An options object that is composed of key binding names and their
|
||||||
* associated keystrokes.
|
* associated keystrokes.
|
||||||
|
@ -141,7 +140,7 @@ public class KeyBindingUtils {
|
||||||
* <p>
|
* <p>
|
||||||
* If there is a problem writing the data then the user will be shown an
|
* If there is a problem writing the data then the user will be shown an
|
||||||
* error dialog.
|
* error dialog.
|
||||||
*
|
*
|
||||||
* @param keyBindingOptions The options that contains key binding data.
|
* @param keyBindingOptions The options that contains key binding data.
|
||||||
*/
|
*/
|
||||||
public static void exportKeyBindings(ToolOptions keyBindingOptions) {
|
public static void exportKeyBindings(ToolOptions keyBindingOptions) {
|
||||||
|
@ -177,14 +176,14 @@ public class KeyBindingUtils {
|
||||||
* Changes the given key event to the new source component and then dispatches that event.
|
* Changes the given key event to the new source component and then dispatches that event.
|
||||||
* This method is intended for clients that wish to effectively take a key event given to
|
* This method is intended for clients that wish to effectively take a key event given to
|
||||||
* one component and give it to another component.
|
* one component and give it to another component.
|
||||||
*
|
*
|
||||||
* <p>This method exists to deal with the complicated nature of key event processing and
|
* <p>This method exists to deal with the complicated nature of key event processing and
|
||||||
* how our (not Java's) framework processes key event bindings to trigger actions. If not
|
* how our (not Java's) framework processes key event bindings to trigger actions. If not
|
||||||
* for our special processing of action key bindings, then this method would not be
|
* for our special processing of action key bindings, then this method would not be
|
||||||
* necessary.
|
* necessary.
|
||||||
*
|
*
|
||||||
* <p><b>This is seldom-used code; if you don't know when to use this code, then don't.</b>
|
* <p><b>This is seldom-used code; if you don't know when to use this code, then don't.</b>
|
||||||
*
|
*
|
||||||
* @param newSource the new target of the event
|
* @param newSource the new target of the event
|
||||||
* @param e the existing event
|
* @param e the existing event
|
||||||
*/
|
*/
|
||||||
|
@ -199,7 +198,7 @@ public class KeyBindingUtils {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Unusual Code Alert!
|
Unusual Code Alert!
|
||||||
|
|
||||||
The KeyboardFocusManager is a complicated beast. Here we use knowledge of one such
|
The KeyboardFocusManager is a complicated beast. Here we use knowledge of one such
|
||||||
complication to correctly route key events. If the client of this method passes
|
complication to correctly route key events. If the client of this method passes
|
||||||
a component whose 'isShowing()' returns false, then the manager will not send the
|
a component whose 'isShowing()' returns false, then the manager will not send the
|
||||||
|
@ -208,13 +207,13 @@ public class KeyBindingUtils {
|
||||||
attached; for example, when we are using said components with a renderer to perform
|
attached; for example, when we are using said components with a renderer to perform
|
||||||
our own painting. In the case of non-attached components, we must call the
|
our own painting. In the case of non-attached components, we must call the
|
||||||
redispatchEvent() method ourselves.
|
redispatchEvent() method ourselves.
|
||||||
|
|
||||||
Why don't we just always call redispatchEvent()? Well, that
|
Why don't we just always call redispatchEvent()? Well, that
|
||||||
method will not pass the new cloned event we just created back through the full
|
method will not pass the new cloned event we just created back through the full
|
||||||
key event pipeline. This means that tool-level (our Tool API, not Java)
|
key event pipeline. This means that tool-level (our Tool API, not Java)
|
||||||
actions will not work, as tool-level actions are handled at the beginning of the
|
actions will not work, as tool-level actions are handled at the beginning of the
|
||||||
key event pipeline, not by the components themselves.
|
key event pipeline, not by the components themselves.
|
||||||
|
|
||||||
Also, we have here guilty knowledge that the aforementioned tool-level key processing
|
Also, we have here guilty knowledge that the aforementioned tool-level key processing
|
||||||
will check to see if the event was consumed. If consumed, then no further processing
|
will check to see if the event was consumed. If consumed, then no further processing
|
||||||
will happen; if not consumed, then the framework will continue to process the event
|
will happen; if not consumed, then the framework will continue to process the event
|
||||||
|
@ -245,7 +244,7 @@ public class KeyBindingUtils {
|
||||||
* <p>
|
* <p>
|
||||||
* The given action must have a keystroke assigned, or this method will do
|
* The given action must have a keystroke assigned, or this method will do
|
||||||
* nothing.
|
* nothing.
|
||||||
*
|
*
|
||||||
* @param component the component to which the given action will be bound
|
* @param component the component to which the given action will be bound
|
||||||
* @param action the action to bind
|
* @param action the action to bind
|
||||||
*/
|
*/
|
||||||
|
@ -263,12 +262,12 @@ public class KeyBindingUtils {
|
||||||
* <p>
|
* <p>
|
||||||
* The given action must have a keystroke assigned, or this method will do
|
* The given action must have a keystroke assigned, or this method will do
|
||||||
* nothing.
|
* nothing.
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* A typical use-case is to register an existing docking action with a text
|
* A typical use-case is to register an existing docking action with a text
|
||||||
* component, which is needed because the docking key event processing will
|
* component, which is needed because the docking key event processing will
|
||||||
* not execute docking- registered actions if a text component has focus.
|
* not execute docking- registered actions if a text component has focus.
|
||||||
*
|
*
|
||||||
* @param component the component to which the given action will be bound
|
* @param component the component to which the given action will be bound
|
||||||
* @param action the action to bind
|
* @param action the action to bind
|
||||||
* @param contextProvider the provider of the context
|
* @param contextProvider the provider of the context
|
||||||
|
@ -289,12 +288,12 @@ public class KeyBindingUtils {
|
||||||
* <p>
|
* <p>
|
||||||
* The given action must have a keystroke assigned, or this method will do
|
* The given action must have a keystroke assigned, or this method will do
|
||||||
* nothing.
|
* nothing.
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* A typical use-case is to register an existing docking action with a text
|
* A typical use-case is to register an existing docking action with a text
|
||||||
* component, which is needed because the docking key event processing will
|
* component, which is needed because the docking key event processing will
|
||||||
* not execute docking- registered actions if a text component has focus.
|
* not execute docking- registered actions if a text component has focus.
|
||||||
*
|
*
|
||||||
* @param component the component to which the given action will be bound
|
* @param component the component to which the given action will be bound
|
||||||
* @param action the action to bind
|
* @param action the action to bind
|
||||||
* @param contextProvider the provider of the context
|
* @param contextProvider the provider of the context
|
||||||
|
@ -311,7 +310,7 @@ public class KeyBindingUtils {
|
||||||
/**
|
/**
|
||||||
* Registers the given action with the given key binding on the given
|
* Registers the given action with the given key binding on the given
|
||||||
* component.
|
* component.
|
||||||
*
|
*
|
||||||
* @param component the component to which the action will be registered
|
* @param component the component to which the action will be registered
|
||||||
* @param keyStroke the keystroke for to which the action will be bound
|
* @param keyStroke the keystroke for to which the action will be bound
|
||||||
* @param action the action to execute when the given keystroke is triggered
|
* @param action the action to execute when the given keystroke is triggered
|
||||||
|
@ -353,7 +352,7 @@ public class KeyBindingUtils {
|
||||||
* action with the same key binding from firing. This is useful when your
|
* action with the same key binding from firing. This is useful when your
|
||||||
* application is using tool-level key bindings that share the same
|
* application is using tool-level key bindings that share the same
|
||||||
* keystroke as a built-in Java action, such as Ctrl-C for the copy action.
|
* keystroke as a built-in Java action, such as Ctrl-C for the copy action.
|
||||||
*
|
*
|
||||||
* @param component the component for which to clear the key binding
|
* @param component the component for which to clear the key binding
|
||||||
* @param action the action from which to get the key binding
|
* @param action the action from which to get the key binding
|
||||||
*/
|
*/
|
||||||
|
@ -373,7 +372,7 @@ public class KeyBindingUtils {
|
||||||
* Note: this method clears the key binding for the
|
* Note: this method clears the key binding for the
|
||||||
* {@link JComponent#WHEN_FOCUSED} and
|
* {@link JComponent#WHEN_FOCUSED} and
|
||||||
* {@link JComponent#WHEN_ANCESTOR_OF_FOCUSED_COMPONENT} focus conditions.
|
* {@link JComponent#WHEN_ANCESTOR_OF_FOCUSED_COMPONENT} focus conditions.
|
||||||
*
|
*
|
||||||
* @param component the component for which to clear the key binding
|
* @param component the component for which to clear the key binding
|
||||||
* @param keyStroke the keystroke of the binding to be cleared
|
* @param keyStroke the keystroke of the binding to be cleared
|
||||||
* @see #clearKeyBinding(JComponent, KeyStroke, int)
|
* @see #clearKeyBinding(JComponent, KeyStroke, int)
|
||||||
|
@ -387,7 +386,7 @@ public class KeyBindingUtils {
|
||||||
* Allows clients to clear Java key bindings. This is useful when your
|
* Allows clients to clear Java key bindings. This is useful when your
|
||||||
* application is using tool-level key bindings that share the same
|
* application is using tool-level key bindings that share the same
|
||||||
* keystroke as a built-in Java action, such as Ctrl-C for the copy action.
|
* keystroke as a built-in Java action, such as Ctrl-C for the copy action.
|
||||||
*
|
*
|
||||||
* @param component the component for which to clear the key binding
|
* @param component the component for which to clear the key binding
|
||||||
* @param keyStroke the keystroke of the binding to be cleared
|
* @param keyStroke the keystroke of the binding to be cleared
|
||||||
* @param focusCondition the particular focus condition under which the
|
* @param focusCondition the particular focus condition under which the
|
||||||
|
@ -405,7 +404,7 @@ public class KeyBindingUtils {
|
||||||
/**
|
/**
|
||||||
* Clears the currently assigned Java key binding for the action by the given name. This
|
* Clears the currently assigned Java key binding for the action by the given name. This
|
||||||
* method will find the currently assigned key binding, if any, and then remove it.
|
* method will find the currently assigned key binding, if any, and then remove it.
|
||||||
*
|
*
|
||||||
* @param component the component for which to clear the key binding
|
* @param component the component for which to clear the key binding
|
||||||
* @param actionName the name of the action that should not have a key binding
|
* @param actionName the name of the action that should not have a key binding
|
||||||
* @see LookAndFeel
|
* @see LookAndFeel
|
||||||
|
@ -420,9 +419,8 @@ public class KeyBindingUtils {
|
||||||
KeyStroke keyStroke = null;
|
KeyStroke keyStroke = null;
|
||||||
KeyStroke[] keys = inputMap.allKeys();
|
KeyStroke[] keys = inputMap.allKeys();
|
||||||
if (keys == null) {
|
if (keys == null) {
|
||||||
Msg.debug(KeyBindingUtils.class,
|
Msg.debug(KeyBindingUtils.class, "Cannot remove action by name; does not exist: '" +
|
||||||
"Cannot remove action by name; does not exist: '" + actionName + "' " +
|
actionName + "' " + "on component: " + component.getClass().getSimpleName());
|
||||||
"on component: " + component.getClass().getSimpleName());
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -442,7 +440,7 @@ public class KeyBindingUtils {
|
||||||
/**
|
/**
|
||||||
* Returns the registered action for the given keystroke, or null of no
|
* Returns the registered action for the given keystroke, or null of no
|
||||||
* action is bound to that keystroke.
|
* action is bound to that keystroke.
|
||||||
*
|
*
|
||||||
* @param component the component for which to check the binding
|
* @param component the component for which to check the binding
|
||||||
* @param keyStroke the keystroke for which to find a bound action
|
* @param keyStroke the keystroke for which to find a bound action
|
||||||
* @param focusCondition the focus condition under which to check for the
|
* @param focusCondition the focus condition under which to check for the
|
||||||
|
@ -464,12 +462,12 @@ public class KeyBindingUtils {
|
||||||
/**
|
/**
|
||||||
* A utility method to get all key binding actions. This method will
|
* A utility method to get all key binding actions. This method will
|
||||||
* only return actions that support {@link KeyBindingType key bindings}.
|
* only return actions that support {@link KeyBindingType key bindings}.
|
||||||
*
|
*
|
||||||
* <p>The mapping returned provides a list of items because it is possible for there to
|
* <p>The mapping returned provides a list of items because it is possible for there to
|
||||||
* exists multiple actions with the same name and owner. (This can happen when multiple copies
|
* exists multiple actions with the same name and owner. (This can happen when multiple copies
|
||||||
* of a component provider are shown, each with their own set of actions that share the
|
* of a component provider are shown, each with their own set of actions that share the
|
||||||
* same name.)
|
* same name.)
|
||||||
*
|
*
|
||||||
* @param tool the tool containing the actions
|
* @param tool the tool containing the actions
|
||||||
* @return the actions mapped by their full name (e.g., 'Name (OwnerName)')
|
* @return the actions mapped by their full name (e.g., 'Name (OwnerName)')
|
||||||
*/
|
*/
|
||||||
|
@ -496,13 +494,12 @@ public class KeyBindingUtils {
|
||||||
* A utility method to get all key binding actions that have the given owner.
|
* A utility method to get all key binding actions that have the given owner.
|
||||||
* This method will remove duplicate actions and will only return actions
|
* This method will remove duplicate actions and will only return actions
|
||||||
* that support {@link KeyBindingType key bindings}.
|
* that support {@link KeyBindingType key bindings}.
|
||||||
*
|
*
|
||||||
* @param tool the tool containing the actions
|
* @param tool the tool containing the actions
|
||||||
* @param owner the action owner name
|
* @param owner the action owner name
|
||||||
* @return the actions
|
* @return the actions
|
||||||
*/
|
*/
|
||||||
public static Set<DockingActionIf> getKeyBindingActionsForOwner(Tool tool,
|
public static Set<DockingActionIf> getKeyBindingActionsForOwner(Tool tool, String owner) {
|
||||||
String owner) {
|
|
||||||
|
|
||||||
Map<String, DockingActionIf> deduper = new HashMap<>();
|
Map<String, DockingActionIf> deduper = new HashMap<>();
|
||||||
Set<DockingActionIf> actions = tool.getDockingActionsByOwnerName(owner);
|
Set<DockingActionIf> actions = tool.getDockingActionsByOwnerName(owner);
|
||||||
|
@ -522,7 +519,7 @@ public class KeyBindingUtils {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns all actions that match the given owner and name
|
* Returns all actions that match the given owner and name
|
||||||
*
|
*
|
||||||
* @param allActions the universe of actions
|
* @param allActions the universe of actions
|
||||||
* @param owner the owner
|
* @param owner the owner
|
||||||
* @param name the name
|
* @param name the name
|
||||||
|
@ -539,13 +536,13 @@ public class KeyBindingUtils {
|
||||||
/**
|
/**
|
||||||
* Takes the existing docking action and allows it to be registered with
|
* Takes the existing docking action and allows it to be registered with
|
||||||
* Swing components
|
* Swing components
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* The new action will not be correctly wired into the Docking Action
|
* The new action will not be correctly wired into the Docking Action
|
||||||
* Context system. This means that the given docking action should not rely
|
* Context system. This means that the given docking action should not rely
|
||||||
* on {@link DockingAction#isEnabledForContext(docking.ActionContext)} to
|
* on {@link DockingAction#isEnabledForContext(docking.ActionContext)} to
|
||||||
* work when called from the Swing widget.
|
* work when called from the Swing widget.
|
||||||
*
|
*
|
||||||
* @param action the docking action to adapt to a Swing {@link Action}
|
* @param action the docking action to adapt to a Swing {@link Action}
|
||||||
* @return the new action
|
* @return the new action
|
||||||
*/
|
*/
|
||||||
|
@ -553,66 +550,10 @@ public class KeyBindingUtils {
|
||||||
return new ActionAdapter(action);
|
return new ActionAdapter(action);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks each action in the given collection against the given new action to make sure that
|
|
||||||
* they share the same default key binding.
|
|
||||||
*
|
|
||||||
* @param newAction the action to check
|
|
||||||
* @param existingActions the actions that have already been checked
|
|
||||||
*/
|
|
||||||
public static void assertSameDefaultKeyBindings(DockingActionIf newAction,
|
|
||||||
Collection<DockingActionIf> existingActions) {
|
|
||||||
|
|
||||||
if (!newAction.getKeyBindingType().supportsKeyBindings()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
KeyBindingData newDefaultBinding = newAction.getDefaultKeyBindingData();
|
|
||||||
KeyStroke defaultKs = getKeyStroke(newDefaultBinding);
|
|
||||||
for (DockingActionIf action : existingActions) {
|
|
||||||
if (!action.getKeyBindingType().supportsKeyBindings()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
KeyBindingData existingDefaultBinding = action.getDefaultKeyBindingData();
|
|
||||||
KeyStroke existingKs = getKeyStroke(existingDefaultBinding);
|
|
||||||
if (!Objects.equals(defaultKs, existingKs)) {
|
|
||||||
logDifferentKeyBindingsWarnigMessage(newAction, action, existingKs);
|
|
||||||
break; // one warning seems like enough
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Logs a warning message for the two given actions to signal that they do not share the
|
|
||||||
* same default key binding
|
|
||||||
*
|
|
||||||
* @param newAction the new action
|
|
||||||
* @param existingAction the action that has already been validated
|
|
||||||
* @param existingDefaultKs the current validated key stroke
|
|
||||||
*/
|
|
||||||
public static void logDifferentKeyBindingsWarnigMessage(DockingActionIf newAction,
|
|
||||||
DockingActionIf existingAction, KeyStroke existingDefaultKs) {
|
|
||||||
|
|
||||||
//@formatter:off
|
|
||||||
String s = "Shared Key Binding Actions have different default values. These " +
|
|
||||||
"must be the same." +
|
|
||||||
"\n\tAction name: '"+existingAction.getName()+"'" +
|
|
||||||
"\n\tAction 1: " + existingAction.getInceptionInformation() +
|
|
||||||
"\n\t\tKey Binding: " + existingDefaultKs +
|
|
||||||
"\n\tAction 2: " + newAction.getInceptionInformation() +
|
|
||||||
"\n\t\tKey Binding: " + newAction.getKeyBinding() +
|
|
||||||
"\nUsing the " +
|
|
||||||
"first value set - " + existingDefaultKs;
|
|
||||||
//@formatter:on
|
|
||||||
|
|
||||||
Msg.warn(KeyBindingUtils.class, s, ReflectionUtilities.createJavaFilteredThrowable());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the given data with system-independent versions of key modifiers. For example,
|
* Updates the given data with system-independent versions of key modifiers. For example,
|
||||||
* the <code>control</code> key will be converted to the <code>command</code> key on the Mac.
|
* the <code>control</code> key will be converted to the <code>command</code> key on the Mac.
|
||||||
*
|
*
|
||||||
* @param keyStroke the keystroke to validate
|
* @param keyStroke the keystroke to validate
|
||||||
* @return the potentially changed keystroke
|
* @return the potentially changed keystroke
|
||||||
*/
|
*/
|
||||||
|
@ -676,7 +617,7 @@ public class KeyBindingUtils {
|
||||||
* and we want it to look like: "Ctrl-M".
|
* and we want it to look like: "Ctrl-M".
|
||||||
* <br>In Java 11 we have seen toString() values get printed with repeated text, such
|
* <br>In Java 11 we have seen toString() values get printed with repeated text, such
|
||||||
* as: "shift ctrl pressed SHIFT". We want to trim off the repeated modifiers.
|
* as: "shift ctrl pressed SHIFT". We want to trim off the repeated modifiers.
|
||||||
*
|
*
|
||||||
* @param keyStroke the key stroke
|
* @param keyStroke the key stroke
|
||||||
* @return the string value; the empty string if the key stroke is null
|
* @return the string value; the empty string if the key stroke is null
|
||||||
*/
|
*/
|
||||||
|
@ -792,14 +733,18 @@ public class KeyBindingUtils {
|
||||||
* Ctrl-Alt-Z
|
* Ctrl-Alt-Z
|
||||||
* ctrl Z
|
* ctrl Z
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
* <p><b>Note:</b> The returned keystroke will always correspond to a {@code pressed} event,
|
* <p><b>Note:</b> The returned keystroke will always correspond to a {@code pressed} event,
|
||||||
* regardless of the value passed in (pressed, typed or released).
|
* regardless of the value passed in (pressed, typed or released).
|
||||||
*
|
*
|
||||||
* @param keyStroke the key stroke
|
* @param keyStroke the key stroke
|
||||||
* @return the new key stroke (as returned by {@link KeyStroke#getKeyStroke(String)}
|
* @return the new key stroke (as returned by {@link KeyStroke#getKeyStroke(String)}
|
||||||
*/
|
*/
|
||||||
public static KeyStroke parseKeyStroke(String keyStroke) {
|
public static KeyStroke parseKeyStroke(String keyStroke) {
|
||||||
|
if (StringUtils.isBlank(keyStroke)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
List<String> pieces = new ArrayList<>();
|
List<String> pieces = new ArrayList<>();
|
||||||
StringTokenizer tokenizer = new StringTokenizer(keyStroke, "- ");
|
StringTokenizer tokenizer = new StringTokenizer(keyStroke, "- ");
|
||||||
while (tokenizer.hasMoreTokens()) {
|
while (tokenizer.hasMoreTokens()) {
|
||||||
|
@ -873,13 +818,6 @@ public class KeyBindingUtils {
|
||||||
return !action.getKeyBindingType().isManaged();
|
return !action.getKeyBindingType().isManaged();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static KeyStroke getKeyStroke(KeyBindingData data) {
|
|
||||||
if (data == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return data.getKeyBinding();
|
|
||||||
}
|
|
||||||
|
|
||||||
// prompts the user for a file location from which to read key binding data
|
// prompts the user for a file location from which to read key binding data
|
||||||
private static InputStream getInputStreamForFile(File startingDir) {
|
private static InputStream getInputStreamForFile(File startingDir) {
|
||||||
File selectedFile = getFileFromUser(startingDir);
|
File selectedFile = getFileFromUser(startingDir);
|
||||||
|
|
|
@ -24,35 +24,40 @@ import docking.Tool;
|
||||||
import docking.action.DockingActionIf;
|
import docking.action.DockingActionIf;
|
||||||
import docking.action.KeyBindingData;
|
import docking.action.KeyBindingData;
|
||||||
import docking.tool.util.DockingToolConstants;
|
import docking.tool.util.DockingToolConstants;
|
||||||
|
import ghidra.framework.options.ActionTrigger;
|
||||||
import ghidra.framework.options.ToolOptions;
|
import ghidra.framework.options.ToolOptions;
|
||||||
|
import gui.event.MouseBinding;
|
||||||
import util.CollectionUtils;
|
import util.CollectionUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An object that maps actions to key strokes.
|
* An object that maps actions to key strokes and mouse bindings.
|
||||||
* <p>
|
* <p>
|
||||||
* This class knows how to load all system actions and how to load any key bindings for those
|
* This class knows how to load all system actions and how to load any key and mouse bindings for
|
||||||
* actions from the tool's options. Clients can make changes to the state of this class that can
|
* those actions from the tool's options. Clients can make changes to the state of this class that
|
||||||
* then be applied to the system by calling {@link #applyChanges()}.
|
* can then be applied to the system by calling {@link #applyChanges()}.
|
||||||
*/
|
*/
|
||||||
public class KeyBindings {
|
public class KeyBindings {
|
||||||
|
|
||||||
private Tool tool;
|
private Tool tool;
|
||||||
|
private ToolOptions keyBindingOptions;
|
||||||
|
|
||||||
private Map<String, List<DockingActionIf>> actionsByFullName;
|
// allows clients to populate a table of all actions
|
||||||
private Map<String, List<String>> actionNamesByKeyStroke = new HashMap<>();
|
|
||||||
private Map<String, KeyStroke> keyStrokesByFullName = new HashMap<>();
|
|
||||||
private List<DockingActionIf> uniqueActions = new ArrayList<>();
|
private List<DockingActionIf> uniqueActions = new ArrayList<>();
|
||||||
|
|
||||||
// to know what has been changed
|
// allows clients to know if a given key stroke or mouse binding is in use
|
||||||
private Map<String, KeyStroke> originalKeyStrokesByFullName = new HashMap<>();
|
private Map<KeyStroke, List<String>> actionNamesByKeyStroke = new HashMap<>();
|
||||||
private String longestActionName = "";
|
private Map<MouseBinding, String> actionNameByMouseBinding = new HashMap<>();
|
||||||
|
|
||||||
private ToolOptions options;
|
// tracks all changes to an action's key stroke and mouse bindings, which allows us to apply
|
||||||
|
// and restore options values
|
||||||
|
private Map<String, ActionKeyBindingState> actionInfoByFullName = new HashMap<>();
|
||||||
|
|
||||||
|
private String longestActionName = "";
|
||||||
|
|
||||||
public KeyBindings(Tool tool) {
|
public KeyBindings(Tool tool) {
|
||||||
this.tool = tool;
|
this.tool = tool;
|
||||||
|
|
||||||
options = tool.getOptions(DockingToolConstants.KEY_BINDINGS);
|
keyBindingOptions = tool.getOptions(DockingToolConstants.KEY_BINDINGS);
|
||||||
|
|
||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
|
@ -61,22 +66,40 @@ public class KeyBindings {
|
||||||
return Collections.unmodifiableList(uniqueActions);
|
return Collections.unmodifiableList(uniqueActions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* used for testing */
|
||||||
public Map<String, KeyStroke> getKeyStrokesByFullActionName() {
|
public Map<String, KeyStroke> getKeyStrokesByFullActionName() {
|
||||||
return Collections.unmodifiableMap(keyStrokesByFullName);
|
Map<String, KeyStroke> result = new HashMap<>();
|
||||||
|
Set<Entry<String, ActionKeyBindingState>> entries = actionInfoByFullName.entrySet();
|
||||||
|
for (Entry<String, ActionKeyBindingState> entry : entries) {
|
||||||
|
String key = entry.getKey();
|
||||||
|
KeyStroke value = entry.getValue().getCurrentKeyStroke();
|
||||||
|
result.put(key, value);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean containsAction(String fullName) {
|
public boolean containsAction(String fullName) {
|
||||||
return actionsByFullName.containsKey(fullName);
|
return actionInfoByFullName.containsKey(fullName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public KeyStroke getKeyStroke(String fullName) {
|
public KeyStroke getKeyStroke(String fullName) {
|
||||||
return keyStrokesByFullName.get(fullName);
|
ActionKeyBindingState info = actionInfoByFullName.get(fullName);
|
||||||
|
return info.getCurrentKeyStroke();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getActionsForKeyStrokeText(String keyStrokeText) {
|
public MouseBinding getMouseBinding(String fullName) {
|
||||||
|
ActionKeyBindingState info = actionInfoByFullName.get(fullName);
|
||||||
|
return info.getCurrentMouseBinding();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getActionForMouseBinding(MouseBinding mouseBinding) {
|
||||||
|
return actionNameByMouseBinding.get(mouseBinding);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getActionsForKeyStrokeText(KeyStroke keyStroke) {
|
||||||
|
|
||||||
StringBuffer sb = new StringBuffer();
|
StringBuffer sb = new StringBuffer();
|
||||||
List<String> names = actionNamesByKeyStroke.get(keyStrokeText);
|
List<String> names = actionNamesByKeyStroke.get(keyStroke);
|
||||||
if (CollectionUtils.isBlank(names)) {
|
if (CollectionUtils.isBlank(names)) {
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
@ -85,14 +108,16 @@ public class KeyBindings {
|
||||||
return n1.compareToIgnoreCase(n2);
|
return n1.compareToIgnoreCase(n2);
|
||||||
});
|
});
|
||||||
|
|
||||||
sb.append("Actions mapped to key " + keyStrokeText + ":\n");
|
String ksName = KeyBindingUtils.parseKeyStroke(keyStroke);
|
||||||
|
sb.append("Actions mapped to key " + ksName + ":\n");
|
||||||
for (int i = 0; i < names.size(); i++) {
|
for (int i = 0; i < names.size(); i++) {
|
||||||
sb.append(" ");
|
sb.append(" ");
|
||||||
|
|
||||||
String name = names.get(i);
|
String name = names.get(i);
|
||||||
List<DockingActionIf> actions = actionsByFullName.get(name);
|
ActionKeyBindingState info = actionInfoByFullName.get(name);
|
||||||
DockingActionIf action = actions.get(0);
|
DockingActionIf action = info.getRepresentativeAction();
|
||||||
sb.append(action.getName());
|
String shortName = action.getName();
|
||||||
|
sb.append(shortName);
|
||||||
sb.append(" (").append(action.getOwnerDescription()).append(')');
|
sb.append(" (").append(action.getOwnerDescription()).append(')');
|
||||||
if (i < names.size() - 1) {
|
if (i < names.size() - 1) {
|
||||||
sb.append("\n");
|
sb.append("\n");
|
||||||
|
@ -105,58 +130,79 @@ public class KeyBindings {
|
||||||
return longestActionName;
|
return longestActionName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean setActionKeyStroke(String actionName, KeyStroke keyStroke) {
|
public boolean isMouseBindingInUse(String fullName, MouseBinding newBinding) {
|
||||||
String ksName = KeyBindingUtils.parseKeyStroke(keyStroke);
|
|
||||||
|
|
||||||
// remove old keystroke for action name
|
String existingName = actionNameByMouseBinding.get(newBinding);
|
||||||
KeyStroke oldKs = keyStrokesByFullName.get(actionName);
|
if (existingName == null || newBinding == null) {
|
||||||
if (oldKs != null) {
|
return false; // no new binding, or not in use
|
||||||
String oldName = KeyBindingUtils.parseKeyStroke(oldKs);
|
}
|
||||||
if (oldName.equals(ksName)) {
|
|
||||||
|
return !Objects.equals(existingName, fullName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean setActionMouseBinding(String fullName, MouseBinding newBinding) {
|
||||||
|
|
||||||
|
MouseBinding currentBinding = getMouseBinding(fullName);
|
||||||
|
if (currentBinding != null) {
|
||||||
|
if (currentBinding.equals(newBinding)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
removeFromKeyMap(oldKs, actionName);
|
|
||||||
}
|
|
||||||
addActionKeyStroke(keyStroke, actionName);
|
|
||||||
|
|
||||||
keyStrokesByFullName.put(actionName, keyStroke);
|
actionNameByMouseBinding.remove(currentBinding);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newBinding != null) {
|
||||||
|
actionNameByMouseBinding.put(newBinding, fullName);
|
||||||
|
}
|
||||||
|
|
||||||
|
ActionKeyBindingState info = actionInfoByFullName.get(fullName);
|
||||||
|
info.setCurrentMouseBinding(newBinding);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean removeKeyStroke(String actionName) {
|
public boolean setActionKeyStroke(String fullName, KeyStroke newKs) {
|
||||||
if (keyStrokesByFullName.containsKey(actionName)) {
|
String newKsName = KeyBindingUtils.parseKeyStroke(newKs);
|
||||||
KeyStroke stroke = keyStrokesByFullName.get(actionName);
|
|
||||||
if (stroke == null) {
|
// remove old keystroke for action name
|
||||||
// nothing to remove; nothing has changed
|
KeyStroke currentKs = getKeyStroke(fullName);
|
||||||
|
if (currentKs != null) {
|
||||||
|
String currentName = KeyBindingUtils.parseKeyStroke(currentKs);
|
||||||
|
if (currentName.equals(newKsName)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
removeFromKeyMap(fullName, currentKs);
|
||||||
removeFromKeyMap(stroke, actionName);
|
|
||||||
keyStrokesByFullName.put(actionName, null);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return false;
|
addActionKeyStroke(fullName, newKs);
|
||||||
|
|
||||||
|
ActionKeyBindingState info = actionInfoByFullName.get(fullName);
|
||||||
|
info.setCurrentKeyStroke(newKs);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean removeKeyStroke(String fullName) {
|
||||||
|
|
||||||
|
ActionKeyBindingState info = actionInfoByFullName.get(fullName);
|
||||||
|
if (info == null) {
|
||||||
|
return false; // not sure if this can happen
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyStroke currentKeyStroke = info.getCurrentKeyStroke();
|
||||||
|
if (currentKeyStroke == null) {
|
||||||
|
return false; // nothing to remove; nothing has changed
|
||||||
|
}
|
||||||
|
|
||||||
|
removeFromKeyMap(fullName, currentKeyStroke);
|
||||||
|
info.setCurrentKeyStroke(null);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Restores the tool options key bindings to the default values originally loaded when the
|
* Restores the tool options key bindings to the default values originally loaded when the
|
||||||
* system started.
|
* system started.
|
||||||
*/
|
*/
|
||||||
public void restoreOptions() {
|
public void restoreOptions() {
|
||||||
|
for (ActionKeyBindingState info : actionInfoByFullName.values()) {
|
||||||
Set<Entry<String, List<DockingActionIf>>> entries = actionsByFullName.entrySet();
|
info.restore(keyBindingOptions);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -164,14 +210,8 @@ public class KeyBindings {
|
||||||
* Cancels any pending changes that have not yet been applied.
|
* Cancels any pending changes that have not yet been applied.
|
||||||
*/
|
*/
|
||||||
public void cancelChanges() {
|
public void cancelChanges() {
|
||||||
Iterator<String> iter = originalKeyStrokesByFullName.keySet().iterator();
|
for (ActionKeyBindingState info : actionInfoByFullName.values()) {
|
||||||
while (iter.hasNext()) {
|
info.cancelChanges();
|
||||||
String actionName = iter.next();
|
|
||||||
KeyStroke originalKS = originalKeyStrokesByFullName.get(actionName);
|
|
||||||
KeyStroke modifiedKS = keyStrokesByFullName.get(actionName);
|
|
||||||
if (modifiedKS != null && !modifiedKS.equals(originalKS)) {
|
|
||||||
keyStrokesByFullName.put(actionName, originalKS);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -179,84 +219,203 @@ public class KeyBindings {
|
||||||
* Applies any pending changes.
|
* Applies any pending changes.
|
||||||
*/
|
*/
|
||||||
public void applyChanges() {
|
public void applyChanges() {
|
||||||
Iterator<String> iter = keyStrokesByFullName.keySet().iterator();
|
for (ActionKeyBindingState info : actionInfoByFullName.values()) {
|
||||||
while (iter.hasNext()) {
|
info.apply(keyBindingOptions);
|
||||||
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) {
|
private void removeFromKeyMap(String actionName, KeyStroke ks) {
|
||||||
if (ks == null) {
|
if (ks == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String ksName = KeyBindingUtils.parseKeyStroke(ks);
|
|
||||||
List<String> list = actionNamesByKeyStroke.get(ksName);
|
List<String> list = actionNamesByKeyStroke.get(ks);
|
||||||
if (list != null) {
|
if (list != null) {
|
||||||
list.remove(actionName);
|
list.remove(actionName);
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
actionNamesByKeyStroke.remove(ksName);
|
actionNamesByKeyStroke.remove(ks);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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() {
|
private void init() {
|
||||||
|
|
||||||
actionsByFullName = KeyBindingUtils.getAllActionsByFullName(tool);
|
actionInfoByFullName = new HashMap<>();
|
||||||
|
|
||||||
|
Map<String, List<DockingActionIf>> actionsByFullName =
|
||||||
|
KeyBindingUtils.getAllActionsByFullName(tool);
|
||||||
Set<Entry<String, List<DockingActionIf>>> entries = actionsByFullName.entrySet();
|
Set<Entry<String, List<DockingActionIf>>> entries = actionsByFullName.entrySet();
|
||||||
for (Entry<String, List<DockingActionIf>> entry : entries) {
|
for (Entry<String, List<DockingActionIf>> entry : entries) {
|
||||||
|
|
||||||
// pick one action, they are all conceptually the same
|
|
||||||
List<DockingActionIf> actions = entry.getValue();
|
List<DockingActionIf> actions = entry.getValue();
|
||||||
DockingActionIf action = actions.get(0);
|
|
||||||
uniqueActions.add(action);
|
|
||||||
|
|
||||||
String actionName = entry.getKey();
|
String fullName = entry.getKey();
|
||||||
KeyStroke ks = options.getKeyStroke(actionName, null);
|
ActionTrigger trigger = keyBindingOptions.getActionTrigger(fullName, null);
|
||||||
keyStrokesByFullName.put(actionName, ks);
|
|
||||||
addActionKeyStroke(ks, actionName);
|
|
||||||
originalKeyStrokesByFullName.put(actionName, ks);
|
|
||||||
|
|
||||||
String shortName = action.getName();
|
KeyStroke ks = null;
|
||||||
|
MouseBinding mb = null;
|
||||||
|
|
||||||
|
if (trigger != null) {
|
||||||
|
ks = trigger.getKeyStroke();
|
||||||
|
mb = trigger.getMouseBinding();
|
||||||
|
}
|
||||||
|
|
||||||
|
ActionKeyBindingState info = new ActionKeyBindingState(actions, ks, mb);
|
||||||
|
actionInfoByFullName.put(fullName, info);
|
||||||
|
|
||||||
|
uniqueActions.add(info.getRepresentativeAction());
|
||||||
|
|
||||||
|
addActionKeyStroke(fullName, ks);
|
||||||
|
|
||||||
|
String shortName = info.getShortName();
|
||||||
if (shortName.length() > longestActionName.length()) {
|
if (shortName.length() > longestActionName.length()) {
|
||||||
longestActionName = shortName;
|
longestActionName = shortName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addActionKeyStroke(KeyStroke ks, String actionName) {
|
private void addActionKeyStroke(String actionName, KeyStroke ks) {
|
||||||
if (ks == null) {
|
if (ks == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String ksName = KeyBindingUtils.parseKeyStroke(ks);
|
|
||||||
List<String> list = actionNamesByKeyStroke.get(ksName);
|
List<String> list = actionNamesByKeyStroke.get(ks);
|
||||||
if (list == null) {
|
if (list == null) {
|
||||||
list = new ArrayList<>();
|
list = new ArrayList<>();
|
||||||
actionNamesByKeyStroke.put(ksName, list);
|
actionNamesByKeyStroke.put(ks, list);
|
||||||
}
|
}
|
||||||
if (!list.contains(actionName)) {
|
if (!list.contains(actionName)) {
|
||||||
list.add(actionName);
|
list.add(actionName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class to store current and original values for key strokes and mouse bindings. This is
|
||||||
|
* used to apply changes and restore default values.
|
||||||
|
*/
|
||||||
|
private class ActionKeyBindingState {
|
||||||
|
|
||||||
|
private List<DockingActionIf> actions = new ArrayList<>();
|
||||||
|
private KeyStroke originalKeyStroke;
|
||||||
|
private KeyStroke currentKeyStroke;
|
||||||
|
private MouseBinding originalMouseBinding;
|
||||||
|
private MouseBinding currentMouseBinding;
|
||||||
|
|
||||||
|
ActionKeyBindingState(List<DockingActionIf> actions, KeyStroke ks, MouseBinding mb) {
|
||||||
|
this.actions.addAll(actions);
|
||||||
|
this.originalKeyStroke = ks;
|
||||||
|
this.currentKeyStroke = ks;
|
||||||
|
this.originalMouseBinding = mb;
|
||||||
|
this.currentMouseBinding = mb;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DockingActionIf getRepresentativeAction() {
|
||||||
|
// pick one action, they are all conceptually the same
|
||||||
|
return actions.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
String getShortName() {
|
||||||
|
// pick one action, they are all conceptually the same
|
||||||
|
return actions.get(0).getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
String getFullName() {
|
||||||
|
return getRepresentativeAction().getFullName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public MouseBinding getCurrentMouseBinding() {
|
||||||
|
return currentMouseBinding;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCurrentMouseBinding(MouseBinding newMouseBinding) {
|
||||||
|
this.currentMouseBinding = newMouseBinding;
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeyStroke getCurrentKeyStroke() {
|
||||||
|
return currentKeyStroke;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCurrentKeyStroke(KeyStroke newKeyStroke) {
|
||||||
|
this.currentKeyStroke = newKeyStroke;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void cancelChanges() {
|
||||||
|
currentKeyStroke = originalKeyStroke;
|
||||||
|
currentMouseBinding = originalMouseBinding;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void apply(ToolOptions keyStrokeOptions) {
|
||||||
|
if (!hasChanged()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyBindingData kbd = getCurrentKeyBindingData();
|
||||||
|
apply(keyStrokeOptions, kbd);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void apply(ToolOptions keyStrokeOptions, KeyBindingData keyBinding) {
|
||||||
|
|
||||||
|
if (keyBinding == null) {
|
||||||
|
// no bindings; bindings have been cleared
|
||||||
|
for (DockingActionIf action : actions) {
|
||||||
|
action.setUnvalidatedKeyBindingData(null);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ActionTrigger newTrigger = keyBinding.getActionTrigger();
|
||||||
|
String fullName = getFullName();
|
||||||
|
keyStrokeOptions.setActionTrigger(fullName, newTrigger);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasChanged() {
|
||||||
|
return !Objects.equals(originalKeyStroke, currentKeyStroke) ||
|
||||||
|
!Objects.equals(originalMouseBinding, currentMouseBinding);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean matches(KeyBindingData kbData) {
|
||||||
|
|
||||||
|
if (CollectionUtils.isAllNull(kbData, currentKeyStroke, currentMouseBinding)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (kbData == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyStroke otherKs = kbData.getKeyBinding();
|
||||||
|
if (!Objects.equals(otherKs, currentKeyStroke)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseBinding otherMb = kbData.getMouseBinding();
|
||||||
|
return Objects.equals(otherMb, currentMouseBinding);
|
||||||
|
}
|
||||||
|
|
||||||
|
private KeyBindingData getCurrentKeyBindingData() {
|
||||||
|
|
||||||
|
if (currentKeyStroke == null && currentMouseBinding == null) {
|
||||||
|
return null; // the key binding data does not exist or has been cleared
|
||||||
|
}
|
||||||
|
|
||||||
|
DockingActionIf action = getRepresentativeAction();
|
||||||
|
KeyBindingData kbData = action.getKeyBindingData();
|
||||||
|
ActionTrigger trigger = new ActionTrigger(currentKeyStroke, currentMouseBinding);
|
||||||
|
return KeyBindingData.update(kbData, trigger);
|
||||||
|
}
|
||||||
|
|
||||||
|
// restores the options to their default values
|
||||||
|
public void restore(ToolOptions options) {
|
||||||
|
DockingActionIf action = getRepresentativeAction();
|
||||||
|
KeyBindingData defaultBinding = action.getDefaultKeyBindingData();
|
||||||
|
|
||||||
|
if (!matches(defaultBinding)) {
|
||||||
|
apply(options, defaultBinding);
|
||||||
|
}
|
||||||
|
|
||||||
|
cancelChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -145,6 +145,7 @@ public class KeyEntryDialog extends DialogComponentProvider {
|
||||||
*/
|
*/
|
||||||
public void setKeyStroke(KeyStroke ks) {
|
public void setKeyStroke(KeyStroke ks) {
|
||||||
keyEntryField.setKeyStroke(ks);
|
keyEntryField.setKeyStroke(ks);
|
||||||
|
updateCollisionPane(ks);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -169,7 +170,7 @@ public class KeyEntryDialog extends DialogComponentProvider {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
action.setUnvalidatedKeyBindingData(new KeyBindingData(newKs));
|
action.setUnvalidatedKeyBindingData(newKs == null ? null : new KeyBindingData(newKs));
|
||||||
|
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
|
@ -192,8 +193,7 @@ public class KeyEntryDialog extends DialogComponentProvider {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String ksName = KeyBindingUtils.parseKeyStroke(ks);
|
String text = keyBindings.getActionsForKeyStrokeText(ks);
|
||||||
String text = keyBindings.getActionsForKeyStrokeText(ksName);
|
|
||||||
try {
|
try {
|
||||||
doc.insertString(0, text, textAttrs);
|
doc.insertString(0, text, textAttrs);
|
||||||
collisionPane.setCaretPosition(0);
|
collisionPane.setCaretPosition(0);
|
||||||
|
|
|
@ -18,8 +18,6 @@ package docking.actions;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
import javax.swing.KeyStroke;
|
|
||||||
|
|
||||||
import org.apache.commons.collections4.Bag;
|
import org.apache.commons.collections4.Bag;
|
||||||
import org.apache.commons.collections4.bag.HashBag;
|
import org.apache.commons.collections4.bag.HashBag;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
@ -28,8 +26,9 @@ import docking.ActionContext;
|
||||||
import docking.DockingWindowManager;
|
import docking.DockingWindowManager;
|
||||||
import docking.action.*;
|
import docking.action.*;
|
||||||
import docking.tool.ToolConstants;
|
import docking.tool.ToolConstants;
|
||||||
import ghidra.framework.options.OptionsChangeListener;
|
import ghidra.framework.options.*;
|
||||||
import ghidra.framework.options.ToolOptions;
|
import ghidra.util.Msg;
|
||||||
|
import utilities.util.reflection.ReflectionUtilities;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A stub action that allows key bindings to be edited through the key bindings options. This
|
* A stub action that allows key bindings to be edited through the key bindings options. This
|
||||||
|
@ -63,7 +62,7 @@ public class SharedStubKeyBindingAction extends DockingAction implements Options
|
||||||
* Note: This collection is weak; the actions will stay as long as they are
|
* Note: This collection is weak; the actions will stay as long as they are
|
||||||
* registered in the tool.
|
* registered in the tool.
|
||||||
*/
|
*/
|
||||||
private WeakHashMap<DockingActionIf, KeyStroke> clientActions = new WeakHashMap<>();
|
private WeakHashMap<DockingActionIf, ActionTrigger> clientActions = new WeakHashMap<>();
|
||||||
|
|
||||||
private ToolOptions keyBindingOptions;
|
private ToolOptions keyBindingOptions;
|
||||||
private Bag<String> actionOwners = new HashBag<>();
|
private Bag<String> actionOwners = new HashBag<>();
|
||||||
|
@ -73,11 +72,13 @@ public class SharedStubKeyBindingAction extends DockingAction implements Options
|
||||||
*
|
*
|
||||||
* @param name The name of the action--this will be displayed in the options as the name of
|
* @param name The name of the action--this will be displayed in the options as the name of
|
||||||
* key binding's action
|
* key binding's action
|
||||||
* @param defaultKs the default key stroke for this stub. The key stroke will be validated
|
* @param defaultActionTrigger the default action trigger for this stub. The action trigger
|
||||||
* each time an action is added to this stub to ensure that the defaults are in sync.
|
* will be validated each time an action is added to this stub to ensure that the
|
||||||
|
* defaults are in sync.
|
||||||
* @param options the tool's key binding options
|
* @param options the tool's key binding options
|
||||||
*/
|
*/
|
||||||
SharedStubKeyBindingAction(String name, KeyStroke defaultKs, ToolOptions options) {
|
SharedStubKeyBindingAction(String name, ActionTrigger defaultActionTrigger,
|
||||||
|
ToolOptions options) {
|
||||||
// Note: we need to have this stub registered to use key bindings so that the options will
|
// Note: we need to have this stub registered to use key bindings so that the options will
|
||||||
// restore the saved key binding to this class, which will then notify any of the
|
// restore the saved key binding to this class, which will then notify any of the
|
||||||
// shared actions using this stub.
|
// shared actions using this stub.
|
||||||
|
@ -87,7 +88,7 @@ public class SharedStubKeyBindingAction extends DockingAction implements Options
|
||||||
// Dummy keybinding actions don't have help--the real action does
|
// Dummy keybinding actions don't have help--the real action does
|
||||||
DockingWindowManager.getHelpService().excludeFromHelp(this);
|
DockingWindowManager.getHelpService().excludeFromHelp(this);
|
||||||
|
|
||||||
setUnvalidatedKeyBindingData(new KeyBindingData(defaultKs));
|
setKeyBindingData(this, defaultActionTrigger);
|
||||||
|
|
||||||
// A listener to keep the shared, stub keybindings in sync with their clients
|
// A listener to keep the shared, stub keybindings in sync with their clients
|
||||||
options.addOptionsChangeListener(this);
|
options.addOptionsChangeListener(this);
|
||||||
|
@ -119,7 +120,7 @@ public class SharedStubKeyBindingAction extends DockingAction implements Options
|
||||||
void addClientAction(DockingActionIf action) {
|
void addClientAction(DockingActionIf action) {
|
||||||
|
|
||||||
// 1) Validate new action keystroke against existing actions
|
// 1) Validate new action keystroke against existing actions
|
||||||
KeyStroke defaultKs = validateActionsHaveTheSameDefaultKeyStroke(action);
|
ActionTrigger defaultKs = validateActionsHaveTheSameDefaultKeyStroke(action);
|
||||||
|
|
||||||
// 2) Add the action and the validated keystroke, as this is the default keystroke
|
// 2) Add the action and the validated keystroke, as this is the default keystroke
|
||||||
clientActions.put(action, defaultKs);
|
clientActions.put(action, defaultKs);
|
||||||
|
@ -159,61 +160,69 @@ public class SharedStubKeyBindingAction extends DockingAction implements Options
|
||||||
return super.getDescription();
|
return super.getDescription();
|
||||||
}
|
}
|
||||||
|
|
||||||
private KeyStroke validateActionsHaveTheSameDefaultKeyStroke(DockingActionIf newAction) {
|
private ActionTrigger validateActionsHaveTheSameDefaultKeyStroke(DockingActionIf newAction) {
|
||||||
|
|
||||||
// this value may be null
|
// this value may be null
|
||||||
KeyBindingData defaultBinding = newAction.getDefaultKeyBindingData();
|
KeyBindingData defaultBinding = newAction.getDefaultKeyBindingData();
|
||||||
KeyStroke newDefaultKs = getKeyStroke(defaultBinding);
|
ActionTrigger newDefaulTrigger = getActionTrigger(defaultBinding);
|
||||||
|
|
||||||
Set<Entry<DockingActionIf, KeyStroke>> entries = clientActions.entrySet();
|
Set<Entry<DockingActionIf, ActionTrigger>> entries = clientActions.entrySet();
|
||||||
for (Entry<DockingActionIf, KeyStroke> entry : entries) {
|
for (Entry<DockingActionIf, ActionTrigger> entry : entries) {
|
||||||
DockingActionIf existingAction = entry.getKey();
|
DockingActionIf existingAction = entry.getKey();
|
||||||
KeyStroke existingDefaultKs = entry.getValue();
|
ActionTrigger existingDefaultTrigger = entry.getValue();
|
||||||
if (Objects.equals(existingDefaultKs, newDefaultKs)) {
|
if (Objects.equals(existingDefaultTrigger, newDefaulTrigger)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyBindingUtils.logDifferentKeyBindingsWarnigMessage(newAction, existingAction,
|
logDifferentKeyBindingsWarnigMessage(newAction, existingAction, existingDefaultTrigger);
|
||||||
existingDefaultKs);
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Not sure which keystroke to prefer here--keep the first one that was set
|
// Not sure which keystroke to prefer here--keep the first one that was set
|
||||||
//
|
//
|
||||||
|
|
||||||
// set the new action's keystroke to be the winner
|
// set the existing action's keystroke to be the winner
|
||||||
newAction.setKeyBindingData(new KeyBindingData(existingDefaultKs));
|
newAction.setKeyBindingData(existingAction.getKeyBindingData());
|
||||||
|
|
||||||
// one message is probably enough;
|
// one message is probably enough;
|
||||||
return existingDefaultKs;
|
return existingDefaultTrigger;
|
||||||
}
|
}
|
||||||
|
|
||||||
return newDefaultKs;
|
return newDefaulTrigger;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateActionKeyStrokeFromOptions(DockingActionIf action, KeyStroke defaultKs) {
|
private void updateActionKeyStrokeFromOptions(DockingActionIf action,
|
||||||
|
ActionTrigger defaultTrigger) {
|
||||||
|
|
||||||
KeyStroke stubKs = defaultKs;
|
ActionTrigger stubTrigger = defaultTrigger;
|
||||||
KeyStroke optionsKs = getKeyStrokeFromOptions(defaultKs);
|
ActionTrigger optionsTrigger = getActionTriggerFromOptions(defaultTrigger);
|
||||||
if (!Objects.equals(defaultKs, optionsKs)) {
|
if (!Objects.equals(defaultTrigger, optionsTrigger)) {
|
||||||
// we use the 'unvalidated' call since this value is provided by the user--we assume
|
setKeyBindingData(action, optionsTrigger);
|
||||||
// that user input is correct; we only validate programmer input
|
stubTrigger = optionsTrigger;
|
||||||
action.setUnvalidatedKeyBindingData(new KeyBindingData(optionsKs));
|
|
||||||
stubKs = optionsKs;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setUnvalidatedKeyBindingData(new KeyBindingData(stubKs));
|
setKeyBindingData(this, stubTrigger);
|
||||||
}
|
}
|
||||||
|
|
||||||
private KeyStroke getKeyStrokeFromOptions(KeyStroke validatedKeyStroke) {
|
private void setKeyBindingData(DockingActionIf action, ActionTrigger actionTrigger) {
|
||||||
KeyStroke ks = keyBindingOptions.getKeyStroke(getFullName(), validatedKeyStroke);
|
KeyBindingData kbData = null;
|
||||||
return ks;
|
if (actionTrigger != null) {
|
||||||
|
kbData = new KeyBindingData(actionTrigger);
|
||||||
|
}
|
||||||
|
|
||||||
|
// we use the 'unvalidated' call since this value is provided by the user--we assume
|
||||||
|
// that user input is correct; we only validate programmer input
|
||||||
|
action.setUnvalidatedKeyBindingData(kbData);
|
||||||
}
|
}
|
||||||
|
|
||||||
private KeyStroke getKeyStroke(KeyBindingData data) {
|
private ActionTrigger getActionTriggerFromOptions(ActionTrigger validatedTrigger) {
|
||||||
|
return keyBindingOptions.getActionTrigger(getFullName(), validatedTrigger);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ActionTrigger getActionTrigger(KeyBindingData data) {
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return data.getKeyBinding();
|
return data.getActionTrigger();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -224,11 +233,11 @@ public class SharedStubKeyBindingAction extends DockingAction implements Options
|
||||||
return; // not my binding
|
return; // not my binding
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyStroke newKs = (KeyStroke) newValue;
|
ActionTrigger newTrigger = (ActionTrigger) newValue;
|
||||||
|
setKeyBindingData(this, newTrigger);
|
||||||
|
|
||||||
for (DockingActionIf action : clientActions.keySet()) {
|
for (DockingActionIf action : clientActions.keySet()) {
|
||||||
// we use the 'unvalidated' call since this value is provided by the user--we assume
|
setKeyBindingData(action, newTrigger);
|
||||||
// that user input is correct; we only validate programmer input
|
|
||||||
action.setUnvalidatedKeyBindingData(new KeyBindingData(newKs));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -253,4 +262,23 @@ public class SharedStubKeyBindingAction extends DockingAction implements Options
|
||||||
clientActions.clear();
|
clientActions.clear();
|
||||||
keyBindingOptions.removeOptionsChangeListener(this);
|
keyBindingOptions.removeOptionsChangeListener(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void logDifferentKeyBindingsWarnigMessage(DockingActionIf newAction,
|
||||||
|
DockingActionIf existingAction, ActionTrigger existingDefaultTrigger) {
|
||||||
|
|
||||||
|
//@formatter:off
|
||||||
|
String s = "Shared Key Binding Actions have different default values. These " +
|
||||||
|
"must be the same." +
|
||||||
|
"\n\tAction name: '"+existingAction.getName()+ "'" +
|
||||||
|
"\n\tAction 1: " + existingAction.getInceptionInformation() +
|
||||||
|
"\n\t\tAction Trigger: " + existingDefaultTrigger +
|
||||||
|
"\n\tAction 2: " + newAction.getInceptionInformation() +
|
||||||
|
"\n\t\tAction Trigger: " + newAction.getKeyBinding() +
|
||||||
|
"\nUsing the " +
|
||||||
|
"first value set - " + existingDefaultTrigger;
|
||||||
|
//@formatter:on
|
||||||
|
|
||||||
|
Msg.warn(SharedStubKeyBindingAction.class, s,
|
||||||
|
ReflectionUtilities.createJavaFilteredThrowable());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,9 @@ import docking.tool.util.DockingToolConstants;
|
||||||
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;
|
||||||
|
import gui.event.MouseBinding;
|
||||||
import util.CollectionUtils;
|
import util.CollectionUtils;
|
||||||
|
import utilities.util.reflection.ReflectionUtilities;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An class to manage actions registered with the tool
|
* An class to manage actions registered with the tool
|
||||||
|
@ -51,7 +53,7 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Map of Maps of Sets
|
Map of Maps of Sets
|
||||||
|
|
||||||
Owner Name ->
|
Owner Name ->
|
||||||
Action Name -> Set of Actions
|
Action Name -> Set of Actions
|
||||||
*/
|
*/
|
||||||
|
@ -60,15 +62,15 @@ 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 options;
|
||||||
private Tool tool;
|
private Tool tool;
|
||||||
private KeyBindingsManager keyBindingsManager;
|
private KeyBindingsManager keyBindingsManager;
|
||||||
private OptionsChangeListener optionChangeListener = (options, optionName, oldValue,
|
private OptionsChangeListener optionChangeListener = (toolOptions, optionName, oldValue,
|
||||||
newValue) -> updateKeyBindingsFromOptions(options, optionName, (KeyStroke) newValue);
|
newValue) -> updateKeyBindingsFromOptions(optionName, (ActionTrigger) newValue);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct an ActionManager
|
* Construct an ActionManager
|
||||||
*
|
*
|
||||||
* @param tool tool using this ActionManager
|
* @param tool tool using this ActionManager
|
||||||
* @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
|
||||||
*/
|
*/
|
||||||
|
@ -76,8 +78,8 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener {
|
||||||
this.tool = 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.options = tool.getOptions(DockingToolConstants.KEY_BINDINGS);
|
||||||
this.keyBindingOptions.addOptionsChangeListener(optionChangeListener);
|
this.options.addOptionsChangeListener(optionChangeListener);
|
||||||
|
|
||||||
createSystemActions();
|
createSystemActions();
|
||||||
SharedActionRegistry.installSharedActions(tool, this);
|
SharedActionRegistry.installSharedActions(tool, this);
|
||||||
|
@ -112,12 +114,12 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener {
|
||||||
// Some System actions support changing the keybinding. In the future, all System actions
|
// Some System actions support changing the keybinding. In the future, all System actions
|
||||||
// may support this.
|
// may support this.
|
||||||
if (action.getKeyBindingType().isManaged()) {
|
if (action.getKeyBindingType().isManaged()) {
|
||||||
KeyBindingData kbd = action.getKeyBindingData();
|
ActionTrigger actionTrigger = getActionTrigger(action);
|
||||||
KeyStroke ks = kbd.getKeyBinding();
|
loadKeyBindingFromOptions(action, actionTrigger);
|
||||||
loadKeyBindingFromOptions(action, ks);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
keyBindingsManager.addSystemAction(action);
|
keyBindingsManager.addSystemAction(action);
|
||||||
|
addActionToMap(action);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void dispose() {
|
public void dispose() {
|
||||||
|
@ -127,12 +129,64 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addActionToMap(DockingActionIf action) {
|
private void addActionToMap(DockingActionIf action) {
|
||||||
|
|
||||||
Set<DockingActionIf> actions = getActionStorage(action);
|
Set<DockingActionIf> actions = getActionStorage(action);
|
||||||
KeyBindingUtils.assertSameDefaultKeyBindings(action, actions);
|
assertSameDefaultActionTrigger(action, actions);
|
||||||
actions.add(action);
|
actions.add(action);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void assertSameDefaultActionTrigger(DockingActionIf newAction,
|
||||||
|
Collection<DockingActionIf> existingActions) {
|
||||||
|
|
||||||
|
if (!newAction.getKeyBindingType().supportsKeyBindings()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyBindingData newDefaultBinding = newAction.getDefaultKeyBindingData();
|
||||||
|
ActionTrigger defaultTrigger = getActionTrigger(newDefaultBinding);
|
||||||
|
for (DockingActionIf action : existingActions) {
|
||||||
|
if (!action.getKeyBindingType().supportsKeyBindings()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyBindingData existingDefaultBinding = action.getDefaultKeyBindingData();
|
||||||
|
ActionTrigger existingTrigger = getActionTrigger(existingDefaultBinding);
|
||||||
|
if (!Objects.equals(defaultTrigger, existingTrigger)) {
|
||||||
|
logDifferentKeyBindingsWarnigMessage(newAction, action, existingTrigger);
|
||||||
|
break; // one warning seems like enough
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Verifies that two equivalent actions (same name and owner) share the same default action
|
||||||
|
* trigger. It is considered a programming mistake for two equivalent actions to have different
|
||||||
|
* triggers.
|
||||||
|
*/
|
||||||
|
private static void logDifferentKeyBindingsWarnigMessage(DockingActionIf newAction,
|
||||||
|
DockingActionIf existingAction, ActionTrigger existingDefaultTrigger) {
|
||||||
|
|
||||||
|
//@formatter:off
|
||||||
|
String s = "Shared Key Binding Actions have different default values. These " +
|
||||||
|
"must be the same." +
|
||||||
|
"\n\tAction name: '"+existingAction.getName()+ "'" +
|
||||||
|
"\n\tAction 1: " + existingAction.getInceptionInformation() +
|
||||||
|
"\n\t\tAction Trigger: " + existingDefaultTrigger +
|
||||||
|
"\n\tAction 2: " + newAction.getInceptionInformation() +
|
||||||
|
"\n\t\tAction Trigger: " + newAction.getKeyBinding() +
|
||||||
|
"\nUsing the " +
|
||||||
|
"first value set - " + existingDefaultTrigger;
|
||||||
|
//@formatter:on
|
||||||
|
|
||||||
|
Msg.warn(ToolActions.class, s, ReflectionUtilities.createJavaFilteredThrowable());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ActionTrigger getActionTrigger(KeyBindingData data) {
|
||||||
|
if (data == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return data.getActionTrigger();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add an action that works specifically with a component provider.
|
* Add an action that works specifically with a component provider.
|
||||||
* @param provider provider associated with the action
|
* @param provider provider associated with the action
|
||||||
|
@ -170,32 +224,45 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyStroke ks = action.getKeyBinding();
|
ActionTrigger actionTrigger = getActionTrigger(action);
|
||||||
loadKeyBindingFromOptions(action, ks);
|
loadKeyBindingFromOptions(action, actionTrigger);
|
||||||
|
|
||||||
keyBindingsManager.addAction(provider, action);
|
keyBindingsManager.addAction(provider, action);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadKeyBindingFromOptions(DockingActionIf action, KeyStroke ks) {
|
private ActionTrigger getActionTrigger(DockingActionIf action) {
|
||||||
String description = "Keybinding for " + action.getFullName();
|
KeyBindingData kbData = action.getKeyBindingData();
|
||||||
keyBindingOptions.registerOption(action.getFullName(), OptionType.KEYSTROKE_TYPE, ks, null,
|
if (kbData != null) {
|
||||||
description);
|
return kbData.getActionTrigger();
|
||||||
KeyStroke newKs = keyBindingOptions.getKeyStroke(action.getFullName(), ks);
|
|
||||||
if (!Objects.equals(ks, newKs)) {
|
|
||||||
action.setUnvalidatedKeyBindingData(new KeyBindingData(newKs));
|
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadKeyBindingFromOptions(DockingActionIf action, ActionTrigger actionTrigger) {
|
||||||
|
|
||||||
|
String fullName = action.getFullName();
|
||||||
|
String description = "Keybinding for " + fullName;
|
||||||
|
options.registerOption(fullName, OptionType.ACTION_TRIGGER, actionTrigger, null,
|
||||||
|
description);
|
||||||
|
|
||||||
|
KeyBindingData existingKbData = action.getKeyBindingData();
|
||||||
|
|
||||||
|
ActionTrigger newTrigger = options.getActionTrigger(fullName, actionTrigger);
|
||||||
|
KeyBindingData newKbData = KeyBindingData.update(existingKbData, newTrigger);
|
||||||
|
action.setUnvalidatedKeyBindingData(newKbData);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void installSharedKeyBinding(ComponentProvider provider, DockingActionIf action) {
|
private void installSharedKeyBinding(ComponentProvider provider, DockingActionIf action) {
|
||||||
|
|
||||||
String name = action.getName();
|
String name = action.getName();
|
||||||
KeyStroke defaultKeyStroke = action.getKeyBinding();
|
|
||||||
|
|
||||||
// get or create the stub to which we will add the action
|
// get or create the stub to which we will add the action
|
||||||
SharedStubKeyBindingAction stub = sharedActionMap.computeIfAbsent(name, key -> {
|
SharedStubKeyBindingAction stub = sharedActionMap.computeIfAbsent(name, key -> {
|
||||||
|
|
||||||
|
ActionTrigger actionTrigger = getActionTrigger(action);
|
||||||
SharedStubKeyBindingAction newStub =
|
SharedStubKeyBindingAction newStub =
|
||||||
new SharedStubKeyBindingAction(name, defaultKeyStroke, keyBindingOptions);
|
new SharedStubKeyBindingAction(name, actionTrigger, options);
|
||||||
registerStub(newStub, defaultKeyStroke);
|
registerStub(newStub, actionTrigger);
|
||||||
return newStub;
|
return newStub;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -209,10 +276,10 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void registerStub(SharedStubKeyBindingAction stub, KeyStroke defaultKeyStroke) {
|
private void registerStub(SharedStubKeyBindingAction stub, ActionTrigger defaultActionTrigger) {
|
||||||
stub.addPropertyChangeListener(this);
|
stub.addPropertyChangeListener(this);
|
||||||
|
|
||||||
loadKeyBindingFromOptions(stub, defaultKeyStroke);
|
loadKeyBindingFromOptions(stub, defaultActionTrigger);
|
||||||
|
|
||||||
keyBindingsManager.addAction(null, stub);
|
keyBindingsManager.addAction(null, stub);
|
||||||
}
|
}
|
||||||
|
@ -243,10 +310,15 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener {
|
||||||
return; // no actions registered for this owner
|
return; // no actions registered for this owner
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Note: this method is called when plugins are removed. 'owner' is the name of the plugin.
|
||||||
|
// This method will also get called while passing the system owner. In that case, we do
|
||||||
|
// not want to remove system actions in this method. We check below for system actions.
|
||||||
|
|
||||||
//@formatter:off
|
//@formatter:off
|
||||||
toCleanup.values()
|
toCleanup.values()
|
||||||
.stream()
|
.stream()
|
||||||
.flatMap(set -> set.stream())
|
.flatMap(set -> set.stream())
|
||||||
|
.filter(action -> !keyBindingsManager.isSystemAction(action)) // (see note above)
|
||||||
.forEach(action -> removeGlobalAction(action))
|
.forEach(action -> removeGlobalAction(action))
|
||||||
;
|
;
|
||||||
//@formatter:on
|
//@formatter:on
|
||||||
|
@ -312,32 +384,33 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener {
|
||||||
private Iterator<DockingActionIf> getAllActionsIterator() {
|
private Iterator<DockingActionIf> getAllActionsIterator() {
|
||||||
// chain all items together, rather than copy the data
|
// chain all items together, rather than copy the data
|
||||||
// Note: do not use Apache's IteratorUtils.chainedIterator. It degrades exponentially
|
// Note: do not use Apache's IteratorUtils.chainedIterator. It degrades exponentially
|
||||||
return Stream
|
//@formatter:off
|
||||||
.concat(
|
return Stream.concat(
|
||||||
actionsByNameByOwner.values()
|
actionsByNameByOwner.values().stream()
|
||||||
.stream()
|
.flatMap(actionsByName -> actionsByName.values().stream())
|
||||||
.flatMap(actionsByName -> actionsByName.values()
|
.flatMap(actions -> actions.stream()),
|
||||||
.stream())
|
sharedActionMap.values().stream()).iterator();
|
||||||
.flatMap(actions -> actions.stream()),
|
//@formatter:on
|
||||||
sharedActionMap.values()
|
|
||||||
.stream())
|
|
||||||
.iterator();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Get the keybindings for each action so that they are still registered as being used;
|
* An odd method that really shoulnd't be on the interface. This is a call that allows the
|
||||||
* otherwise the options will be removed because they are noted as not being used.
|
* framework to signal that the ToolOptions have been rebuilt, such as when restoring from xml.
|
||||||
|
* During a rebuild, ToolOptions does not send out events, so this class does not get any of the
|
||||||
|
* values from the new options. This method tells us to get the new version of the options from
|
||||||
|
* the tool.
|
||||||
*/
|
*/
|
||||||
public synchronized void restoreKeyBindings() {
|
public synchronized void optionsRebuilt() {
|
||||||
keyBindingOptions = tool.getOptions(DockingToolConstants.KEY_BINDINGS);
|
|
||||||
|
// grab the new, rebuilt options
|
||||||
|
options = 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)) {
|
||||||
KeyStroke ks = action.getKeyBinding();
|
KeyBindingData currentKbData = action.getKeyBindingData();
|
||||||
KeyStroke newKs = keyBindingOptions.getKeyStroke(action.getFullName(), ks);
|
ActionTrigger optionsTrigger = options.getActionTrigger(action.getFullName(), null);
|
||||||
if (!Objects.equals(ks, newKs)) {
|
KeyBindingData newKbData = KeyBindingData.update(currentKbData, optionsTrigger);
|
||||||
action.setUnvalidatedKeyBindingData(new KeyBindingData(newKs));
|
action.setUnvalidatedKeyBindingData(newKbData);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -377,8 +450,7 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener {
|
||||||
keyBindingsManager.removeAction(action);
|
keyBindingsManager.removeAction(action);
|
||||||
|
|
||||||
getActionStorage(action).remove(action);
|
getActionStorage(action).remove(action);
|
||||||
if (!action.getKeyBindingType()
|
if (!action.getKeyBindingType().isShared()) {
|
||||||
.isShared()) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -391,12 +463,10 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener {
|
||||||
private Set<DockingActionIf> getActionStorage(DockingActionIf action) {
|
private Set<DockingActionIf> getActionStorage(DockingActionIf action) {
|
||||||
String owner = action.getOwner();
|
String owner = action.getOwner();
|
||||||
String name = action.getName();
|
String name = action.getName();
|
||||||
return actionsByNameByOwner.get(owner)
|
return actionsByNameByOwner.get(owner).get(name);
|
||||||
.get(name);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateKeyBindingsFromOptions(ToolOptions options, String optionName,
|
private void updateKeyBindingsFromOptions(String optionName, ActionTrigger newTrigger) {
|
||||||
KeyStroke newKs) {
|
|
||||||
|
|
||||||
// note: the 'shared actions' update themselves, so we only need to handle standard actions
|
// note: the 'shared actions' update themselves, so we only need to handle standard actions
|
||||||
|
|
||||||
|
@ -405,21 +475,30 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener {
|
||||||
String name = matcher.group(1);
|
String name = matcher.group(1);
|
||||||
String owner = matcher.group(2);
|
String owner = matcher.group(2);
|
||||||
|
|
||||||
Set<DockingActionIf> actions = actionsByNameByOwner.get(owner)
|
Set<DockingActionIf> actions = actionsByNameByOwner.get(owner).get(name);
|
||||||
.get(name);
|
if (actions.isEmpty()) {
|
||||||
for (DockingActionIf action : actions) {
|
// An empty actions list implies that the action changed in the options is a shared
|
||||||
KeyStroke oldKs = action.getKeyBinding();
|
// action or a system action. Shared actions will update themselves. Here we will
|
||||||
if (Objects.equals(oldKs, newKs)) {
|
// handle system actions.
|
||||||
continue; // prevent bouncing
|
DockingActionIf systemAction = keyBindingsManager.getSystemAction(optionName);
|
||||||
|
if (systemAction != null) {
|
||||||
|
KeyBindingData oldKbData = systemAction.getKeyBindingData();
|
||||||
|
KeyBindingData newKbData = KeyBindingData.update(oldKbData, newTrigger);
|
||||||
|
systemAction.setUnvalidatedKeyBindingData(newKbData);
|
||||||
}
|
}
|
||||||
action.setUnvalidatedKeyBindingData(new KeyBindingData(newKs));
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (DockingActionIf action : actions) {
|
||||||
|
KeyBindingData oldKbData = action.getKeyBindingData();
|
||||||
|
KeyBindingData newKbData = KeyBindingData.update(oldKbData, newTrigger);
|
||||||
|
action.setUnvalidatedKeyBindingData(newKbData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void propertyChange(PropertyChangeEvent evt) {
|
public void propertyChange(PropertyChangeEvent evt) {
|
||||||
if (!evt.getPropertyName()
|
if (!evt.getPropertyName().equals(DockingActionIf.KEYBINDING_DATA_PROPERTY)) {
|
||||||
.equals(DockingActionIf.KEYBINDING_DATA_PROPERTY)) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -431,15 +510,19 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Check to see if we need to update the options to reflect the change to the action's key
|
||||||
|
// binding data.
|
||||||
|
//
|
||||||
KeyBindingData newKeyBindingData = (KeyBindingData) evt.getNewValue();
|
KeyBindingData newKeyBindingData = (KeyBindingData) evt.getNewValue();
|
||||||
KeyStroke newKs = null;
|
ActionTrigger newTrigger = null;
|
||||||
if (newKeyBindingData != null) {
|
if (newKeyBindingData != null) {
|
||||||
newKs = newKeyBindingData.getKeyBinding();
|
newTrigger = newKeyBindingData.getActionTrigger();
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyStroke currentKs = keyBindingOptions.getKeyStroke(action.getFullName(), null);
|
ActionTrigger currentTrigger = options.getActionTrigger(action.getFullName(), null);
|
||||||
if (!Objects.equals(currentKs, newKs)) {
|
if (!Objects.equals(currentTrigger, newTrigger)) {
|
||||||
keyBindingOptions.setKeyStroke(action.getFullName(), newKs);
|
options.setActionTrigger(action.getFullName(), newTrigger);
|
||||||
keyBindingsChanged();
|
keyBindingsChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -456,8 +539,7 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener {
|
||||||
Iterator<DockingActionIf> it = actionGuiHelper.getComponentActions(provider);
|
Iterator<DockingActionIf> it = actionGuiHelper.getComponentActions(provider);
|
||||||
while (it.hasNext()) {
|
while (it.hasNext()) {
|
||||||
DockingActionIf action = it.next();
|
DockingActionIf action = it.next();
|
||||||
if (action.getName()
|
if (action.getName().equals(actionName)) {
|
||||||
.equals(actionName)) {
|
|
||||||
return action;
|
return action;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -476,7 +558,11 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Action getAction(KeyStroke ks) {
|
public Action getAction(KeyStroke ks) {
|
||||||
return keyBindingsManager.getDockingKeyAction(ks);
|
return keyBindingsManager.getDockingAction(ks);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Action getAction(MouseBinding mb) {
|
||||||
|
return keyBindingsManager.getDockingAction(mb);
|
||||||
}
|
}
|
||||||
|
|
||||||
DockingActionIf getSharedStubKeyBindingAction(String name) {
|
DockingActionIf getSharedStubKeyBindingAction(String name) {
|
||||||
|
@ -487,23 +573,22 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener {
|
||||||
* Allows clients to register an action by using a placeholder. This is useful when
|
* Allows clients to register an action by using a placeholder. This is useful when
|
||||||
* an API wishes to have a central object (like a plugin) register actions for transient
|
* an API wishes to have a central object (like a plugin) register actions for transient
|
||||||
* providers, that may not be loaded until needed.
|
* providers, that may not be loaded until needed.
|
||||||
*
|
*
|
||||||
* <p>This method may be called multiple times with the same conceptual placeholder--the
|
* <p>This method may be called multiple times with the same conceptual placeholder--the
|
||||||
* placeholder will only be added once.
|
* placeholder will only be added once.
|
||||||
*
|
*
|
||||||
* @param placeholder the placeholder containing information related to the action it represents
|
* @param placeholder the placeholder containing information related to the action it represents
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void registerSharedActionPlaceholder(SharedDockingActionPlaceholder placeholder) {
|
public void registerSharedActionPlaceholder(SharedDockingActionPlaceholder placeholder) {
|
||||||
|
|
||||||
String name = placeholder.getName();
|
String name = placeholder.getName();
|
||||||
KeyStroke defaultKeyStroke = placeholder.getKeyBinding();
|
|
||||||
|
|
||||||
SharedStubKeyBindingAction stub = sharedActionMap.computeIfAbsent(name, key -> {
|
SharedStubKeyBindingAction stub = sharedActionMap.computeIfAbsent(name, key -> {
|
||||||
|
|
||||||
|
ActionTrigger actionTrigger = getActionTrigger(placeholder);
|
||||||
SharedStubKeyBindingAction newStub =
|
SharedStubKeyBindingAction newStub =
|
||||||
new SharedStubKeyBindingAction(name, defaultKeyStroke, keyBindingOptions);
|
new SharedStubKeyBindingAction(name, actionTrigger, options);
|
||||||
registerStub(newStub, defaultKeyStroke);
|
registerStub(newStub, actionTrigger);
|
||||||
return newStub;
|
return newStub;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -511,4 +596,11 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener {
|
||||||
stub.addActionOwner(owner);
|
stub.addActionOwner(owner);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ActionTrigger getActionTrigger(SharedDockingActionPlaceholder placeholder) {
|
||||||
|
KeyStroke defaultKs = placeholder.getKeyBinding();
|
||||||
|
if (defaultKs != null) {
|
||||||
|
return new ActionTrigger(defaultKs);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,10 +21,9 @@ import java.beans.PropertyChangeListener;
|
||||||
|
|
||||||
import javax.swing.JButton;
|
import javax.swing.JButton;
|
||||||
|
|
||||||
import docking.ActionContext;
|
import docking.DockingActionPerformer;
|
||||||
import docking.DockingWindowManager;
|
import docking.DockingWindowManager;
|
||||||
import docking.action.*;
|
import docking.action.*;
|
||||||
import ghidra.util.Swing;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class to manager toolbar buttons.
|
* Class to manager toolbar buttons.
|
||||||
|
@ -113,23 +112,7 @@ public class ToolBarItemManager implements PropertyChangeListener, ActionListene
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void actionPerformed(ActionEvent event) {
|
public void actionPerformed(ActionEvent event) {
|
||||||
DockingWindowManager.clearMouseOverHelp();
|
DockingActionPerformer.perform(toolBarAction, event, getWindowManager());
|
||||||
ActionContext context = getWindowManager().createActionContext(toolBarAction);
|
|
||||||
|
|
||||||
context.setSourceObject(event.getSource());
|
|
||||||
context.setEventClickModifiers(event.getModifiers());
|
|
||||||
|
|
||||||
// this gives the UI some time to repaint before executing the action
|
|
||||||
Swing.runLater(() -> {
|
|
||||||
if (toolBarAction.isValidContext(context) &&
|
|
||||||
toolBarAction.isEnabledForContext(context)) {
|
|
||||||
if (toolBarAction instanceof ToggleDockingActionIf) {
|
|
||||||
ToggleDockingActionIf toggleAction = (ToggleDockingActionIf) toolBarAction;
|
|
||||||
toggleAction.setSelected(!toggleAction.isSelected());
|
|
||||||
}
|
|
||||||
toolBarAction.actionPerformed(context);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private DockingWindowManager getWindowManager() {
|
private DockingWindowManager getWindowManager() {
|
||||||
|
|
|
@ -149,9 +149,7 @@ public abstract class AbstractDockingTest extends AbstractGuiTest {
|
||||||
|
|
||||||
public static Window getWindowByTitleContaining(Window parentWindow, String text) {
|
public static Window getWindowByTitleContaining(Window parentWindow, String text) {
|
||||||
Set<Window> winList = getWindows(parentWindow);
|
Set<Window> winList = getWindows(parentWindow);
|
||||||
Iterator<Window> iter = winList.iterator();
|
for (Window w : winList) {
|
||||||
while (iter.hasNext()) {
|
|
||||||
Window w = iter.next();
|
|
||||||
if (!w.isShowing()) {
|
if (!w.isShowing()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -169,9 +167,7 @@ public abstract class AbstractDockingTest extends AbstractGuiTest {
|
||||||
|
|
||||||
protected static Window getWindowByTitle(Window parentWindow, String title) {
|
protected static Window getWindowByTitle(Window parentWindow, String title) {
|
||||||
Set<Window> winList = getWindows(parentWindow);
|
Set<Window> winList = getWindows(parentWindow);
|
||||||
Iterator<Window> iter = winList.iterator();
|
for (Window w : winList) {
|
||||||
while (iter.hasNext()) {
|
|
||||||
Window w = iter.next();
|
|
||||||
if (!w.isShowing()) {
|
if (!w.isShowing()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -212,9 +208,7 @@ public abstract class AbstractDockingTest extends AbstractGuiTest {
|
||||||
while (totalTime <= timeout) {
|
while (totalTime <= timeout) {
|
||||||
|
|
||||||
Set<Window> winList = getAllWindows();
|
Set<Window> winList = getAllWindows();
|
||||||
Iterator<Window> it = winList.iterator();
|
for (Window w : winList) {
|
||||||
while (it.hasNext()) {
|
|
||||||
Window w = it.next();
|
|
||||||
if (windowClass.isAssignableFrom(w.getClass()) && w.isShowing()) {
|
if (windowClass.isAssignableFrom(w.getClass()) && w.isShowing()) {
|
||||||
return w;
|
return w;
|
||||||
}
|
}
|
||||||
|
@ -499,9 +493,7 @@ public abstract class AbstractDockingTest extends AbstractGuiTest {
|
||||||
while (totalTime <= DEFAULT_WINDOW_TIMEOUT) {
|
while (totalTime <= DEFAULT_WINDOW_TIMEOUT) {
|
||||||
|
|
||||||
Set<Window> winList = getAllWindows();
|
Set<Window> winList = getAllWindows();
|
||||||
Iterator<Window> iter = winList.iterator();
|
for (Window w : winList) {
|
||||||
while (iter.hasNext()) {
|
|
||||||
Window w = iter.next();
|
|
||||||
if ((w instanceof JDialog) && w.isShowing()) {
|
if ((w instanceof JDialog) && w.isShowing()) {
|
||||||
String windowTitle = getTitleForWindow(w);
|
String windowTitle = getTitleForWindow(w);
|
||||||
if (title.equals(windowTitle)) {
|
if (title.equals(windowTitle)) {
|
||||||
|
@ -534,9 +526,7 @@ public abstract class AbstractDockingTest extends AbstractGuiTest {
|
||||||
while (totalTime <= DEFAULT_WAIT_TIMEOUT) {
|
while (totalTime <= DEFAULT_WAIT_TIMEOUT) {
|
||||||
|
|
||||||
Set<Window> winList = getWindows(window);
|
Set<Window> winList = getWindows(window);
|
||||||
Iterator<Window> iter = winList.iterator();
|
for (Window w : winList) {
|
||||||
while (iter.hasNext()) {
|
|
||||||
Window w = iter.next();
|
|
||||||
if ((w instanceof JDialog) && w.isShowing()) {
|
if ((w instanceof JDialog) && w.isShowing()) {
|
||||||
String windowTitle = getTitleForWindow(w);
|
String windowTitle = getTitleForWindow(w);
|
||||||
if (title.equals(windowTitle)) {
|
if (title.equals(windowTitle)) {
|
||||||
|
@ -637,9 +627,7 @@ public abstract class AbstractDockingTest extends AbstractGuiTest {
|
||||||
private static <T extends DialogComponentProvider> T getDialogComponent(Window parentWindow,
|
private static <T extends DialogComponentProvider> T getDialogComponent(Window parentWindow,
|
||||||
Class<T> ghidraClass) {
|
Class<T> ghidraClass) {
|
||||||
Set<Window> winList = getWindows(parentWindow);
|
Set<Window> winList = getWindows(parentWindow);
|
||||||
Iterator<Window> iter = winList.iterator();
|
for (Window w : winList) {
|
||||||
while (iter.hasNext()) {
|
|
||||||
Window w = iter.next();
|
|
||||||
DialogComponentProvider dialogComponentProvider =
|
DialogComponentProvider dialogComponentProvider =
|
||||||
getDialogComponentProvider(w, ghidraClass);
|
getDialogComponentProvider(w, ghidraClass);
|
||||||
if (dialogComponentProvider != null) {
|
if (dialogComponentProvider != null) {
|
||||||
|
@ -953,9 +941,7 @@ public abstract class AbstractDockingTest extends AbstractGuiTest {
|
||||||
// So, just ignore the exception. Client code that *really* wants all windows,
|
// So, just ignore the exception. Client code that *really* wants all windows,
|
||||||
// like that which waits for windows, should be calling this method repeatedly anyway.
|
// like that which waits for windows, should be calling this method repeatedly anyway.
|
||||||
}
|
}
|
||||||
Iterator<Window> iter = dockableWinList.iterator();
|
for (Window w : dockableWinList) {
|
||||||
while (iter.hasNext()) {
|
|
||||||
Window w = iter.next();
|
|
||||||
windowSet.add(w);
|
windowSet.add(w);
|
||||||
findOwnedWindows(w, windowSet);
|
findOwnedWindows(w, windowSet);
|
||||||
}
|
}
|
||||||
|
@ -1123,9 +1109,8 @@ public abstract class AbstractDockingTest extends AbstractGuiTest {
|
||||||
public static Set<DockingActionIf> getActionsByOwnerAndName(Tool tool, String owner,
|
public static Set<DockingActionIf> getActionsByOwnerAndName(Tool tool, String owner,
|
||||||
String name) {
|
String name) {
|
||||||
Set<DockingActionIf> ownerActions = tool.getDockingActionsByOwnerName(owner);
|
Set<DockingActionIf> ownerActions = tool.getDockingActionsByOwnerName(owner);
|
||||||
return ownerActions.stream()
|
return ownerActions.stream().filter(action -> action.getName().equals(name)).collect(
|
||||||
.filter(action -> action.getName().equals(name))
|
Collectors.toSet());
|
||||||
.collect(Collectors.toSet());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -23,8 +23,7 @@ import docking.Tool;
|
||||||
public interface DockingToolConstants {
|
public interface DockingToolConstants {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Name of options for key bindings that map action name to a
|
* Name of options for key bindings that map action name to a key stroke or mouse binding.
|
||||||
* key stroke object.
|
*/
|
||||||
*/
|
|
||||||
public final static String KEY_BINDINGS = "Key Bindings";
|
public final static String KEY_BINDINGS = "Key Bindings";
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,6 +81,14 @@ public class HintTextField extends JTextField {
|
||||||
validateField();
|
validateField();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the hint for this text field
|
||||||
|
* @param hint the hint text
|
||||||
|
*/
|
||||||
|
public void setHint(String hint) {
|
||||||
|
this.hint = hint;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Key listener allows us to check field validity on every key typed
|
* Key listener allows us to check field validity on every key typed
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -32,10 +32,12 @@ import docking.*;
|
||||||
import docking.action.*;
|
import docking.action.*;
|
||||||
import docking.test.AbstractDockingTest;
|
import docking.test.AbstractDockingTest;
|
||||||
import docking.tool.util.DockingToolConstants;
|
import docking.tool.util.DockingToolConstants;
|
||||||
|
import ghidra.framework.options.ActionTrigger;
|
||||||
import ghidra.framework.options.ToolOptions;
|
import ghidra.framework.options.ToolOptions;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
import ghidra.util.SpyErrorLogger;
|
import ghidra.util.SpyErrorLogger;
|
||||||
import ghidra.util.exception.AssertException;
|
import ghidra.util.exception.AssertException;
|
||||||
|
import gui.event.MouseBinding;
|
||||||
|
|
||||||
public class SharedKeyBindingDockingActionTest extends AbstractDockingTest {
|
public class SharedKeyBindingDockingActionTest extends AbstractDockingTest {
|
||||||
|
|
||||||
|
@ -468,7 +470,15 @@ public class SharedKeyBindingDockingActionTest extends AbstractDockingTest {
|
||||||
|
|
||||||
private void setSharedKeyBinding(KeyStroke newKs) {
|
private void setSharedKeyBinding(KeyStroke newKs) {
|
||||||
ToolOptions options = getKeyBindingOptions();
|
ToolOptions options = getKeyBindingOptions();
|
||||||
runSwing(() -> options.setKeyStroke(SHARED_FULL_NAME, newKs));
|
runSwing(() -> {
|
||||||
|
ActionTrigger actionTrigger = options.getActionTrigger(SHARED_FULL_NAME, null);
|
||||||
|
MouseBinding existingMouseBinding = null;
|
||||||
|
if (actionTrigger != null) {
|
||||||
|
existingMouseBinding = actionTrigger.getMouseBinding();
|
||||||
|
}
|
||||||
|
ActionTrigger newTrigger = new ActionTrigger(newKs, existingMouseBinding);
|
||||||
|
options.setActionTrigger(SHARED_FULL_NAME, newTrigger);
|
||||||
|
});
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -496,7 +506,10 @@ public class SharedKeyBindingDockingActionTest extends AbstractDockingTest {
|
||||||
|
|
||||||
public SharedNameAction(String owner, KeyStroke ks) {
|
public SharedNameAction(String owner, KeyStroke ks) {
|
||||||
super(SHARED_NAME, owner, KeyBindingType.SHARED);
|
super(SHARED_NAME, owner, KeyBindingType.SHARED);
|
||||||
setKeyBindingData(new KeyBindingData(ks));
|
|
||||||
|
if (ks != null) {
|
||||||
|
setKeyBindingData(new KeyBindingData(ks));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -32,13 +32,10 @@
|
||||||
<logger name="org.jdom" level="WARN"/>
|
<logger name="org.jdom" level="WARN"/>
|
||||||
|
|
||||||
<logger name="generic.help" level="DEBUG"/>
|
<logger name="generic.help" level="DEBUG"/>
|
||||||
|
<logger name="generic.random" level="WARN"/>
|
||||||
<logger name="generic.watchdog" level="DEBUG" />
|
<logger name="generic.watchdog" level="DEBUG" />
|
||||||
|
|
||||||
<logger name="docking.help" level="DEBUG"/>
|
<logger name="docking" level="DEBUG"/>
|
||||||
<logger name="docking.event.mouse" level="DEBUG" />
|
|
||||||
<logger name="docking.framework" level="DEBUG" />
|
|
||||||
<logger name="docking.widgets.table" level="DEBUG" />
|
|
||||||
<logger name="docking.widgets.filechooser" level="DEBUG" />
|
|
||||||
|
|
||||||
<logger name="ghidra.feature.fid" level="INFO" />
|
<logger name="ghidra.feature.fid" level="INFO" />
|
||||||
<logger name="ghidra.framework" level="DEBUG"/>
|
<logger name="ghidra.framework" level="DEBUG"/>
|
||||||
|
@ -57,8 +54,6 @@
|
||||||
<!-- Ignore warnings about missing content classes in test env -->
|
<!-- Ignore warnings about missing content classes in test env -->
|
||||||
<logger name="ghidra.framework.project.tool.GhidraToolTemplate" level="ERROR"/>
|
<logger name="ghidra.framework.project.tool.GhidraToolTemplate" level="ERROR"/>
|
||||||
|
|
||||||
<logger name="functioncalls" level="DEBUG" />
|
|
||||||
<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"/>
|
||||||
<logger name="ghidra.net" level="WARN"/>
|
<logger name="ghidra.net" level="WARN"/>
|
||||||
<logger name="ghidra.app.plugin.core.misc.RecoverySnapshotMgrPlugin" level="INFO"/>
|
<logger name="ghidra.app.plugin.core.misc.RecoverySnapshotMgrPlugin" level="INFO"/>
|
||||||
|
@ -77,8 +72,10 @@
|
||||||
<logger name="ghidra.app.script" level="INFO" />
|
<logger name="ghidra.app.script" level="INFO" />
|
||||||
<logger name="ghidra.app.util.importer" level="DEBUG" />
|
<logger name="ghidra.app.util.importer" level="DEBUG" />
|
||||||
<logger name="ghidra.app.util.opinion" level="DEBUG" />
|
<logger name="ghidra.app.util.opinion" level="DEBUG" />
|
||||||
<logger name="ghidra.util.classfinder" level="DEBUG" />
|
<logger name="ghidra.util.classfinder" level="DEBUG" />
|
||||||
|
<logger name="ghidra.util.extensions" level="DEBUG" />
|
||||||
<logger name="ghidra.util.task" level="DEBUG" />
|
<logger name="ghidra.util.task" level="DEBUG" />
|
||||||
|
<logger name="functioncalls" level="DEBUG" />
|
||||||
<logger name="org.jungrapht.visualization" level="WARN" />
|
<logger name="org.jungrapht.visualization" level="WARN" />
|
||||||
<logger name="org.jungrapht.visualization.DefaultVisualizationServer" level="DEBUG" />
|
<logger name="org.jungrapht.visualization.DefaultVisualizationServer" level="DEBUG" />
|
||||||
|
|
||||||
|
|
|
@ -30,15 +30,11 @@
|
||||||
<logger name="org.jdom" level="WARN"/>
|
<logger name="org.jdom" level="WARN"/>
|
||||||
|
|
||||||
<logger name="generic.help" level="DEBUG"/>
|
<logger name="generic.help" level="DEBUG"/>
|
||||||
|
<logger name="generic.random" level="WARN"/>
|
||||||
<logger name="generic.watchdog" level="DEBUG" />
|
<logger name="generic.watchdog" level="DEBUG" />
|
||||||
|
|
||||||
<logger name="docking.help" level="DEBUG"/>
|
<logger name="docking" level="DEBUG"/>
|
||||||
<logger name="docking.event.mouse" level="DEBUG" />
|
|
||||||
<logger name="docking.framework" level="DEBUG" />
|
|
||||||
<logger name="docking.framework.SplashScreen" level="TRACE" />
|
|
||||||
<logger name="docking.widgets.table" level="DEBUG" />
|
|
||||||
<logger name="docking.widgets.filechooser" level="DEBUG" />
|
|
||||||
|
|
||||||
<logger name="ghidra.feature.fid" level="INFO" />
|
<logger name="ghidra.feature.fid" level="INFO" />
|
||||||
<logger name="ghidra.framework" level="DEBUG"/>
|
<logger name="ghidra.framework" level="DEBUG"/>
|
||||||
<logger name="ghidra.graph" level="DEBUG" />
|
<logger name="ghidra.graph" level="DEBUG" />
|
||||||
|
@ -55,9 +51,7 @@
|
||||||
|
|
||||||
<!-- Ignore warnings about missing content classes in test env -->
|
<!-- Ignore warnings about missing content classes in test env -->
|
||||||
<logger name="ghidra.framework.project.tool.GhidraToolTemplate" level="ERROR"/>
|
<logger name="ghidra.framework.project.tool.GhidraToolTemplate" level="ERROR"/>
|
||||||
|
|
||||||
<logger name="functioncalls" level="DEBUG" />
|
|
||||||
<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"/>
|
||||||
<logger name="ghidra.net" level="WARN"/>
|
<logger name="ghidra.net" level="WARN"/>
|
||||||
<logger name="ghidra.app.plugin.core.misc.RecoverySnapshotMgrPlugin" level="INFO"/>
|
<logger name="ghidra.app.plugin.core.misc.RecoverySnapshotMgrPlugin" level="INFO"/>
|
||||||
|
@ -78,6 +72,7 @@
|
||||||
<logger name="ghidra.app.util.opinion" level="DEBUG" />
|
<logger name="ghidra.app.util.opinion" level="DEBUG" />
|
||||||
<logger name="ghidra.util.classfinder" level="DEBUG" />
|
<logger name="ghidra.util.classfinder" level="DEBUG" />
|
||||||
<logger name="ghidra.util.task" level="DEBUG" />
|
<logger name="ghidra.util.task" level="DEBUG" />
|
||||||
|
<logger name="functioncalls" level="DEBUG" />
|
||||||
<logger name="org.jungrapht.visualization" level="WARN" />
|
<logger name="org.jungrapht.visualization" level="WARN" />
|
||||||
<logger name="org.jungrapht.visualization.DefaultVisualizationServer" level="DEBUG" />
|
<logger name="org.jungrapht.visualization.DefaultVisualizationServer" level="DEBUG" />
|
||||||
|
|
||||||
|
|
|
@ -49,6 +49,7 @@ public abstract class AbstractOptions implements Options {
|
||||||
set.add(Color.class);
|
set.add(Color.class);
|
||||||
set.add(Font.class);
|
set.add(Font.class);
|
||||||
set.add(KeyStroke.class);
|
set.add(KeyStroke.class);
|
||||||
|
set.add(ActionTrigger.class);
|
||||||
set.add(File.class);
|
set.add(File.class);
|
||||||
set.add(Date.class);
|
set.add(Date.class);
|
||||||
return set;
|
return set;
|
||||||
|
@ -154,6 +155,17 @@ public abstract class AbstractOptions implements Options {
|
||||||
if (type == OptionType.FONT_TYPE) {
|
if (type == OptionType.FONT_TYPE) {
|
||||||
warnShouldUseTheme("font");
|
warnShouldUseTheme("font");
|
||||||
}
|
}
|
||||||
|
if (type == OptionType.KEYSTROKE_TYPE) {
|
||||||
|
type = OptionType.ACTION_TRIGGER;
|
||||||
|
if (defaultValue instanceof KeyStroke) {
|
||||||
|
defaultValue = new ActionTrigger((KeyStroke) defaultValue);
|
||||||
|
}
|
||||||
|
if (editorSupplier != null) {
|
||||||
|
Msg.error(this, "Custom KeyStroke property editors are no longer supported. " +
|
||||||
|
"Use ActionTrigger instead");
|
||||||
|
editorSupplier = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!type.isCompatible(defaultValue)) {
|
if (!type.isCompatible(defaultValue)) {
|
||||||
throw new IllegalStateException(
|
throw new IllegalStateException(
|
||||||
|
@ -187,8 +199,7 @@ public abstract class AbstractOptions implements Options {
|
||||||
}
|
}
|
||||||
|
|
||||||
Option option =
|
Option option =
|
||||||
createRegisteredOption(optionName, type, description, help, defaultValue,
|
createRegisteredOption(optionName, type, description, help, defaultValue, editor);
|
||||||
editor);
|
|
||||||
|
|
||||||
valueMap.put(optionName, option);
|
valueMap.put(optionName, option);
|
||||||
}
|
}
|
||||||
|
@ -235,7 +246,7 @@ public abstract class AbstractOptions implements Options {
|
||||||
|
|
||||||
// There are several cases where an existing option may exist when registering an option
|
// There are several cases where an existing option may exist when registering an option
|
||||||
// 1) the option was accessed before it was registered
|
// 1) the option was accessed before it was registered
|
||||||
// 2) the option was loaded from a store (database or toolstate)
|
// 2) the option was loaded from a store (database or tool state)
|
||||||
// 3) the option was registered more than once.
|
// 3) the option was registered more than once.
|
||||||
//
|
//
|
||||||
// The only time this is a problem is if the exiting option type is not compatible with
|
// The only time this is a problem is if the exiting option type is not compatible with
|
||||||
|
@ -288,13 +299,21 @@ public abstract class AbstractOptions implements Options {
|
||||||
valueMap.put(optionName, option);
|
valueMap.put(optionName, option);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (type != OptionType.NO_TYPE && type != option.getOptionType()) {
|
|
||||||
throw new IllegalStateException(
|
validateOptionType(option, type);
|
||||||
"Expected option type: " + type + ", but was type: " + option.getOptionType());
|
|
||||||
}
|
|
||||||
return option;
|
return option;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void validateOptionType(Option option, OptionType type) {
|
||||||
|
|
||||||
|
if (type == option.getOptionType() || type == OptionType.NO_TYPE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"Expected option type: " + type + ", but was type: " + option.getOptionType());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void putObject(String optionName, Object newValue) {
|
public void putObject(String optionName, Object newValue) {
|
||||||
if (newValue == null) {
|
if (newValue == null) {
|
||||||
|
@ -503,9 +522,30 @@ public abstract class AbstractOptions implements Options {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public KeyStroke getKeyStroke(String optionName, KeyStroke defaultValue) {
|
public KeyStroke getKeyStroke(String optionName, KeyStroke defaultValue) {
|
||||||
Option option = getOption(optionName, OptionType.KEYSTROKE_TYPE, defaultValue);
|
|
||||||
|
ActionTrigger defaultTrigger = null;
|
||||||
|
if (defaultValue != null) {
|
||||||
|
defaultTrigger = new ActionTrigger(defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
Option option = getOption(optionName, OptionType.ACTION_TRIGGER, defaultTrigger);
|
||||||
try {
|
try {
|
||||||
return (KeyStroke) option.getValue(defaultValue);
|
ActionTrigger actionTrigger = (ActionTrigger) option.getValue(defaultTrigger);
|
||||||
|
if (actionTrigger != null) {
|
||||||
|
return actionTrigger.getKeyStroke();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
catch (ClassCastException e) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ActionTrigger getActionTrigger(String optionName, ActionTrigger defaultValue) {
|
||||||
|
Option option = getOption(optionName, OptionType.ACTION_TRIGGER, defaultValue);
|
||||||
|
try {
|
||||||
|
return (ActionTrigger) option.getValue(defaultValue);
|
||||||
}
|
}
|
||||||
catch (ClassCastException e) {
|
catch (ClassCastException e) {
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
|
@ -592,7 +632,16 @@ public abstract class AbstractOptions implements Options {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setKeyStroke(String optionName, KeyStroke value) {
|
public void setKeyStroke(String optionName, KeyStroke value) {
|
||||||
putObject(optionName, value, OptionType.KEYSTROKE_TYPE);
|
ActionTrigger actionTrigger = null;
|
||||||
|
if (value != null) {
|
||||||
|
actionTrigger = new ActionTrigger(value);
|
||||||
|
}
|
||||||
|
setActionTrigger(optionName, actionTrigger);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setActionTrigger(String optionName, ActionTrigger value) {
|
||||||
|
putObject(optionName, value, OptionType.ACTION_TRIGGER);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -0,0 +1,208 @@
|
||||||
|
/* ###
|
||||||
|
* IP: GHIDRA
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package ghidra.framework.options;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import javax.swing.KeyStroke;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import gui.event.MouseBinding;
|
||||||
|
import util.CollectionUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a way to trigger an action in the system. A trigger is based on a key stroke, a mouse
|
||||||
|
* binding or both.
|
||||||
|
*/
|
||||||
|
public class ActionTrigger {
|
||||||
|
|
||||||
|
private final static Pattern TO_STRING_PATTERN =
|
||||||
|
Pattern.compile(".*Key Stroke\\[(.*)\\].*Mouse Binding\\[(.*)\\]");
|
||||||
|
|
||||||
|
private final static String KEY_STROKE = "KeyStroke";
|
||||||
|
private final static String MOUSE_BINDING = "MouseBinding";
|
||||||
|
|
||||||
|
private KeyStroke keyStroke;
|
||||||
|
private MouseBinding mouseBinding;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an action trigger with the given key stroke.
|
||||||
|
* @param keyStroke the key stroke
|
||||||
|
*/
|
||||||
|
public ActionTrigger(KeyStroke keyStroke) {
|
||||||
|
this(keyStroke, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an action trigger with the given mouse binding.
|
||||||
|
* @param mouseBinding the mouse binding
|
||||||
|
*/
|
||||||
|
public ActionTrigger(MouseBinding mouseBinding) {
|
||||||
|
this(null, mouseBinding);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A convenience constructor for creating an action trigger with either or both values set. At
|
||||||
|
* least one of the values must be non-null.
|
||||||
|
*
|
||||||
|
* @param keyStroke the key stroke; may be null
|
||||||
|
* @param mouseBinding the mouse binding; may be null
|
||||||
|
*/
|
||||||
|
public ActionTrigger(KeyStroke keyStroke, MouseBinding mouseBinding) {
|
||||||
|
if (CollectionUtils.isAllNull(keyStroke, mouseBinding)) {
|
||||||
|
throw new NullPointerException("Both the key stroke and mouse bindng cannot be null");
|
||||||
|
}
|
||||||
|
this.keyStroke = keyStroke;
|
||||||
|
this.mouseBinding = mouseBinding;
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeyStroke getKeyStroke() {
|
||||||
|
return keyStroke;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MouseBinding getMouseBinding() {
|
||||||
|
return mouseBinding;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder buffy = new StringBuilder("ActionTrigger: ");
|
||||||
|
|
||||||
|
buffy.append("Key Stroke[");
|
||||||
|
if (keyStroke != null) {
|
||||||
|
buffy.append(keyStroke.toString());
|
||||||
|
}
|
||||||
|
buffy.append("], Mouse Binding[");
|
||||||
|
|
||||||
|
if (mouseBinding != null) {
|
||||||
|
buffy.append(mouseBinding.toString());
|
||||||
|
}
|
||||||
|
buffy.append(']');
|
||||||
|
|
||||||
|
return buffy.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new action trigger from the given string. The string is expected to be the result
|
||||||
|
* of calling {@link #toString()} on an instance of this class.
|
||||||
|
*
|
||||||
|
* @param string the string to parse.
|
||||||
|
* @return the new instance or null of the string is invalid.
|
||||||
|
*/
|
||||||
|
public static ActionTrigger getActionTrigger(String string) {
|
||||||
|
|
||||||
|
Matcher matcher = TO_STRING_PATTERN.matcher(string);
|
||||||
|
if (!matcher.matches()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String ksString = matcher.group(1);
|
||||||
|
String mbString = matcher.group(2);
|
||||||
|
|
||||||
|
KeyStroke ks = null;
|
||||||
|
if (!StringUtils.isBlank(ksString)) {
|
||||||
|
ks = KeyStroke.getKeyStroke(ksString);
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseBinding mb = null;
|
||||||
|
if (!StringUtils.isBlank(mbString)) {
|
||||||
|
mb = MouseBinding.getMouseBinding(mbString);
|
||||||
|
}
|
||||||
|
|
||||||
|
return create(ks, mb);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes this action trigger's data into the given save state.
|
||||||
|
* @param saveState the save state
|
||||||
|
*/
|
||||||
|
public void writeState(SaveState saveState) {
|
||||||
|
|
||||||
|
String ksString = "";
|
||||||
|
if (keyStroke != null) {
|
||||||
|
ksString = keyStroke.toString();
|
||||||
|
}
|
||||||
|
saveState.putString(KEY_STROKE, ksString);
|
||||||
|
|
||||||
|
String mbString = "";
|
||||||
|
if (mouseBinding != null) {
|
||||||
|
mbString = mouseBinding.toString();
|
||||||
|
}
|
||||||
|
saveState.putString(MOUSE_BINDING, mbString);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new action trigger by reading data from the given save state.
|
||||||
|
* @param saveState the save state
|
||||||
|
* @return the new action trigger
|
||||||
|
*/
|
||||||
|
public static ActionTrigger create(SaveState saveState) {
|
||||||
|
|
||||||
|
KeyStroke ks = null;
|
||||||
|
String value = saveState.getString(KEY_STROKE, null);
|
||||||
|
if (!StringUtils.isBlank(value)) {
|
||||||
|
ks = KeyStroke.getKeyStroke(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseBinding mb = null;
|
||||||
|
value = saveState.getString(MOUSE_BINDING, null);
|
||||||
|
if (value != null) {
|
||||||
|
mb = MouseBinding.getMouseBinding(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return create(ks, mb);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ActionTrigger create(KeyStroke ks, MouseBinding mb) {
|
||||||
|
if (ks == null && mb == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new ActionTrigger(ks, mb);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
final int prime = 31;
|
||||||
|
int result = 1;
|
||||||
|
result = prime * result + ((keyStroke == null) ? 0 : keyStroke.hashCode());
|
||||||
|
result = prime * result + ((mouseBinding == null) ? 0 : mouseBinding.hashCode());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (obj == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (getClass() != obj.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ActionTrigger other = (ActionTrigger) obj;
|
||||||
|
if (!Objects.equals(keyStroke, other.keyStroke)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Objects.equals(mouseBinding, other.mouseBinding);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -19,6 +19,8 @@ import java.beans.PropertyEditor;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import javax.swing.KeyStroke;
|
||||||
|
|
||||||
import org.apache.commons.io.FilenameUtils;
|
import org.apache.commons.io.FilenameUtils;
|
||||||
|
|
||||||
import ghidra.util.HelpLocation;
|
import ghidra.util.HelpLocation;
|
||||||
|
@ -104,6 +106,15 @@ public class FileOptions extends AbstractOptions {
|
||||||
@Override
|
@Override
|
||||||
protected Option createUnregisteredOption(String optionName, OptionType type,
|
protected Option createUnregisteredOption(String optionName, OptionType type,
|
||||||
Object defaultValue) {
|
Object defaultValue) {
|
||||||
|
|
||||||
|
if (type == OptionType.KEYSTROKE_TYPE) {
|
||||||
|
// convert key strokes to action triggers
|
||||||
|
type = OptionType.ACTION_TRIGGER;
|
||||||
|
if (defaultValue instanceof KeyStroke keyStroke) {
|
||||||
|
defaultValue = new ActionTrigger(keyStroke);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return new FileOption(optionName, type, null, null, defaultValue, false, null);
|
return new FileOption(optionName, type, null, null, defaultValue, false, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,8 @@ public enum OptionType {
|
||||||
FILE_TYPE(File.class, new FileStringAdapter()),
|
FILE_TYPE(File.class, new FileStringAdapter()),
|
||||||
COLOR_TYPE(Color.class, new ColorStringAdapter()),
|
COLOR_TYPE(Color.class, new ColorStringAdapter()),
|
||||||
FONT_TYPE(Font.class, new FontStringAdapter()),
|
FONT_TYPE(Font.class, new FontStringAdapter()),
|
||||||
KEYSTROKE_TYPE(KeyStroke.class, new KeyStrokeStringAdapter());
|
KEYSTROKE_TYPE(KeyStroke.class, new KeyStrokeStringAdapter()),
|
||||||
|
ACTION_TRIGGER(ActionTrigger.class, new ActionTriggerStringAdapter());
|
||||||
|
|
||||||
private Class<?> clazz;
|
private Class<?> clazz;
|
||||||
private StringAdapter stringAdapter;
|
private StringAdapter stringAdapter;
|
||||||
|
@ -241,8 +242,7 @@ public enum OptionType {
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
Msg.error(this,
|
Msg.error(this,
|
||||||
"Can't create customOption instance for: " + customOptionClassName +
|
"Can't create customOption instance for: " + customOptionClassName + e);
|
||||||
e);
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -331,4 +331,11 @@ public enum OptionType {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static class ActionTriggerStringAdapter extends StringAdapter {
|
||||||
|
@Override
|
||||||
|
Object stringToObject(String string) {
|
||||||
|
return ActionTrigger.getActionTrigger(string);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -116,7 +116,7 @@ public interface Options {
|
||||||
* Note, this method should not be used for
|
* Note, this method should not be used for
|
||||||
* colors and font as doing so will result in those colors and fonts becoming disconnected
|
* colors and font as doing so will result in those colors and fonts becoming disconnected
|
||||||
* to the current theme. Instead use
|
* to the current theme. Instead use
|
||||||
*
|
*
|
||||||
* {@link #registerThemeColorBinding(String, String, HelpLocation, String)} or
|
* {@link #registerThemeColorBinding(String, String, HelpLocation, String)} or
|
||||||
* {@link #registerThemeFontBinding(String, String, HelpLocation, String)}.
|
* {@link #registerThemeFontBinding(String, String, HelpLocation, String)}.
|
||||||
* @param optionName the name of the option being registered.
|
* @param optionName the name of the option being registered.
|
||||||
|
@ -139,7 +139,7 @@ public interface Options {
|
||||||
* to the current theme. Instead use
|
* to the current theme. Instead use
|
||||||
* {@link #registerThemeColorBinding(String, String, HelpLocation, String)} or
|
* {@link #registerThemeColorBinding(String, String, HelpLocation, String)} or
|
||||||
* {@link #registerThemeFontBinding(String, String, HelpLocation, String)}.
|
* {@link #registerThemeFontBinding(String, String, HelpLocation, String)}.
|
||||||
*
|
*
|
||||||
* @param optionName the name of the option being registered.
|
* @param optionName the name of the option being registered.
|
||||||
* @param type the OptionType for this options.
|
* @param type the OptionType for this options.
|
||||||
* @param defaultValue the defaultValue for the option. In this version of the method, the default
|
* @param defaultValue the defaultValue for the option. In this version of the method, the default
|
||||||
|
@ -166,7 +166,7 @@ public interface Options {
|
||||||
* may be thrown. This API will not use the supplier when in headless mode, this avoiding the
|
* may be thrown. This API will not use the supplier when in headless mode, this avoiding the
|
||||||
* creation of GUI components. For this to work correctly, clients using custom property
|
* creation of GUI components. For this to work correctly, clients using custom property
|
||||||
* editors must defer construction of the editor until the supplier is called.
|
* editors must defer construction of the editor until the supplier is called.
|
||||||
*
|
*
|
||||||
* @param optionName the name of the option being registered.
|
* @param optionName the name of the option being registered.
|
||||||
* @param type the OptionType for this options.
|
* @param type the OptionType for this options.
|
||||||
* @param defaultValue the defaultValue for the option. In this version of the method, the default
|
* @param defaultValue the defaultValue for the option. In this version of the method, the default
|
||||||
|
@ -392,16 +392,27 @@ public interface Options {
|
||||||
public Font getFont(String optionName, Font defaultValue);
|
public Font getFont(String optionName, Font defaultValue);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the KeyStrokg for the given action name.
|
* Get the KeyStroke for the given action name.
|
||||||
* @param optionName the option name
|
* @param optionName the option name
|
||||||
* @param defaultValue value that is stored and returned if there is no
|
* @param defaultValue value that is stored and returned if there is no
|
||||||
* option with the given name
|
* option with the given name
|
||||||
* @return KeyStroke option
|
* @return KeyStroke option
|
||||||
* @throws IllegalArgumentException is a option exists with the given
|
* @throws IllegalArgumentException is a option exists with the given
|
||||||
* name but it is not a KeyStroke
|
* name but it is not a KeyStroke
|
||||||
|
* @deprecated use {@link #getActionTrigger(String, ActionTrigger)} instead
|
||||||
*/
|
*/
|
||||||
|
@Deprecated(since = "11.1", forRemoval = true)
|
||||||
public KeyStroke getKeyStroke(String optionName, KeyStroke defaultValue);
|
public KeyStroke getKeyStroke(String optionName, KeyStroke defaultValue);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the {@link ActionTrigger} for the given full action name.
|
||||||
|
* @param optionName the action name
|
||||||
|
* @param defaultValue value that is stored and returned if there is no
|
||||||
|
* option with the given name
|
||||||
|
* @return the action trigger
|
||||||
|
*/
|
||||||
|
public ActionTrigger getActionTrigger(String optionName, ActionTrigger defaultValue);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the string value for the given option name.
|
* Get the string value for the given option name.
|
||||||
* @param optionName option name
|
* @param optionName option name
|
||||||
|
@ -507,9 +518,20 @@ public interface Options {
|
||||||
* @param value KeyStroke to set
|
* @param value KeyStroke to set
|
||||||
* @throws IllegalArgumentException if a option with the given
|
* @throws IllegalArgumentException if a option with the given
|
||||||
* name already exists, but it is not a KeyStroke
|
* name already exists, but it is not a KeyStroke
|
||||||
|
* @deprecated use {@link #setActionTrigger(String, ActionTrigger)} instead
|
||||||
*/
|
*/
|
||||||
|
@Deprecated(since = "11.1", forRemoval = true)
|
||||||
public void setKeyStroke(String optionName, KeyStroke value);
|
public void setKeyStroke(String optionName, KeyStroke value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the action trigger value for the option
|
||||||
|
* @param optionName name of the option
|
||||||
|
* @param value action trigger to set
|
||||||
|
* @throws IllegalArgumentException if a option with the given
|
||||||
|
* name already exists, but it is not an action trigger
|
||||||
|
*/
|
||||||
|
public void setActionTrigger(String optionName, ActionTrigger value);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the String value for the option.
|
* Set the String value for the option.
|
||||||
* @param optionName name of the option
|
* @param optionName name of the option
|
||||||
|
@ -570,14 +592,14 @@ public interface Options {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Restores <b>all</b> options contained herein to their default values.
|
* Restores <b>all</b> options contained herein to their default values.
|
||||||
*
|
*
|
||||||
* @see #restoreDefaultValue(String)
|
* @see #restoreDefaultValue(String)
|
||||||
*/
|
*/
|
||||||
public void restoreDefaultValues();
|
public void restoreDefaultValues();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Restores the option denoted by the given name to its default value.
|
* Restores the option denoted by the given name to its default value.
|
||||||
*
|
*
|
||||||
* @param optionName The name of the option to restore
|
* @param optionName The name of the option to restore
|
||||||
* @see #restoreDefaultValues()
|
* @see #restoreDefaultValues()
|
||||||
*/
|
*/
|
||||||
|
@ -585,11 +607,11 @@ public interface Options {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a Options object that is a sub-options of this options.
|
* Returns a Options object that is a sub-options of this options.
|
||||||
*
|
*
|
||||||
* <p>Note: the option path can have {@link Options#DELIMITER} characters which will be
|
* <p>Note: the option path can have {@link Options#DELIMITER} characters which will be
|
||||||
* used to create a hierarchy with each element in the path resulting in sub-option of the
|
* used to create a hierarchy with each element in the path resulting in sub-option of the
|
||||||
* previous path element.
|
* previous path element.
|
||||||
*
|
*
|
||||||
* @param path the path for the sub-options object
|
* @param path the path for the sub-options object
|
||||||
* @return an Options object that is a sub-options of this options
|
* @return an Options object that is a sub-options of this options
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -64,8 +64,8 @@ public class SubOptions implements Options {
|
||||||
Set<String> childCategories = AbstractOptions.getChildCategories(optionPaths);
|
Set<String> childCategories = AbstractOptions.getChildCategories(optionPaths);
|
||||||
List<Options> childOptions = new ArrayList<>(childCategories.size());
|
List<Options> childOptions = new ArrayList<>(childCategories.size());
|
||||||
for (String categoryName : childCategories) {
|
for (String categoryName : childCategories) {
|
||||||
childOptions.add(new SubOptions(options, categoryName, prefix + categoryName +
|
childOptions.add(
|
||||||
DELIMITER));
|
new SubOptions(options, categoryName, prefix + categoryName + DELIMITER));
|
||||||
}
|
}
|
||||||
return childOptions;
|
return childOptions;
|
||||||
}
|
}
|
||||||
|
@ -170,6 +170,11 @@ public class SubOptions implements Options {
|
||||||
return options.getKeyStroke(prefix + optionName, defaultValue);
|
return options.getKeyStroke(prefix + optionName, defaultValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ActionTrigger getActionTrigger(String optionName, ActionTrigger defaultValue) {
|
||||||
|
return options.getActionTrigger(prefix + optionName, defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getString(String optionName, String defaultValue) {
|
public String getString(String optionName, String defaultValue) {
|
||||||
return options.getString(prefix + optionName, defaultValue);
|
return options.getString(prefix + optionName, defaultValue);
|
||||||
|
@ -235,6 +240,11 @@ public class SubOptions implements Options {
|
||||||
options.setKeyStroke(prefix + optionName, value);
|
options.setKeyStroke(prefix + optionName, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setActionTrigger(String optionName, ActionTrigger value) {
|
||||||
|
options.setActionTrigger(prefix + optionName, value);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setString(String optionName, String value) {
|
public void setString(String optionName, String value) {
|
||||||
options.setString(prefix + optionName, value);
|
options.setString(prefix + optionName, value);
|
||||||
|
|
|
@ -128,6 +128,12 @@ public class ToolOptions extends AbstractOptions {
|
||||||
Class<?> c = Class.forName(element.getAttributeValue(CLASS_ATTRIBUTE));
|
Class<?> c = Class.forName(element.getAttributeValue(CLASS_ATTRIBUTE));
|
||||||
Constructor<?> constructor = c.getDeclaredConstructor();
|
Constructor<?> constructor = c.getDeclaredConstructor();
|
||||||
WrappedOption wo = (WrappedOption) constructor.newInstance();
|
WrappedOption wo = (WrappedOption) constructor.newInstance();
|
||||||
|
wo.readState(new SaveState(element));
|
||||||
|
|
||||||
|
if (wo instanceof WrappedKeyStroke wrappedKs) {
|
||||||
|
wo = wrappedKs.toWrappedActionTrigger();
|
||||||
|
}
|
||||||
|
|
||||||
Option option = createUnregisteredOption(optionName, wo.getOptionType(), null);
|
Option option = createUnregisteredOption(optionName, wo.getOptionType(), null);
|
||||||
valueMap.put(optionName, option);
|
valueMap.put(optionName, option);
|
||||||
|
|
||||||
|
@ -138,7 +144,6 @@ public class ToolOptions extends AbstractOptions {
|
||||||
option.doSetCurrentValue(null); // use doSet so that it is not registered
|
option.doSetCurrentValue(null); // use doSet so that it is not registered
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
wo.readState(new SaveState(element));
|
|
||||||
option.doSetCurrentValue(wo.getObject()); // use doSet so that it is not registered
|
option.doSetCurrentValue(wo.getObject()); // use doSet so that it is not registered
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -256,6 +261,9 @@ public class ToolOptions extends AbstractOptions {
|
||||||
if (value instanceof KeyStroke) {
|
if (value instanceof KeyStroke) {
|
||||||
return new WrappedKeyStroke((KeyStroke) value);
|
return new WrappedKeyStroke((KeyStroke) value);
|
||||||
}
|
}
|
||||||
|
if (value instanceof ActionTrigger) {
|
||||||
|
return new WrappedActionTrigger((ActionTrigger) value);
|
||||||
|
}
|
||||||
if (value instanceof File) {
|
if (value instanceof File) {
|
||||||
return new WrappedFile((File) value);
|
return new WrappedFile((File) value);
|
||||||
}
|
}
|
||||||
|
@ -415,6 +423,15 @@ public class ToolOptions extends AbstractOptions {
|
||||||
@Override
|
@Override
|
||||||
protected Option createUnregisteredOption(String optionName, OptionType type,
|
protected Option createUnregisteredOption(String optionName, OptionType type,
|
||||||
Object defaultValue) {
|
Object defaultValue) {
|
||||||
|
|
||||||
|
if (type == OptionType.KEYSTROKE_TYPE) {
|
||||||
|
// convert key strokes to action triggers
|
||||||
|
type = OptionType.ACTION_TRIGGER;
|
||||||
|
if (defaultValue instanceof KeyStroke keyStroke) {
|
||||||
|
defaultValue = new ActionTrigger(keyStroke);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return new ToolOption(optionName, type, null, null, defaultValue, false, null);
|
return new ToolOption(optionName, type, null, null, defaultValue, false, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
/* ###
|
||||||
|
* IP: GHIDRA
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package ghidra.framework.options;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public class WrappedActionTrigger implements WrappedOption {
|
||||||
|
|
||||||
|
private ActionTrigger actionTrigger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default constructor
|
||||||
|
*/
|
||||||
|
WrappedActionTrigger() {
|
||||||
|
// for reflection
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a wrapper object using the given ActionTrigger.
|
||||||
|
* @param actionTrigger the action trigger
|
||||||
|
*/
|
||||||
|
WrappedActionTrigger(ActionTrigger actionTrigger) {
|
||||||
|
this.actionTrigger = actionTrigger;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getObject() {
|
||||||
|
return actionTrigger;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void readState(SaveState saveState) {
|
||||||
|
actionTrigger = ActionTrigger.create(saveState);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeState(SaveState saveState) {
|
||||||
|
if (actionTrigger == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
actionTrigger.writeState(saveState);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OptionType getOptionType() {
|
||||||
|
return OptionType.ACTION_TRIGGER;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return Objects.toString(actionTrigger);
|
||||||
|
}
|
||||||
|
}
|
|
@ -38,6 +38,7 @@ class WrappedKeyStroke implements WrappedOption {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a wrapper object using the given KeyStroke.
|
* Construct a wrapper object using the given KeyStroke.
|
||||||
|
* @param ks the keystroke
|
||||||
*/
|
*/
|
||||||
WrappedKeyStroke(KeyStroke ks) {
|
WrappedKeyStroke(KeyStroke ks) {
|
||||||
this.keyStroke = ks;
|
this.keyStroke = ks;
|
||||||
|
@ -48,31 +49,15 @@ class WrappedKeyStroke implements WrappedOption {
|
||||||
return keyStroke;
|
return keyStroke;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Read the components for a Key Stroke from the given
|
|
||||||
* SaveState object to restore this WrappedKeyStroke.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void readState(SaveState saveState) {
|
public void readState(SaveState saveState) {
|
||||||
if (saveState.hasValue(KEY_CODE)) {
|
if (saveState.hasValue(KEY_CODE)) {
|
||||||
int keyCode = saveState.getInt(KEY_CODE, 0);
|
int keyCode = saveState.getInt(KEY_CODE, 0);
|
||||||
int modifiers = saveState.getInt(MODIFIERS, 0);
|
int modifiers = saveState.getInt(MODIFIERS, 0);
|
||||||
String version = System.getProperty("java.version");
|
|
||||||
if (version.startsWith("1.4")) {
|
|
||||||
modifiers &= 0x0f;
|
|
||||||
modifiers |= modifiers << 6;
|
|
||||||
}
|
|
||||||
else if (version.startsWith("1.3")) {
|
|
||||||
modifiers &= 0x0f;
|
|
||||||
}
|
|
||||||
keyStroke = KeyStroke.getKeyStroke(keyCode, modifiers);
|
keyStroke = KeyStroke.getKeyStroke(keyCode, modifiers);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Write the components for the wrapped Key Stroke to the given
|
|
||||||
* SaveState object.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void writeState(SaveState saveState) {
|
public void writeState(SaveState saveState) {
|
||||||
if (keyStroke == null) {
|
if (keyStroke == null) {
|
||||||
|
@ -91,4 +76,17 @@ class WrappedKeyStroke implements WrappedOption {
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return Objects.toString(keyStroke);
|
return Objects.toString(keyStroke);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A method to allow for converting the deprecated options key stroke usage to the new action
|
||||||
|
* trigger usage
|
||||||
|
* @return a WrappedActionTrigger
|
||||||
|
*/
|
||||||
|
public WrappedActionTrigger toWrappedActionTrigger() {
|
||||||
|
ActionTrigger trigger = null;
|
||||||
|
if (keyStroke != null) {
|
||||||
|
trigger = new ActionTrigger(keyStroke);
|
||||||
|
}
|
||||||
|
return new WrappedActionTrigger(trigger);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
238
Ghidra/Framework/Gui/src/main/java/gui/event/MouseBinding.java
Normal file
238
Ghidra/Framework/Gui/src/main/java/gui/event/MouseBinding.java
Normal file
|
@ -0,0 +1,238 @@
|
||||||
|
/* ###
|
||||||
|
* 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 gui.event;
|
||||||
|
|
||||||
|
import static org.apache.commons.lang3.StringUtils.*;
|
||||||
|
|
||||||
|
import java.awt.event.InputEvent;
|
||||||
|
import java.awt.event.MouseEvent;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import ghidra.util.Msg;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple class that represents a mouse button and any modifiers needed to bind an action to a
|
||||||
|
* mouse input event.
|
||||||
|
* <P>
|
||||||
|
* The modifiers used by this class will include the button down mask for the given button. This
|
||||||
|
* is done to match how {@link MouseEvent} uses its modifiers.
|
||||||
|
*/
|
||||||
|
public class MouseBinding {
|
||||||
|
|
||||||
|
private static final Pattern BUTTON_PATTERN =
|
||||||
|
Pattern.compile("button(\\d+)", Pattern.CASE_INSENSITIVE);
|
||||||
|
|
||||||
|
private static final String SHIFT = "Shift";
|
||||||
|
private static final String CTRL = "Ctrl";
|
||||||
|
private static final String ALT = "Alt";
|
||||||
|
private static final String META = "Meta";
|
||||||
|
|
||||||
|
private int modifiers = -1;
|
||||||
|
private int button = -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a binding with the given button number of the desired mouse button (e.g., 1, 2,...)
|
||||||
|
* @param button the button number
|
||||||
|
*/
|
||||||
|
public MouseBinding(int button) {
|
||||||
|
this(button, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a binding with the given button number of the desired mouse button (e.g., 1, 2,...)
|
||||||
|
* as well as any desired modifiers (e.g., {@link InputEvent#SHIFT_DOWN_MASK}).
|
||||||
|
* @param button the button number
|
||||||
|
* @param modifiers the event modifiers
|
||||||
|
*/
|
||||||
|
public MouseBinding(int button, int modifiers) {
|
||||||
|
this.button = button;
|
||||||
|
|
||||||
|
// The button down mask is applied to the mouse event modifiers by Java. Thus, for us to
|
||||||
|
// match the mouse event modifiers, we need to add the button down mask here.
|
||||||
|
this.modifiers = InputEvent.getMaskForButton(button);
|
||||||
|
|
||||||
|
if (modifiers > 0) {
|
||||||
|
this.modifiers |= modifiers;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The button used by this class
|
||||||
|
* @return the button used by this class
|
||||||
|
*/
|
||||||
|
public int getButton() {
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The modifiers used by this class
|
||||||
|
* @return the modifiers used by this class
|
||||||
|
*/
|
||||||
|
public int getModifiers() {
|
||||||
|
return modifiers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A user-friendly display string for this class
|
||||||
|
* @return a user-friendly display string for this class
|
||||||
|
*/
|
||||||
|
public String getDisplayText() {
|
||||||
|
String modifiersText = InputEvent.getModifiersExText(modifiers);
|
||||||
|
if (StringUtils.isBlank(modifiersText)) {
|
||||||
|
// not sure if this can happen, since we add the button number to the modifiers
|
||||||
|
return "Button" + button;
|
||||||
|
}
|
||||||
|
return modifiersText;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a mouse binding for the given event
|
||||||
|
* @param e the event
|
||||||
|
* @return the mouse binding
|
||||||
|
*/
|
||||||
|
public static MouseBinding getMouseBinding(MouseEvent e) {
|
||||||
|
return new MouseBinding(e.getButton(), e.getModifiersEx());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a mouse binding from the given string. The string is expected to be of the form:
|
||||||
|
* {@code Ctrl+Button1}, which is the form of the text generated by {@link #getDisplayText()}.
|
||||||
|
*
|
||||||
|
* @param mouseString the mouse string
|
||||||
|
* @return the mouse binding or null if an invalid string was given
|
||||||
|
*/
|
||||||
|
public static MouseBinding getMouseBinding(String mouseString) {
|
||||||
|
|
||||||
|
int button = getButton(mouseString);
|
||||||
|
if (button == -1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// be flexible on the tokens for splitting, even though '+' seems to be the standard
|
||||||
|
StringTokenizer tokenizer = new StringTokenizer(mouseString, "- +");
|
||||||
|
List<String> pieces = new ArrayList<>();
|
||||||
|
while (tokenizer.hasMoreTokens()) {
|
||||||
|
String token = tokenizer.nextToken();
|
||||||
|
if (!pieces.contains(token)) {
|
||||||
|
pieces.add(token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int modifiers = 0;
|
||||||
|
for (Iterator<String> iterator = pieces.iterator(); iterator.hasNext();) {
|
||||||
|
String piece = iterator.next();
|
||||||
|
if (indexOfIgnoreCase(piece, SHIFT) != -1) {
|
||||||
|
modifiers |= InputEvent.SHIFT_DOWN_MASK;
|
||||||
|
iterator.remove();
|
||||||
|
}
|
||||||
|
else if (indexOfIgnoreCase(piece, CTRL) != -1) {
|
||||||
|
modifiers |= InputEvent.CTRL_DOWN_MASK;
|
||||||
|
iterator.remove();
|
||||||
|
}
|
||||||
|
else if (indexOfIgnoreCase(piece, ALT) != -1) {
|
||||||
|
modifiers |= InputEvent.ALT_DOWN_MASK;
|
||||||
|
iterator.remove();
|
||||||
|
}
|
||||||
|
else if (indexOfIgnoreCase(piece, META) != -1) {
|
||||||
|
modifiers |= InputEvent.META_DOWN_MASK;
|
||||||
|
iterator.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new MouseBinding(button, modifiers);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getButton(String mouseString) {
|
||||||
|
|
||||||
|
Matcher buttonMatcher = BUTTON_PATTERN.matcher(mouseString);
|
||||||
|
if (buttonMatcher.find()) {
|
||||||
|
String numberString = buttonMatcher.group(1);
|
||||||
|
try {
|
||||||
|
int intValue = Integer.parseInt(numberString);
|
||||||
|
if (intValue > 0) {
|
||||||
|
return intValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (NumberFormatException e) {
|
||||||
|
Msg.error(MouseBinding.class, "Unable to parse button number %s in text %s"
|
||||||
|
.formatted(numberString, mouseString));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the given mouse event is the mouse released event for the mouse button used
|
||||||
|
* by this class. This method will ignore modifier text, since modifiers can be pressed and
|
||||||
|
* released independent of the mouse button's release.
|
||||||
|
*
|
||||||
|
* @param e the event
|
||||||
|
* @return true if the given mouse event is the mouse released event for the mouse button used
|
||||||
|
* by this class
|
||||||
|
*/
|
||||||
|
public boolean isMatchingRelease(MouseEvent e) {
|
||||||
|
|
||||||
|
int otherButton = e.getButton();
|
||||||
|
if (button != otherButton) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int id = e.getID();
|
||||||
|
if (id == MouseEvent.MOUSE_RELEASED || id == MouseEvent.MOUSE_CLICKED) {
|
||||||
|
// not sure if released and clicked are sent for every OS / mouse combo
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return getDisplayText();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(button, modifiers);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (obj == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (getClass() != obj.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseBinding other = (MouseBinding) obj;
|
||||||
|
if (button != other.button) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (modifiers != other.modifiers) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,152 @@
|
||||||
|
/* ###
|
||||||
|
* 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 gui.event;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import java.awt.event.InputEvent;
|
||||||
|
import java.awt.event.MouseEvent;
|
||||||
|
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class MouseBindingTest {
|
||||||
|
|
||||||
|
private static final int CTRL = InputEvent.CTRL_DOWN_MASK;
|
||||||
|
private static final int SHIFT = InputEvent.SHIFT_DOWN_MASK;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testConstructor_InvalidButton() {
|
||||||
|
|
||||||
|
try {
|
||||||
|
new MouseBinding(0);
|
||||||
|
fail();
|
||||||
|
}
|
||||||
|
catch (IllegalArgumentException e) {
|
||||||
|
// expected
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
new MouseBinding(-1);
|
||||||
|
fail();
|
||||||
|
}
|
||||||
|
catch (IllegalArgumentException e) {
|
||||||
|
// expected
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetMouseBinding_BadButton() {
|
||||||
|
assertNull(MouseBinding.getMouseBinding("Button"));
|
||||||
|
assertNull(MouseBinding.getMouseBinding("Button0"));
|
||||||
|
assertNull(MouseBinding.getMouseBinding("Cats"));
|
||||||
|
assertNull(MouseBinding.getMouseBinding("Buttons12"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetMouseBindingFromText() {
|
||||||
|
|
||||||
|
MouseBinding mb = MouseBinding.getMouseBinding("Button1");
|
||||||
|
int button = 1;
|
||||||
|
assertEquals(button, mb.getButton());
|
||||||
|
assertModifiers(mb, buttonMask(button));
|
||||||
|
mb = MouseBinding.getMouseBinding("Button2");
|
||||||
|
button = 2;
|
||||||
|
assertEquals(button, mb.getButton());
|
||||||
|
assertModifiers(mb, buttonMask(button));
|
||||||
|
|
||||||
|
mb = MouseBinding.getMouseBinding("Ctrl+Button1");
|
||||||
|
button = 1;
|
||||||
|
assertEquals(button, mb.getButton());
|
||||||
|
assertModifiers(mb, CTRL, buttonMask(button));
|
||||||
|
|
||||||
|
mb = MouseBinding.getMouseBinding("Ctrl+Shift+Button2");
|
||||||
|
button = 2;
|
||||||
|
assertEquals(button, mb.getButton());
|
||||||
|
assertModifiers(mb, CTRL, SHIFT, buttonMask(button));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetMouseBindingFromEvent() {
|
||||||
|
|
||||||
|
int button = 1;
|
||||||
|
int modifiers = buttonMask(1);
|
||||||
|
JPanel source = new JPanel();
|
||||||
|
MouseEvent event = new MouseEvent(source, MouseEvent.MOUSE_PRESSED,
|
||||||
|
System.currentTimeMillis(), modifiers, 0, 0, 1, false, button);
|
||||||
|
MouseBinding mb = MouseBinding.getMouseBinding(event);
|
||||||
|
assertEquals(button, mb.getButton());
|
||||||
|
assertModifiers(mb, buttonMask(button));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsMatchingRelease() {
|
||||||
|
|
||||||
|
int button = 1;
|
||||||
|
MouseBinding mb = new MouseBinding(button);
|
||||||
|
|
||||||
|
int modifiers = buttonMask(1);
|
||||||
|
JPanel source = new JPanel();
|
||||||
|
MouseEvent pressed = new MouseEvent(source, MouseEvent.MOUSE_PRESSED,
|
||||||
|
System.currentTimeMillis(), modifiers, 0, 0, 1, false, button);
|
||||||
|
assertFalse(mb.isMatchingRelease(pressed));
|
||||||
|
|
||||||
|
MouseEvent released = new MouseEvent(source, MouseEvent.MOUSE_RELEASED,
|
||||||
|
System.currentTimeMillis(), modifiers, 0, 0, 1, false, button);
|
||||||
|
assertTrue(mb.isMatchingRelease(released));
|
||||||
|
|
||||||
|
MouseEvent clicked = new MouseEvent(source, MouseEvent.MOUSE_RELEASED,
|
||||||
|
System.currentTimeMillis(), modifiers, 0, 0, 1, false, button);
|
||||||
|
assertTrue(mb.isMatchingRelease(clicked));
|
||||||
|
|
||||||
|
// test that modifiers are ignored when determining what is a matching release
|
||||||
|
modifiers = InputEvent.SHIFT_DOWN_MASK ^ buttonMask(button);
|
||||||
|
released = new MouseEvent(source, MouseEvent.MOUSE_RELEASED, System.currentTimeMillis(),
|
||||||
|
modifiers, 0, 0, 1, false, button);
|
||||||
|
assertTrue(mb.isMatchingRelease(released));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetDisplayString() {
|
||||||
|
|
||||||
|
int button = 1;
|
||||||
|
MouseBinding mb = new MouseBinding(button);
|
||||||
|
assertEquals("Button1", mb.getDisplayText());
|
||||||
|
|
||||||
|
mb = MouseBinding.getMouseBinding("Button1");
|
||||||
|
assertEquals("Button1", mb.getDisplayText());
|
||||||
|
|
||||||
|
mb = MouseBinding.getMouseBinding("Button1 pressed");
|
||||||
|
assertEquals("Button1", mb.getDisplayText());
|
||||||
|
|
||||||
|
mb = MouseBinding.getMouseBinding("Shift+Button2");
|
||||||
|
assertEquals("Shift+Button2", mb.getDisplayText());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertModifiers(MouseBinding mb, int... expected) {
|
||||||
|
int actual = mb.getModifiers();
|
||||||
|
int allMods = 0;
|
||||||
|
for (int mod : expected) {
|
||||||
|
allMods ^= mod;
|
||||||
|
}
|
||||||
|
assertEquals(allMods, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int buttonMask(int buttonNumber) {
|
||||||
|
return InputEvent.getMaskForButton(buttonNumber);
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,6 +20,8 @@ import java.io.IOException;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
|
import javax.swing.KeyStroke;
|
||||||
|
|
||||||
import db.*;
|
import db.*;
|
||||||
import ghidra.framework.options.*;
|
import ghidra.framework.options.*;
|
||||||
import ghidra.util.*;
|
import ghidra.util.*;
|
||||||
|
@ -362,6 +364,15 @@ class OptionsDB extends AbstractOptions {
|
||||||
type = OptionType.values()[record.getByteValue(TYPE_COL)];
|
type = OptionType.values()[record.getByteValue(TYPE_COL)];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
else if (type == OptionType.KEYSTROKE_TYPE) {
|
||||||
|
// convert key strokes to action triggers
|
||||||
|
type = OptionType.ACTION_TRIGGER;
|
||||||
|
if (defaultValue instanceof KeyStroke keyStroke) {
|
||||||
|
defaultValue = new ActionTrigger(keyStroke);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return new DBOption(optionName, type, null, null, defaultValue, false, null);
|
return new DBOption(optionName, type, null, null, defaultValue, false, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1402,7 +1402,7 @@ public abstract class PluginTool extends AbstractDockingTool {
|
||||||
|
|
||||||
protected void restoreOptionsFromXml(Element root) {
|
protected void restoreOptionsFromXml(Element root) {
|
||||||
optionsMgr.setConfigState(root.getChild("OPTIONS"));
|
optionsMgr.setConfigState(root.getChild("OPTIONS"));
|
||||||
toolActions.restoreKeyBindings();
|
toolActions.optionsRebuilt();
|
||||||
setToolOptionsHelpLocation();
|
setToolOptionsHelpLocation();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1418,7 +1418,6 @@ public abstract class PluginTool extends AbstractDockingTool {
|
||||||
|
|
||||||
protected void restorePluginsFromXml(Element elem) throws PluginException {
|
protected void restorePluginsFromXml(Element elem) throws PluginException {
|
||||||
pluginMgr.restorePluginsFromXml(elem);
|
pluginMgr.restorePluginsFromXml(elem);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PluginEvent[] getLastEvents() {
|
PluginEvent[] getLastEvents() {
|
||||||
|
@ -1553,10 +1552,6 @@ public abstract class PluginTool extends AbstractDockingTool {
|
||||||
return winMgr.getActiveComponentProvider();
|
return winMgr.getActiveComponentProvider();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void refreshKeybindings() {
|
|
||||||
toolActions.restoreKeyBindings();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setUnconfigurable() {
|
public void setUnconfigurable() {
|
||||||
isConfigurable = false;
|
isConfigurable = false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,8 +28,7 @@ import javax.swing.table.TableColumn;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
import docking.DockingUtils;
|
import docking.*;
|
||||||
import docking.KeyEntryTextField;
|
|
||||||
import docking.action.DockingActionIf;
|
import docking.action.DockingActionIf;
|
||||||
import docking.actions.*;
|
import docking.actions.*;
|
||||||
import docking.tool.util.DockingToolConstants;
|
import docking.tool.util.DockingToolConstants;
|
||||||
|
@ -37,13 +36,12 @@ 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 ghidra.framework.options.Options;
|
import ghidra.framework.options.*;
|
||||||
import ghidra.framework.options.ToolOptions;
|
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
import ghidra.util.HTMLUtilities;
|
import ghidra.util.*;
|
||||||
import ghidra.util.Swing;
|
|
||||||
import ghidra.util.layout.PairLayout;
|
import ghidra.util.layout.PairLayout;
|
||||||
import ghidra.util.layout.VerticalLayout;
|
import ghidra.util.layout.VerticalLayout;
|
||||||
|
import gui.event.MouseBinding;
|
||||||
import help.Help;
|
import help.Help;
|
||||||
import help.HelpService;
|
import help.HelpService;
|
||||||
import resources.Icons;
|
import resources.Icons;
|
||||||
|
@ -66,7 +64,8 @@ public class KeyBindingsPanel extends JPanel {
|
||||||
private JPanel infoPanel;
|
private JPanel infoPanel;
|
||||||
private MultiLineLabel collisionLabel;
|
private MultiLineLabel collisionLabel;
|
||||||
private KeyBindingsTableModel tableModel;
|
private KeyBindingsTableModel tableModel;
|
||||||
private KeyEntryTextField ksField;
|
private ActionBindingListener actionBindingListener = new ActionBindingListener();
|
||||||
|
private ActionBindingPanel actionBindingPanel;
|
||||||
private GTableFilterPanel<DockingActionIf> tableFilterPanel;
|
private GTableFilterPanel<DockingActionIf> tableFilterPanel;
|
||||||
private EmptyBorderButton helpButton;
|
private EmptyBorderButton helpButton;
|
||||||
|
|
||||||
|
@ -207,11 +206,11 @@ public class KeyBindingsPanel extends JPanel {
|
||||||
}
|
}
|
||||||
|
|
||||||
private JPanel createKeyEntryPanel() {
|
private JPanel createKeyEntryPanel() {
|
||||||
ksField = new KeyEntryTextField(20, keyStroke -> keyStrokeChanged(keyStroke));
|
actionBindingPanel = new ActionBindingPanel(actionBindingListener);
|
||||||
|
|
||||||
// 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));
|
||||||
p.add(ksField);
|
p.add(actionBindingPanel);
|
||||||
|
|
||||||
JPanel keyPanel = new JPanel(new BorderLayout());
|
JPanel keyPanel = new JPanel(new BorderLayout());
|
||||||
|
|
||||||
|
@ -221,8 +220,7 @@ public class KeyBindingsPanel extends JPanel {
|
||||||
MultiLineLabel mlabel =
|
MultiLineLabel mlabel =
|
||||||
new MultiLineLabel("To add or change a key binding, select an action\n" +
|
new MultiLineLabel("To add or change a key binding, select an action\n" +
|
||||||
"and type any key combination\n \n" +
|
"and type any key combination\n \n" +
|
||||||
"To remove a key binding, select an action and\n" +
|
"To remove a key binding, select an action and\n" + "press <Enter> or <Backspace>");
|
||||||
"press <Enter> or <Backspace>");
|
|
||||||
JPanel labelPanel = new JPanel();
|
JPanel labelPanel = new JPanel();
|
||||||
labelPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 0, 0));
|
labelPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 0, 0));
|
||||||
BoxLayout bl = new BoxLayout(labelPanel, BoxLayout.X_AXIS);
|
BoxLayout bl = new BoxLayout(labelPanel, BoxLayout.X_AXIS);
|
||||||
|
@ -334,8 +332,12 @@ public class KeyBindingsPanel extends JPanel {
|
||||||
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 name : optionNames) {
|
||||||
KeyStroke newKeyStroke = keyBindingOptions.getKeyStroke(name, null);
|
ActionTrigger actionTrigger = keyBindingOptions.getActionTrigger(name, null);
|
||||||
localActionMap.put(name, newKeyStroke);
|
KeyStroke optionsKs = null;
|
||||||
|
if (actionTrigger != null) {
|
||||||
|
optionsKs = actionTrigger.getKeyStroke();
|
||||||
|
}
|
||||||
|
localActionMap.put(name, optionsKs);
|
||||||
}
|
}
|
||||||
return localActionMap;
|
return localActionMap;
|
||||||
}
|
}
|
||||||
|
@ -383,9 +385,9 @@ public class KeyBindingsPanel extends JPanel {
|
||||||
return action.getFullName();
|
return action.getFullName();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showActionsMappedToKeyStroke(String ksName) {
|
private void showActionsMappedToKeyStroke(KeyStroke ks) {
|
||||||
|
|
||||||
String text = keyBindings.getActionsForKeyStrokeText(ksName);
|
String text = keyBindings.getActionsForKeyStrokeText(ks);
|
||||||
if (StringUtils.isBlank(text)) {
|
if (StringUtils.isBlank(text)) {
|
||||||
text = " ";
|
text = " ";
|
||||||
}
|
}
|
||||||
|
@ -413,9 +415,6 @@ public class KeyBindingsPanel extends JPanel {
|
||||||
|
|
||||||
Map<String, KeyStroke> keyStrokesByActionName =
|
Map<String, KeyStroke> keyStrokesByActionName =
|
||||||
createActionNameToKeyStrokeMap(keyBindingOptions);
|
createActionNameToKeyStrokeMap(keyBindingOptions);
|
||||||
if (keyStrokesByActionName == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean changes = false;
|
boolean changes = false;
|
||||||
|
|
||||||
|
@ -445,7 +444,7 @@ public class KeyBindingsPanel extends JPanel {
|
||||||
/**
|
/**
|
||||||
* Processes KeyStroke entry from the text field.
|
* Processes KeyStroke entry from the text field.
|
||||||
*/
|
*/
|
||||||
private void keyStrokeChanged(KeyStroke ks) {
|
private void updateKeyStroke(KeyStroke ks) {
|
||||||
clearInfoPanel();
|
clearInfoPanel();
|
||||||
|
|
||||||
DockingActionIf action = getSelectedAction();
|
DockingActionIf action = getSelectedAction();
|
||||||
|
@ -458,25 +457,56 @@ public class KeyBindingsPanel extends JPanel {
|
||||||
String errorMessage = toolActions.validateActionKeyBinding(action, ks);
|
String errorMessage = toolActions.validateActionKeyBinding(action, ks);
|
||||||
if (errorMessage != null) {
|
if (errorMessage != null) {
|
||||||
statusLabel.setText(errorMessage);
|
statusLabel.setText(errorMessage);
|
||||||
ksField.clearField();
|
actionBindingPanel.clearKeyStroke();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String selectedActionName = getSelectedActionName();
|
String selectedActionName = action.getFullName();
|
||||||
if (selectedActionName != null) {
|
if (setActionKeyStroke(selectedActionName, ks)) {
|
||||||
if (setActionKeyStroke(selectedActionName, ks)) {
|
showActionsMappedToKeyStroke(ks);
|
||||||
String keyStrokeText = KeyBindingUtils.parseKeyStroke(ks);
|
tableModel.fireTableDataChanged();
|
||||||
showActionsMappedToKeyStroke(keyStrokeText);
|
changesMade(true);
|
||||||
tableModel.fireTableDataChanged();
|
|
||||||
changesMade(true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateMouseBinding(MouseBinding mb) {
|
||||||
|
|
||||||
|
clearInfoPanel();
|
||||||
|
|
||||||
|
DockingActionIf action = getSelectedAction();
|
||||||
|
if (action == null) {
|
||||||
|
statusLabel.setText("No action is selected.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String selectedActionName = action.getFullName();
|
||||||
|
if (setMouseBinding(selectedActionName, mb)) {
|
||||||
|
tableModel.fireTableDataChanged();
|
||||||
|
changesMade(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean setMouseBinding(String actionName, MouseBinding mouseBinding) {
|
||||||
|
|
||||||
|
if (keyBindings.isMouseBindingInUse(actionName, mouseBinding)) {
|
||||||
|
|
||||||
|
String existingName = keyBindings.getActionForMouseBinding(mouseBinding);
|
||||||
|
String message = """
|
||||||
|
Mouse binding '%s' already in use by '%s'.
|
||||||
|
The existing binding must be cleared before it can be used again.
|
||||||
|
""".formatted(mouseBinding, existingName);
|
||||||
|
Msg.showInfo(this, actionBindingPanel, "Mouse Binding In Use", message);
|
||||||
|
actionBindingPanel.clearMouseBinding();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return keyBindings.setActionMouseBinding(actionName, mouseBinding);
|
||||||
|
}
|
||||||
|
|
||||||
// returns true if the key stroke is a new value
|
// returns true if the key stroke is a new value
|
||||||
private boolean setActionKeyStroke(String actionName, KeyStroke keyStroke) {
|
private boolean setActionKeyStroke(String actionName, KeyStroke keyStroke) {
|
||||||
if (!isValidKeyStroke(keyStroke)) {
|
if (!isValidKeyStroke(keyStroke)) {
|
||||||
ksField.setText("");
|
actionBindingPanel.clearKeyStroke();
|
||||||
return keyBindings.removeKeyStroke(actionName);
|
return keyBindings.removeKeyStroke(actionName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -513,20 +543,22 @@ public class KeyBindingsPanel extends JPanel {
|
||||||
String fullActionName = getSelectedActionName();
|
String fullActionName = getSelectedActionName();
|
||||||
if (fullActionName == null) {
|
if (fullActionName == null) {
|
||||||
statusLabel.setText("");
|
statusLabel.setText("");
|
||||||
|
actionBindingPanel.setEnabled(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
actionBindingPanel.setEnabled(true);
|
||||||
|
|
||||||
helpButton.setEnabled(true);
|
helpButton.setEnabled(true);
|
||||||
KeyStroke ks = keyBindings.getKeyStroke(fullActionName);
|
|
||||||
String ksName = "";
|
|
||||||
clearInfoPanel();
|
clearInfoPanel();
|
||||||
|
|
||||||
|
KeyStroke ks = keyBindings.getKeyStroke(fullActionName);
|
||||||
if (ks != null) {
|
if (ks != null) {
|
||||||
ksName = KeyBindingUtils.parseKeyStroke(ks);
|
showActionsMappedToKeyStroke(ks);
|
||||||
showActionsMappedToKeyStroke(ksName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ksField.setText(ksName);
|
MouseBinding mb = keyBindings.getMouseBinding(fullActionName);
|
||||||
|
actionBindingPanel.setKeyBindingData(ks, mb);
|
||||||
|
|
||||||
// make sure the label gets enough space
|
// make sure the label gets enough space
|
||||||
statusLabel.setPreferredSize(
|
statusLabel.setPreferredSize(
|
||||||
|
@ -543,8 +575,7 @@ public class KeyBindingsPanel extends JPanel {
|
||||||
}
|
}
|
||||||
|
|
||||||
private class KeyBindingsTableModel extends AbstractSortedTableModel<DockingActionIf> {
|
private class KeyBindingsTableModel extends AbstractSortedTableModel<DockingActionIf> {
|
||||||
private final String[] columnNames =
|
private final String[] columnNames = { "Action Name", "KeyBinding", "Plugin Name" };
|
||||||
{ "Action Name", "KeyBinding", "Plugin Name" };
|
|
||||||
|
|
||||||
private List<DockingActionIf> actions;
|
private List<DockingActionIf> actions;
|
||||||
|
|
||||||
|
@ -561,15 +592,23 @@ public class KeyBindingsPanel extends JPanel {
|
||||||
@Override
|
@Override
|
||||||
public Object getColumnValueForRow(DockingActionIf action, int columnIndex) {
|
public Object getColumnValueForRow(DockingActionIf action, int columnIndex) {
|
||||||
|
|
||||||
|
String fullName = action.getFullName();
|
||||||
switch (columnIndex) {
|
switch (columnIndex) {
|
||||||
case ACTION_NAME:
|
case ACTION_NAME:
|
||||||
return action.getName();
|
return action.getName();
|
||||||
case KEY_BINDING:
|
case KEY_BINDING:
|
||||||
KeyStroke ks = keyBindings.getKeyStroke(action.getFullName());
|
String text = "";
|
||||||
|
KeyStroke ks = keyBindings.getKeyStroke(fullName);
|
||||||
if (ks != null) {
|
if (ks != null) {
|
||||||
return KeyBindingUtils.parseKeyStroke(ks);
|
text += KeyBindingUtils.parseKeyStroke(ks);
|
||||||
}
|
}
|
||||||
return "";
|
|
||||||
|
MouseBinding mb = keyBindings.getMouseBinding(fullName);
|
||||||
|
if (mb != null) {
|
||||||
|
text += " (" + mb.getDisplayText() + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
return text.trim();
|
||||||
case PLUGIN_NAME:
|
case PLUGIN_NAME:
|
||||||
return action.getOwnerDescription();
|
return action.getOwnerDescription();
|
||||||
}
|
}
|
||||||
|
@ -606,4 +645,17 @@ public class KeyBindingsPanel extends JPanel {
|
||||||
return String.class;
|
return String.class;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class ActionBindingListener implements DockingActionInputBindingListener {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void keyStrokeChanged(KeyStroke ks) {
|
||||||
|
updateKeyStroke(ks);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseBindingChanged(MouseBinding mb) {
|
||||||
|
updateMouseBinding(mb);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue