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;
|
package ghidra.test;
|
||||||
|
|
||||||
import java.awt.Window;
|
|
||||||
import java.awt.event.MouseEvent;
|
import java.awt.event.MouseEvent;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.*;
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
import docking.*;
|
import docking.DialogComponentProvider;
|
||||||
import docking.action.DockingActionIf;
|
import docking.action.DockingActionIf;
|
||||||
import docking.widgets.fieldpanel.FieldPanel;
|
import docking.widgets.fieldpanel.FieldPanel;
|
||||||
import docking.widgets.fieldpanel.field.Field;
|
import docking.widgets.fieldpanel.field.Field;
|
||||||
import docking.widgets.fieldpanel.listener.FieldMouseListener;
|
import docking.widgets.fieldpanel.listener.FieldMouseListener;
|
||||||
import docking.widgets.fieldpanel.support.FieldLocation;
|
import docking.widgets.fieldpanel.support.FieldLocation;
|
||||||
import ghidra.GhidraTestApplicationLayout;
|
import ghidra.GhidraTestApplicationLayout;
|
||||||
import ghidra.app.plugin.core.analysis.AutoAnalysisManager;
|
|
||||||
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
|
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
|
||||||
import ghidra.framework.ApplicationConfiguration;
|
import ghidra.framework.ApplicationConfiguration;
|
||||||
import ghidra.framework.GhidraApplicationConfiguration;
|
import ghidra.framework.GhidraApplicationConfiguration;
|
||||||
@ -41,10 +40,10 @@ import ghidra.program.model.listing.Program;
|
|||||||
import ghidra.util.TaskUtilities;
|
import ghidra.util.TaskUtilities;
|
||||||
import ghidra.util.exception.AssertException;
|
import ghidra.util.exception.AssertException;
|
||||||
import junit.framework.AssertionFailedError;
|
import junit.framework.AssertionFailedError;
|
||||||
import util.CollectionUtils;
|
|
||||||
import utility.application.ApplicationLayout;
|
import utility.application.ApplicationLayout;
|
||||||
|
|
||||||
public abstract class AbstractGhidraHeadedIntegrationTest extends AbstractGhidraHeadlessIntegrationTest {
|
public abstract class AbstractGhidraHeadedIntegrationTest
|
||||||
|
extends AbstractGhidraHeadlessIntegrationTest {
|
||||||
|
|
||||||
public AbstractGhidraHeadedIntegrationTest() {
|
public AbstractGhidraHeadedIntegrationTest() {
|
||||||
super();
|
super();
|
||||||
@ -113,42 +112,6 @@ public abstract class AbstractGhidraHeadedIntegrationTest extends AbstractGhidra
|
|||||||
return null;
|
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) {
|
public static PluginTool showTool(final PluginTool tool) {
|
||||||
runSwing(() -> {
|
runSwing(() -> {
|
||||||
boolean wasErrorGUIEnabled = isUseErrorGUI();
|
boolean wasErrorGUIEnabled = isUseErrorGUI();
|
||||||
@ -162,9 +125,7 @@ public abstract class AbstractGhidraHeadedIntegrationTest extends AbstractGhidra
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows the given DialogComponentProvider using the given tool's
|
* Shows the given DialogComponentProvider using the given tool's
|
||||||
* {@link PluginTool#showDialog(DialogComponentProvider)} method. After calling show on a
|
* {@link PluginTool#showDialog(DialogComponentProvider)} method.
|
||||||
* new thread the method will then wait for the dialog to be shown by calling
|
|
||||||
* {@link TestEnv#waitForDialogComponent(Window, Class, int)}.
|
|
||||||
*
|
*
|
||||||
* @param tool The tool used to show the given provider.
|
* @param tool The tool used to show the given provider.
|
||||||
* @param provider The DialogComponentProvider to show.
|
* @param provider The DialogComponentProvider to show.
|
||||||
@ -206,22 +167,8 @@ public abstract class AbstractGhidraHeadedIntegrationTest extends AbstractGhidra
|
|||||||
waitForSwing();
|
waitForSwing();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public static DockingActionIf getAction(Plugin plugin, String actionName) {
|
||||||
* @deprecated use {@link #waitForBusyTool(PluginTool)} instead
|
return getAction(plugin.getTool(), plugin.getName(), actionName);
|
||||||
*/
|
|
||||||
@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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -230,6 +177,7 @@ public abstract class AbstractGhidraHeadedIntegrationTest extends AbstractGhidra
|
|||||||
*
|
*
|
||||||
* @param project The project which with the tool is associated.
|
* @param project The project which with the tool is associated.
|
||||||
* @param tool The tool to be saved
|
* @param tool The tool to be saved
|
||||||
|
* @return the new tool
|
||||||
*/
|
*/
|
||||||
public static PluginTool saveTool(final Project project, final PluginTool tool) {
|
public static PluginTool saveTool(final Project project, final PluginTool tool) {
|
||||||
AtomicReference<PluginTool> ref = new AtomicReference<>();
|
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 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
|
* 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.
|
* 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 {
|
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.
|
* 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.
|
* @param newMenuData the MenuData to be used to put this action on the tool's menu bar
|
||||||
*/
|
*/
|
||||||
public void setMenuBarData(MenuData newMenuData) {
|
public void setMenuBarData(MenuData newMenuData) {
|
||||||
MenuBarData oldData = menuBarData;
|
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.
|
* 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.
|
* @param newMenuData the MenuData to be used to put this action on the tool's popup menu
|
||||||
*/
|
*/
|
||||||
public void setPopupMenuData(MenuData newMenuData) {
|
public void setPopupMenuData(MenuData newMenuData) {
|
||||||
PopupMenuData oldData = popupMenuData;
|
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.
|
* 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.
|
* @param newToolBarData the ToolBarData to be used to put this action on the tool's toolbar
|
||||||
*/
|
*/
|
||||||
public void setToolBarData(ToolBarData newToolBarData) {
|
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.
|
* @param newKeyBindingData the KeyBindingData to be used to assign this action to a keybinding.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
@ -339,10 +334,7 @@ public abstract class DockingAction implements DockingActionIf {
|
|||||||
/**
|
/**
|
||||||
* <b>Users creating actions should not call this method, but should instead call
|
* <b>Users creating actions should not call this method, but should instead call
|
||||||
* {@link #setKeyBindingData(KeyBindingData)}.</b>
|
* {@link #setKeyBindingData(KeyBindingData)}.</b>
|
||||||
* @param newKeyBindingData the 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
|
||||||
* @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).
|
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void setUnvalidatedKeyBindingData(KeyBindingData newKeyBindingData) {
|
public void setUnvalidatedKeyBindingData(KeyBindingData newKeyBindingData) {
|
||||||
@ -364,7 +356,7 @@ public abstract class DockingAction implements DockingActionIf {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the description to be used in the tooltip.
|
* 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) {
|
public void setDescription(String newDescription) {
|
||||||
if (SystemUtilities.isEqual(newDescription, description)) {
|
if (SystemUtilities.isEqual(newDescription, description)) {
|
||||||
|
@ -33,27 +33,30 @@ public interface DockingActionIf extends HelpDescriptor {
|
|||||||
public static final String TOOLBAR_DATA_PROPERTY = "ToolBar";
|
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
|
* @param listener The property change listener that will be notified of
|
||||||
* property change events.
|
* 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.
|
* 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
|
* @param listener The property change listener that will be notified of
|
||||||
* property change events.
|
* property change events.
|
||||||
* @see #addPropertyChangeListener(PropertyChangeListener)
|
* @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
|
* @param newValue true to enable the action, false to disable it
|
||||||
* disable it
|
* @return the enabled value of the action after this call
|
||||||
*/
|
*/
|
||||||
public boolean setEnabled(boolean newValue);
|
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)
|
* 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.
|
* method to actually perform the action logic for this action.
|
||||||
* @param context the {@link ActionContext} object that provides information about where and how
|
* @param context the {@link ActionContext} object that provides information about where and how
|
||||||
* this action was invoked.
|
* 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 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.
|
* method will only be called if the action has popup {@link PopupMenuData} set.
|
||||||
* <p>
|
* <p>
|
||||||
* Generally, actions don't need to override this method as the default implementation will
|
* 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
|
* defer to the {@link #isEnabledForContext(ActionContext)}, which will have the effect
|
||||||
* action to the popup only if it is enabled for a given context. By overriding this method,
|
* 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
|
* 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
|
* disabled for the context, by having this method return true even if the
|
||||||
* {@link #isEnabledForContext()} method will return false, resulting in the action appearing
|
* {@link #isEnabledForContext(ActionContext)} method will return false, resulting in the
|
||||||
* in the popup menu, but begin disabled.
|
* action appearing in the popup menu, but begin disabled.
|
||||||
*
|
*
|
||||||
* @param context the {@link ActionContext} from the active provider.
|
* @param context the {@link ActionContext} from the active provider.
|
||||||
* @return true if this action is appropriate for the given context.
|
* @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
|
* 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.
|
* @param context the {@link ActionContext} from the active provider.
|
||||||
* @return true if this action is appropriate for the given context.
|
* @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
|
* 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
|
* If you want a global action to only work on the global context, then override this method
|
||||||
* and return false.
|
* 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.
|
* @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.
|
* 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.
|
* @param context the current {@link ActionContext} for the window.
|
||||||
* @return true if the action should be enabled for the context or false otherwise.
|
* @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
|
* Returns a string that includes source file and line number information of where
|
||||||
* created.
|
* 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
|
* 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
|
* @param keyBindingData if non-null, assigns a keybinding to the action. Otherwise, removes
|
||||||
* any keybinding from the action.
|
* 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
|
* <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
|
* {@link #setKeyBindingData(KeyBindingData)} so that keybindings are set exactly as they
|
||||||
* are given.
|
* are given.
|
||||||
*
|
*
|
||||||
* @param newKeyBindingData the 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
|
||||||
* @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).
|
|
||||||
*/
|
*/
|
||||||
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.*;
|
||||||
import docking.action.*;
|
import docking.action.*;
|
||||||
import docking.tool.util.DockingToolConstants;
|
import docking.tool.util.DockingToolConstants;
|
||||||
import ghidra.framework.options.OptionType;
|
import ghidra.framework.options.*;
|
||||||
import ghidra.framework.options.Options;
|
|
||||||
import ghidra.util.exception.AssertException;
|
import ghidra.util.exception.AssertException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -34,8 +33,9 @@ import ghidra.util.exception.AssertException;
|
|||||||
public class DockingToolActionManager implements PropertyChangeListener {
|
public class DockingToolActionManager implements PropertyChangeListener {
|
||||||
|
|
||||||
private DockingWindowManager winMgr;
|
private DockingWindowManager winMgr;
|
||||||
private Map<String, List<DockingActionIf>> actionMap;
|
private Map<String, List<DockingActionIf>> actionMap = new HashMap<>();
|
||||||
private Options keyBindingOptions;
|
private Map<String, SharedStubKeyBindingAction> sharedActionMap = new HashMap<>();
|
||||||
|
private ToolOptions keyBindingOptions;
|
||||||
private DockingTool dockingTool;
|
private DockingTool dockingTool;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -48,7 +48,6 @@ public class DockingToolActionManager implements PropertyChangeListener {
|
|||||||
public DockingToolActionManager(DockingTool tool, DockingWindowManager windowManager) {
|
public DockingToolActionManager(DockingTool tool, DockingWindowManager windowManager) {
|
||||||
this.dockingTool = tool;
|
this.dockingTool = tool;
|
||||||
this.winMgr = windowManager;
|
this.winMgr = windowManager;
|
||||||
actionMap = new HashMap<>();
|
|
||||||
keyBindingOptions = tool.getOptions(DockingToolConstants.KEY_BINDINGS);
|
keyBindingOptions = tool.getOptions(DockingToolConstants.KEY_BINDINGS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,18 +87,47 @@ public class DockingToolActionManager implements PropertyChangeListener {
|
|||||||
public synchronized void addToolAction(DockingActionIf action) {
|
public synchronized void addToolAction(DockingActionIf action) {
|
||||||
action.addPropertyChangeListener(this);
|
action.addPropertyChangeListener(this);
|
||||||
addActionToMap(action);
|
addActionToMap(action);
|
||||||
if (action.isKeyBindingManaged()) {
|
setKeyBindingOption(action);
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
winMgr.addToolAction(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
|
* Removes the given action from the tool
|
||||||
* @param action the action to be removed.
|
* @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)"
|
* @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
|
* @return list of actions; empty if no action exists with the given name
|
||||||
*/
|
*/
|
||||||
public List<DockingActionIf> getDockingActionsByFullActionName(String fullActionName) {
|
public List<DockingActionIf> getDockingActionsByFullActionName(String fullName) {
|
||||||
List<DockingActionIf> list = actionMap.get(fullActionName);
|
List<DockingActionIf> list = actionMap.get(fullName);
|
||||||
if (list == null) {
|
if (list == null) {
|
||||||
return new ArrayList<>();
|
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
|
* A convenience method to close all of the windows and frames that the current Java
|
||||||
* windowing environment knows about
|
* windowing environment knows about
|
||||||
|
*
|
||||||
* @deprecated instead call the new {@link #closeAllWindows()}
|
* @deprecated instead call the new {@link #closeAllWindows()}
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
@ -1136,6 +1137,39 @@ public abstract class AbstractDockingTest extends AbstractGenericTest {
|
|||||||
return CollectionUtils.any(actions);
|
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
|
* 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
|
* Simulates a user initiated keystroke using the keybinding of the given action
|
||||||
*
|
*
|
||||||
* @param destination the action's destination component
|
* @param destination the component for the action being executed
|
||||||
* @param action The action to simulate pressing.
|
* @param action The action to simulate pressing
|
||||||
*/
|
*/
|
||||||
public static void triggerActionKey(Component destination, DockingActionIf action) {
|
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;
|
import generic.test.AbstractGenericTest;
|
||||||
|
|
||||||
public class DockingKeybindingActionTest extends AbstractGenericTest {
|
public class DockingActionKeybindingTest extends AbstractGenericTest {
|
||||||
|
|
||||||
public DockingKeybindingActionTest() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testKeybinding_Unmodified() {
|
public void testKeybinding_Unmodified() {
|
@ -30,6 +30,7 @@ import docking.DockingUtils;
|
|||||||
import docking.KeyEntryTextField;
|
import docking.KeyEntryTextField;
|
||||||
import docking.action.DockingActionIf;
|
import docking.action.DockingActionIf;
|
||||||
import docking.action.KeyBindingData;
|
import docking.action.KeyBindingData;
|
||||||
|
import docking.tool.util.DockingToolConstants;
|
||||||
import docking.util.KeyBindingUtils;
|
import docking.util.KeyBindingUtils;
|
||||||
import docking.widgets.MultiLineLabel;
|
import docking.widgets.MultiLineLabel;
|
||||||
import docking.widgets.OptionDialog;
|
import docking.widgets.OptionDialog;
|
||||||
@ -38,7 +39,6 @@ import docking.widgets.table.*;
|
|||||||
import ghidra.framework.options.Options;
|
import ghidra.framework.options.Options;
|
||||||
import ghidra.framework.options.ToolOptions;
|
import ghidra.framework.options.ToolOptions;
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
import ghidra.framework.plugintool.util.ToolConstants;
|
|
||||||
import ghidra.util.HTMLUtilities;
|
import ghidra.util.HTMLUtilities;
|
||||||
import ghidra.util.ReservedKeyBindings;
|
import ghidra.util.ReservedKeyBindings;
|
||||||
import ghidra.util.exception.AssertException;
|
import ghidra.util.exception.AssertException;
|
||||||
@ -78,10 +78,6 @@ public class KeyBindingsPanel extends JPanel {
|
|||||||
private PropertyChangeListener propertyChangeListener;
|
private PropertyChangeListener propertyChangeListener;
|
||||||
private GTableFilterPanel<DockingActionIf> tableFilterPanel;
|
private GTableFilterPanel<DockingActionIf> tableFilterPanel;
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
* @param options options that have the key binding mappings.
|
|
||||||
*/
|
|
||||||
public KeyBindingsPanel(PluginTool tool, Options options) {
|
public KeyBindingsPanel(PluginTool tool, Options options) {
|
||||||
this.tool = tool;
|
this.tool = tool;
|
||||||
this.options = options;
|
this.options = options;
|
||||||
@ -350,7 +346,7 @@ public class KeyBindingsPanel extends JPanel {
|
|||||||
// run this after the current pending events in the swing
|
// run this after the current pending events in the swing
|
||||||
// thread so that the screen will repaint itself
|
// thread so that the screen will repaint itself
|
||||||
SwingUtilities.invokeLater(() -> {
|
SwingUtilities.invokeLater(() -> {
|
||||||
ToolOptions keyBindingOptions = tool.getOptions(ToolConstants.KEY_BINDINGS);
|
ToolOptions keyBindingOptions = tool.getOptions(DockingToolConstants.KEY_BINDINGS);
|
||||||
KeyBindingUtils.exportKeyBindings(keyBindingOptions);
|
KeyBindingUtils.exportKeyBindings(keyBindingOptions);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -447,16 +443,6 @@ public class KeyBindingsPanel extends JPanel {
|
|||||||
selectionModel.addListSelectionListener(new TableSelectionListener());
|
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) {
|
private boolean checkAction(String actionName, KeyStroke keyStroke) {
|
||||||
String ksName = KeyEntryTextField.parseKeyStroke(keyStroke);
|
String ksName = KeyEntryTextField.parseKeyStroke(keyStroke);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user