mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2024-11-25 21:51:47 +00:00
GT-2869 - Shared Key Bindings - created new shared keybinding concept
that replaces the DummyKeyBindingsOptionsAction
This commit is contained in:
parent
1fa2bd7979
commit
c9bd3a8b2b
@ -15,21 +15,20 @@
|
||||
*/
|
||||
package ghidra.test;
|
||||
|
||||
import java.awt.Window;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import docking.*;
|
||||
import docking.DialogComponentProvider;
|
||||
import docking.action.DockingActionIf;
|
||||
import docking.widgets.fieldpanel.FieldPanel;
|
||||
import docking.widgets.fieldpanel.field.Field;
|
||||
import docking.widgets.fieldpanel.listener.FieldMouseListener;
|
||||
import docking.widgets.fieldpanel.support.FieldLocation;
|
||||
import ghidra.GhidraTestApplicationLayout;
|
||||
import ghidra.app.plugin.core.analysis.AutoAnalysisManager;
|
||||
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
|
||||
import ghidra.framework.ApplicationConfiguration;
|
||||
import ghidra.framework.GhidraApplicationConfiguration;
|
||||
@ -41,10 +40,10 @@ import ghidra.program.model.listing.Program;
|
||||
import ghidra.util.TaskUtilities;
|
||||
import ghidra.util.exception.AssertException;
|
||||
import junit.framework.AssertionFailedError;
|
||||
import util.CollectionUtils;
|
||||
import utility.application.ApplicationLayout;
|
||||
|
||||
public abstract class AbstractGhidraHeadedIntegrationTest extends AbstractGhidraHeadlessIntegrationTest {
|
||||
public abstract class AbstractGhidraHeadedIntegrationTest
|
||||
extends AbstractGhidraHeadlessIntegrationTest {
|
||||
|
||||
public AbstractGhidraHeadedIntegrationTest() {
|
||||
super();
|
||||
@ -113,42 +112,6 @@ public abstract class AbstractGhidraHeadedIntegrationTest extends AbstractGhidra
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the action by the given owner name and action name.
|
||||
* If you do not know the owner name, then use
|
||||
* the call {@link #getActions(DockingTool, String)} instead.
|
||||
*
|
||||
* <P>Note: more specific test case subclasses provide other methods for finding actions
|
||||
* when you have an owner name (which is usually the plugin name).
|
||||
*
|
||||
* @param tool the tool containing all system actions
|
||||
* @param name the name to match
|
||||
* @return the matching action; null if no matching action can be found
|
||||
*/
|
||||
public static DockingActionIf getAction(DockingTool tool, String owner, String name) {
|
||||
String fullName = name + " (" + owner + ")";
|
||||
List<DockingActionIf> actions = tool.getDockingActionsByFullActionName(fullName);
|
||||
if (actions.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (actions.size() > 1) {
|
||||
// This shouldn't happen
|
||||
throw new AssertionFailedError(
|
||||
"Found more than one action for name '" + fullName + "'");
|
||||
}
|
||||
|
||||
return CollectionUtils.any(actions);
|
||||
}
|
||||
|
||||
public static DockingActionIf getAction(Plugin plugin, String actionName) {
|
||||
return getAction(plugin.getTool(), plugin.getName(), actionName);
|
||||
}
|
||||
|
||||
public static DockingActionIf getLocalAction(ComponentProvider provider, String actionName) {
|
||||
return getAction(provider.getTool(), provider.getName(), actionName);
|
||||
}
|
||||
|
||||
public static PluginTool showTool(final PluginTool tool) {
|
||||
runSwing(() -> {
|
||||
boolean wasErrorGUIEnabled = isUseErrorGUI();
|
||||
@ -162,9 +125,7 @@ public abstract class AbstractGhidraHeadedIntegrationTest extends AbstractGhidra
|
||||
|
||||
/**
|
||||
* Shows the given DialogComponentProvider using the given tool's
|
||||
* {@link PluginTool#showDialog(DialogComponentProvider)} method. After calling show on a
|
||||
* new thread the method will then wait for the dialog to be shown by calling
|
||||
* {@link TestEnv#waitForDialogComponent(Window, Class, int)}.
|
||||
* {@link PluginTool#showDialog(DialogComponentProvider)} method.
|
||||
*
|
||||
* @param tool The tool used to show the given provider.
|
||||
* @param provider The DialogComponentProvider to show.
|
||||
@ -206,22 +167,8 @@ public abstract class AbstractGhidraHeadedIntegrationTest extends AbstractGhidra
|
||||
waitForSwing();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use {@link #waitForBusyTool(PluginTool)} instead
|
||||
*/
|
||||
@Deprecated
|
||||
public static void waitForAnalysis() {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<Program, AutoAnalysisManager> programToManagersMap =
|
||||
(Map<Program, AutoAnalysisManager>) getInstanceField("managerMap",
|
||||
AutoAnalysisManager.class);
|
||||
|
||||
Collection<AutoAnalysisManager> managers = programToManagersMap.values();
|
||||
for (AutoAnalysisManager manager : managers) {
|
||||
while (manager.isAnalyzing()) {
|
||||
sleep(DEFAULT_WAIT_DELAY);
|
||||
}
|
||||
}
|
||||
public static DockingActionIf getAction(Plugin plugin, String actionName) {
|
||||
return getAction(plugin.getTool(), plugin.getName(), actionName);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -230,6 +177,7 @@ public abstract class AbstractGhidraHeadedIntegrationTest extends AbstractGhidra
|
||||
*
|
||||
* @param project The project which with the tool is associated.
|
||||
* @param tool The tool to be saved
|
||||
* @return the new tool
|
||||
*/
|
||||
public static PluginTool saveTool(final Project project, final PluginTool tool) {
|
||||
AtomicReference<PluginTool> ref = new AtomicReference<>();
|
||||
|
@ -51,11 +51,6 @@ import utilities.util.reflection.ReflectionUtilities;
|
||||
* method allows actions to manage their own enablement. Otherwise, the default behavior for this
|
||||
* method is to return the current enabled property of the action. This allows for the possibility
|
||||
* for plugins to manage the enablement of its actions.
|
||||
* <p>
|
||||
* By default, actions that are not enabledForContext do not appear in the popup menu. To change
|
||||
* that behavior, implementors can also override {@link #deleteThisContextMethod(ActionContext)}.
|
||||
* This method is used to determine if the action should appear on the popup menu based on the given
|
||||
* context.
|
||||
*/
|
||||
public abstract class DockingAction implements DockingActionIf {
|
||||
|
||||
@ -283,8 +278,8 @@ public abstract class DockingAction implements DockingActionIf {
|
||||
//==================================================================================================
|
||||
|
||||
/**
|
||||
* Sets the {@link #MenuData} to be used to put this action on the tool's menu bar.
|
||||
* @param newMenuData the MenuData to be used to put this action on the tool's menu bar.
|
||||
* Sets the {@link MenuData} to be used to put this action on the tool's menu bar
|
||||
* @param newMenuData the MenuData to be used to put this action on the tool's menu bar
|
||||
*/
|
||||
public void setMenuBarData(MenuData newMenuData) {
|
||||
MenuBarData oldData = menuBarData;
|
||||
@ -295,8 +290,8 @@ public abstract class DockingAction implements DockingActionIf {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link #MenuData} to be used to put this action in the tool's popup menu.
|
||||
* @param newMenuData the MenuData to be used to put this action on the tool's popup menu.
|
||||
* Sets the {@link MenuData} to be used to put this action in the tool's popup menu
|
||||
* @param newMenuData the MenuData to be used to put this action on the tool's popup menu
|
||||
*/
|
||||
public void setPopupMenuData(MenuData newMenuData) {
|
||||
PopupMenuData oldData = popupMenuData;
|
||||
@ -307,8 +302,8 @@ public abstract class DockingAction implements DockingActionIf {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link #ToolBarData} to be used to put this action on the tool's toolbar.
|
||||
* @param newToolBarData the ToolBarData to be used to put this action on the tool's toolbar.
|
||||
* Sets the {@link ToolBarData} to be used to put this action on the tool's toolbar
|
||||
* @param newToolBarData the ToolBarData to be used to put this action on the tool's toolbar
|
||||
*/
|
||||
public void setToolBarData(ToolBarData newToolBarData) {
|
||||
|
||||
@ -321,7 +316,7 @@ public abstract class DockingAction implements DockingActionIf {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link #KeyBindingData} to be used to assign this action to a keybinding.
|
||||
* Sets the {@link KeyBindingData} to be used to assign this action to a keybinding.
|
||||
* @param newKeyBindingData the KeyBindingData to be used to assign this action to a keybinding.
|
||||
*/
|
||||
@Override
|
||||
@ -339,10 +334,7 @@ public abstract class DockingAction implements DockingActionIf {
|
||||
/**
|
||||
* <b>Users creating actions should not call this method, but should instead call
|
||||
* {@link #setKeyBindingData(KeyBindingData)}.</b>
|
||||
* @param newKeyBindingData the KeyBindingData to be used to assign this action to a keybinding.
|
||||
* @param validate true signals that this method should convert keybindings to their
|
||||
* OS-dependent form (for example, on Mac a <tt>Ctrl</tt>
|
||||
* key is changed to the <tt>Command</tt> key).
|
||||
* @param newKeyBindingData the KeyBindingData to be used to assign this action to a keybinding
|
||||
*/
|
||||
@Override
|
||||
public void setUnvalidatedKeyBindingData(KeyBindingData newKeyBindingData) {
|
||||
@ -364,7 +356,7 @@ public abstract class DockingAction implements DockingActionIf {
|
||||
|
||||
/**
|
||||
* Sets the description to be used in the tooltip.
|
||||
* @param description the description to be set.
|
||||
* @param newDescription the description to be set.
|
||||
*/
|
||||
public void setDescription(String newDescription) {
|
||||
if (SystemUtilities.isEqual(newDescription, description)) {
|
||||
|
@ -33,27 +33,30 @@ public interface DockingActionIf extends HelpDescriptor {
|
||||
public static final String TOOLBAR_DATA_PROPERTY = "ToolBar";
|
||||
|
||||
/**
|
||||
* Returns the name of the action.
|
||||
* Returns the name of the action
|
||||
* @return the name
|
||||
*/
|
||||
public abstract String getName();
|
||||
public String getName();
|
||||
|
||||
/**
|
||||
* Returns the owner of this action.
|
||||
* Returns the owner of this action
|
||||
* @return the owner
|
||||
*/
|
||||
public abstract String getOwner();
|
||||
public String getOwner();
|
||||
|
||||
/**
|
||||
* Returns a short description of this action. Generally used for a tooltip.
|
||||
* Returns a short description of this action. Generally used for a tooltip
|
||||
* @return the description
|
||||
*/
|
||||
public abstract String getDescription();
|
||||
public String getDescription();
|
||||
|
||||
/**
|
||||
* Adds a listener to be notified if any property changes.
|
||||
* Adds a listener to be notified if any property changes
|
||||
* @param listener The property change listener that will be notified of
|
||||
* property change events.
|
||||
* @see AbstractAction#addPropertyChangeListener(java.beans.PropertyChangeListener)
|
||||
* @see Action#addPropertyChangeListener(java.beans.PropertyChangeListener)
|
||||
*/
|
||||
public abstract void addPropertyChangeListener(PropertyChangeListener listener);
|
||||
public void addPropertyChangeListener(PropertyChangeListener listener);
|
||||
|
||||
/**
|
||||
* Removes a listener to be notified of property changes.
|
||||
@ -61,15 +64,15 @@ public interface DockingActionIf extends HelpDescriptor {
|
||||
* @param listener The property change listener that will be notified of
|
||||
* property change events.
|
||||
* @see #addPropertyChangeListener(PropertyChangeListener)
|
||||
* @see AbstractAction#addPropertyChangeListener(java.beans.PropertyChangeListener)
|
||||
* @see Action#addPropertyChangeListener(java.beans.PropertyChangeListener)
|
||||
*/
|
||||
public abstract void removePropertyChangeListener(PropertyChangeListener listener);
|
||||
public void removePropertyChangeListener(PropertyChangeListener listener);
|
||||
|
||||
/**
|
||||
* Enables or disables the action.
|
||||
* Enables or disables the action
|
||||
*
|
||||
* @param newValue true to enable the action, false to
|
||||
* disable it
|
||||
* @param newValue true to enable the action, false to disable it
|
||||
* @return the enabled value of the action after this call
|
||||
*/
|
||||
public boolean setEnabled(boolean newValue);
|
||||
|
||||
@ -124,32 +127,34 @@ public interface DockingActionIf extends HelpDescriptor {
|
||||
|
||||
/**
|
||||
* Returns the full name (the action name combined with the owner name)
|
||||
* @return the full name
|
||||
*/
|
||||
public abstract String getFullName();
|
||||
public String getFullName();
|
||||
|
||||
/**
|
||||
* method to actually perform the action logic for this action.
|
||||
* @param context the {@link ActionContext} object that provides information about where and how
|
||||
* this action was invoked.
|
||||
*/
|
||||
public abstract void actionPerformed(ActionContext context);
|
||||
public void actionPerformed(ActionContext context);
|
||||
|
||||
/**
|
||||
* method is used to determine if this action should be displayed on the current popup. This
|
||||
* method will only be called if the action has popup {@link PopupMenuData} set.
|
||||
* <p>
|
||||
* Generally, actions don't need to override this method as the default implementation will
|
||||
* defer to the {@link #isEnabledForContext()}, which will have the effect of adding the
|
||||
* action to the popup only if it is enabled for a given context. By overriding this method,
|
||||
* defer to the {@link #isEnabledForContext(ActionContext)}, which will have the effect
|
||||
* of adding the action to the popup only if it is enabled for a given context.
|
||||
* By overriding this method,
|
||||
* you can change this behavior so that the action will be added to the popup, even if it is
|
||||
* disabled for the context, by having this method return true even if the
|
||||
* {@link #isEnabledForContext()} method will return false, resulting in the action appearing
|
||||
* in the popup menu, but begin disabled.
|
||||
* {@link #isEnabledForContext(ActionContext)} method will return false, resulting in the
|
||||
* action appearing in the popup menu, but begin disabled.
|
||||
*
|
||||
* @param context the {@link ActionContext} from the active provider.
|
||||
* @return true if this action is appropriate for the given context.
|
||||
*/
|
||||
public abstract boolean isAddToPopup(ActionContext context);
|
||||
public boolean isAddToPopup(ActionContext context);
|
||||
|
||||
/**
|
||||
* Method that actions implement to indicate if this action is valid (knows how to work with, is
|
||||
@ -162,7 +167,7 @@ public interface DockingActionIf extends HelpDescriptor {
|
||||
* @param context the {@link ActionContext} from the active provider.
|
||||
* @return true if this action is appropriate for the given context.
|
||||
*/
|
||||
public abstract boolean isValidContext(ActionContext context);
|
||||
public boolean isValidContext(ActionContext context);
|
||||
|
||||
/**
|
||||
* Method that actions implement to indicate if this action is valid (knows how to work with, is
|
||||
@ -172,10 +177,10 @@ public interface DockingActionIf extends HelpDescriptor {
|
||||
* If you want a global action to only work on the global context, then override this method
|
||||
* and return false.
|
||||
*
|
||||
* @param context the global {@link ActionContext} from the active provider.
|
||||
* @param globalContext the global {@link ActionContext} from the active provider.
|
||||
* @return true if this action is appropriate for the given context.
|
||||
*/
|
||||
public abstract boolean isValidGlobalContext(ActionContext globalContext);
|
||||
public boolean isValidGlobalContext(ActionContext globalContext);
|
||||
|
||||
/**
|
||||
* Method used to determine if this action should be enabled for the given context.
|
||||
@ -202,13 +207,14 @@ public interface DockingActionIf extends HelpDescriptor {
|
||||
* @param context the current {@link ActionContext} for the window.
|
||||
* @return true if the action should be enabled for the context or false otherwise.
|
||||
*/
|
||||
public abstract boolean isEnabledForContext(ActionContext context);
|
||||
public boolean isEnabledForContext(ActionContext context);
|
||||
|
||||
/**
|
||||
* Returns a string that includes source file and line number information of where this action was
|
||||
* created.
|
||||
* Returns a string that includes source file and line number information of where
|
||||
* this action was created
|
||||
* @return the inception information
|
||||
*/
|
||||
public abstract String getInceptionInformation();
|
||||
public String getInceptionInformation();
|
||||
|
||||
/**
|
||||
* Returns a JButton that is suitable for this action. For example, It creates a ToggleButton
|
||||
@ -250,7 +256,7 @@ public interface DockingActionIf extends HelpDescriptor {
|
||||
* @param keyBindingData if non-null, assigns a keybinding to the action. Otherwise, removes
|
||||
* any keybinding from the action.
|
||||
*/
|
||||
public abstract void setKeyBindingData(KeyBindingData keyBindingData);
|
||||
public void setKeyBindingData(KeyBindingData keyBindingData);
|
||||
|
||||
/**
|
||||
* <b>Users creating actions should not call this method, but should instead call
|
||||
@ -260,11 +266,25 @@ public interface DockingActionIf extends HelpDescriptor {
|
||||
* {@link #setKeyBindingData(KeyBindingData)} so that keybindings are set exactly as they
|
||||
* are given.
|
||||
*
|
||||
* @param newKeyBindingData the KeyBindingData to be used to assign this action to a keybinding.
|
||||
* @param validate true signals that this method should convert keybindings to their
|
||||
* OS-dependent form (for example, on Mac a <tt>Ctrl</tt>
|
||||
* key is changed to the <tt>Command</tt> key).
|
||||
* @param newKeyBindingData the KeyBindingData to be used to assign this action to a keybinding
|
||||
*/
|
||||
public abstract void setUnvalidatedKeyBindingData(KeyBindingData newKeyBindingData);
|
||||
public void setUnvalidatedKeyBindingData(KeyBindingData newKeyBindingData);
|
||||
|
||||
/**
|
||||
* Returns true if this action shares a keybinding with other actions. If this returns true,
|
||||
* then this action, and any action that shares a name with this action, will be updated
|
||||
* to the same key binding value whenever the key binding options change.
|
||||
*
|
||||
* <p>This will be false for the vast majority of actions. If you are unsure if your action
|
||||
* should use a shared keybinding, then do not set this value to true.
|
||||
*
|
||||
* <p>This value is not meant to change over the life of the action. Thus, there is no
|
||||
* <code>set</code> method to change this value. Rather, you should override this method
|
||||
* to return <code>true</code> as desired.
|
||||
*
|
||||
* @return true to share a shared keybinding
|
||||
*/
|
||||
public default boolean usesSharedKeyBinding() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -24,8 +24,7 @@ import javax.swing.KeyStroke;
|
||||
import docking.*;
|
||||
import docking.action.*;
|
||||
import docking.tool.util.DockingToolConstants;
|
||||
import ghidra.framework.options.OptionType;
|
||||
import ghidra.framework.options.Options;
|
||||
import ghidra.framework.options.*;
|
||||
import ghidra.util.exception.AssertException;
|
||||
|
||||
/**
|
||||
@ -34,8 +33,9 @@ import ghidra.util.exception.AssertException;
|
||||
public class DockingToolActionManager implements PropertyChangeListener {
|
||||
|
||||
private DockingWindowManager winMgr;
|
||||
private Map<String, List<DockingActionIf>> actionMap;
|
||||
private Options keyBindingOptions;
|
||||
private Map<String, List<DockingActionIf>> actionMap = new HashMap<>();
|
||||
private Map<String, SharedStubKeyBindingAction> sharedActionMap = new HashMap<>();
|
||||
private ToolOptions keyBindingOptions;
|
||||
private DockingTool dockingTool;
|
||||
|
||||
/**
|
||||
@ -48,7 +48,6 @@ public class DockingToolActionManager implements PropertyChangeListener {
|
||||
public DockingToolActionManager(DockingTool tool, DockingWindowManager windowManager) {
|
||||
this.dockingTool = tool;
|
||||
this.winMgr = windowManager;
|
||||
actionMap = new HashMap<>();
|
||||
keyBindingOptions = tool.getOptions(DockingToolConstants.KEY_BINDINGS);
|
||||
}
|
||||
|
||||
@ -88,18 +87,47 @@ public class DockingToolActionManager implements PropertyChangeListener {
|
||||
public synchronized void addToolAction(DockingActionIf action) {
|
||||
action.addPropertyChangeListener(this);
|
||||
addActionToMap(action);
|
||||
if (action.isKeyBindingManaged()) {
|
||||
KeyStroke ks = action.getKeyBinding();
|
||||
keyBindingOptions.registerOption(action.getFullName(), OptionType.KEYSTROKE_TYPE, ks,
|
||||
null, null);
|
||||
KeyStroke newKs = keyBindingOptions.getKeyStroke(action.getFullName(), ks);
|
||||
if (ks != newKs) {
|
||||
action.setUnvalidatedKeyBindingData(new KeyBindingData(newKs));
|
||||
}
|
||||
}
|
||||
setKeyBindingOption(action);
|
||||
winMgr.addToolAction(action);
|
||||
}
|
||||
|
||||
private void setKeyBindingOption(DockingActionIf action) {
|
||||
|
||||
if (!action.isKeyBindingManaged()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (action.usesSharedKeyBinding()) {
|
||||
installSharedKeyBinding(action);
|
||||
return;
|
||||
}
|
||||
|
||||
KeyStroke ks = action.getKeyBinding();
|
||||
keyBindingOptions.registerOption(action.getFullName(), OptionType.KEYSTROKE_TYPE, ks, null,
|
||||
null);
|
||||
KeyStroke newKs = keyBindingOptions.getKeyStroke(action.getFullName(), ks);
|
||||
if (!Objects.equals(ks, newKs)) {
|
||||
action.setUnvalidatedKeyBindingData(new KeyBindingData(newKs));
|
||||
}
|
||||
}
|
||||
|
||||
private void installSharedKeyBinding(DockingActionIf action) {
|
||||
String name = action.getName();
|
||||
KeyStroke defaultKeyStroke = action.getKeyBinding();
|
||||
|
||||
// get or create the stub to which we will add the action
|
||||
SharedStubKeyBindingAction stub = sharedActionMap.computeIfAbsent(name, key -> {
|
||||
|
||||
SharedStubKeyBindingAction newStub =
|
||||
new SharedStubKeyBindingAction(name, keyBindingOptions);
|
||||
keyBindingOptions.registerOption(newStub.getFullName(), OptionType.KEYSTROKE_TYPE,
|
||||
defaultKeyStroke, null, null);
|
||||
return newStub;
|
||||
});
|
||||
|
||||
stub.addClientAction(action);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the given action from the tool
|
||||
* @param action the action to be removed.
|
||||
@ -173,8 +201,8 @@ public class DockingToolActionManager implements PropertyChangeListener {
|
||||
* @param fullActionName full name for the action, e.g., "My Action (My Plugin)"
|
||||
* @return list of actions; empty if no action exists with the given name
|
||||
*/
|
||||
public List<DockingActionIf> getDockingActionsByFullActionName(String fullActionName) {
|
||||
List<DockingActionIf> list = actionMap.get(fullActionName);
|
||||
public List<DockingActionIf> getDockingActionsByFullActionName(String fullName) {
|
||||
List<DockingActionIf> list = actionMap.get(fullName);
|
||||
if (list == null) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
@ -0,0 +1,180 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package docking.actions;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import javax.swing.KeyStroke;
|
||||
|
||||
import docking.ActionContext;
|
||||
import docking.DockingWindowManager;
|
||||
import docking.action.*;
|
||||
import ghidra.framework.options.OptionsChangeListener;
|
||||
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
|
||||
* allows plugins to create actions that share keybindings without having to manage those
|
||||
* keybindings themselves.
|
||||
*
|
||||
* <p>Clients should not be using this class directly.
|
||||
*/
|
||||
class SharedStubKeyBindingAction extends DockingAction implements OptionsChangeListener {
|
||||
|
||||
static final String SHARED_OWNER = "Tool";
|
||||
|
||||
/*
|
||||
* We save the client actions for later validate and options updating. We also need the
|
||||
* default key binding data, which is stored in the value of this map.
|
||||
*
|
||||
* Note: This collection is weak; the actions will stay as long as they are
|
||||
* registered in the tool.
|
||||
*/
|
||||
private WeakHashMap<DockingActionIf, KeyStroke> clientActions = new WeakHashMap<>();
|
||||
|
||||
private ToolOptions keyBindingOptions;
|
||||
|
||||
/**
|
||||
* Creates a new dummy action by the given name and default keystroke value
|
||||
*
|
||||
* @param name The name of the action--this will be displayed in the options as the name of
|
||||
* key binding's action
|
||||
* @param options the tool's key binding options
|
||||
*/
|
||||
public SharedStubKeyBindingAction(String name, ToolOptions options) {
|
||||
super(name, SHARED_OWNER);
|
||||
this.keyBindingOptions = options;
|
||||
|
||||
// Dummy keybinding actions don't have help--the real action does
|
||||
DockingWindowManager.getHelpService().excludeFromHelp(this);
|
||||
|
||||
// A listener to keep the shared, stub keybindings in sync with their clients
|
||||
options.addOptionsChangeListener(this);
|
||||
}
|
||||
|
||||
void addClientAction(DockingActionIf action) {
|
||||
|
||||
// 1) Validate new action keystroke against existing actions
|
||||
KeyStroke validatedKeyStroke = validateActionsHaveTheSameDefaultKeyStroke(action);
|
||||
|
||||
// 2) Update the given action with the current option value. This allows clients to
|
||||
// add and remove actions after the tool has been initialized.
|
||||
validatedKeyStroke = updateKeyStrokeFromOptions(validatedKeyStroke);
|
||||
|
||||
clientActions.put(action, validatedKeyStroke);
|
||||
}
|
||||
|
||||
private KeyStroke validateActionsHaveTheSameDefaultKeyStroke(DockingActionIf newAction) {
|
||||
|
||||
// this value may be null
|
||||
KeyBindingData defaultBinding = newAction.getDefaultKeyBindingData();
|
||||
KeyStroke newDefaultKs = getKeyStroke(defaultBinding);
|
||||
|
||||
Set<Entry<DockingActionIf, KeyStroke>> entries = clientActions.entrySet();
|
||||
for (Entry<DockingActionIf, KeyStroke> entry : entries) {
|
||||
DockingActionIf existingAction = entry.getKey();
|
||||
KeyStroke existingDefaultKs = entry.getValue();
|
||||
if (Objects.equals(existingDefaultKs, newDefaultKs)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
logDifferentKeyBindingsWarnigMessage(newAction, existingAction, existingDefaultKs);
|
||||
|
||||
//
|
||||
// Not sure which keystroke to prefer here--keep the first one that was set
|
||||
//
|
||||
|
||||
// set the new action's keystroke to be the winner
|
||||
newAction.setKeyBindingData(new KeyBindingData(existingDefaultKs));
|
||||
|
||||
// one message is probably enough;
|
||||
return existingDefaultKs;
|
||||
}
|
||||
|
||||
return newDefaultKs;
|
||||
}
|
||||
|
||||
private void logDifferentKeyBindingsWarnigMessage(DockingActionIf newAction,
|
||||
DockingActionIf existingAction, KeyStroke existingDefaultKs) {
|
||||
|
||||
//@formatter:off
|
||||
String s = "Shared Key Binding Actions have different deafult values. These " +
|
||||
"must be the same." +
|
||||
"\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(this, s, ReflectionUtilities.createJavaFilteredThrowable());
|
||||
}
|
||||
|
||||
private KeyStroke updateKeyStrokeFromOptions(KeyStroke validatedKeyStroke) {
|
||||
return keyBindingOptions.getKeyStroke(getFullName(), validatedKeyStroke);
|
||||
}
|
||||
|
||||
private KeyStroke getKeyStroke(KeyBindingData data) {
|
||||
if (data == null) {
|
||||
return null;
|
||||
}
|
||||
return data.getKeyBinding();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void optionsChanged(ToolOptions options, String optionName, Object oldValue,
|
||||
Object newValue) {
|
||||
|
||||
if (!optionName.startsWith(getName())) {
|
||||
return; // not my binding
|
||||
}
|
||||
|
||||
KeyStroke newKs = (KeyStroke) newValue;
|
||||
for (DockingActionIf action : clientActions.keySet()) {
|
||||
|
||||
// Note: update this to say why we are using the 'unvalidated' call instead of the
|
||||
// setKeyBindingData() call
|
||||
action.setUnvalidatedKeyBindingData(new KeyBindingData(newKs));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionContext context) {
|
||||
// no-op; this is a dummy!
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAddToPopup(ActionContext context) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabledForContext(ActionContext context) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
super.dispose();
|
||||
clientActions.clear();
|
||||
keyBindingOptions.removeOptionsChangeListener(this);
|
||||
}
|
||||
}
|
@ -483,6 +483,7 @@ public abstract class AbstractDockingTest extends AbstractGenericTest {
|
||||
/**
|
||||
* A convenience method to close all of the windows and frames that the current Java
|
||||
* windowing environment knows about
|
||||
*
|
||||
* @deprecated instead call the new {@link #closeAllWindows()}
|
||||
*/
|
||||
@Deprecated
|
||||
@ -1136,6 +1137,39 @@ public abstract class AbstractDockingTest extends AbstractGenericTest {
|
||||
return CollectionUtils.any(actions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the action by the given owner name and action name.
|
||||
* If you do not know the owner name, then use
|
||||
* the call {@link #getActions(DockingTool, String)} instead.
|
||||
*
|
||||
* <P>Note: more specific test case subclasses provide other methods for finding actions
|
||||
* when you have an owner name (which is usually the plugin name).
|
||||
*
|
||||
* @param tool the tool containing all system actions
|
||||
* @param owner the owner of the action
|
||||
* @param name the name to match
|
||||
* @return the matching action; null if no matching action can be found
|
||||
*/
|
||||
public static DockingActionIf getAction(DockingTool tool, String owner, String name) {
|
||||
String fullName = name + " (" + owner + ")";
|
||||
List<DockingActionIf> actions = tool.getDockingActionsByFullActionName(fullName);
|
||||
if (actions.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (actions.size() > 1) {
|
||||
// This shouldn't happen
|
||||
throw new AssertionFailedError(
|
||||
"Found more than one action for name '" + fullName + "'");
|
||||
}
|
||||
|
||||
return CollectionUtils.any(actions);
|
||||
}
|
||||
|
||||
public static DockingActionIf getLocalAction(ComponentProvider provider, String actionName) {
|
||||
return getAction(provider.getTool(), provider.getName(), actionName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the given dialog's action that has the given name
|
||||
*
|
||||
@ -1417,8 +1451,8 @@ public abstract class AbstractDockingTest extends AbstractGenericTest {
|
||||
/**
|
||||
* Simulates a user initiated keystroke using the keybinding of the given action
|
||||
*
|
||||
* @param destination the action's destination component
|
||||
* @param action The action to simulate pressing.
|
||||
* @param destination the component for the action being executed
|
||||
* @param action The action to simulate pressing
|
||||
*/
|
||||
public static void triggerActionKey(Component destination, DockingActionIf action) {
|
||||
|
||||
|
@ -0,0 +1,289 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package docking.actions;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.swing.KeyStroke;
|
||||
|
||||
import org.apache.commons.collections4.IterableUtils;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import docking.*;
|
||||
import docking.action.*;
|
||||
import docking.test.AbstractDockingTest;
|
||||
import docking.tool.util.DockingToolConstants;
|
||||
import ghidra.framework.options.ToolOptions;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.SpyErrorLogger;
|
||||
|
||||
public class SharedKeybindingDockingActionTest extends AbstractDockingTest {
|
||||
|
||||
private static final String SHARED_NAME = "Shared Action Name";
|
||||
private static final String SHARED_OWNER = SharedStubKeyBindingAction.SHARED_OWNER;
|
||||
|
||||
// format: getName() + " (" + getOwner() + ")";
|
||||
private static final String SHARED_FULL_NAME = SHARED_NAME + " (" + SHARED_OWNER + ")";
|
||||
|
||||
private static final KeyStroke DEFAULT_KS_1 = KeyStroke.getKeyStroke(KeyEvent.VK_A, 0);
|
||||
private static final KeyStroke DEFAULT_KS_DIFFERENT_THAN_1 =
|
||||
KeyStroke.getKeyStroke(KeyEvent.VK_B, 0);
|
||||
private static final String OWNER_1 = "Owner1";
|
||||
private static final String OWNER_2 = "Owner2";
|
||||
|
||||
private SpyErrorLogger spyLogger = new SpyErrorLogger();
|
||||
|
||||
private DockingTool tool;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
tool = new FakeDockingTool();
|
||||
|
||||
Msg.setErrorLogger(spyLogger);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSharedKeyBinding_SameDefaultKeyBindings() {
|
||||
|
||||
TestAction action1 = new TestAction(OWNER_1, DEFAULT_KS_1);
|
||||
TestAction action2 = new TestAction(OWNER_2, DEFAULT_KS_1);
|
||||
|
||||
tool.addAction(action1);
|
||||
tool.addAction(action2);
|
||||
|
||||
assertNoLoggedMessages();
|
||||
assertKeyBinding(action1, DEFAULT_KS_1);
|
||||
assertKeyBinding(action2, DEFAULT_KS_1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSharedKeyBinding_OptionsChange() {
|
||||
|
||||
TestAction action1 = new TestAction(OWNER_1, DEFAULT_KS_1);
|
||||
TestAction action2 = new TestAction(OWNER_2, DEFAULT_KS_1);
|
||||
|
||||
tool.addAction(action1);
|
||||
tool.addAction(action2);
|
||||
|
||||
KeyStroke newKs = KeyStroke.getKeyStroke(KeyEvent.VK_Z, 0);
|
||||
setSharedKeyBinding(newKs);
|
||||
|
||||
assertNoLoggedMessages();
|
||||
assertKeyBinding(action1, newKs);
|
||||
assertKeyBinding(action2, newKs);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSharedKeyBinding_DifferentDefaultKeyBindings() {
|
||||
|
||||
TestAction action1 = new TestAction(OWNER_1, DEFAULT_KS_1);
|
||||
TestAction action2 = new TestAction(OWNER_2, DEFAULT_KS_DIFFERENT_THAN_1);
|
||||
|
||||
tool.addAction(action1);
|
||||
tool.addAction(action2);
|
||||
|
||||
// both bindings should keep the first one that was set when they are different
|
||||
assertImproperDefaultBindingMessage();
|
||||
assertKeyBinding(action1, DEFAULT_KS_1);
|
||||
assertKeyBinding(action2, DEFAULT_KS_1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSharedKeyBinding_NoDefaultKeyBindings() {
|
||||
|
||||
TestAction action1 = new TestAction(OWNER_1, null);
|
||||
TestAction action2 = new TestAction(OWNER_2, null);
|
||||
|
||||
tool.addAction(action1);
|
||||
tool.addAction(action2);
|
||||
|
||||
// both bindings are null; this is allowed
|
||||
assertNoLoggedMessages();
|
||||
assertKeyBinding(action1, null);
|
||||
assertKeyBinding(action2, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSharedKeyBinding_OneDefaultOneUndefinedDefaultKeyBinding() {
|
||||
TestAction action1 = new TestAction(OWNER_1, DEFAULT_KS_1);
|
||||
TestAction action2 = new TestAction(OWNER_2, null);
|
||||
|
||||
tool.addAction(action1);
|
||||
tool.addAction(action2);
|
||||
|
||||
// both bindings should keep the first one that was set when they are different
|
||||
assertImproperDefaultBindingMessage();
|
||||
assertKeyBinding(action1, DEFAULT_KS_1);
|
||||
assertKeyBinding(action2, DEFAULT_KS_1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSharedKeyBinding_RemoveAction() {
|
||||
|
||||
TestAction action1 = new TestAction(OWNER_1, DEFAULT_KS_1);
|
||||
TestAction action2 = new TestAction(OWNER_2, DEFAULT_KS_1);
|
||||
|
||||
tool.addAction(action1);
|
||||
tool.addAction(action2);
|
||||
|
||||
tool.removeAction(action1);
|
||||
|
||||
assertActionNotInTool(action1);
|
||||
assertActionInTool(action2);
|
||||
|
||||
tool.removeAction(action2);
|
||||
assertActionNotInTool(action2);
|
||||
|
||||
String sharedName = action1.getFullName();
|
||||
assertNoSharedKeyBindingStubInstalled(sharedName);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSharedKeyBinding_AddSameActionTwice() {
|
||||
|
||||
TestAction action1 = new TestAction(OWNER_1, DEFAULT_KS_1);
|
||||
|
||||
tool.addAction(action1);
|
||||
tool.addAction(action1);
|
||||
|
||||
assertOnlyOneVersionOfActionInTool(action1);
|
||||
|
||||
assertNoLoggedMessages();
|
||||
assertKeyBinding(action1, DEFAULT_KS_1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSharedKeyBinding_OnlyOneEntryInOptions() {
|
||||
|
||||
TestAction action1 = new TestAction(OWNER_1, DEFAULT_KS_1);
|
||||
TestAction action2 = new TestAction(OWNER_2, DEFAULT_KS_1);
|
||||
|
||||
tool.addAction(action1);
|
||||
tool.addAction(action2);
|
||||
|
||||
// verify that the actions are not in the options, but that the shared action is
|
||||
ToolOptions keyOptions = tool.getOptions(DockingToolConstants.KEY_BINDINGS);
|
||||
List<String> names = keyOptions.getOptionNames();
|
||||
assertTrue(names.contains(SHARED_FULL_NAME));
|
||||
assertFalse(names.contains(action1.getFullName()));
|
||||
assertFalse(names.contains(action2.getFullName()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSharedKeyBinding_AddActionAfterOptionHasChanged() {
|
||||
|
||||
TestAction action1 = new TestAction(OWNER_1, DEFAULT_KS_1);
|
||||
TestAction action2 = new TestAction(OWNER_2, DEFAULT_KS_1);
|
||||
|
||||
tool.addAction(action1);
|
||||
KeyStroke newKs = KeyStroke.getKeyStroke(KeyEvent.VK_Z, 0);
|
||||
setSharedKeyBinding(newKs);
|
||||
|
||||
assertKeyBinding(action1, newKs);
|
||||
|
||||
// verify the newly added keybinding gets the newly changed option
|
||||
tool.addAction(action2);
|
||||
assertKeyBinding(action1, newKs);
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// Private Methods
|
||||
//==================================================================================================
|
||||
|
||||
private void assertOnlyOneVersionOfActionInTool(TestAction action) {
|
||||
Set<DockingActionIf> actions = getActions(tool, action.getName());
|
||||
assertEquals("There should be only one instance of this action in the tool: " + action, 1,
|
||||
actions.size());
|
||||
}
|
||||
|
||||
private void assertActionInTool(TestAction action) {
|
||||
|
||||
Set<DockingActionIf> actions = getActions(tool, action.getName());
|
||||
for (DockingActionIf toolAction : actions) {
|
||||
if (toolAction == action) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
fail("Action is not in the tool: " + action);
|
||||
}
|
||||
|
||||
private void assertActionNotInTool(TestAction action) {
|
||||
Set<DockingActionIf> actions = getActions(tool, action.getName());
|
||||
for (DockingActionIf toolAction : actions) {
|
||||
assertNotSame(toolAction, action);
|
||||
}
|
||||
}
|
||||
|
||||
private void assertNoSharedKeyBindingStubInstalled(String sharedName) {
|
||||
|
||||
List<DockingActionIf> actions = tool.getDockingActionsByFullActionName(sharedName);
|
||||
assertTrue("There should be no actions registered for '" + sharedName + "'",
|
||||
actions.isEmpty());
|
||||
}
|
||||
|
||||
private void setSharedKeyBinding(KeyStroke newKs) {
|
||||
ToolOptions options = getKeyBindingOptions();
|
||||
runSwing(() -> options.setKeyStroke(SHARED_FULL_NAME, newKs));
|
||||
waitForSwing();
|
||||
}
|
||||
|
||||
private ToolOptions getKeyBindingOptions() {
|
||||
return tool.getOptions(DockingToolConstants.KEY_BINDINGS);
|
||||
}
|
||||
|
||||
private void assertNoLoggedMessages() {
|
||||
assertTrue("Spy logger not empty: " + spyLogger, IterableUtils.isEmpty(spyLogger));
|
||||
}
|
||||
|
||||
private void assertImproperDefaultBindingMessage() {
|
||||
spyLogger.assertLogMessage("shared", "key", "binding", "action", "different", "default");
|
||||
}
|
||||
|
||||
private void assertKeyBinding(TestAction action, KeyStroke expectedKs) {
|
||||
assertEquals(expectedKs, action.getKeyBinding());
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// Inner Classes
|
||||
//==================================================================================================
|
||||
|
||||
private class TestAction extends DockingAction {
|
||||
|
||||
public TestAction(String owner, KeyStroke ks) {
|
||||
super(SHARED_NAME, owner);
|
||||
|
||||
if (ks != null) {
|
||||
setKeyBindingData(new KeyBindingData(ks));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean usesSharedKeyBinding() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionContext context) {
|
||||
fail("Action performed should not have been called");
|
||||
}
|
||||
}
|
||||
}
|
@ -26,11 +26,7 @@ import org.junit.Test;
|
||||
|
||||
import generic.test.AbstractGenericTest;
|
||||
|
||||
public class DockingKeybindingActionTest extends AbstractGenericTest {
|
||||
|
||||
public DockingKeybindingActionTest() {
|
||||
super();
|
||||
}
|
||||
public class DockingActionKeybindingTest extends AbstractGenericTest {
|
||||
|
||||
@Test
|
||||
public void testKeybinding_Unmodified() {
|
@ -30,6 +30,7 @@ import docking.DockingUtils;
|
||||
import docking.KeyEntryTextField;
|
||||
import docking.action.DockingActionIf;
|
||||
import docking.action.KeyBindingData;
|
||||
import docking.tool.util.DockingToolConstants;
|
||||
import docking.util.KeyBindingUtils;
|
||||
import docking.widgets.MultiLineLabel;
|
||||
import docking.widgets.OptionDialog;
|
||||
@ -38,7 +39,6 @@ import docking.widgets.table.*;
|
||||
import ghidra.framework.options.Options;
|
||||
import ghidra.framework.options.ToolOptions;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.framework.plugintool.util.ToolConstants;
|
||||
import ghidra.util.HTMLUtilities;
|
||||
import ghidra.util.ReservedKeyBindings;
|
||||
import ghidra.util.exception.AssertException;
|
||||
@ -78,10 +78,6 @@ public class KeyBindingsPanel extends JPanel {
|
||||
private PropertyChangeListener propertyChangeListener;
|
||||
private GTableFilterPanel<DockingActionIf> tableFilterPanel;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param options options that have the key binding mappings.
|
||||
*/
|
||||
public KeyBindingsPanel(PluginTool tool, Options options) {
|
||||
this.tool = tool;
|
||||
this.options = options;
|
||||
@ -350,7 +346,7 @@ public class KeyBindingsPanel extends JPanel {
|
||||
// run this after the current pending events in the swing
|
||||
// thread so that the screen will repaint itself
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
ToolOptions keyBindingOptions = tool.getOptions(ToolConstants.KEY_BINDINGS);
|
||||
ToolOptions keyBindingOptions = tool.getOptions(DockingToolConstants.KEY_BINDINGS);
|
||||
KeyBindingUtils.exportKeyBindings(keyBindingOptions);
|
||||
});
|
||||
});
|
||||
@ -447,16 +443,6 @@ public class KeyBindingsPanel extends JPanel {
|
||||
selectionModel.addListSelectionListener(new TableSelectionListener());
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the keyMap and the actionMap and enable the apply button on
|
||||
* the dialog.
|
||||
* @param action plugin action could be null if ksName is not associated
|
||||
* with a plugin action
|
||||
* @param defaultActionName name of the action
|
||||
* @param ksName keystroke name
|
||||
* @return true if the old keystroke is different from the current
|
||||
* keystroke
|
||||
*/
|
||||
private boolean checkAction(String actionName, KeyStroke keyStroke) {
|
||||
String ksName = KeyEntryTextField.parseKeyStroke(keyStroke);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user