GT-2925 - Key Bindings - Support Window Menu Provider Key Bindings -

Step 1 - basic plumbing and tests
This commit is contained in:
dragonmacher 2019-06-21 18:13:37 -04:00
parent ed0a441862
commit 8c11c1eebd
50 changed files with 1180 additions and 843 deletions

View file

@ -23,6 +23,8 @@ import java.util.Map.Entry;
import javax.swing.*; import javax.swing.*;
import org.apache.commons.lang3.StringUtils;
import docking.*; import docking.*;
import docking.action.ToggleDockingAction; import docking.action.ToggleDockingAction;
import docking.action.ToolBarData; import docking.action.ToolBarData;
@ -34,7 +36,6 @@ import docking.widgets.filter.FilterTextField;
import docking.widgets.label.GLabel; import docking.widgets.label.GLabel;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.util.HelpLocation; import ghidra.util.HelpLocation;
import ghidra.util.StringUtilities;
import ghidra.util.task.SwingUpdateManager; import ghidra.util.task.SwingUpdateManager;
import resources.Icons; import resources.Icons;
@ -351,7 +352,7 @@ class FilterAction extends ToggleDockingAction {
String curType = itr.next(); String curType = itr.next();
Boolean lEnabled = typeEnabledMap.get(curType); Boolean lEnabled = typeEnabledMap.get(curType);
StringBuffer buildMetaCurTypeBuff = new StringBuffer(curType); StringBuffer buildMetaCurTypeBuff = new StringBuffer(curType);
int firstIndex = StringUtilities.indexOfIgnoreCase(curType, filteredText, 0); int firstIndex = StringUtils.indexOfIgnoreCase(curType, filteredText, 0);
int lastIndex = firstIndex + filteredText.length(); int lastIndex = firstIndex + filteredText.length();
buildMetaCurTypeBuff.insert(lastIndex, "</b>");//THIS MUST ALWAYS COME BEFORE FIRST INDEX (FOR NO MATH on INDEX) buildMetaCurTypeBuff.insert(lastIndex, "</b>");//THIS MUST ALWAYS COME BEFORE FIRST INDEX (FOR NO MATH on INDEX)
buildMetaCurTypeBuff.insert(firstIndex, "<b>"); buildMetaCurTypeBuff.insert(firstIndex, "<b>");
@ -409,7 +410,7 @@ class FilterAction extends ToggleDockingAction {
while (iteratorIndex.hasNext()) { while (iteratorIndex.hasNext()) {
Entry<String, Boolean> entry = iteratorIndex.next(); Entry<String, Boolean> entry = iteratorIndex.next();
String checkboxName = entry.getKey(); String checkboxName = entry.getKey();
if (StringUtilities.containsIgnoreCase(checkboxName, filteredText)) { if (StringUtils.containsIgnoreCase(checkboxName, filteredText)) {
checkboxNameList.add(checkboxName); checkboxNameList.add(checkboxName);
} }
} }

View file

@ -17,13 +17,14 @@ package ghidra.app.plugin.core.navigation.locationreferences;
import java.awt.Color; import java.awt.Color;
import org.apache.commons.lang3.StringUtils;
import docking.widgets.fieldpanel.support.Highlight; import docking.widgets.fieldpanel.support.Highlight;
import ghidra.app.util.viewer.field.*; import ghidra.app.util.viewer.field.*;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.data.Composite; import ghidra.program.model.data.Composite;
import ghidra.program.model.listing.Data; import ghidra.program.model.listing.Data;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.util.StringUtilities;
import ghidra.util.datastruct.Accumulator; import ghidra.util.datastruct.Accumulator;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
@ -115,7 +116,7 @@ public class GenericCompositeDataTypeLocationDescriptor extends GenericDataTypeL
else if (OperandFieldFactory.class.isAssignableFrom(fieldFactoryClass)) { else if (OperandFieldFactory.class.isAssignableFrom(fieldFactoryClass)) {
// Not sure how to get the correct part of the text. This is a hack for now. // Not sure how to get the correct part of the text. This is a hack for now.
int offset = StringUtilities.indexOfIgnoreCase(text, typeAndFieldName, 0); int offset = StringUtils.indexOfIgnoreCase(text, typeAndFieldName, 0);
if (offset != -1) { if (offset != -1) {
return new Highlight[] { return new Highlight[] {
new Highlight(offset, offset + typeAndFieldName.length() - 1, highlightColor) }; new Highlight(offset, offset + typeAndFieldName.length() - 1, highlightColor) };

View file

@ -28,6 +28,7 @@ import javax.swing.KeyStroke;
import docking.*; import docking.*;
import docking.action.*; import docking.action.*;
import docking.actions.KeyBindingUtils;
import docking.widgets.table.GTable; import docking.widgets.table.GTable;
import generic.jar.ResourceFile; import generic.jar.ResourceFile;
import ghidra.app.script.GhidraScriptUtil; import ghidra.app.script.GhidraScriptUtil;
@ -92,7 +93,7 @@ class GhidraScriptActionManager {
action.setKeyBindingData(null); action.setKeyBindingData(null);
} }
else { else {
KeyStroke stroke = DockingKeyBindingAction.parseKeyStroke(strokeStr); KeyStroke stroke = KeyBindingUtils.parseKeyStroke(strokeStr);
if (stroke == null) { if (stroke == null) {
break; break;
} }
@ -137,7 +138,7 @@ class GhidraScriptActionManager {
saveState.putString(scriptFile.getName(), ""); saveState.putString(scriptFile.getName(), "");
} }
else { else {
String strokeStr = DockingKeyBindingAction.parseKeyStroke(stroke); String strokeStr = KeyBindingUtils.parseKeyStroke(stroke);
saveState.putString(scriptFile.getName(), strokeStr); saveState.putString(scriptFile.getName(), strokeStr);
} }
} }

View file

@ -17,7 +17,7 @@ package ghidra.app.plugin.core.script;
import javax.swing.KeyStroke; import javax.swing.KeyStroke;
import docking.DockingKeyBindingAction; import docking.actions.KeyBindingUtils;
class KeyBindingsInfo implements Comparable<KeyBindingsInfo> { class KeyBindingsInfo implements Comparable<KeyBindingsInfo> {
boolean hasAction; boolean hasAction;
@ -31,7 +31,7 @@ class KeyBindingsInfo implements Comparable<KeyBindingsInfo> {
KeyBindingsInfo(boolean hasAction, KeyStroke stroke) { KeyBindingsInfo(boolean hasAction, KeyStroke stroke) {
this.hasAction = hasAction; this.hasAction = hasAction;
this.keystroke = stroke == null ? "" : DockingKeyBindingAction.parseKeyStroke(stroke); this.keystroke = stroke == null ? "" : KeyBindingUtils.parseKeyStroke(stroke);
} }
@Override @Override

View file

@ -28,7 +28,7 @@ import javax.swing.KeyStroke;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import docking.DockingKeyBindingAction; import docking.actions.KeyBindingUtils;
import generic.jar.ResourceFile; import generic.jar.ResourceFile;
import ghidra.util.HTMLUtilities; import ghidra.util.HTMLUtilities;
import ghidra.util.Msg; import ghidra.util.Msg;
@ -318,7 +318,7 @@ public class ScriptInfo {
} }
} }
keyBinding = DockingKeyBindingAction.parseKeyStroke(buildy.toString()); keyBinding = KeyBindingUtils.parseKeyStroke(buildy.toString());
if (keyBinding == null) { if (keyBinding == null) {
// note: this message will be cleared by the parseHeader() method // note: this message will be cleared by the parseHeader() method
keybindingErrorMessage = "Unable to parse keybinding: " + buildy; keybindingErrorMessage = "Unable to parse keybinding: " + buildy;
@ -479,7 +479,7 @@ public class ScriptInfo {
} }
return ""; return "";
} }
return DockingKeyBindingAction.parseKeyStroke(keyStroke); return KeyBindingUtils.parseKeyStroke(keyStroke);
} }
private String toToolTip(String string) { private String toToolTip(String string) {

View file

@ -0,0 +1,375 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package docking;
import static org.junit.Assert.*;
import java.awt.event.ActionEvent;
import java.util.Set;
import javax.swing.*;
import org.junit.*;
import docking.action.DockingActionIf;
import docking.actions.KeyEntryDialog;
import docking.tool.util.DockingToolConstants;
import ghidra.framework.options.ToolOptions;
import ghidra.framework.plugintool.PluginTool;
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
import ghidra.test.TestEnv;
import ghidra.util.Msg;
import ghidra.util.SpyErrorLogger;
import resources.Icons;
import resources.ResourceManager;
public class ComponentProviderActionsTest extends AbstractGhidraHeadedIntegrationTest {
// note: this has to happen after the test framework is initialized, so it cannot be static
private final Icon ICON = ResourceManager.loadImage("images/refresh.png");
private static final String PROVIDER_NAME = "Test Action Provider";
private static final KeyStroke CONTROL_T =
KeyStroke.getKeyStroke(Character.valueOf('t'), DockingUtils.CONTROL_KEY_MODIFIER_MASK);
private TestEnv env;
private PluginTool tool;
private TestActionsComponentProvider provider;
private SpyErrorLogger spyLogger = new SpyErrorLogger();
@Before
public void setUp() throws Exception {
env = new TestEnv();
tool = env.launchDefaultTool();
provider = new TestActionsComponentProvider(tool);
Msg.setErrorLogger(spyLogger);
}
@After
public void tearDown() {
env.dispose();
}
@Test
public void testIcon_WithIcon_BeforeAddedToTool() {
setIcon(ICON);
showProvider();
assertWindowMenuActionHasIcon(ICON);
}
@Test
public void testIcon_WithIcon_AfterAddedToTool() {
setIcon(null);
showProvider();
assertWindowMenuActionHasIcon(Icons.EMPTY_ICON);
setIcon(ICON);
assertWindowMenuActionHasIcon(ICON);
}
@Test
public void testIcon_WithoutIcon() {
showProvider();
assertWindowMenuActionHasIcon(Icons.EMPTY_ICON);
}
@Test
public void testSetKeyBinding_ViaDialog_FromWindowMenu() {
showProvider();
KeyStroke newKs = CONTROL_T;
setKeyBindingViaF4Dialog_FromWindowsMenu(newKs);
assertProviderKeyStroke(newKs);
assertOptionsKeyStroke(newKs);
assertMenuItemHasKeyStroke(newKs);
}
@Test
public void testSetKeyBinding_ViaOptions_WithoutToolbarAction() {
showProvider();
KeyStroke newKs = CONTROL_T;
setOptionsKeyStroke(newKs);
assertProviderKeyStroke(newKs);
assertOptionsKeyStroke(newKs);
assertMenuItemHasKeyStroke(newKs);
}
@Test
public void testSetKeyBinding_ViaOptions_WithToolbarAction() {
showProvider();
KeyStroke newKs = CONTROL_T;
setOptionsKeyStroke(newKs);
assertProviderKeyStroke(newKs);
assertOptionsKeyStroke(newKs);
assertMenuItemHasKeyStroke(newKs);
}
@Test
public void testSetKeyBinding_ViaDialog_FromToolBar() {
setToolbarIcon(ICON);
showProvider();
KeyStroke newKs = CONTROL_T;
setKeyBindingViaF4Dialog_FromToolsToolbar(newKs);
assertProviderKeyStroke(newKs);
assertOptionsKeyStroke(newKs);
assertMenuItemHasKeyStroke(newKs);
}
@Test
public void testSetKeyBinding_TransientProvider_CannotBeSetFromWindowMenu() {
switchToTransientProvider();
showProvider();
assertCannotShowKeyBindingDialog_FromWindowsMenu();
}
@Test
public void testSetKeyBinding_TransientProvider_CannotBeSetFromToolbar() {
switchToTransientProvider();
setErrorsExpected(true);
setToolbarIcon(ICON);
setErrorsExpected(false);
spyLogger.assertLogMessage("Transient", "not", "toolbar");
}
@Test
public void testSetIcon_NullIconWithToolbarAction() {
try {
setToolbarIcon(null);
fail("Expected an exception passing a null icon when specifying a toolbar action");
}
catch (Exception e) {
// expected
}
}
@Test
public void testChangeActionRelatedStateAfterConstruction_setTransient() {
setToolbarIcon(ICON);
showProvider(); // this creates the 'Show Provider' action
assertShowProviderActionIsInToolbar();
setErrorsExpected(true);
switchToTransientProvider();
setErrorsExpected(false);
assertShowProviderActionNotInToolbar();
spyLogger.assertLogMessage("Transient", "not", "toolbar");
}
//==================================================================================================
// Private Methods
//==================================================================================================
private void assertShowProviderActionIsInToolbar() {
assertNotNull("The 'Show Provider' action is not in the toolbar",
getToolbarShowProviderAction());
}
private void assertShowProviderActionNotInToolbar() {
assertNull("The 'Show Provider' action is in the toolbar", getToolbarShowProviderAction());
}
private void switchToTransientProvider() {
provider.setTransient();
}
private void showProvider() {
provider.addToTool();
tool.showComponentProvider(provider, true);
waitForSwing();
}
private void setIcon(Icon icon) {
runSwing(() -> provider.setIcon(icon));
}
private void setToolbarIcon(Icon icon) {
runSwing(() -> provider.setIcon(icon, true));
}
private DockingActionIf getShowProviderAction() {
DockingActionIf showProviderAction =
getAction(tool, provider.getOwner(), provider.getName());
assertNotNull("Could not find action to show ", showProviderAction);
return showProviderAction;
}
private DockingActionIf getWindowMenuShowProviderAction() {
waitForSwing();
DockingActionIf action = waitFor(() -> {
Set<DockingActionIf> actions = getActionsByName(tool, PROVIDER_NAME);
//@formatter:off
return actions
.stream()
.filter(a -> a.getOwner().equals(DockingWindowManager.DOCKING_WINDOWS_OWNER))
.findFirst()
.get()
;
//@formatter:on
});
assertNotNull("Window menu action not installed for provider", action);
assertTrue(action.getClass().getSimpleName().contains("ShowComponentAction"));
return action;
}
private DockingActionIf getToolbarShowProviderAction() {
DockingWindowManager dwm = tool.getWindowManager();
ActionToGuiMapper guiActions =
(ActionToGuiMapper) getInstanceField("actionToGuiMapper", dwm);
GlobalMenuAndToolBarManager toolbarManager =
(GlobalMenuAndToolBarManager) getInstanceField("menuAndToolBarManager", guiActions);
DockingActionIf action = provider.getShowProviderAction();
DockingActionIf toolbarAction = toolbarManager.getToolbarAction(action.getName());
return toolbarAction;
}
private void setOptionsKeyStroke(KeyStroke newKs) {
ToolOptions keyOptions = tool.getOptions(DockingToolConstants.KEY_BINDINGS);
// shared option name/format: "Provider Name (Tool)" - the shared action's owner is the Tool
runSwing(() -> keyOptions.setKeyStroke(provider.getName() + " (Tool)", newKs));
waitForSwing();
}
private void assertProviderKeyStroke(KeyStroke expectedKs) {
DockingActionIf action = getShowProviderAction();
KeyStroke actionKs = action.getKeyBinding();
assertEquals(expectedKs, actionKs);
}
private void assertOptionsKeyStroke(KeyStroke expectedKs) {
ToolOptions options = getKeyBindingOptions();
// Option name: the action name with the 'Tool' as the owner
String fullName = provider.getName() + " (Tool)";
KeyStroke optionsKs = runSwing(() -> options.getKeyStroke(fullName, null));
assertEquals("Key stroke in options does not match expected key stroke", expectedKs,
optionsKs);
}
private void assertWindowMenuActionHasIcon(Icon expected) {
DockingActionIf action = getWindowMenuShowProviderAction();
assertEquals("Windows menu icons for provider does not match the value set on the provider",
expected, action.getMenuBarData().getMenuIcon());
}
private void assertCannotShowKeyBindingDialog_FromWindowsMenu() {
// simulate the user mousing over the 'Window' menu's action
DockingActionIf windowMenuAction = getWindowMenuShowProviderAction();
DockingWindowManager.setMouseOverAction(windowMenuAction);
performLaunchKeyStrokeDialogAction();
DialogComponentProvider warningDialog = waitForDialogComponent("Unable to Set Keybinding");
close(warningDialog);
}
private void setKeyBindingViaF4Dialog_FromWindowsMenu(KeyStroke ks) {
// simulate the user mousing over the 'Window' menu's action
DockingActionIf windowMenuAction = getWindowMenuShowProviderAction();
DockingWindowManager.setMouseOverAction(windowMenuAction);
performLaunchKeyStrokeDialogAction();
KeyEntryDialog dialog = waitForDialogComponent(KeyEntryDialog.class);
runSwing(() -> dialog.setKeyStroke(ks));
pressButtonByText(dialog, "OK");
assertFalse("Invalid key stroke: " + ks, runSwing(() -> dialog.isVisible()));
}
private void assertMenuItemHasKeyStroke(KeyStroke expected) {
DockingActionIf action = getWindowMenuShowProviderAction();
assertEquals(
"Windows menu key binding for provider does not match the value of the provider",
expected, action.getKeyBinding());
}
private void setKeyBindingViaF4Dialog_FromToolsToolbar(KeyStroke ks) {
// simulate the user mousing over the 'Window' menu's action
DockingActionIf toolbarAction = getToolbarShowProviderAction();
assertNotNull("Provider action not installed in toolbar", toolbarAction);
DockingWindowManager.setMouseOverAction(toolbarAction);
performLaunchKeyStrokeDialogAction();
KeyEntryDialog dialog = waitForDialogComponent(KeyEntryDialog.class);
runSwing(() -> dialog.setKeyStroke(ks));
pressButtonByText(dialog, "OK");
assertFalse("Invalid key stroke: " + ks, runSwing(() -> dialog.isVisible()));
}
private void performLaunchKeyStrokeDialogAction() {
DockingWindowManager dwm = tool.getWindowManager();
ActionToGuiMapper actionMapper = dwm.getActionToGuiMapper();
Action action = actionMapper.getDockingKeyAction(KeyStroke.getKeyStroke("F4"));
assertNotNull(action);
runSwing(() -> action.actionPerformed(new ActionEvent(this, 0, "")), false);
}
private ToolOptions getKeyBindingOptions() {
return tool.getOptions(DockingToolConstants.KEY_BINDINGS);
}
private class TestActionsComponentProvider extends ComponentProvider {
private JComponent component = new JTextField("Hey!");
TestActionsComponentProvider(DockingTool tool) {
super(tool, PROVIDER_NAME, "Fooberry Plugin");
}
@Override
public JComponent getComponent() {
return component;
}
}
}

View file

@ -24,6 +24,7 @@ import javax.swing.*;
import org.junit.*; import org.junit.*;
import docking.*; import docking.*;
import docking.actions.KeyEntryDialog;
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin; import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
import ghidra.app.plugin.core.data.DataPlugin; import ghidra.app.plugin.core.data.DataPlugin;
import ghidra.app.plugin.core.function.FunctionPlugin; import ghidra.app.plugin.core.function.FunctionPlugin;

View file

@ -33,8 +33,8 @@ import javax.swing.tree.TreePath;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.junit.*; import org.junit.*;
import docking.DockingKeyBindingAction;
import docking.action.DockingActionIf; import docking.action.DockingActionIf;
import docking.actions.KeyBindingUtils;
import docking.options.editor.*; import docking.options.editor.*;
import docking.widgets.MultiLineLabel; import docking.widgets.MultiLineLabel;
import docking.widgets.filechooser.GhidraFileChooser; import docking.widgets.filechooser.GhidraFileChooser;
@ -712,7 +712,7 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
if (StringUtils.isBlank(keyBindingColumnValue)) { if (StringUtils.isBlank(keyBindingColumnValue)) {
return null; return null;
} }
return DockingKeyBindingAction.parseKeyStroke(keyBindingColumnValue); return KeyBindingUtils.parseKeyStroke(keyBindingColumnValue);
} }
private void assertOptionsKeyStroke(String actionName, String pluginName, KeyStroke value) private void assertOptionsKeyStroke(String actionName, String pluginName, KeyStroke value)

View file

@ -55,30 +55,16 @@ public class DummyTool implements Tool {
this.project = project; this.project = project;
} }
/**
* Sets the type name of the tool.
* @exception PropertyVetoException thrown if a VetoableChangeListener
* rejects the change
*/
@Override @Override
public void setToolName(String typeName) throws PropertyVetoException { public void setToolName(String typeName) throws PropertyVetoException {
name = typeName; name = typeName;
} }
/**
* Tells the tool to stop functioning and release its resources.
* Called by Session when it wants to dispose of a tool. The tool
* MUST NOT call System.exit() in response to this method call. Instead
* the tool should dispose of all its windows and other resources.
*/
@Override @Override
public void exit() { public void exit() {
//do nothing //do nothing
} }
/* (non-Javadoc)
* @see ghidra.framework.model.Tool#close()
*/
@Override @Override
public void close() { public void close() {
if (project != null) { if (project != null) {
@ -92,37 +78,21 @@ public class DummyTool implements Tool {
return true; return true;
} }
/**
* Associates a unique(within a session) name to a tool instance.
*/
@Override @Override
public void putInstanceName(String newInstanceName) { public void putInstanceName(String newInstanceName) {
this.instanceName = newInstanceName; this.instanceName = newInstanceName;
} }
/**
* Returns the name associated with the tool's type.
*/
@Override @Override
public String getToolName() { public String getToolName() {
return name; return name;
} }
/**
* Sets the tool visible or invisible. This method is used by
* the Session to make it's tools visible or invisible depending
* on whether or not the session this tool is in is the current Session.
*
* @param visibility true specifies that the tool should be visible.
*/
@Override @Override
public void setVisible(boolean visibility) { public void setVisible(boolean visibility) {
//do nothing //do nothing
} }
/**
* @see ghidra.framework.model.Tool#isVisible()
*/
@Override @Override
public boolean isVisible() { public boolean isVisible() {
return false; return false;
@ -133,238 +103,129 @@ public class DummyTool implements Tool {
//do nothing //do nothing
} }
/**
* returns a combination of the type name and the instance name of the
* form typename(instancename)
*/
@Override @Override
public String getName() { public String getName() {
return name + instanceName; return name + instanceName;
} }
/**
* Returns a list of eventNames that this Tool is interested in.
*/
@Override @Override
public String[] getConsumedToolEventNames() { public String[] getConsumedToolEventNames() {
return new String[] { "DummyToolEvent" }; return new String[] { "DummyToolEvent" };
} }
/**
* Returns the tool's unique name.
*/
@Override @Override
public String getInstanceName() { public String getInstanceName() {
return instanceName; return instanceName;
} }
/**
* Adds a ToolListener to be notified only for a specific ToolEvent.
*
* @param listener The ToolListener to be added.
* @param toolEvent The name of the desired event.
*/
public void addToolListener(ToolListener listener, String toolEvent) { public void addToolListener(ToolListener listener, String toolEvent) {
//do nothing //do nothing
} }
/**
* Adds a ToolListener to be notified only for a specific ToolEvent.
*
* @param listener The ToolListener to be added.
* @param toolEvent The name of the desired event.
*/
@Override @Override
public void addToolListener(ToolListener listener) { public void addToolListener(ToolListener listener) {
//do nothing //do nothing
} }
/**
* Returns the names of all the possible ToolEvents that this
* tool might generate. Used by the ConnectionManager to connect
* tools together.
*/
@Override @Override
public String[] getToolEventNames() { public String[] getToolEventNames() {
return new String[] { "DummyToolEvent" }; return new String[] { "DummyToolEvent" };
} }
/**
* Removes a ToolListener from receiving the specific event.
*
* @param listener The ToolListener to be removed.
* @param toolEvent The name of the event that no longer is of interest.
*/
public void removeToolListener(ToolListener listener, String toolEvent) { public void removeToolListener(ToolListener listener, String toolEvent) {
//do nothing //do nothing
} }
/**
* Removes a ToolListener from receiving the specific event.
*
* @param listener The ToolListener to be removed.
* @param toolEvent The name of the event that no longer is of interest.
*/
@Override @Override
public void removeToolListener(ToolListener listener) { public void removeToolListener(ToolListener listener) {
//do nothing //do nothing
} }
/**
* Check whether this tool has changed its configuration.
* This is called to check if a tool needs to save its state, when the
* tool exits or the session the tool is in closes.
*
* @return true if the tool's configuration has changed, false otherwise
*/
@Override @Override
public boolean hasConfigChanged() { public boolean hasConfigChanged() {
return false; return false;
} }
/**
* Add a change listener that is notified when a tool changes its state.
*/
public void addChangeListener(ChangeListener l) { public void addChangeListener(ChangeListener l) {
//do nothing //do nothing
} }
/**
* Add property change listener.
*/
@Override @Override
public void addPropertyChangeListener(PropertyChangeListener l) { public void addPropertyChangeListener(PropertyChangeListener l) {
//do nothing //do nothing
} }
/**
* Get the classes of the data types that this tool supports,
* i.e., what data types can be dropped onto this tool.
*/
@Override @Override
public Class<?>[] getSupportedDataTypes() { public Class<?>[] getSupportedDataTypes() {
return new Class[] { Program.class }; return new Class[] { Program.class };
} }
/**
* When the user drags a data file onto a tool, an event will be fired
* that the tool will respond to by accepting the data.
*
* @param data the data to be used by the running tool
*/
@Override @Override
public boolean acceptDomainFiles(DomainFile[] data) { public boolean acceptDomainFiles(DomainFile[] data) {
return true; return true;
} }
/**
* Get the domain files that this tool currently has open.
*/
@Override @Override
public DomainFile[] getDomainFiles() { public DomainFile[] getDomainFiles() {
return null; return null;
} }
/**
* Remove the change listener.
*/
public void removeChangeListener(ChangeListener l) { public void removeChangeListener(ChangeListener l) {
//do nothing //do nothing
} }
/**
* Tells tool to write its config state from the given output stream.
*/
@Override @Override
public void setConfigChanged(boolean changed) { public void setConfigChanged(boolean changed) {
//do nothing //do nothing
} }
/**
* Tells tool to write its config state from the given output stream.
*/
@Override @Override
public Element saveToXml(boolean includeConfigState) { public Element saveToXml(boolean includeConfigState) {
return null; return null;
} }
/**
* Tells tool to write its config state from the given output stream.
*/
@Override @Override
public Element saveDataStateToXml(boolean isTransactionState) { public Element saveDataStateToXml(boolean isTransactionState) {
return null; return null;
} }
/**
* Tells tool to write its config state from the given output stream.
*/
public void restoreFromXml(Element root) { public void restoreFromXml(Element root) {
//do nothing //do nothing
} }
/**
* Tells tool to write its config state from the given output stream.
*/
@Override @Override
public void restoreDataStateFromXml(Element root) { public void restoreDataStateFromXml(Element root) {
//do nothing //do nothing
} }
/**
* Remove property change listener.
*/
@Override @Override
public void removePropertyChangeListener(PropertyChangeListener l) { public void removePropertyChangeListener(PropertyChangeListener l) {
//do nothing //do nothing
} }
/**
* This method is invoked when the registered ToolEvent event occurs.
*
* @param toolEvent The ToolEvent.
*/
@Override @Override
public void processToolEvent(PluginEvent toolEvent) { public void processToolEvent(PluginEvent toolEvent) {
//do nothing //do nothing
} }
/**
* Fire the plugin event by notifying the event manager which
* calls the listeners.
*/
@Override @Override
public void firePluginEvent(PluginEvent event) { public void firePluginEvent(PluginEvent event) {
//do nothing //do nothing
} }
/**
* Get the description of the tool.
*/
public String getDescription() { public String getDescription() {
return description; return description;
} }
/**
* Set the description of the tool.
*/
public void setDescription(String description) { public void setDescription(String description) {
this.description = description; this.description = description;
} }
/**
* Set the icon for this tool configuration.
*/
@Override @Override
public void setIconURL(ToolIconURL iconURL) { public void setIconURL(ToolIconURL iconURL) {
this.iconURL = iconURL; this.iconURL = iconURL;
} }
/**
* Get the icon for this tool configuration.
*
* @return Icon
*/
@Override @Override
public ToolIconURL getIconURL() { public ToolIconURL getIconURL() {
return iconURL; return iconURL;
@ -380,9 +241,6 @@ public class DummyTool implements Tool {
return getToolTemplate(true); return getToolTemplate(true);
} }
/**
* @see ghidra.framework.model.Tool#enableClose()
*/
public void enableClose() { public void enableClose() {
//do nothing //do nothing
} }

View file

@ -24,6 +24,8 @@ import java.util.regex.*;
import javax.swing.JComponent; import javax.swing.JComponent;
import org.apache.commons.lang3.StringUtils;
import docking.widgets.SearchLocation; import docking.widgets.SearchLocation;
import docking.widgets.fieldpanel.Layout; import docking.widgets.fieldpanel.Layout;
import docking.widgets.fieldpanel.LayoutModel; import docking.widgets.fieldpanel.LayoutModel;
@ -37,7 +39,6 @@ import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.program.model.pcode.HighFunction; import ghidra.program.model.pcode.HighFunction;
import ghidra.util.Msg; import ghidra.util.Msg;
import ghidra.util.StringUtilities;
/** /**
* *
@ -522,7 +523,7 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener {
java.util.function.Function<String, SearchMatch> function = textLine -> { java.util.function.Function<String, SearchMatch> function = textLine -> {
int index = StringUtilities.indexOfIgnoreCase(textLine, searchString); int index = StringUtils.indexOfIgnoreCase(textLine, searchString);
if (index == -1) { if (index == -1) {
return SearchMatch.NO_MATCH; return SearchMatch.NO_MATCH;
} }
@ -534,7 +535,7 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener {
java.util.function.Function<String, SearchMatch> function = textLine -> { java.util.function.Function<String, SearchMatch> function = textLine -> {
int index = StringUtilities.lastIndexOfIgnoreCase(textLine, searchString); int index = StringUtils.lastIndexOfIgnoreCase(textLine, searchString);
if (index == -1) { if (index == -1) {
return SearchMatch.NO_MATCH; return SearchMatch.NO_MATCH;
} }

View file

@ -23,7 +23,7 @@ import javax.swing.JFrame;
import docking.action.DockingActionIf; import docking.action.DockingActionIf;
import docking.actions.ToolActions; import docking.actions.ToolActions;
import ghidra.framework.options.ToolOptions; import ghidra.framework.options.ToolOptions;
import ghidra.util.SystemUtilities; import ghidra.util.Swing;
/** /**
* A partial implementation of {@link DockingTool} that serves as a place to share common * A partial implementation of {@link DockingTool} that serves as a place to share common
@ -32,7 +32,7 @@ import ghidra.util.SystemUtilities;
public abstract class AbstractDockingTool implements DockingTool { public abstract class AbstractDockingTool implements DockingTool {
protected DockingWindowManager winMgr; protected DockingWindowManager winMgr;
protected ToolActions actionMgr; protected ToolActions toolActions;
protected Map<String, ToolOptions> optionsMap = new HashMap<>(); protected Map<String, ToolOptions> optionsMap = new HashMap<>();
protected boolean configChangedFlag; protected boolean configChangedFlag;
@ -57,17 +57,20 @@ public abstract class AbstractDockingTool implements DockingTool {
@Override @Override
public void addComponentProvider(ComponentProvider provider, boolean show) { public void addComponentProvider(ComponentProvider provider, boolean show) {
Runnable r = () -> winMgr.addComponent(provider, show); Runnable r = () -> {
SystemUtilities.runSwingNow(r); winMgr.addComponent(provider, show);
toolActions.addToolAction(provider.getShowProviderAction());
};
Swing.runNow(r);
} }
@Override @Override
public void removeComponentProvider(ComponentProvider provider) { public void removeComponentProvider(ComponentProvider provider) {
Runnable r = () -> { Runnable r = () -> {
actionMgr.removeComponentActions(provider); toolActions.removeComponentActions(provider);
winMgr.removeComponent(provider); winMgr.removeComponent(provider);
}; };
SystemUtilities.runSwingNow(r); Swing.runNow(r);
} }
@Override @Override
@ -96,41 +99,41 @@ public abstract class AbstractDockingTool implements DockingTool {
@Override @Override
public void addAction(DockingActionIf action) { public void addAction(DockingActionIf action) {
actionMgr.addToolAction(action); toolActions.addToolAction(action);
} }
@Override @Override
public void removeAction(DockingActionIf action) { public void removeAction(DockingActionIf action) {
actionMgr.removeToolAction(action); toolActions.removeToolAction(action);
} }
@Override @Override
public void addLocalAction(ComponentProvider provider, DockingActionIf action) { public void addLocalAction(ComponentProvider provider, DockingActionIf action) {
actionMgr.addLocalAction(provider, action); toolActions.addLocalAction(provider, action);
} }
@Override @Override
public void removeLocalAction(ComponentProvider provider, DockingActionIf action) { public void removeLocalAction(ComponentProvider provider, DockingActionIf action) {
actionMgr.removeProviderAction(provider, action); toolActions.removeProviderAction(provider, action);
} }
@Override @Override
public Set<DockingActionIf> getAllActions() { public Set<DockingActionIf> getAllActions() {
Set<DockingActionIf> actions = actionMgr.getAllActions(); Set<DockingActionIf> actions = toolActions.getAllActions();
ActionToGuiMapper am = winMgr.getActionManager(); ActionToGuiMapper am = winMgr.getActionToGuiMapper();
actions.addAll(am.getAllActions()); actions.addAll(am.getAllActions());
return actions; return actions;
} }
@Override @Override
public Set<DockingActionIf> getDockingActionsByOwnerName(String owner) { public Set<DockingActionIf> getDockingActionsByOwnerName(String owner) {
return actionMgr.getActions(owner); return toolActions.getActions(owner);
} }
@Override @Override
public void showComponentProvider(ComponentProvider provider, boolean visible) { public void showComponentProvider(ComponentProvider provider, boolean visible) {
Runnable r = () -> winMgr.showComponent(provider, visible); Runnable r = () -> winMgr.showComponent(provider, visible);
SystemUtilities.runSwingNow(r); Swing.runNow(r);
} }
@Override @Override
@ -150,7 +153,7 @@ public abstract class AbstractDockingTool implements DockingTool {
@Override @Override
public void toFront(ComponentProvider provider) { public void toFront(ComponentProvider provider) {
Runnable r = () -> winMgr.toFront(provider); Runnable r = () -> winMgr.toFront(provider);
SystemUtilities.runSwingNow(r); Swing.runNow(r);
} }
@Override @Override

View file

@ -18,6 +18,7 @@ package docking;
import java.util.Iterator; import java.util.Iterator;
import docking.action.DockingActionIf; import docking.action.DockingActionIf;
import docking.action.KeyBindingsManager;
/** /**
* A class that exists primarily to provide access to action-related package-level methods of the * A class that exists primarily to provide access to action-related package-level methods of the
@ -78,4 +79,20 @@ public class ActionToGuiHelper {
public void removeProviderAction(ComponentProvider provider, DockingActionIf action) { public void removeProviderAction(ComponentProvider provider, DockingActionIf action) {
windowManager.removeProviderAction(provider, action); windowManager.removeProviderAction(provider, action);
} }
/**
* Initializes key bindings manager of docking window manager
*
* @param keyBindingsManager key bindings manager
*/
public void setKeyBindingsManager(KeyBindingsManager keyBindingsManager) {
windowManager.setKeyBindingsManager(keyBindingsManager);
}
/**
* Call this method to signal that key bindings for one or more actions have changed
*/
public void keyBindingsChanged() {
windowManager.scheduleUpdate();
}
} }

View file

@ -30,26 +30,21 @@ import ghidra.util.*;
*/ */
public class ActionToGuiMapper { public class ActionToGuiMapper {
private HashSet<DockingActionIf> globalActions = new LinkedHashSet<>(); private static boolean enableDiagnosticActions;
private Set<DockingActionIf> globalActions = new LinkedHashSet<>();
private MenuHandler menuBarMenuHandler; private MenuHandler menuBarMenuHandler;
private MenuGroupMap menuGroupMap; private MenuGroupMap menuGroupMap;
private static boolean enableDiagnosticActions;
private KeyBindingsManager keyBindingsManager; private KeyBindingsManager keyBindingsManager;
private GlobalMenuAndToolBarManager menuAndToolBarManager; private GlobalMenuAndToolBarManager menuAndToolBarManager;
private PopupActionManager popupActionManager; private PopupActionManager popupActionManager;
private DockingAction keyBindingsAction;
ActionToGuiMapper(DockingWindowManager winMgr) { ActionToGuiMapper(DockingWindowManager winMgr, KeyBindingsManager keyBindingsManager) {
this.keyBindingsManager = keyBindingsManager;
menuGroupMap = new MenuGroupMap(); menuGroupMap = new MenuGroupMap();
menuBarMenuHandler = new MenuBarMenuHandler(winMgr); menuBarMenuHandler = new MenuBarMenuHandler(winMgr);
keyBindingsManager = new KeyBindingsManager(winMgr);
menuAndToolBarManager = menuAndToolBarManager =
new GlobalMenuAndToolBarManager(winMgr, menuBarMenuHandler, menuGroupMap); new GlobalMenuAndToolBarManager(winMgr, menuBarMenuHandler, menuGroupMap);
popupActionManager = new PopupActionManager(winMgr, menuGroupMap); popupActionManager = new PopupActionManager(winMgr, menuGroupMap);
@ -60,12 +55,10 @@ public class ActionToGuiMapper {
private void initializeHelpActions() { private void initializeHelpActions() {
DockingWindowsContextSensitiveHelpListener.install(); DockingWindowsContextSensitiveHelpListener.install();
keyBindingsAction = new KeyBindingAction(this);
keyBindingsManager.addReservedAction(new HelpAction(false, ReservedKeyBindings.HELP_KEY1)); keyBindingsManager.addReservedAction(new HelpAction(false, ReservedKeyBindings.HELP_KEY1));
keyBindingsManager.addReservedAction(new HelpAction(false, ReservedKeyBindings.HELP_KEY2)); keyBindingsManager.addReservedAction(new HelpAction(false, ReservedKeyBindings.HELP_KEY2));
keyBindingsManager.addReservedAction( keyBindingsManager.addReservedAction(
new HelpAction(true, ReservedKeyBindings.HELP_INFO_KEY)); new HelpAction(true, ReservedKeyBindings.HELP_INFO_KEY));
keyBindingsManager.addReservedAction(keyBindingsAction);
if (enableDiagnosticActions) { if (enableDiagnosticActions) {
keyBindingsManager.addReservedAction(new ShowFocusInfoAction()); keyBindingsManager.addReservedAction(new ShowFocusInfoAction());
@ -157,10 +150,6 @@ public class ActionToGuiMapper {
return actions; return actions;
} }
public Action getDockingKeyAction(KeyStroke keyStroke) {
return keyBindingsManager.getDockingKeyAction(keyStroke);
}
Set<DockingActionIf> getGlobalActions() { Set<DockingActionIf> getGlobalActions() {
return globalActions; return globalActions;
} }
@ -227,4 +216,8 @@ public class ActionToGuiMapper {
public void showPopupMenu(ComponentPlaceholder componentInfo, MouseEvent e) { public void showPopupMenu(ComponentPlaceholder componentInfo, MouseEvent e) {
popupActionManager.popupMenu(componentInfo, e); popupActionManager.popupMenu(componentInfo, e);
} }
Action getDockingKeyAction(KeyStroke keyStroke) {
return keyBindingsManager.getDockingKeyAction(keyStroke);
}
} }

View file

@ -25,9 +25,9 @@ import javax.swing.*;
import docking.action.*; import docking.action.*;
import docking.help.HelpDescriptor; import docking.help.HelpDescriptor;
import docking.help.HelpService; import docking.help.HelpService;
import ghidra.util.HelpLocation; import ghidra.util.*;
import ghidra.util.UniversalIdGenerator;
import ghidra.util.exception.AssertException; import ghidra.util.exception.AssertException;
import utilities.util.reflection.ReflectionUtilities;
/** /**
* Abstract base class for creating dockable GUI components within a tool. * Abstract base class for creating dockable GUI components within a tool.
@ -61,13 +61,25 @@ import ghidra.util.exception.AssertException;
* <li>{@link #componentActivated()} and {@link #componentDeactived()} * <li>{@link #componentActivated()} and {@link #componentDeactived()}
* <li>{@link #componentHidden()} and {@link #componentShown()} * <li>{@link #componentHidden()} and {@link #componentShown()}
* </ul> * </ul>
*
* <p> * <p>
* Note: This class was created so that implementors could add local actions within the constructor * <b><u>Show Provider Action</u></b> - Each provider has an action to show the provider. For
* typical, non-transient providers (see {@link #setTransient()}) the action will appear in
* the tool's <b>Window</b> menu. You can have your provider also appear in the tool's toolbar
* by calling {@link #setIcon(Icon, boolean)}, passing <code>true</code> for
* <code>isToolbarAction</code>.
* <p>
* Historical Note: This class was created so that implementors could add local actions within the constructor
* without having to understand that they must first add themselves to the WindowManager. * without having to understand that they must first add themselves to the WindowManager.
*/ */
public abstract class ComponentProvider implements HelpDescriptor, ActionContextProvider { public abstract class ComponentProvider implements HelpDescriptor, ActionContextProvider {
private static final String TRANSIENT_PROVIDER_TOOLBAR_WARNING_MESSAGE =
"Transient providers are not added to the toolbar";
public static final String DEFAULT_WINDOW_GROUP = "Default"; public static final String DEFAULT_WINDOW_GROUP = "Default";
private static final String TOOLBAR_GROUP = "View";
// maps for mapping old provider names and owner to new names and/or owner // maps for mapping old provider names and owner to new names and/or owner
private static Map<String, String> oldOwnerMap = new HashMap<>(); private static Map<String, String> oldOwnerMap = new HashMap<>();
private static Map<String, String> oldNameMap = new HashMap<>(); private static Map<String, String> oldNameMap = new HashMap<>();
@ -77,14 +89,21 @@ public abstract class ComponentProvider implements HelpDescriptor, ActionContext
private String title; private String title;
private String subTitle; private String subTitle;
private String tabText; private String tabText;
private Icon icon;
private Set<DockingActionIf> actionSet = new LinkedHashSet<>(); private Set<DockingActionIf> actionSet = new LinkedHashSet<>();
private String windowMenuGroup;
/** True if this provider's action should appear in the toolbar */
private boolean isToolbarAction;
private boolean isTransient; private boolean isTransient;
private HelpLocation helpLocation; private HelpLocation helpLocation;
private Icon icon;
private String windowMenuGroup;
private String group = DEFAULT_WINDOW_GROUP; private String group = DEFAULT_WINDOW_GROUP;
private WindowPosition defaultWindowPosition = WindowPosition.WINDOW; private WindowPosition defaultWindowPosition = WindowPosition.WINDOW;
private WindowPosition defaultIntraGroupPosition = WindowPosition.STACK; private WindowPosition defaultIntraGroupPosition = WindowPosition.STACK;
private DockingAction showProviderAction;
private final Class<?> contextType; private final Class<?> contextType;
private long instanceID = UniversalIdGenerator.nextID().getValue(); private long instanceID = UniversalIdGenerator.nextID().getValue();
@ -118,6 +137,32 @@ public abstract class ComponentProvider implements HelpDescriptor, ActionContext
this.contextType = contextType; this.contextType = contextType;
} }
/**
* Returns the action used to show this provider
* @return the action
*/
DockingActionIf getShowProviderAction() {
createShowProviderAction();
return showProviderAction;
}
private void createShowProviderAction() {
if (showProviderAction != null) {
return;
}
showProviderAction = new ShowProviderAction();
}
private void removeShowProviderAction() {
if (showProviderAction == null) {
return; // not installed
}
dockingTool.removeAction(showProviderAction);
showProviderAction = null;
}
/** /**
* Returns the component to be displayed * Returns the component to be displayed
* @return the component to be displayed * @return the component to be displayed
@ -453,8 +498,55 @@ public abstract class ComponentProvider implements HelpDescriptor, ActionContext
* @param icon the icon to use for this provider. * @param icon the icon to use for this provider.
*/ */
public void setIcon(Icon icon) { public void setIcon(Icon icon) {
setIcon(icon, false);
}
/**
* Convenience method for setting the provider's icon
*
* @param icon the icon to use for this provider
* @param isToolbarAction true will cause this action to get added to the toolbar; if this
* value is true, then the icon cannot be null
*/
public void setIcon(Icon icon, boolean isToolbarAction) {
this.icon = icon; this.icon = icon;
this.isToolbarAction = isToolbarAction;
if (isToolbarAction) {
Objects.requireNonNull(icon,
"Icon cannot be null when requesting the provider's action appear in the toolbar");
if (isTransient) {
Msg.error(this, TRANSIENT_PROVIDER_TOOLBAR_WARNING_MESSAGE,
ReflectionUtilities.createJavaFilteredThrowable());
isToolbarAction = false;
}
}
if (isInTool()) { if (isInTool()) {
/*
TODO
4) Wire default 'close' action to keybinding
5) Add global action for (show last provider)
6) Remove plugin code that creates the 'show' actions
Questions:
C) How to wire universal close action (it is focus-dependent)
Fix:
-Toolbar description for key doesn't match menu (goes away)
-dummy actions getting added to toolbar
-cleanup odd relationship with keybindings action and UI (move to tool??
or ToolActions...<= this)
*/
dockingTool.getWindowManager().setIcon(this, icon); dockingTool.getWindowManager().setIcon(this, icon);
} }
} }
@ -465,7 +557,6 @@ public abstract class ComponentProvider implements HelpDescriptor, ActionContext
* @return the menu group for this provider or null if this provider should appear in the * @return the menu group for this provider or null if this provider should appear in the
* top-level menu. * top-level menu.
*/ */
public String getWindowSubMenuName() { public String getWindowSubMenuName() {
return windowMenuGroup; return windowMenuGroup;
} }
@ -473,7 +564,7 @@ public abstract class ComponentProvider implements HelpDescriptor, ActionContext
/** /**
* Returns true if this component goes away during a user session (most providers remain in * Returns true if this component goes away during a user session (most providers remain in
* the tool all session long, visible or not) * the tool all session long, visible or not)
* @return true if transitent * @return true if transient
*/ */
public boolean isTransient() { public boolean isTransient() {
return isTransient; return isTransient;
@ -485,6 +576,15 @@ public abstract class ComponentProvider implements HelpDescriptor, ActionContext
*/ */
protected void setTransient() { protected void setTransient() {
isTransient = true; isTransient = true;
// avoid visually disturbing the user by adding/removing toolbar actions for temp providers
if (isToolbarAction) {
isToolbarAction = false;
Msg.error(this, TRANSIENT_PROVIDER_TOOLBAR_WARNING_MESSAGE,
ReflectionUtilities.createJavaFilteredThrowable());
}
removeShowProviderAction();
} }
/** /**
@ -633,4 +733,29 @@ public abstract class ComponentProvider implements HelpDescriptor, ActionContext
return "owner=" + oldOwner + "name=" + oldName; return "owner=" + oldOwner + "name=" + oldName;
} }
private class ShowProviderAction extends DockingAction {
ShowProviderAction() {
super(name, owner);
if (isToolbarAction) {
setToolBarData(new ToolBarData(icon, TOOLBAR_GROUP));
}
}
@Override
public void actionPerformed(ActionContext context) {
dockingTool.showComponentProvider(ComponentProvider.this, true);
}
@Override
public boolean isKeyBindingManaged() {
return false;
}
@Override
public boolean usesSharedKeyBinding() {
return !isTransient;
}
}
} }

View file

@ -69,7 +69,7 @@ public class DialogComponentProviderPopupActionManager {
return; return;
} }
ActionToGuiMapper actionManager = dwm.getActionManager(); ActionToGuiMapper actionManager = dwm.getActionToGuiMapper();
MenuGroupMap menuGroupMap = actionManager.getMenuGroupMap(); MenuGroupMap menuGroupMap = actionManager.getMenuGroupMap();
MenuManager menuMgr = MenuManager menuMgr =
new MenuManager("Popup", '\0', null, true, popupMenuHandler, menuGroupMap); new MenuManager("Popup", '\0', null, true, popupMenuHandler, menuGroupMap);

View file

@ -65,7 +65,7 @@ public class DockableComponent extends JPanel implements ContainerListener {
this.componentInfo = placeholder; this.componentInfo = placeholder;
winMgr = placeholder.getNode().winMgr; winMgr = placeholder.getNode().winMgr;
actionMgr = winMgr.getActionManager(); actionMgr = winMgr.getActionToGuiMapper();
popupListener = new MouseAdapter() { popupListener = new MouseAdapter() {
@Override @Override

View file

@ -60,7 +60,7 @@ class DockableToolBarManager {
ComponentPlaceholder placeholder = dockableComp.getComponentWindowingPlaceholder(); ComponentPlaceholder placeholder = dockableComp.getComponentWindowingPlaceholder();
DockingWindowManager winMgr = DockingWindowManager winMgr =
dockableComp.getComponentWindowingPlaceholder().getNode().winMgr; dockableComp.getComponentWindowingPlaceholder().getNode().winMgr;
ActionToGuiMapper actionManager = winMgr.getActionManager(); ActionToGuiMapper actionManager = winMgr.getActionToGuiMapper();
menuGroupMap = actionManager.getMenuGroupMap(); menuGroupMap = actionManager.getMenuGroupMap();
MenuHandler menuHandler = actionManager.getMenuHandler(); MenuHandler menuHandler = actionManager.getMenuHandler();

View file

@ -15,15 +15,13 @@
*/ */
package docking; package docking;
import java.awt.event.*; import java.awt.event.ActionEvent;
import java.util.*;
import javax.swing.AbstractAction; import javax.swing.AbstractAction;
import javax.swing.KeyStroke; import javax.swing.KeyStroke;
import docking.action.DockingActionIf; import docking.action.DockingActionIf;
import ghidra.util.Msg; import docking.actions.KeyBindingUtils;
import ghidra.util.StringUtilities;
/** /**
* A class that can be used as an interface for using actions associated with keybindings. This * A class that can be used as an interface for using actions associated with keybindings. This
@ -31,17 +29,6 @@ import ghidra.util.StringUtilities;
*/ */
public class DockingKeyBindingAction extends AbstractAction { public class DockingKeyBindingAction extends AbstractAction {
private static final String RELEASED = "released";
private static final String TYPED = "typed";
private static final String PRESSED = "pressed";
private static final String SHIFT = "Shift";
private static final String CTRL = "Ctrl";
private static final String CONTROL = "Control";
private static final String ALT = "Alt";
private static final String META = "Meta";
private static final String MODIFIER_SEPARATOR = "-";
private DockingActionIf docakbleAction; private DockingActionIf docakbleAction;
protected KeyStroke keyStroke; protected KeyStroke keyStroke;
@ -49,7 +36,7 @@ public class DockingKeyBindingAction extends AbstractAction {
public DockingKeyBindingAction(DockingWindowManager winMgr, DockingActionIf action, public DockingKeyBindingAction(DockingWindowManager winMgr, DockingActionIf action,
KeyStroke keyStroke) { KeyStroke keyStroke) {
super(parseKeyStroke(keyStroke)); super(KeyBindingUtils.parseKeyStroke(keyStroke));
this.winMgr = winMgr; this.winMgr = winMgr;
this.docakbleAction = action; this.docakbleAction = action;
this.keyStroke = keyStroke; this.keyStroke = keyStroke;
@ -94,147 +81,4 @@ public class DockingKeyBindingAction extends AbstractAction {
return new ActionContext(localProvider, null); return new ActionContext(localProvider, null);
} }
/**
* Convert the toString() form of the keyStroke.
* <br>In Java 1.4.2 & earlier, Ctrl-M is returned as "keyCode CtrlM-P"
* and we want it to look like: "Ctrl-M".
* <br>In Java 1.5.0, Ctrl-M is returned as "ctrl pressed M"
* and we want it to look like: "Ctrl-M".
*/
public static String parseKeyStroke(KeyStroke keyStroke) {
final String keyPressSuffix = "-P";
String keyString = keyStroke.toString();
int type = keyStroke.getKeyEventType();
if (type == KeyEvent.KEY_TYPED) {
return String.valueOf(keyStroke.getKeyChar());
}
// get the character used in the key stroke
int firstIndex = keyString.lastIndexOf(' ') + 1;
int ctrlIndex = keyString.indexOf(CTRL, firstIndex);
if (ctrlIndex >= 0) {
firstIndex = ctrlIndex + CTRL.length();
}
int altIndex = keyString.indexOf(ALT, firstIndex);
if (altIndex >= 0) {
firstIndex = altIndex + ALT.length();
}
int shiftIndex = keyString.indexOf(SHIFT, firstIndex);
if (shiftIndex >= 0) {
firstIndex = shiftIndex + SHIFT.length();
}
int metaIndex = keyString.indexOf(META, firstIndex);
if (metaIndex >= 0) {
firstIndex = metaIndex + META.length();
}
int lastIndex = keyString.length();
if (keyString.endsWith(keyPressSuffix)) {
lastIndex -= keyPressSuffix.length();
}
if (lastIndex >= 0) {
keyString = keyString.substring(firstIndex, lastIndex);
}
int modifiers = keyStroke.getModifiers();
StringBuffer buffer = new StringBuffer();
if ((modifiers & InputEvent.SHIFT_MASK) != 0) {
buffer.insert(0, SHIFT + MODIFIER_SEPARATOR);
}
if ((modifiers & InputEvent.ALT_MASK) != 0) {
buffer.insert(0, ALT + MODIFIER_SEPARATOR);
}
if ((modifiers & InputEvent.CTRL_MASK) != 0) {
buffer.insert(0, CTRL + MODIFIER_SEPARATOR);
}
if ((modifiers & InputEvent.META_MASK) != 0) {
buffer.insert(0, META + MODIFIER_SEPARATOR);
}
buffer.append(keyString);
return buffer.toString();
}
/**
* Parses the given text into a KeyStroke. This method relies upon
* {@link KeyStroke#getKeyStroke(String)} for parsing. Before making that call, this method
* will perform fixup on the given text for added flexibility. For example, the given
* text may contain spaces or dashes as the separators between parts in the string. Also,
* the text is converted such that it is not case-sensitive. So, the following example
* formats are allowed:
* <pre>
* Alt-F
* alt p
* Ctrl-Alt-Z
* ctrl Z
* </pre>
*
* @param keyStroke
* @return
*/
public static KeyStroke parseKeyStroke(String keyStroke) {
List<String> pieces = new ArrayList<>();
StringTokenizer tokenizer = new StringTokenizer(keyStroke, "- ");
while (tokenizer.hasMoreTokens()) {
String token = tokenizer.nextToken();
if (!pieces.contains(token)) {
pieces.add(token);
}
}
StringBuffer keyStrokeBuff = new StringBuffer();
for (Iterator<String> iterator = pieces.iterator(); iterator.hasNext();) {
String piece = iterator.next();
if (StringUtilities.indexOfIgnoreCase(piece, SHIFT) != -1) {
keyStrokeBuff.append("shift ");
iterator.remove();
}
else if (StringUtilities.indexOfIgnoreCase(piece, CTRL) != -1) {
keyStrokeBuff.append("ctrl ");
iterator.remove();
}
else if (StringUtilities.indexOfIgnoreCase(piece, CONTROL) != -1) {
keyStrokeBuff.append("ctrl ");
iterator.remove();
}
else if (StringUtilities.indexOfIgnoreCase(piece, ALT) != -1) {
keyStrokeBuff.append("alt ");
iterator.remove();
}
else if (StringUtilities.indexOfIgnoreCase(piece, META) != -1) {
keyStrokeBuff.append("meta ");
iterator.remove();
}
else if (StringUtilities.indexOfIgnoreCase(piece, PRESSED) != -1) {
iterator.remove();
}
else if (StringUtilities.indexOfIgnoreCase(piece, TYPED) != -1) {
iterator.remove();
}
else if (StringUtilities.indexOfIgnoreCase(piece, RELEASED) != -1) {
iterator.remove();
}
}
keyStrokeBuff.append(PRESSED).append(' ');
// at this point we should only have left one piece--the key ID
int leftover = pieces.size();
if (leftover > 1 || leftover == 0) {
Msg.warn(DockingKeyBindingAction.class, "Invalid keystroke string found. Expected " +
"format of '[modifier] ... key'. Found: '" + keyStroke + "'");
if (leftover == 0) {
return null; // nothing to do
}
}
String key = pieces.get(0);
keyStrokeBuff.append(key.toUpperCase());
return KeyStroke.getKeyStroke(keyStrokeBuff.toString());
}
} }

View file

@ -234,5 +234,4 @@ public interface DockingTool {
* @return true if the tool's configuration has changed * @return true if the tool's configuration has changed
*/ */
public boolean hasConfigChanged(); public boolean hasConfigChanged();
} }

View file

@ -29,13 +29,13 @@ import javax.swing.*;
import org.jdom.Element; import org.jdom.Element;
import docking.action.DockingActionIf; import docking.action.DockingActionIf;
import docking.action.KeyBindingsManager;
import docking.help.HelpService; import docking.help.HelpService;
import generic.util.WindowUtilities; import generic.util.WindowUtilities;
import ghidra.framework.OperatingSystem; import ghidra.framework.OperatingSystem;
import ghidra.framework.Platform; import ghidra.framework.Platform;
import ghidra.framework.options.PreferenceState; import ghidra.framework.options.PreferenceState;
import ghidra.util.HelpLocation; import ghidra.util.*;
import ghidra.util.SystemUtilities;
import ghidra.util.datastruct.*; import ghidra.util.datastruct.*;
import ghidra.util.exception.AssertException; import ghidra.util.exception.AssertException;
import ghidra.util.task.SwingUpdateManager; import ghidra.util.task.SwingUpdateManager;
@ -87,7 +87,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
private Map<String, ComponentProvider> providerNameCache = new HashMap<>(); private Map<String, ComponentProvider> providerNameCache = new HashMap<>();
private Map<String, PreferenceState> preferenceStateMap = new HashMap<>(); private Map<String, PreferenceState> preferenceStateMap = new HashMap<>();
private DockWinListener docListener; private DockWinListener docListener;
private ActionToGuiMapper actionManager; private ActionToGuiMapper actionToGuiMapper;
private WeakSet<DockingContextListener> contextListeners = private WeakSet<DockingContextListener> contextListeners =
WeakDataStructureFactory.createSingleThreadAccessWeakSet(); WeakDataStructureFactory.createSingleThreadAccessWeakSet();
@ -138,7 +138,6 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
} }
root = new RootNode(this, toolName, images, modal, factory); root = new RootNode(this, toolName, images, modal, factory);
actionManager = new ActionToGuiMapper(this);
KeyboardFocusManager km = KeyboardFocusManager.getCurrentKeyboardFocusManager(); KeyboardFocusManager km = KeyboardFocusManager.getCurrentKeyboardFocusManager();
km.addPropertyChangeListener("permanentFocusOwner", this); km.addPropertyChangeListener("permanentFocusOwner", this);
@ -319,7 +318,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
* given keystroke. * given keystroke.
*/ */
Action getActionForKeyStroke(KeyStroke keyStroke) { Action getActionForKeyStroke(KeyStroke keyStroke) {
return actionManager.getDockingKeyAction(keyStroke); return actionToGuiMapper.getDockingKeyAction(keyStroke);
} }
/** /**
@ -336,8 +335,8 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
return placeholderManager; return placeholderManager;
} }
ActionToGuiMapper getActionManager() { ActionToGuiMapper getActionToGuiMapper() {
return actionManager; return actionToGuiMapper;
} }
RootNode getRootNode() { RootNode getRootNode() {
@ -646,7 +645,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
* @param owner the name of the owner whose associated component and actions should be removed. * @param owner the name of the owner whose associated component and actions should be removed.
*/ */
public void removeAll(String owner) { public void removeAll(String owner) {
actionManager.removeAll(owner); actionToGuiMapper.removeAll(owner);
placeholderManager.removeAll(owner); placeholderManager.removeAll(owner);
scheduleUpdate(); scheduleUpdate();
} }
@ -655,6 +654,10 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
// Package-level Action Methods // Package-level Action Methods
//================================================================================================== //==================================================================================================
void setKeyBindingsManager(KeyBindingsManager keyBindingsManager) {
actionToGuiMapper = new ActionToGuiMapper(this, keyBindingsManager);
}
Iterator<DockingActionIf> getComponentActions(ComponentProvider provider) { Iterator<DockingActionIf> getComponentActions(ComponentProvider provider) {
ComponentPlaceholder placeholder = getActivePlaceholder(provider); ComponentPlaceholder placeholder = getActivePlaceholder(provider);
if (placeholder != null) { if (placeholder != null) {
@ -668,7 +671,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
void removeProviderAction(ComponentProvider provider, DockingActionIf action) { void removeProviderAction(ComponentProvider provider, DockingActionIf action) {
ComponentPlaceholder placeholder = getActivePlaceholder(provider); ComponentPlaceholder placeholder = getActivePlaceholder(provider);
if (placeholder != null) { if (placeholder != null) {
actionManager.removeLocalAction(action); actionToGuiMapper.removeLocalAction(action);
placeholder.removeAction(action); placeholder.removeAction(action);
} }
} }
@ -679,16 +682,16 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
throw new IllegalArgumentException("Unknown component provider: " + provider); throw new IllegalArgumentException("Unknown component provider: " + provider);
} }
placeholder.addAction(action); placeholder.addAction(action);
actionManager.addLocalAction(action, provider); actionToGuiMapper.addLocalAction(action, provider);
} }
void addToolAction(DockingActionIf action) { void addToolAction(DockingActionIf action) {
actionManager.addToolAction(action); actionToGuiMapper.addToolAction(action);
scheduleUpdate(); scheduleUpdate();
} }
void removeToolAction(DockingActionIf action) { void removeToolAction(DockingActionIf action) {
actionManager.removeToolAction(action); actionToGuiMapper.removeToolAction(action);
scheduleUpdate(); scheduleUpdate();
} }
@ -712,6 +715,12 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
if (placeholder != null) { if (placeholder != null) {
showComponent(placeholder, visibleState, true); showComponent(placeholder, visibleState, true);
} }
else {
// a null placeholder implies the client is trying to show a provider that has not
// been added to the tool
Msg.warn(this, "Attempting to show an unknown Component Provider '" +
provider.getName() + "' - " + "check that the provider has been added to the tool");
}
} }
public void toFront(ComponentProvider provider) { public void toFront(ComponentProvider provider) {
@ -791,7 +800,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
KeyboardFocusManager mgr = KeyboardFocusManager.getCurrentKeyboardFocusManager(); KeyboardFocusManager mgr = KeyboardFocusManager.getCurrentKeyboardFocusManager();
mgr.removePropertyChangeListener("permanentFocusOwner", this); mgr.removePropertyChangeListener("permanentFocusOwner", this);
actionManager.dispose(); actionToGuiMapper.dispose();
root.dispose(); root.dispose();
placeholderManager.disposePlaceholders(); placeholderManager.disposePlaceholders();
@ -993,7 +1002,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
Iterator<DockingActionIf> iter = placeholder.getActions(); Iterator<DockingActionIf> iter = placeholder.getActions();
while (iter.hasNext()) { while (iter.hasNext()) {
DockingActionIf action = iter.next(); DockingActionIf action = iter.next();
actionManager.removeLocalAction(action); actionToGuiMapper.removeLocalAction(action);
} }
ComponentNode node = placeholder.getNode(); ComponentNode node = placeholder.getNode();
@ -1084,7 +1093,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
return; return;
} }
actionManager.removeAll(DOCKING_WINDOWS_OWNER); actionToGuiMapper.removeAll(DOCKING_WINDOWS_OWNER);
Map<String, List<ComponentPlaceholder>> permanentMap = new HashMap<>(); Map<String, List<ComponentPlaceholder>> permanentMap = new HashMap<>();
Map<String, List<ComponentPlaceholder>> transientMap = new HashMap<>(); Map<String, List<ComponentPlaceholder>> transientMap = new HashMap<>();
@ -1111,7 +1120,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
createActions(permanentMap, false); createActions(permanentMap, false);
createWindowActions(); createWindowActions();
actionManager.update(); actionToGuiMapper.update();
} }
private boolean isWindowMenuShowing() { private boolean isWindowMenuShowing() {
@ -1153,7 +1162,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
} }
Collections.sort(actionList); Collections.sort(actionList);
for (ShowComponentAction action : actionList) { for (ShowComponentAction action : actionList) {
actionManager.addToolAction(action); actionToGuiMapper.addToolAction(action);
} }
} }
@ -1191,7 +1200,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
Collections.sort(actions); Collections.sort(actions);
for (ShowWindowAction action : actions) { for (ShowWindowAction action : actions) {
actionManager.addToolAction(action); actionToGuiMapper.addToolAction(action);
} }
} }
@ -1387,7 +1396,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
if (root == null) { if (root == null) {
return; return;
} }
actionManager.setActive(active); actionToGuiMapper.setActive(active);
if (active) { if (active) {
setActiveManager(this); setActiveManager(this);
if (focusedPlaceholder != null && root.getWindow(focusedPlaceholder) == window) { if (focusedPlaceholder != null && root.getWindow(focusedPlaceholder) == window) {
@ -1938,7 +1947,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
* when we know that an update is not need, as we are in the middle of an update. * when we know that an update is not need, as we are in the middle of an update.
*/ */
void doSetMenuGroup(String[] menuPath, String group) { void doSetMenuGroup(String[] menuPath, String group) {
actionManager.setMenuGroup(menuPath, group); actionToGuiMapper.setMenuGroup(menuPath, group);
} }
/** /**
@ -1954,7 +1963,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
* its level * its level
*/ */
public void setMenuGroup(String[] menuPath, String group, String menuSubGroup) { public void setMenuGroup(String[] menuPath, String group, String menuSubGroup) {
actionManager.setMenuGroup(menuPath, group, menuSubGroup); actionToGuiMapper.setMenuGroup(menuPath, group, menuSubGroup);
scheduleUpdate(); scheduleUpdate();
} }
@ -2060,7 +2069,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
public void contextChanged(ComponentProvider provider) { public void contextChanged(ComponentProvider provider) {
if (provider == null) { if (provider == null) {
actionManager.contextChangedAll(); // update all windows; actionToGuiMapper.contextChangedAll(); // update all windows;
return; return;
} }
@ -2069,7 +2078,7 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
return; return;
} }
placeHolder.contextChanged(); placeHolder.contextChanged();
actionManager.contextChanged(placeHolder); actionToGuiMapper.contextChanged(placeHolder);
} }
public void addContextListener(DockingContextListener listener) { public void addContextListener(DockingContextListener listener) {

View file

@ -17,11 +17,10 @@ package docking;
import java.util.*; import java.util.*;
import javax.swing.SwingUtilities;
import docking.action.DockingActionIf; import docking.action.DockingActionIf;
import docking.menu.MenuGroupMap; import docking.menu.MenuGroupMap;
import docking.menu.MenuHandler; import docking.menu.MenuHandler;
import ghidra.util.Swing;
public class GlobalMenuAndToolBarManager implements DockingWindowListener { public class GlobalMenuAndToolBarManager implements DockingWindowListener {
@ -66,6 +65,17 @@ public class GlobalMenuAndToolBarManager implements DockingWindowListener {
} }
} }
public DockingActionIf getToolbarAction(String actionName) {
for (WindowActionManager actionManager : windowToActionManagerMap.values()) {
DockingActionIf action = actionManager.getToolbarAction(actionName);
if (action != null) {
return action;
}
}
return null;
}
public void dispose() { public void dispose() {
for (WindowActionManager actionManager : windowToActionManagerMap.values()) { for (WindowActionManager actionManager : windowToActionManagerMap.values()) {
actionManager.dispose(); actionManager.dispose();
@ -130,7 +140,7 @@ public class GlobalMenuAndToolBarManager implements DockingWindowListener {
} }
private List<DockingActionIf> getActionsForWindow(WindowNode windowNode) { private List<DockingActionIf> getActionsForWindow(WindowNode windowNode) {
ActionToGuiMapper actionManager = windowManager.getActionManager(); ActionToGuiMapper actionManager = windowManager.getActionToGuiMapper();
Collection<DockingActionIf> globalActions = actionManager.getGlobalActions(); Collection<DockingActionIf> globalActions = actionManager.getGlobalActions();
List<DockingActionIf> actionsForWindow = new ArrayList<>(globalActions.size()); List<DockingActionIf> actionsForWindow = new ArrayList<>(globalActions.size());
Set<Class<?>> contextTypes = windowNode.getContextTypes(); Set<Class<?>> contextTypes = windowNode.getContextTypes();
@ -143,12 +153,7 @@ public class GlobalMenuAndToolBarManager implements DockingWindowListener {
} }
public void contextChangedAll() { public void contextChangedAll() {
if (SwingUtilities.isEventDispatchThread()) { Swing.runIfSwingOrRunLater(this::updateAllWindowActions);
updateAllWindowActions();
}
else {
SwingUtilities.invokeLater(() -> updateAllWindowActions());
}
} }
private void updateAllWindowActions() { private void updateAllWindowActions() {

View file

@ -1,6 +1,5 @@
/* ### /* ###
* IP: GHIDRA * IP: GHIDRA
* REVIEWED: YES
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -22,6 +21,8 @@ import java.awt.event.KeyListener;
import javax.swing.JTextField; import javax.swing.JTextField;
import javax.swing.KeyStroke; import javax.swing.KeyStroke;
import docking.actions.KeyBindingUtils;
/** /**
* Text field captures key strokes and notifies a listener to process the key entry. * Text field captures key strokes and notifies a listener to process the key entry.
*/ */
@ -43,21 +44,31 @@ public class KeyEntryTextField extends JTextField {
} }
/** /**
* Get the current key stroke. * Get the current key stroke
* @return the key stroke
*/ */
public KeyStroke getCurrentKeyStroke() { public KeyStroke getKeyStroke() {
return currentKeyStroke; return currentKeyStroke;
} }
/** /**
* Converts the toString() form of the keyStroke, e.g., Ctrl-M * Sets the current key stroke
* is returned as "keyCode CtrlM-P" and we want it to look like: * @param ks the new key stroke
* "Ctrl-M" */
* @param ks the keystroke to parse. public void setKeyStroke(KeyStroke ks) {
* @return the parse string for the keystroke. processEntry(ks);
setText(parseKeyStroke(ks));
}
/**
* Converts the toString() form of the keyStroke, e.g., Ctrl-M is returned as
* "keyCode CtrlM-P" and we want it to look like: "Ctrl-M"
*
* @param ks the keystroke to parse
* @return the parse string for the keystroke
*/ */
public static String parseKeyStroke(KeyStroke ks) { public static String parseKeyStroke(KeyStroke ks) {
return DockingKeyBindingAction.parseKeyStroke(ks); return KeyBindingUtils.parseKeyStroke(ks);
} }
public void clearField() { public void clearField() {
@ -66,8 +77,6 @@ public class KeyEntryTextField extends JTextField {
currentKeyStroke = null; currentKeyStroke = null;
} }
//////////////////////////////////////////////////////////////////////
private void processEntry(KeyStroke ks) { private void processEntry(KeyStroke ks) {
ksName = null; ksName = null;
@ -78,7 +87,7 @@ public class KeyEntryTextField extends JTextField {
char keyChar = ks.getKeyChar(); char keyChar = ks.getKeyChar();
if (!Character.isWhitespace(keyChar) && if (!Character.isWhitespace(keyChar) &&
Character.getType(keyChar) != Character.DIRECTIONALITY_LEFT_TO_RIGHT_OVERRIDE) { Character.getType(keyChar) != Character.DIRECTIONALITY_LEFT_TO_RIGHT_OVERRIDE) {
ksName = DockingKeyBindingAction.parseKeyStroke(ks); ksName = KeyBindingUtils.parseKeyStroke(ks);
} }
} }
listener.processEntry(ks); listener.processEntry(ks);
@ -111,7 +120,7 @@ public class KeyEntryTextField extends JTextField {
KeyStroke keyStroke = null; KeyStroke keyStroke = null;
if (!isClearKey(keyCode) && !isModifiersOnly(e)) { if (!isClearKey(keyCode) && !isModifiersOnly(e)) {
keyStroke = KeyStroke.getKeyStroke(keyCode, e.getModifiersEx() | e.getModifiers()); keyStroke = KeyStroke.getKeyStroke(keyCode, e.getModifiersEx());
} }
processEntry(keyStroke); processEntry(keyStroke);

View file

@ -319,7 +319,7 @@ class RootNode extends WindowNode {
invalid = false; invalid = false;
} }
winMgr.getActionManager().update(); winMgr.getActionToGuiMapper().update();
windowWrapper.validate(); windowWrapper.validate();
} }

View file

@ -18,8 +18,7 @@ package docking;
import javax.swing.Icon; import javax.swing.Icon;
import javax.swing.ImageIcon; import javax.swing.ImageIcon;
import docking.action.DockingAction; import docking.action.*;
import docking.action.MenuData;
import ghidra.util.HelpLocation; import ghidra.util.HelpLocation;
import resources.ResourceManager; import resources.ResourceManager;
@ -33,8 +32,11 @@ class ShowComponentAction extends DockingAction implements Comparable<ShowCompon
protected static final ImageIcon EMPTY_ICON = protected static final ImageIcon EMPTY_ICON =
ResourceManager.loadImage("images/EmptyIcon16.gif"); ResourceManager.loadImage("images/EmptyIcon16.gif");
protected static final String MENU_WINDOW = "&" + DockingWindowManager.COMPONENT_MENU_NAME; protected static final String MENU_WINDOW = "&" + DockingWindowManager.COMPONENT_MENU_NAME;
private ComponentPlaceholder info;
protected DockingWindowManager winMgr; protected DockingWindowManager winMgr;
private ComponentPlaceholder info;
private String title;
private boolean isTransient;
private static String truncateTitleAsNeeded(String title) { private static String truncateTitleAsNeeded(String title) {
if (title.length() <= MAX_LENGTH) { if (title.length() <= MAX_LENGTH) {
@ -48,14 +50,14 @@ class ShowComponentAction extends DockingAction implements Comparable<ShowCompon
super(truncateTitleAsNeeded(name), DockingWindowManager.DOCKING_WINDOWS_OWNER); super(truncateTitleAsNeeded(name), DockingWindowManager.DOCKING_WINDOWS_OWNER);
} }
/**
* Constructs a new ShowComponentAction object.
* @param winMgr the DockingWindowManager that this action belongs to.
* @param info the info of the component to be shown when this action is invoked.
*/
ShowComponentAction(DockingWindowManager winMgr, ComponentPlaceholder info, String subMenuName, ShowComponentAction(DockingWindowManager winMgr, ComponentPlaceholder info, String subMenuName,
boolean isTransient) { boolean isTransient) {
super(truncateTitleAsNeeded(info.getTitle()), DockingWindowManager.DOCKING_WINDOWS_OWNER); super(info.getProvider().getName(), DockingWindowManager.DOCKING_WINDOWS_OWNER);
this.info = info;
this.winMgr = winMgr;
this.title = truncateTitleAsNeeded(info.getTitle());
this.isTransient = isTransient;
String group = isTransient ? "Transient" : "Permanent"; String group = isTransient ? "Transient" : "Permanent";
Icon icon = info.getIcon(); Icon icon = info.getIcon();
@ -69,15 +71,18 @@ class ShowComponentAction extends DockingAction implements Comparable<ShowCompon
winMgr.doSetMenuGroup(new String[] { MENU_WINDOW, subMenuName }, group); winMgr.doSetMenuGroup(new String[] { MENU_WINDOW, subMenuName }, group);
} }
else { else {
setMenuBarData( setMenuBarData(new MenuData(new String[] { MENU_WINDOW, title }, icon, "Permanent"));
new MenuData(new String[] { MENU_WINDOW, getName() }, icon, "Permanent"));
} }
this.info = info; // keybinding data used to show the binding in the menu
this.winMgr = winMgr;
// Use provider Help for this action
ComponentProvider provider = info.getProvider(); ComponentProvider provider = info.getProvider();
DockingActionIf action = provider.getShowProviderAction();
KeyBindingData kbData = action.getKeyBindingData();
if (kbData != null) {
setKeyBindingData(kbData);
}
// Use provider Help for this action
HelpLocation helpLocation = provider.getHelpLocation(); HelpLocation helpLocation = provider.getHelpLocation();
if (helpLocation != null) { if (helpLocation != null) {
setHelpLocation(helpLocation); setHelpLocation(helpLocation);
@ -89,6 +94,21 @@ class ShowComponentAction extends DockingAction implements Comparable<ShowCompon
} }
} }
@Override
public boolean isKeyBindingManaged() {
return false;
}
@Override
public boolean usesSharedKeyBinding() {
if (isTransient) {
return false; // temporary window
}
// 'info' is null when this action is used to 'show all' instances of a given provider
return info != null;
}
@Override @Override
public void actionPerformed(ActionContext context) { public void actionPerformed(ActionContext context) {
winMgr.showComponent(info, true, true); winMgr.showComponent(info, true, true);

View file

@ -72,6 +72,10 @@ public class WindowActionManager {
} }
} }
public DockingActionIf getToolbarAction(String actionName) {
return toolBarMgr.getAction(actionName);
}
public void update() { public void update() {
JMenuBar menuBar = menuBarMgr.getMenuBar(); JMenuBar menuBar = menuBarMgr.getMenuBar();
if (menuBar.getMenuCount() > 0) { if (menuBar.getMenuCount() > 0) {

View file

@ -15,12 +15,10 @@
*/ */
package docking.action; package docking.action;
import java.awt.event.*;
import javax.swing.KeyStroke; import javax.swing.KeyStroke;
import docking.DockingUtils;
import docking.KeyBindingPrecedence; import docking.KeyBindingPrecedence;
import docking.actions.KeyBindingUtils;
public class KeyBindingData { public class KeyBindingData {
private KeyStroke keyStroke; private KeyStroke keyStroke;
@ -49,6 +47,7 @@ public class KeyBindingData {
/** /**
* Returns an accelerator keystroke to be associated with this action. * Returns an accelerator keystroke to be associated with this action.
* @return the binding
*/ */
public KeyStroke getKeyBinding() { public KeyStroke getKeyBinding() {
return keyStroke; return keyStroke;
@ -56,6 +55,7 @@ public class KeyBindingData {
/** /**
* Returns the keyBindingPrecedence for this action * Returns the keyBindingPrecedence for this action
* @return the precedence
*/ */
public KeyBindingPrecedence getKeyBindingPrecedence() { public KeyBindingPrecedence getKeyBindingPrecedence() {
return keyBindingPrecedence; return keyBindingPrecedence;
@ -88,63 +88,8 @@ public class KeyBindingData {
KeyBindingPrecedence precedence = newKeyBindingData.getKeyBindingPrecedence(); KeyBindingPrecedence precedence = newKeyBindingData.getKeyBindingPrecedence();
if (precedence == KeyBindingPrecedence.ReservedActionsLevel) { if (precedence == KeyBindingPrecedence.ReservedActionsLevel) {
return createReservedKeyBindingData(validateKeyStroke(keyBinding)); return createReservedKeyBindingData(KeyBindingUtils.validateKeyStroke(keyBinding));
} }
return new KeyBindingData(validateKeyStroke(keyBinding), precedence); return new KeyBindingData(KeyBindingUtils.validateKeyStroke(keyBinding), precedence);
}
/**
* Updates the given data with system-independent versions of key modifiers. For example,
* the <tt>control</tt> key will be converted to the <tt>command</tt> key on the Mac.
*
* @param keyStroke the keystroke to validate
* @return the potentially changed keystroke
*/
public static KeyStroke validateKeyStroke(KeyStroke keyStroke) {
if (keyStroke == null) {
return null;
}
// remove system-dependent control key mask and transform deprecated modifiers
int modifiers = keyStroke.getModifiers();
if ((modifiers & InputEvent.CTRL_DOWN_MASK) == InputEvent.CTRL_DOWN_MASK) {
modifiers = modifiers ^ InputEvent.CTRL_DOWN_MASK;
modifiers = modifiers | DockingUtils.CONTROL_KEY_MODIFIER_MASK;
}
if ((modifiers & InputEvent.CTRL_MASK) == InputEvent.CTRL_MASK) {
modifiers = modifiers ^ InputEvent.CTRL_MASK;
modifiers = modifiers | DockingUtils.CONTROL_KEY_MODIFIER_MASK;
}
if ((modifiers & ActionEvent.CTRL_MASK) == ActionEvent.CTRL_MASK) {
modifiers = modifiers ^ ActionEvent.CTRL_MASK;
modifiers = modifiers | DockingUtils.CONTROL_KEY_MODIFIER_MASK;
}
if ((modifiers & InputEvent.SHIFT_MASK) == InputEvent.SHIFT_MASK) {
modifiers = modifiers ^ InputEvent.SHIFT_MASK;
modifiers = modifiers | InputEvent.SHIFT_DOWN_MASK;
}
if ((modifiers & InputEvent.ALT_MASK) == InputEvent.ALT_MASK) {
modifiers = modifiers ^ InputEvent.ALT_MASK;
modifiers = modifiers | InputEvent.ALT_DOWN_MASK;
}
if ((modifiers & InputEvent.META_MASK) == InputEvent.META_MASK) {
modifiers = modifiers ^ InputEvent.META_MASK;
modifiers = modifiers | InputEvent.META_DOWN_MASK;
}
int eventType = keyStroke.getKeyEventType();
if (eventType == KeyEvent.KEY_TYPED) {
// we know that typed events have a key code of VK_UNDEFINED
return KeyStroke.getKeyStroke(keyStroke.getKeyChar(), modifiers);
}
// key pressed or released
boolean isOnKeyRelease = keyStroke.isOnKeyRelease();
return KeyStroke.getKeyStroke(keyStroke.getKeyCode(), modifiers, isOnKeyRelease);
} }
} }

View file

@ -1,6 +1,5 @@
/* ### /* ###
* IP: GHIDRA * IP: GHIDRA
* REVIEWED: YES
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -14,10 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package docking; package docking.action;
import ghidra.util.ReservedKeyBindings;
import ghidra.util.exception.AssertException;
import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener; import java.beans.PropertyChangeListener;
@ -26,7 +22,9 @@ import java.util.*;
import javax.swing.Action; import javax.swing.Action;
import javax.swing.KeyStroke; import javax.swing.KeyStroke;
import docking.action.*; import docking.*;
import ghidra.util.ReservedKeyBindings;
import ghidra.util.exception.AssertException;
public class KeyBindingsManager implements PropertyChangeListener { public class KeyBindingsManager implements PropertyChangeListener {
@ -37,8 +35,8 @@ public class KeyBindingsManager implements PropertyChangeListener {
public KeyBindingsManager(DockingWindowManager winMgr) { public KeyBindingsManager(DockingWindowManager winMgr) {
this.winMgr = winMgr; this.winMgr = winMgr;
dockingKeyMap = new HashMap<KeyStroke, DockingKeyBindingAction>(); dockingKeyMap = new HashMap<>();
actionToProviderMap = new HashMap<DockingActionIf, ComponentProvider>(); actionToProviderMap = new HashMap<>();
} }
public void addAction(DockingActionIf action, ComponentProvider optionalProvider) { public void addAction(DockingActionIf action, ComponentProvider optionalProvider) {
@ -59,6 +57,10 @@ public class KeyBindingsManager implements PropertyChangeListener {
addReservedKeyBinding(action, keyBinding); addReservedKeyBinding(action, keyBinding);
} }
public void addReservedAction(DockingActionIf action, KeyStroke ks) {
addReservedKeyBinding(action, ks);
}
public void removeAction(DockingActionIf action) { public void removeAction(DockingActionIf action) {
action.removePropertyChangeListener(this); action.removePropertyChangeListener(this);
actionToProviderMap.remove(action); actionToProviderMap.remove(action);
@ -74,7 +76,8 @@ public class KeyBindingsManager implements PropertyChangeListener {
DockingKeyBindingAction existingAction = dockingKeyMap.get(keyStroke); DockingKeyBindingAction existingAction = dockingKeyMap.get(keyStroke);
if (existingAction == null) { if (existingAction == null) {
dockingKeyMap.put(keyStroke, new MultipleKeyAction(winMgr, provider, action, keyStroke)); dockingKeyMap.put(keyStroke,
new MultipleKeyAction(winMgr, provider, action, keyStroke));
return; return;
} }
@ -93,6 +96,8 @@ public class KeyBindingsManager implements PropertyChangeListener {
"action to a given keystroke: " + keyStroke); "action to a given keystroke: " + keyStroke);
} }
KeyBindingData binding = KeyBindingData.createReservedKeyBindingData(keyStroke);
action.setKeyBindingData(binding);
dockingKeyMap.put(keyStroke, new ReservedKeyBindingAction(winMgr, action, keyStroke)); dockingKeyMap.put(keyStroke, new ReservedKeyBindingAction(winMgr, action, keyStroke));
} }
@ -147,7 +152,7 @@ public class KeyBindingsManager implements PropertyChangeListener {
} }
public List<DockingActionIf> getLocalActions() { public List<DockingActionIf> getLocalActions() {
return new ArrayList<DockingActionIf>(actionToProviderMap.keySet()); return new ArrayList<>(actionToProviderMap.keySet());
} }
public Action getDockingKeyAction(KeyStroke keyStroke) { public Action getDockingKeyAction(KeyStroke keyStroke) {

View file

@ -21,6 +21,7 @@ import java.util.*;
import javax.swing.*; import javax.swing.*;
import docking.*; import docking.*;
import docking.actions.KeyBindingUtils;
import ghidra.util.Swing; import ghidra.util.Swing;
/** /**
@ -149,7 +150,7 @@ public class MultipleKeyAction extends DockingKeyBindingAction {
if (list.size() > 1) { if (list.size() > 1) {
// popup dialog to show multiple actions // popup dialog to show multiple actions
if (dialog == null) { if (dialog == null) {
dialog = new ActionDialog(parseKeyStroke(keyStroke), list); dialog = new ActionDialog(KeyBindingUtils.parseKeyStroke(keyStroke), list);
} }
else { else {
dialog.setActionList(list); dialog.setActionList(list);

View file

@ -1,6 +1,5 @@
/* ### /* ###
* IP: GHIDRA * IP: GHIDRA
* REVIEWED: YES
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -14,11 +13,12 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package docking; package docking.action;
import javax.swing.KeyStroke; import javax.swing.KeyStroke;
import docking.action.DockingActionIf; import docking.DockingKeyBindingAction;
import docking.DockingWindowManager;
class ReservedKeyBindingAction extends DockingKeyBindingAction { class ReservedKeyBindingAction extends DockingKeyBindingAction {

View file

@ -13,24 +13,23 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package docking.action; package docking.actions;
import java.awt.Component; import java.awt.Component;
import java.util.Set;
import docking.*; import docking.ActionContext;
import docking.actions.KeyBindingUtils; import docking.DockingWindowManager;
import docking.action.*;
import ghidra.util.Msg; import ghidra.util.Msg;
import ghidra.util.ReservedKeyBindings;
public class KeyBindingAction extends DockingAction { public class KeyBindingAction extends DockingAction {
private final ActionToGuiMapper dockingActionManager;
public KeyBindingAction(ActionToGuiMapper dockingActionManager) { public static String NAME = "Set KeyBinding";
super("Set KeyBinding", DockingWindowManager.DOCKING_WINDOWS_OWNER); private ToolActions toolActions;
this.dockingActionManager = dockingActionManager;
createReservedKeyBinding(ReservedKeyBindings.UPDATE_KEY_BINDINGS_KEY); public KeyBindingAction(ToolActions toolActions) {
setEnabled(true); super(NAME, DockingWindowManager.DOCKING_WINDOWS_OWNER);
this.toolActions = toolActions;
// Help actions don't have help // Help actions don't have help
DockingWindowManager.getHelpService().excludeFromHelp(this); DockingWindowManager.getHelpService().excludeFromHelp(this);
@ -58,7 +57,7 @@ public class KeyBindingAction extends DockingAction {
return; return;
} }
KeyEntryDialog d = new KeyEntryDialog(action, dockingActionManager); KeyEntryDialog d = new KeyEntryDialog(action, toolActions);
DockingWindowManager.showDialog(d); DockingWindowManager.showDialog(d);
} }
@ -75,9 +74,7 @@ public class KeyBindingAction extends DockingAction {
// It is not key binding managed, which means that it may be a shared key binding // It is not key binding managed, which means that it may be a shared key binding
String actionName = dockingAction.getName(); String actionName = dockingAction.getName();
Set<DockingActionIf> allActions = dockingActionManager.getAllActions(); DockingActionIf sharedAction = toolActions.getSharedStubKeyBindingAction(actionName);
DockingActionIf sharedAction =
KeyBindingUtils.getSharedKeyBindingAction(allActions, actionName);
if (sharedAction != null) { if (sharedAction != null) {
return sharedAction; return sharedAction;
} }

View file

@ -15,8 +15,11 @@
*/ */
package docking.actions; package docking.actions;
import static org.apache.commons.lang3.StringUtils.indexOfIgnoreCase;
import java.awt.Component; import java.awt.Component;
import java.awt.KeyboardFocusManager; import java.awt.KeyboardFocusManager;
import java.awt.event.*;
import java.io.*; import java.io.*;
import java.util.*; import java.util.*;
@ -31,6 +34,7 @@ import org.jdom.output.XMLOutputter;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import docking.DockingTool; import docking.DockingTool;
import docking.DockingUtils;
import docking.action.*; import docking.action.*;
import docking.widgets.filechooser.GhidraFileChooser; import docking.widgets.filechooser.GhidraFileChooser;
import ghidra.framework.options.ToolOptions; import ghidra.framework.options.ToolOptions;
@ -53,10 +57,20 @@ import utilities.util.reflection.ReflectionUtilities;
public class KeyBindingUtils { public class KeyBindingUtils {
private static final String LAST_KEY_BINDING_EXPORT_DIRECTORY = "LastKeyBindingExportDirectory"; private static final String LAST_KEY_BINDING_EXPORT_DIRECTORY = "LastKeyBindingExportDirectory";
private static final String RELEASED = "released";
private static final String TYPED = "typed";
private static final String PRESSED = "pressed";
private static final String SHIFT = "Shift";
private static final String CTRL = "Ctrl";
private static final String CONTROL = "Control";
private static final String ALT = "Alt";
private static final String META = "Meta";
private static final String MODIFIER_SEPARATOR = "-";
private static final Logger log = LogManager.getLogger(KeyBindingUtils.class); private static final Logger log = LogManager.getLogger(KeyBindingUtils.class);
public static final String PREFERENCES_FILE_EXTENSION = ".kbxml"; public static final String PREFERENCES_FILE_EXTENSION = ".kbxml";
private static final GhidraFileFilter FILE_FILTER = new GhidraFileFilter() { private static final GhidraFileFilter FILE_FILTER = new GhidraFileFilter() {
@Override @Override
public boolean accept(File pathname, GhidraFileChooserModel model) { public boolean accept(File pathname, GhidraFileChooserModel model) {
@ -398,36 +412,6 @@ public class KeyBindingUtils {
return Sets.filter(ownerMatch, action -> action.getName().equals(name)); return Sets.filter(ownerMatch, action -> action.getName().equals(name));
} }
/**
* A method to locate the {@link SharedStubKeyBindingAction} representative for the given
* action name. This method is not useful to general clients.
*
* @param allActions all actions in the system
* @param sharedName the name of the shared action
* @return the shared action representative
*/
public static DockingActionIf getSharedKeyBindingAction(Set<DockingActionIf> allActions,
String sharedName) {
String owner = "Tool";
for (DockingActionIf action : allActions) {
if (!(action instanceof SharedStubKeyBindingAction)) {
continue;
}
if (action.getOwner().equals(owner) && action.getName().equals(sharedName)) {
return action;
}
}
return null;
}
private static boolean isIgnored(DockingActionIf action) {
// not keybinding managed; a shared keybinding implies that this action should not be in
// the UI, as there will be a single proxy in place of all actions sharing that binding
return !action.isKeyBindingManaged() || action.usesSharedKeyBinding();
}
/** /**
* Takes the existing docking action and allows it to be registered with * Takes the existing docking action and allows it to be registered with
* Swing components * Swing components
@ -493,6 +477,252 @@ public class KeyBindingUtils {
Msg.warn(KeyBindingUtils.class, s, ReflectionUtilities.createJavaFilteredThrowable()); Msg.warn(KeyBindingUtils.class, s, ReflectionUtilities.createJavaFilteredThrowable());
} }
/**
* Updates the given data with system-independent versions of key modifiers. For example,
* the <tt>control</tt> key will be converted to the <tt>command</tt> key on the Mac.
*
* @param keyStroke the keystroke to validate
* @return the potentially changed keystroke
*/
// TODO ignore the deprecation, as this method is responsible for fixing deprecated usage.
// When all actions no longer user the deprecated modifiers, the deprecated elements
// of this method can be removed
@SuppressWarnings("deprecation")
public static KeyStroke validateKeyStroke(KeyStroke keyStroke) {
if (keyStroke == null) {
return null;
}
// remove system-dependent control key mask and transform deprecated modifiers
int modifiers = keyStroke.getModifiers();
if ((modifiers & InputEvent.CTRL_DOWN_MASK) == InputEvent.CTRL_DOWN_MASK) {
modifiers = modifiers ^ InputEvent.CTRL_DOWN_MASK;
modifiers = modifiers | DockingUtils.CONTROL_KEY_MODIFIER_MASK;
}
if ((modifiers & InputEvent.CTRL_MASK) == InputEvent.CTRL_MASK) {
modifiers = modifiers ^ InputEvent.CTRL_MASK;
modifiers = modifiers | DockingUtils.CONTROL_KEY_MODIFIER_MASK;
}
if ((modifiers & ActionEvent.CTRL_MASK) == ActionEvent.CTRL_MASK) {
modifiers = modifiers ^ ActionEvent.CTRL_MASK;
modifiers = modifiers | DockingUtils.CONTROL_KEY_MODIFIER_MASK;
}
if ((modifiers & InputEvent.SHIFT_MASK) == InputEvent.SHIFT_MASK) {
modifiers = modifiers ^ InputEvent.SHIFT_MASK;
modifiers = modifiers | InputEvent.SHIFT_DOWN_MASK;
}
if ((modifiers & InputEvent.ALT_MASK) == InputEvent.ALT_MASK) {
modifiers = modifiers ^ InputEvent.ALT_MASK;
modifiers = modifiers | InputEvent.ALT_DOWN_MASK;
}
if ((modifiers & InputEvent.META_MASK) == InputEvent.META_MASK) {
modifiers = modifiers ^ InputEvent.META_MASK;
modifiers = modifiers | InputEvent.META_DOWN_MASK;
}
int eventType = keyStroke.getKeyEventType();
if (eventType == KeyEvent.KEY_TYPED) {
// we know that typed events have a key code of VK_UNDEFINED
return KeyStroke.getKeyStroke(Character.valueOf(keyStroke.getKeyChar()), modifiers);
}
// key pressed or released
boolean isOnKeyRelease = keyStroke.isOnKeyRelease();
return KeyStroke.getKeyStroke(keyStroke.getKeyCode(), modifiers, isOnKeyRelease);
}
/**
* Convert the toString() form of the keyStroke.
* <br>In Java 1.4.2 & earlier, Ctrl-M is returned as "keyCode CtrlM-P"
* and we want it to look like: "Ctrl-M".
* <br>In Java 1.5.0, Ctrl-M is returned as "ctrl pressed M"
* and we want it to look like: "Ctrl-M".
*
* @param keyStroke the key stroke
* @return the string value; the empty string if the key stroke is null
*/
public static String parseKeyStroke(KeyStroke keyStroke) {
if (keyStroke == null) {
return "";
}
final String keyPressSuffix = "-P";
String keyString = keyStroke.toString();
int type = keyStroke.getKeyEventType();
if (type == KeyEvent.KEY_TYPED) {
return String.valueOf(keyStroke.getKeyChar());
}
// get the character used in the key stroke
int firstIndex = keyString.lastIndexOf(' ') + 1;
int ctrlIndex = keyString.indexOf(CTRL, firstIndex);
if (ctrlIndex >= 0) {
firstIndex = ctrlIndex + CTRL.length();
}
int altIndex = keyString.indexOf(ALT, firstIndex);
if (altIndex >= 0) {
firstIndex = altIndex + ALT.length();
}
int shiftIndex = keyString.indexOf(SHIFT, firstIndex);
if (shiftIndex >= 0) {
firstIndex = shiftIndex + SHIFT.length();
}
int metaIndex = keyString.indexOf(META, firstIndex);
if (metaIndex >= 0) {
firstIndex = metaIndex + META.length();
}
int lastIndex = keyString.length();
if (keyString.endsWith(keyPressSuffix)) {
lastIndex -= keyPressSuffix.length();
}
if (lastIndex >= 0) {
keyString = keyString.substring(firstIndex, lastIndex);
}
int modifiers = keyStroke.getModifiers();
StringBuilder buffy = new StringBuilder();
if (isShift(modifiers)) {
buffy.insert(0, SHIFT + MODIFIER_SEPARATOR);
}
if (isAlt(modifiers)) {
buffy.insert(0, ALT + MODIFIER_SEPARATOR);
}
if (isControl(modifiers)) {
buffy.insert(0, CTRL + MODIFIER_SEPARATOR);
}
if (isMeta(modifiers)) {
buffy.insert(0, META + MODIFIER_SEPARATOR);
}
buffy.append(keyString);
return buffy.toString();
}
// ignore the deprecated; remove when we are confident that all tool actions no longer use the
// deprecated InputEvent mask types
@SuppressWarnings("deprecation")
private static boolean isShift(int mask) {
return (mask & InputEvent.SHIFT_DOWN_MASK) != 0 || (mask & InputEvent.SHIFT_MASK) != 0;
}
// ignore the deprecated; remove when we are confident that all tool actions no longer use the
// deprecated InputEvent mask types
@SuppressWarnings("deprecation")
private static boolean isAlt(int mask) {
return (mask & InputEvent.ALT_DOWN_MASK) != 0 || (mask & InputEvent.ALT_MASK) != 0;
}
// ignore the deprecated; remove when we are confident that all tool actions no longer use the
// deprecated InputEvent mask types
@SuppressWarnings("deprecation")
private static boolean isControl(int mask) {
return (mask & InputEvent.CTRL_DOWN_MASK) != 0 || (mask & InputEvent.CTRL_MASK) != 0;
}
// ignore the deprecated; remove when we are confident that all tool actions no longer use the
// deprecated InputEvent mask types
@SuppressWarnings("deprecation")
private static boolean isMeta(int mask) {
return (mask & InputEvent.META_DOWN_MASK) != 0 || (mask & InputEvent.META_MASK) != 0;
}
/**
* Parses the given text into a KeyStroke. This method relies upon
* {@link KeyStroke#getKeyStroke(String)} for parsing. Before making that call, this method
* will perform fixup on the given text for added flexibility. For example, the given
* text may contain spaces or dashes as the separators between parts in the string. Also,
* the text is converted such that it is not case-sensitive. So, the following example
* formats are allowed:
* <pre>
* Alt-F
* alt p
* Ctrl-Alt-Z
* ctrl Z
* </pre>
*
* @param keyStroke
* @return the new key stroke (as returned by {@link KeyStroke#getKeyStroke(String)}
*/
public static KeyStroke parseKeyStroke(String keyStroke) {
List<String> pieces = new ArrayList<>();
StringTokenizer tokenizer = new StringTokenizer(keyStroke, "- ");
while (tokenizer.hasMoreTokens()) {
String token = tokenizer.nextToken();
if (!pieces.contains(token)) {
pieces.add(token);
}
}
StringBuilder buffy = new StringBuilder();
for (Iterator<String> iterator = pieces.iterator(); iterator.hasNext();) {
String piece = iterator.next();
if (indexOfIgnoreCase(piece, SHIFT) != -1) {
buffy.append("shift ");
iterator.remove();
}
else if (indexOfIgnoreCase(piece, CTRL) != -1) {
buffy.append("ctrl ");
iterator.remove();
}
else if (indexOfIgnoreCase(piece, CONTROL) != -1) {
buffy.append("ctrl ");
iterator.remove();
}
else if (indexOfIgnoreCase(piece, ALT) != -1) {
buffy.append("alt ");
iterator.remove();
}
else if (indexOfIgnoreCase(piece, META) != -1) {
buffy.append("meta ");
iterator.remove();
}
else if (indexOfIgnoreCase(piece, PRESSED) != -1) {
iterator.remove();
}
else if (indexOfIgnoreCase(piece, TYPED) != -1) {
iterator.remove();
}
else if (indexOfIgnoreCase(piece, RELEASED) != -1) {
iterator.remove();
}
}
buffy.append(PRESSED).append(' ');
// at this point we should only have left one piece--the key ID
int leftover = pieces.size();
if (leftover > 1 || leftover == 0) {
Msg.warn(KeyBindingUtils.class, "Invalid keystroke string found. Expected " +
"format of '[modifier] ... key'. Found: '" + keyStroke + "'");
if (leftover == 0) {
return null; // nothing to do
}
}
String key = pieces.get(0);
buffy.append(key.toUpperCase());
return KeyStroke.getKeyStroke(buffy.toString());
}
//==================================================================================================
// Private Methods
//==================================================================================================
private static boolean isIgnored(DockingActionIf action) {
// not keybinding managed; a shared keybinding implies that this action should not be in
// the UI, as there will be a single proxy in place of all actions sharing that binding
return !action.isKeyBindingManaged() || action.usesSharedKeyBinding();
}
private static KeyStroke getKeyStroke(KeyBindingData data) { private static KeyStroke getKeyStroke(KeyBindingData data) {
if (data == null) { if (data == null) {
return null; return null;
@ -500,12 +730,7 @@ public class KeyBindingUtils {
return data.getKeyBinding(); return data.getKeyBinding();
} }
//================================================================================================== // prompts the user for a file location from which to read key binding data
// Private Methods
//==================================================================================================
// prompts the user for a file location from which to read key binding
// data
private static InputStream getInputStreamForFile(File startingDir) { private static InputStream getInputStreamForFile(File startingDir) {
File selectedFile = getFileFromUser(startingDir); File selectedFile = getFileFromUser(startingDir);
@ -587,4 +812,5 @@ public class KeyBindingUtils {
return selectedFile; return selectedFile;
} }
} }

View file

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package docking.action; package docking.actions;
import java.awt.*; import java.awt.*;
import java.util.*; import java.util.*;
@ -23,7 +23,7 @@ import javax.swing.*;
import javax.swing.text.*; import javax.swing.text.*;
import docking.*; import docking.*;
import docking.actions.KeyBindingUtils; import docking.action.*;
import docking.widgets.label.GIconLabel; import docking.widgets.label.GIconLabel;
import ghidra.util.HelpLocation; import ghidra.util.HelpLocation;
import ghidra.util.ReservedKeyBindings; import ghidra.util.ReservedKeyBindings;
@ -35,7 +35,7 @@ import resources.ResourceManager;
*/ */
public class KeyEntryDialog extends DialogComponentProvider { public class KeyEntryDialog extends DialogComponentProvider {
private ActionToGuiMapper actionManager; private ToolActions toolActions;
private DockingActionIf action; private DockingActionIf action;
private JPanel defaultPanel; private JPanel defaultPanel;
private KeyEntryTextField keyEntryField; private KeyEntryTextField keyEntryField;
@ -45,10 +45,10 @@ public class KeyEntryDialog extends DialogComponentProvider {
private SimpleAttributeSet textAttrSet; private SimpleAttributeSet textAttrSet;
private Color bgColor; private Color bgColor;
public KeyEntryDialog(DockingActionIf action, ActionToGuiMapper actionManager) { public KeyEntryDialog(DockingActionIf action, ToolActions actions) {
super("Set Key Binding for " + action.getName(), true); super("Set Key Binding for " + action.getName(), true);
this.actionManager = actionManager;
this.action = action; this.action = action;
this.toolActions = actions;
setUpAttributes(); setUpAttributes();
createPanel(); createPanel();
KeyStroke keyBinding = action.getKeyBinding(); KeyStroke keyBinding = action.getKeyBinding();
@ -105,7 +105,7 @@ public class KeyEntryDialog extends DialogComponentProvider {
p.add(keyEntryField); p.add(keyEntryField);
KeyStroke keyBinding = action.getKeyBinding(); KeyStroke keyBinding = action.getKeyBinding();
if (keyBinding != null) { if (keyBinding != null) {
keyEntryField.setText(DockingKeyBindingAction.parseKeyStroke(keyBinding)); keyEntryField.setText(KeyBindingUtils.parseKeyStroke(keyBinding));
} }
setFocusComponent(keyEntryField); setFocusComponent(keyEntryField);
defaultPanel.add(p, BorderLayout.CENTER); defaultPanel.add(p, BorderLayout.CENTER);
@ -129,6 +129,14 @@ public class KeyEntryDialog extends DialogComponentProvider {
return p; return p;
} }
/**
* Sets the given keystroke value into the text field of this dialog
* @param ks the keystroke to set
*/
public void setKeyStroke(KeyStroke ks) {
keyEntryField.setKeyStroke(ks);
}
@Override @Override
protected void cancelCallback() { protected void cancelCallback() {
close(); close();
@ -136,23 +144,37 @@ public class KeyEntryDialog extends DialogComponentProvider {
@Override @Override
protected void okCallback() { protected void okCallback() {
KeyStroke keyStroke = keyEntryField.getCurrentKeyStroke(); KeyStroke newKeyStroke = keyEntryField.getKeyStroke();
if (keyStroke != null && ReservedKeyBindings.isReservedKeystroke(keyStroke)) { if (newKeyStroke != null && ReservedKeyBindings.isReservedKeystroke(newKeyStroke)) {
setStatusText(keyEntryField.getText() + " is a reserved keystroke"); setStatusText(keyEntryField.getText() + " is a reserved keystroke");
return; return;
} }
clearStatusText(); clearStatusText();
Set<DockingActionIf> allActions = actionManager.getAllActions(); KeyStroke existingKeyStroke = action.getKeyBinding();
Set<DockingActionIf> actions = if (Objects.equals(existingKeyStroke, newKeyStroke)) {
KeyBindingUtils.getActions(allActions, action.getOwner(), action.getName()); return;
for (DockingActionIf element : actions) {
if (element.isKeyBindingManaged()) {
element.setUnvalidatedKeyBindingData(new KeyBindingData(keyStroke));
}
} }
KeyBindingData kbData = new KeyBindingData(newKeyStroke);
if (action instanceof SharedStubKeyBindingAction) {
action.setUnvalidatedKeyBindingData(kbData);
}
else {
Set<DockingActionIf> allActions = toolActions.getAllActions();
Set<DockingActionIf> actions =
KeyBindingUtils.getActions(allActions, action.getOwner(), action.getName());
for (DockingActionIf element : actions) {
if (element.isKeyBindingManaged()) {
element.setUnvalidatedKeyBindingData(kbData);
}
}
}
toolActions.keyBindingsChanged();
close(); close();
} }
@ -178,7 +200,7 @@ public class KeyEntryDialog extends DialogComponentProvider {
return; return;
} }
String ksName = DockingKeyBindingAction.parseKeyStroke(ks); String ksName = KeyBindingUtils.parseKeyStroke(ks);
try { try {
doc.insertString(0, "Actions mapped to " + ksName + "\n\n", textAttrSet); doc.insertString(0, "Actions mapped to " + ksName + "\n\n", textAttrSet);
for (int i = 0; i < list.size(); i++) { for (int i = 0; i < list.size(); i++) {
@ -218,7 +240,7 @@ public class KeyEntryDialog extends DialogComponentProvider {
} }
private MultipleKeyAction getMultipleKeyAction(KeyStroke ks) { private MultipleKeyAction getMultipleKeyAction(KeyStroke ks) {
Action keyAction = actionManager.getDockingKeyAction(ks); Action keyAction = toolActions.getAction(ks);
if (keyAction instanceof MultipleKeyAction) { if (keyAction instanceof MultipleKeyAction) {
return (MultipleKeyAction) keyAction; return (MultipleKeyAction) keyAction;
} }

View file

@ -20,6 +20,8 @@ import java.util.Map.Entry;
import javax.swing.KeyStroke; import javax.swing.KeyStroke;
import org.apache.commons.lang3.StringUtils;
import docking.ActionContext; import docking.ActionContext;
import docking.DockingWindowManager; import docking.DockingWindowManager;
import docking.action.*; import docking.action.*;
@ -83,6 +85,27 @@ public class SharedStubKeyBindingAction extends DockingAction implements Options
updateActionKeyStrokeFromOptions(action, defaultKs); updateActionKeyStrokeFromOptions(action, defaultKs);
} }
public String getOwnersDescription() {
List<String> owners = getDistinctOwners();
Collections.sort(owners);
if (owners.size() == 1) {
return owners.get(0);
}
return '(' + StringUtils.join(owners, ", ") + ')';
}
private List<String> getDistinctOwners() {
List<String> results = new ArrayList<>();
Set<DockingActionIf> actions = clientActions.keySet();
for (DockingActionIf action : actions) {
String owner = action.getOwner();
if (!results.contains(owner)) {
results.add(owner);
}
}
return results;
}
private KeyStroke validateActionsHaveTheSameDefaultKeyStroke(DockingActionIf newAction) { private KeyStroke validateActionsHaveTheSameDefaultKeyStroke(DockingActionIf newAction) {
// this value may be null // this value may be null

View file

@ -19,6 +19,7 @@ import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener; import java.beans.PropertyChangeListener;
import java.util.*; import java.util.*;
import javax.swing.Action;
import javax.swing.KeyStroke; import javax.swing.KeyStroke;
import org.apache.commons.collections4.map.LazyMap; import org.apache.commons.collections4.map.LazyMap;
@ -27,6 +28,7 @@ import docking.*;
import docking.action.*; import docking.action.*;
import docking.tool.util.DockingToolConstants; import docking.tool.util.DockingToolConstants;
import ghidra.framework.options.*; import ghidra.framework.options.*;
import ghidra.util.ReservedKeyBindings;
import ghidra.util.exception.AssertException; import ghidra.util.exception.AssertException;
import util.CollectionUtils; import util.CollectionUtils;
@ -35,7 +37,6 @@ import util.CollectionUtils;
*/ */
public class ToolActions implements PropertyChangeListener { public class ToolActions implements PropertyChangeListener {
private DockingWindowManager winMgr;
private ActionToGuiHelper actionGuiHelper; private ActionToGuiHelper actionGuiHelper;
/* /*
@ -51,6 +52,7 @@ public class ToolActions implements PropertyChangeListener {
private ToolOptions keyBindingOptions; private ToolOptions keyBindingOptions;
private DockingTool dockingTool; private DockingTool dockingTool;
private KeyBindingsManager keyBindingsManager;
/** /**
* Construct an ActionManager * Construct an ActionManager
@ -61,9 +63,15 @@ public class ToolActions implements PropertyChangeListener {
*/ */
public ToolActions(DockingTool tool, DockingWindowManager windowManager) { public ToolActions(DockingTool tool, DockingWindowManager windowManager) {
this.dockingTool = tool; this.dockingTool = tool;
this.winMgr = windowManager; this.actionGuiHelper = new ActionToGuiHelper(windowManager);
this.actionGuiHelper = new ActionToGuiHelper(winMgr); this.keyBindingsManager = new KeyBindingsManager(windowManager);
keyBindingOptions = tool.getOptions(DockingToolConstants.KEY_BINDINGS); this.keyBindingOptions = tool.getOptions(DockingToolConstants.KEY_BINDINGS);
KeyBindingAction keyBindingAction = new KeyBindingAction(this);
keyBindingsManager.addReservedAction(keyBindingAction,
ReservedKeyBindings.UPDATE_KEY_BINDINGS_KEY);
actionGuiHelper.setKeyBindingsManager(keyBindingsManager);
} }
public void dispose() { public void dispose() {
@ -132,6 +140,7 @@ public class ToolActions implements PropertyChangeListener {
SharedStubKeyBindingAction newStub = SharedStubKeyBindingAction newStub =
new SharedStubKeyBindingAction(name, keyBindingOptions); new SharedStubKeyBindingAction(name, keyBindingOptions);
newStub.addPropertyChangeListener(this);
keyBindingOptions.registerOption(newStub.getFullName(), OptionType.KEYSTROKE_TYPE, keyBindingOptions.registerOption(newStub.getFullName(), OptionType.KEYSTROKE_TYPE,
defaultKeyStroke, null, null); defaultKeyStroke, null, null);
return newStub; return newStub;
@ -279,27 +288,42 @@ public class ToolActions implements PropertyChangeListener {
@Override @Override
public void propertyChange(PropertyChangeEvent evt) { public void propertyChange(PropertyChangeEvent evt) {
if (evt.getPropertyName().equals(DockingActionIf.KEYBINDING_DATA_PROPERTY)) { if (!evt.getPropertyName().equals(DockingActionIf.KEYBINDING_DATA_PROPERTY)) {
DockingAction action = (DockingAction) evt.getSource(); return;
if (!action.isKeyBindingManaged()) { }
dockingTool.setConfigChanged(true);
return; DockingAction action = (DockingAction) evt.getSource();
} if (!action.isKeyBindingManaged()) {
KeyBindingData keyBindingData = (KeyBindingData) evt.getNewValue(); // this reads unusually, but we need to notify the tool to rebuild its 'Window' menu
KeyStroke newKeyStroke = keyBindingData.getKeyBinding(); // in the case that this action is one of the tool's special actions
Options opt = dockingTool.getOptions(DockingToolConstants.KEY_BINDINGS); keyBindingsChanged();
KeyStroke optKeyStroke = opt.getKeyStroke(action.getFullName(), null); return;
if (newKeyStroke == null) { }
opt.removeOption(action.getFullName());
} KeyBindingData keyBindingData = (KeyBindingData) evt.getNewValue();
else if (!newKeyStroke.equals(optKeyStroke)) { KeyStroke newKeyStroke = keyBindingData.getKeyBinding();
opt.setKeyStroke(action.getFullName(), newKeyStroke); Options opt = dockingTool.getOptions(DockingToolConstants.KEY_BINDINGS);
dockingTool.setConfigChanged(true); KeyStroke optKeyStroke = opt.getKeyStroke(action.getFullName(), null);
} if (newKeyStroke == null) {
opt.removeOption(action.getFullName());
}
else if (!newKeyStroke.equals(optKeyStroke)) {
opt.setKeyStroke(action.getFullName(), newKeyStroke);
keyBindingsChanged();
} }
} }
DockingActionIf getSharedStubKeyBindingAction(String name) { DockingActionIf getSharedStubKeyBindingAction(String name) {
return sharedActionMap.get(name); return sharedActionMap.get(name);
} }
Action getAction(KeyStroke ks) {
return keyBindingsManager.getDockingKeyAction(ks);
}
// triggered by a user-initiated action
void keyBindingsChanged() {
dockingTool.setConfigChanged(true);
actionGuiHelper.keyBindingsChanged();
}
} }

View file

@ -1,6 +1,5 @@
/* ### /* ###
* IP: GHIDRA * IP: GHIDRA
* REVIEWED: YES
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -24,7 +23,7 @@ import docking.action.DockingActionIf;
import docking.action.MenuData; import docking.action.MenuData;
/** /**
* Manages the main menu bar on the main frame. * Manages the main menu bar on the main frame
*/ */
public class MenuBarManager implements MenuGroupListener { public class MenuBarManager implements MenuGroupListener {
@ -32,36 +31,41 @@ public class MenuBarManager implements MenuGroupListener {
private Map<String, MenuManager> menuManagers; private Map<String, MenuManager> menuManagers;
private final MenuGroupMap menuGroupMap; private final MenuGroupMap menuGroupMap;
/**
* Constructs a new MenuBarManager
*/
public MenuBarManager(MenuHandler actionHandler, MenuGroupMap menuGroupMap) { public MenuBarManager(MenuHandler actionHandler, MenuGroupMap menuGroupMap) {
this.menuGroupMap = menuGroupMap; this.menuGroupMap = menuGroupMap;
menuManagers = new TreeMap<String, MenuManager>(); menuManagers = new TreeMap<>();
this.menuHandler = actionHandler; this.menuHandler = actionHandler;
} }
public void clearActions() { public void clearActions() {
menuManagers = new TreeMap<String, MenuManager>(); menuManagers = new TreeMap<>();
} }
/** /**
* Adds an action to the menu. * Adds an action to the menu
* @param action the action to be added. * @param action the action to be added
* @param groupMgr the MenuGroupMap
*/ */
public void addAction(DockingActionIf action) { public void addAction(DockingActionIf action) {
MenuManager menuManager = getMenuManager(action);
if (menuManager == null) {
return;
}
menuManager.addAction(action);
}
private MenuManager getMenuManager(DockingActionIf action) {
MenuData menuBarData = action.getMenuBarData(); MenuData menuBarData = action.getMenuBarData();
if (menuBarData == null) { if (menuBarData == null) {
return; return null;
} }
String[] menuPath = menuBarData.getMenuPath(); String[] menuPath = menuBarData.getMenuPath();
if (menuPath == null || menuPath.length <= 1) { if (menuPath == null || menuPath.length <= 1) {
return; return null;
} }
MenuManager menuMgr = getMenuManager(menuPath[0]);
menuMgr.addAction(action);
return getMenuManager(menuPath[0]);
} }
/** /**
@ -104,17 +108,13 @@ public class MenuBarManager implements MenuGroupListener {
MenuManager mgr = menuManagers.get(menuName); MenuManager mgr = menuManagers.get(menuName);
if (mgr == null) { if (mgr == null) {
mgr = mgr = new MenuManager(menuName, new String[] { menuName }, mk, 1, null, false,
new MenuManager(menuName, new String[] { menuName }, mk, 1, null, false, menuHandler, menuGroupMap);
menuHandler, menuGroupMap);
menuManagers.put(menuName, mgr); menuManagers.put(menuName, mgr);
} }
return mgr; return mgr;
} }
/**
* Returns a JMenuBar for all the actions.
*/
public JMenuBar getMenuBar() { public JMenuBar getMenuBar() {
MenuManager fileMenu = menuManagers.get("File"); MenuManager fileMenu = menuManagers.get("File");
MenuManager editMenu = menuManagers.get("Edit"); MenuManager editMenu = menuManagers.get("Edit");
@ -153,6 +153,7 @@ public class MenuBarManager implements MenuGroupListener {
* @param menuPath the menu path whose group changed. * @param menuPath the menu path whose group changed.
* @param group the new group for the given menuPath. * @param group the new group for the given menuPath.
*/ */
@Override
public void menuGroupChanged(String[] menuPath, String group) { public void menuGroupChanged(String[] menuPath, String group) {
if (menuPath != null && menuPath.length > 1) { if (menuPath != null && menuPath.length > 1) {
MenuManager mgr = getMenuManager(menuPath[0]); MenuManager mgr = getMenuManager(menuPath[0]);

View file

@ -43,13 +43,10 @@ class MenuItemManager implements ManagedMenuItem, PropertyChangeListener, Action
// listeners to handle help activation // listeners to handle help activation
// -this listener covers activation by keyboard and by mouse *when enabled* // -this listener covers activation by keyboard and by mouse *when enabled*
private ChangeListener buttonModelChangeListener; private ChangeListener buttonModelChangeListener;
// -this listener covers activation by mouse *when the action is disabled* // -this listener covers activation by mouse *when the action is disabled*
private MouseAdapter menuHoverListener; private MouseAdapter menuHoverListener;
/**
* Constructs a new MenuItemManger
* @param dockableAction the action whose menuItem is being managed.
*/
MenuItemManager(MenuHandler actionHandler, DockingActionIf dockingAction, MenuItemManager(MenuHandler actionHandler, DockingActionIf dockingAction,
boolean usePopupPath) { boolean usePopupPath) {
this.menuHandler = actionHandler; this.menuHandler = actionHandler;
@ -104,9 +101,6 @@ class MenuItemManager implements ManagedMenuItem, PropertyChangeListener, Action
}; };
} }
/**
* @see ghidra.framework.docking.menu.ManagedMenuItem#getWindowGroup()
*/
@Override @Override
public String getGroup() { public String getGroup() {
MenuData menuData = isPopup ? action.getPopupMenuData() : action.getMenuBarData(); MenuData menuData = isPopup ? action.getPopupMenuData() : action.getMenuBarData();
@ -119,10 +113,6 @@ class MenuItemManager implements ManagedMenuItem, PropertyChangeListener, Action
return menuData == null ? null : menuData.getMenuSubGroup(); return menuData == null ? null : menuData.getMenuSubGroup();
} }
/**
*
* @see ghidra.framework.docking.menu.ManagedMenuItem#dispose()
*/
@Override @Override
public void dispose() { public void dispose() {
if (action != null) { if (action != null) {
@ -138,9 +128,6 @@ class MenuItemManager implements ManagedMenuItem, PropertyChangeListener, Action
action = null; action = null;
} }
/**
* @see ghidra.framework.docking.menu.ManagedMenuItem#getMenuItem()
*/
@Override @Override
public JMenuItem getMenuItem() { public JMenuItem getMenuItem() {
if (menuItem != null) { if (menuItem != null) {
@ -158,17 +145,10 @@ class MenuItemManager implements ManagedMenuItem, PropertyChangeListener, Action
return menuItem; return menuItem;
} }
/**
* Returns the owner associated with this items action.
*/
public String getOwner() { public String getOwner() {
return action.getOwner(); return action.getOwner();
} }
/**
* Changes the menuItem to reflect changes in the actions properties.
* @see java.beans.PropertyChangeListener#propertyChange(java.beans.PropertyChangeEvent)
*/
@Override @Override
public void propertyChange(PropertyChangeEvent e) { public void propertyChange(PropertyChangeEvent e) {
if (menuItem == null) { if (menuItem == null) {
@ -209,16 +189,10 @@ class MenuItemManager implements ManagedMenuItem, PropertyChangeListener, Action
} }
} }
/**
* Returns the action associated with this menu item.
*/
public DockingActionIf getAction() { public DockingActionIf getAction() {
return action; return action;
} }
/**
* @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
*/
@Override @Override
public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) {
if (menuHandler != null) { if (menuHandler != null) {

View file

@ -1,6 +1,5 @@
/* ### /* ###
* IP: GHIDRA * IP: GHIDRA
* REVIEWED: YES
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -30,8 +29,8 @@ import docking.action.MenuData;
public class MenuManager implements ManagedMenuItem { public class MenuManager implements ManagedMenuItem {
private static String NULL_GROUP_NAME = "<null group>"; private static String NULL_GROUP_NAME = "<null group>";
private Set<ManagedMenuItem> managedMenuItems = new HashSet<ManagedMenuItem>(); private Set<ManagedMenuItem> managedMenuItems = new HashSet<>();
private Map<String, MenuManager> subMenus = new HashMap<String, MenuManager>(); private Map<String, MenuManager> subMenus = new HashMap<>();
private String name; private String name;
private final String[] menuPath; private final String[] menuPath;
@ -50,9 +49,9 @@ public class MenuManager implements ManagedMenuItem {
* @param name the name of the menu. * @param name the name of the menu.
* @param mnemonicKey the key to use for the menu mnemonic * @param mnemonicKey the key to use for the menu mnemonic
* @param group the group of the menu. * @param group the group of the menu.
* @param showKeyBindings if true, includes the keybinding text on the menu item.
* @param usePopupPath if true, registers actions with popup paths as popup items. * @param usePopupPath if true, registers actions with popup paths as popup items.
* @param menuHandler Listener to be notified of menu behavior. * @param menuHandler Listener to be notified of menu behavior.
* @param menuGroupMap maps menu groups to menu paths
*/ */
public MenuManager(String name, char mnemonicKey, String group, boolean usePopupPath, public MenuManager(String name, char mnemonicKey, String group, boolean usePopupPath,
MenuHandler menuHandler, MenuGroupMap menuGroupMap) { MenuHandler menuHandler, MenuGroupMap menuGroupMap) {
@ -68,9 +67,9 @@ public class MenuManager implements ManagedMenuItem {
* @param mnemonicKey the key to use for the menu mnemonic * @param mnemonicKey the key to use for the menu mnemonic
* @param level the number of parent menus that this menu is in. * @param level the number of parent menus that this menu is in.
* @param group the group of this menu. * @param group the group of this menu.
* @param showKeyBindings if true, includes the keybinding text on the menu item.
* @param usePopupPath if true, registers actions with popup paths as popup items. * @param usePopupPath if true, registers actions with popup paths as popup items.
* @param menuHandler Listener to be notified of menu behavior. * @param menuHandler Listener to be notified of menu behavior.
* @param menuGroupMap maps menu groups to menu paths
*/ */
MenuManager(String name, String[] menuPath, char mnemonicKey, int level, String group, MenuManager(String name, String[] menuPath, char mnemonicKey, int level, String group,
boolean usePopupPath, MenuHandler menuHandler, MenuGroupMap menuGroupMap) { boolean usePopupPath, MenuHandler menuHandler, MenuGroupMap menuGroupMap) {
@ -96,10 +95,8 @@ public class MenuManager implements ManagedMenuItem {
} }
/** /**
* Adds an action to this menu. Can create subMenus depending on the menuPath of the * Adds an action to this menu. Can create subMenus depending on the menuPath of the action
* action. * @param action the action to be added
* @param action the action to be added.
* @param menuGroupMap group map for menuItems
*/ */
public void addAction(DockingActionIf action) { public void addAction(DockingActionIf action) {
checkForSwingThread(); checkForSwingThread();
@ -122,9 +119,8 @@ public class MenuManager implements ManagedMenuItem {
submenuGroup = subMenuName; submenuGroup = subMenuName;
} }
mgr = mgr = new MenuManager(cleanSubMenuName, submenuPath, mnemonic, submenuLevel,
new MenuManager(cleanSubMenuName, submenuPath, mnemonic, submenuLevel, submenuGroup, usePopupPath, menuHandler, menuGroupMap);
submenuGroup, usePopupPath, menuHandler, menuGroupMap);
subMenus.put(cleanSubMenuName, mgr); subMenus.put(cleanSubMenuName, mgr);
managedMenuItems.add(mgr); managedMenuItems.add(mgr);
} }
@ -198,7 +194,7 @@ public class MenuManager implements ManagedMenuItem {
menu.addMenuListener(menuHandler); menu.addMenuListener(menuHandler);
} }
List<ManagedMenuItem> list = new ArrayList<ManagedMenuItem>(managedMenuItems); List<ManagedMenuItem> list = new ArrayList<>(managedMenuItems);
Collections.sort(list, comparator); Collections.sort(list, comparator);
String lastGroup = null; String lastGroup = null;
@ -259,7 +255,7 @@ public class MenuManager implements ManagedMenuItem {
if (popupMenu == null) { if (popupMenu == null) {
popupMenu = new JPopupMenu(name); popupMenu = new JPopupMenu(name);
List<ManagedMenuItem> list = new ArrayList<ManagedMenuItem>(managedMenuItems); List<ManagedMenuItem> list = new ArrayList<>(managedMenuItems);
Collections.sort(list, comparator); Collections.sort(list, comparator);
String lastGroup = NULL_GROUP_NAME; String lastGroup = NULL_GROUP_NAME;
boolean hasMenuItems = false; boolean hasMenuItems = false;

View file

@ -15,16 +15,17 @@
*/ */
package docking.menu; package docking.menu;
import ghidra.util.StringUtilities;
import java.awt.event.*; import java.awt.event.*;
import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener; import java.beans.PropertyChangeListener;
import javax.swing.*; import javax.swing.*;
import org.apache.commons.lang3.StringUtils;
import docking.*; import docking.*;
import docking.action.*; import docking.action.*;
import ghidra.util.StringUtilities;
/** /**
* Class to manager toolbar buttons. * Class to manager toolbar buttons.
@ -93,7 +94,7 @@ public class ToolBarItemManager implements PropertyChangeListener, ActionListene
StringBuilder buffy = new StringBuilder(toolTipText); StringBuilder buffy = new StringBuilder(toolTipText);
if (StringUtilities.startsWithIgnoreCase(toolTipText, "<HTML>")) { if (StringUtilities.startsWithIgnoreCase(toolTipText, "<HTML>")) {
String endHTMLTag = "</HTML>"; String endHTMLTag = "</HTML>";
int closeTagIndex = StringUtilities.indexOfIgnoreCase(toolTipText, endHTMLTag); int closeTagIndex = StringUtils.indexOfIgnoreCase(toolTipText, endHTMLTag);
if (closeTagIndex < 0) { if (closeTagIndex < 0) {
// no closing tag, which is acceptable // no closing tag, which is acceptable
buffy.append(START_KEYBINDING_TEXT).append(keyBindingText).append( buffy.append(START_KEYBINDING_TEXT).append(keyBindingText).append(
@ -204,16 +205,13 @@ public class ToolBarItemManager implements PropertyChangeListener, ActionListene
final ActionContext finalContext = tempContext; final ActionContext finalContext = tempContext;
// this gives the UI some time to repaint before executing the action // this gives the UI some time to repaint before executing the action
SwingUtilities.invokeLater(new Runnable() { SwingUtilities.invokeLater(() -> {
@Override if (toolBarAction.isEnabledForContext(finalContext)) {
public void run() { if (toolBarAction instanceof ToggleDockingActionIf) {
if (toolBarAction.isEnabledForContext(finalContext)) { ToggleDockingActionIf toggleAction = (ToggleDockingActionIf) toolBarAction;
if (toolBarAction instanceof ToggleDockingActionIf) { toggleAction.setSelected(!toggleAction.isSelected());
ToggleDockingActionIf toggleAction = (ToggleDockingActionIf) toolBarAction;
toggleAction.setSelected(!toggleAction.isSelected());
}
toolBarAction.actionPerformed(finalContext);
} }
toolBarAction.actionPerformed(finalContext);
} }
}); });
} }

View file

@ -31,7 +31,7 @@ import docking.widgets.VariableHeightPanel;
*/ */
public class ToolBarManager { public class ToolBarManager {
private Map<String, List<ToolBarItemManager>> groupToItemsMap = private Map<String, List<ToolBarItemManager>> groupToItemsMap =
new TreeMap<String, List<ToolBarItemManager>>(new GroupComparator()); new TreeMap<>(new GroupComparator());
private Comparator<? super ToolBarItemManager> toolBarItemComparator = private Comparator<? super ToolBarItemManager> toolBarItemComparator =
new ToolBarItemManagerComparator(); new ToolBarItemManagerComparator();
@ -47,9 +47,6 @@ public class ToolBarManager {
toolBar = null; toolBar = null;
} }
/**
* Adds the action to the toolbar.
*/
public void addAction(DockingActionIf action) { public void addAction(DockingActionIf action) {
ToolBarData toolBarData = action.getToolBarData(); ToolBarData toolBarData = action.getToolBarData();
if (toolBarData == null) { if (toolBarData == null) {
@ -61,7 +58,7 @@ public class ToolBarManager {
String group = toolBarData.getToolBarGroup(); String group = toolBarData.getToolBarGroup();
List<ToolBarItemManager> items = groupToItemsMap.get(group); List<ToolBarItemManager> items = groupToItemsMap.get(group);
if (items == null) { if (items == null) {
items = new ArrayList<ToolBarItemManager>(); items = new ArrayList<>();
groupToItemsMap.put(group, items); groupToItemsMap.put(group, items);
} }
items.add(new ToolBarItemManager(action, windowManager)); items.add(new ToolBarItemManager(action, windowManager));
@ -96,9 +93,6 @@ public class ToolBarManager {
groupToItemsMap.clear(); groupToItemsMap.clear();
} }
/**
* Returns true if the toolbar is empty.
*/
public boolean isEmpty() { public boolean isEmpty() {
return groupToItemsMap.isEmpty(); return groupToItemsMap.isEmpty();
} }

View file

@ -1114,7 +1114,8 @@ public abstract class AbstractDockingTest extends AbstractGenericTest {
} }
/** /**
* A helper method to find all actions with the given owner's name * A helper method to find all actions with the given owner's name (this will not include
* reserved system actions)
* *
* @param tool the tool containing all system actions * @param tool the tool containing all system actions
* @param name the owner's name to match * @param name the owner's name to match
@ -1125,7 +1126,8 @@ public abstract class AbstractDockingTest extends AbstractGenericTest {
} }
/** /**
* A helper method to find all actions by name, with the given owner's name * A helper method to find all actions by name, with the given owner's name (this will not
* include reserved system actions)
* *
* @param tool the tool containing all system actions * @param tool the tool containing all system actions
* @param owner the owner's name * @param owner the owner's name
@ -1167,7 +1169,8 @@ public abstract class AbstractDockingTest extends AbstractGenericTest {
/** /**
* Finds the action by the given owner name and action name. * Finds the action by the given owner name and action name.
* If you do not know the owner name, then use * If you do not know the owner name, then use
* the call {@link #getActionsByName(DockingTool, String)} instead. * the call {@link #getActionsByName(DockingTool, String)} instead (this will not include
* reserved system actions).
* *
* <P>Note: more specific test case subclasses provide other methods for finding actions * <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). * when you have an owner name (which is usually the plugin name).

View file

@ -37,7 +37,7 @@ public class FakeDockingTool extends AbstractDockingTool {
List<Image> windowIcons = ApplicationInformationDisplayFactory.getWindowIcons(); List<Image> windowIcons = ApplicationInformationDisplayFactory.getWindowIcons();
winMgr = new DockingWindowManager("EMPTY", windowIcons, listener, false /*isModal*/, winMgr = new DockingWindowManager("EMPTY", windowIcons, listener, false /*isModal*/,
true /*isDockable*/, true /*hasStatus*/, null /*DropTargetFactory*/); true /*isDockable*/, true /*hasStatus*/, null /*DropTargetFactory*/);
actionMgr = new ToolActions(this, winMgr); toolActions = new ToolActions(this, winMgr);
} }
@Override @Override

View file

@ -303,7 +303,7 @@ public class JavaSourceFile {
if (nameAndMaybeDeclaraction.length == 2) { if (nameAndMaybeDeclaraction.length == 2) {
return nameAndMaybeDeclaraction[0].endsWith("Action"); return nameAndMaybeDeclaraction[0].endsWith("Action");
} }
return StringUtilities.containsIgnoreCase(nameAndMaybeDeclaraction[0], "action"); return StringUtils.containsIgnoreCase(nameAndMaybeDeclaraction[0], "action");
} }
private JavaSourceLine findEndOfUnknownLine(int lineNumber) { private JavaSourceLine findEndOfUnknownLine(int lineNumber) {

View file

@ -348,22 +348,6 @@ public class StringUtilities {
return string.regionMatches(true, startIndex, postfix, 0, postfix.length()); return string.regionMatches(true, startIndex, postfix, 0, postfix.length());
} }
/**
* Returns true if the given <tt>containingString</tt> contains the given
* <tt>substring</tt>, ignoring case.
*
* @param containingString the string which may contain the prefix
* @param substring the string for which to search within the containing string
* @return true if the given <tt>containingString</tt> contains the given
* <tt>substring</tt>, ignoring case.
*/
public static boolean containsIgnoreCase(String containingString, String substring) {
if ((containingString == null) || (substring == null)) {
return false;
}
return (indexOfIgnoreCase(containingString, substring, 0) >= 0);
}
/** /**
* Returns true if all the given <tt>searches</tt> are contained in the given string. * Returns true if all the given <tt>searches</tt> are contained in the given string.
* *
@ -455,61 +439,6 @@ public class StringUtilities {
return true; return true;
} }
/**
* Returns the index of the first occurrence the given <tt>substring</tt> in the given
* <tt>containingString</tt>, ignoring case to look for the substring.
* <p>
* This method is a convenience method for calling:
* <pre>
* <tt>indexOfIgnoreCase( containingString, substring, 0 );</tt>
* </pre>
* @param containingString the string which may contain the substring
* @param substring the string for which to search within the containing string
* @return index of substring within the given containing string
*/
public static int indexOfIgnoreCase(String containingString, String substring) {
if ((containingString == null) || (substring == null)) {
return -1;
}
return indexOfIgnoreCase(containingString, substring, 0);
}
/**
* Returns the index of the first occurrence the given <tt>substring</tt> in the given
* <tt>containingString</tt>, starting at the given <tt>index</tt>,
* ignoring case to look for the substring.
* <p>
* @param containingString the string which may contain the substring
* @param substring the string for which to search within the containing string
* @param index the index from which to start the comparison
* @return index of substring within the given containing string
*/
public static int indexOfIgnoreCase(String containingString, String substring, int index) {
if ((containingString == null) || (substring == null)) {
return -1;
}
return (containingString.toLowerCase().indexOf(substring.toLowerCase(), index));
}
/**
* Returns the index of the last occurrence the given <tt>substring</tt> in the given
* <tt>containingString</tt>, ignoring case to look for the substring.
* <p>
* This method is a convenience method for calling:
* <pre>
* <tt>lastIndexOfIgnoreCase( containingString, substring, 0 );</tt>
* </pre>
* @param containingString the string which may contain the substring
* @param substring the string for which to search within the containing string
* @return index of substring within the given containing string
*/
public static int lastIndexOfIgnoreCase(String containingString, String substring) {
if ((containingString == null) || (substring == null)) {
return -1;
}
return (containingString.toLowerCase().lastIndexOf(substring.toLowerCase()));
}
/** /**
* Convert tabs in the given string to spaces. * Convert tabs in the given string to spaces.
* *
@ -532,10 +461,8 @@ public class StringUtilities {
char c = str.charAt(i); char c = str.charAt(i);
if (c == '\t') { if (c == '\t') {
int nSpaces = tabSize - (linepos % tabSize); int nSpaces = tabSize - (linepos % tabSize);
String pad = padString("", ' ', nSpaces); String pad = pad("", ' ', nSpaces);
buffer.append(pad); buffer.append(pad);
linepos += nSpaces; linepos += nSpaces;
} }
else { else {
@ -606,23 +533,6 @@ public class StringUtilities {
return padded; return padded;
} }
/**
* Pads the source string to the specified length, using the filler string
* as the pad. If length is negative, left justifies the string, appending
* the filler; if length is positive, right justifies the source string.
*
* @param source the original string to pad.
* @param filler the type of characters with which to pad
* @param length the length of padding to add (0 results in no changes)
* @return the padded string
* @deprecated use {@link #pad(String, char, int)}; functionally the same, but smaller
* and more consistent name
*/
@Deprecated
public static String padString(String source, char filler, int length) {
return pad(source, filler, length);
}
/** /**
* Pads the source string to the specified length, using the filler string * Pads the source string to the specified length, using the filler string
* as the pad. If length is negative, left justifies the string, appending * as the pad. If length is negative, left justifies the string, appending

View file

@ -33,6 +33,8 @@ import resources.icons.TranslateIcon;
*/ */
public class Icons { public class Icons {
public static final ImageIcon EMPTY_ICON = ResourceManager.loadImage("images/EmptyIcon16.gif");
public static final ImageIcon ADD_ICON = ResourceManager.loadImage("images/Plus2.png"); public static final ImageIcon ADD_ICON = ResourceManager.loadImage("images/Plus2.png");
public static final ImageIcon COLLAPSE_ALL_ICON = public static final ImageIcon COLLAPSE_ALL_ICON =

View file

@ -17,10 +17,8 @@ package ghidra.util;
import static ghidra.util.HTMLUtilities.HTML; import static ghidra.util.HTMLUtilities.HTML;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import java.awt.Color; import java.awt.Color;
import java.util.Arrays;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@ -53,7 +51,7 @@ public class HTMLUtilitiesTest {
String s = "This text has<BR>an existing BR tag"; String s = "This text has<BR>an existing BR tag";
String html = HTMLUtilities.toHTML(s); String html = HTMLUtilities.toHTML(s);
assertEquals(HTML + s, html); assertEquals(HTML + s, html);
assertLogMessage("cannot", "wrap"); spyLogger.assertLogMessage("cannot", "wrap");
} }
@Test @Test
@ -61,7 +59,7 @@ public class HTMLUtilitiesTest {
String s = "This text has<BR>\nan existing BR tag and a newline"; String s = "This text has<BR>\nan existing BR tag and a newline";
String html = HTMLUtilities.toHTML(s); String html = HTMLUtilities.toHTML(s);
assertEquals(HTML + s, html); assertEquals(HTML + s, html);
assertLogMessage("cannot", "wrap"); spyLogger.assertLogMessage("cannot", "wrap");
} }
@Test @Test
@ -140,16 +138,6 @@ public class HTMLUtilitiesTest {
assertEquals("#FF0000", rgb); assertEquals("#FF0000", rgb);
} }
private void assertLogMessage(String... words) {
for (String message : spyLogger) {
if (StringUtilities.containsAllIgnoreCase(message, words)) {
return;
}
}
fail("Did not find log message containing all these words: " + Arrays.toString(words));
}
@Test @Test
public void testLinkPlaceholder() { public void testLinkPlaceholder() {
String placeholderStr = String placeholderStr =

View file

@ -94,26 +94,6 @@ public class StringUtilitiesTest {
assertEquals(-1, StringUtilities.indexOfWord(sentenceWithTestNotAsAWord, word)); assertEquals(-1, StringUtilities.indexOfWord(sentenceWithTestNotAsAWord, word));
} }
@Test
public void testLastIndexOfIgnoresCase() {
String bob = "bob";
String endsWithBob = "endsWithBob";
assertEquals(8, StringUtilities.lastIndexOfIgnoreCase(endsWithBob, bob));
String endsWithBobUpperCase = "endsWithBOB";
assertEquals(8, StringUtilities.lastIndexOfIgnoreCase(endsWithBobUpperCase, bob));
String startsWithBob = "bobWithTrailingText";
assertEquals(0, StringUtilities.lastIndexOfIgnoreCase(startsWithBob, bob));
String justBob = "bOb";
assertEquals(0, StringUtilities.lastIndexOfIgnoreCase(justBob, bob));
String manyBobs = "This is a string, bob, that has bob, many bobs...and then some text";
assertEquals(42, StringUtilities.lastIndexOfIgnoreCase(manyBobs, bob));
}
@Test @Test
public void testIsAllBlank() { public void testIsAllBlank() {

View file

@ -15,7 +15,8 @@
*/ */
package ghidra.framework.plugintool; package ghidra.framework.plugintool;
import static ghidra.framework.model.ToolTemplate.*; import static ghidra.framework.model.ToolTemplate.TOOL_INSTANCE_NAME_XML_NAME;
import static ghidra.framework.model.ToolTemplate.TOOL_NAME_XML_NAME;
import java.awt.*; import java.awt.*;
import java.awt.event.KeyEvent; import java.awt.event.KeyEvent;
@ -158,7 +159,7 @@ public abstract class PluginTool extends AbstractDockingTool
eventMgr = new EventManager(this); eventMgr = new EventManager(this);
serviceMgr = new ServiceManager(); serviceMgr = new ServiceManager();
installServices(); installServices();
actionMgr = new ToolActions(this, winMgr); toolActions = new ToolActions(this, winMgr);
pluginMgr = new PluginManager(this, serviceMgr); pluginMgr = new PluginManager(this, serviceMgr);
dialogMgr = new DialogManager(this); dialogMgr = new DialogManager(this);
initActions(); initActions();
@ -460,7 +461,7 @@ public abstract class PluginTool extends AbstractDockingTool
winMgr.setVisible(false); winMgr.setVisible(false);
eventMgr.clearLastEvents(); eventMgr.clearLastEvents();
pluginMgr.dispose(); pluginMgr.dispose();
actionMgr.dispose(); toolActions.dispose();
if (project != null) { if (project != null) {
project.releaseFiles(this); project.releaseFiles(this);
@ -1300,7 +1301,7 @@ public abstract class PluginTool extends AbstractDockingTool
protected void restoreOptionsFromXml(Element root) { protected void restoreOptionsFromXml(Element root) {
optionsMgr.setConfigState(root.getChild("OPTIONS")); optionsMgr.setConfigState(root.getChild("OPTIONS"));
actionMgr.restoreKeyBindings(); toolActions.restoreKeyBindings();
setToolOptionsHelpLocation(); setToolOptionsHelpLocation();
} }
@ -1321,7 +1322,7 @@ public abstract class PluginTool extends AbstractDockingTool
} }
void removeAll(String owner) { void removeAll(String owner) {
actionMgr.removeToolActions(owner); toolActions.removeToolActions(owner);
winMgr.removeAll(owner); winMgr.removeAll(owner);
} }
@ -1496,7 +1497,7 @@ public abstract class PluginTool extends AbstractDockingTool
} }
public void refreshKeybindings() { public void refreshKeybindings() {
actionMgr.restoreKeyBindings(); toolActions.restoreKeyBindings();
} }
public void setUnconfigurable() { public void setUnconfigurable() {

View file

@ -32,6 +32,7 @@ import docking.KeyEntryTextField;
import docking.action.DockingActionIf; import docking.action.DockingActionIf;
import docking.action.KeyBindingData; import docking.action.KeyBindingData;
import docking.actions.KeyBindingUtils; import docking.actions.KeyBindingUtils;
import docking.actions.SharedStubKeyBindingAction;
import docking.tool.util.DockingToolConstants; import docking.tool.util.DockingToolConstants;
import docking.widgets.MultiLineLabel; import docking.widgets.MultiLineLabel;
import docking.widgets.OptionDialog; import docking.widgets.OptionDialog;
@ -429,9 +430,6 @@ public class KeyBindingsPanel extends JPanel {
tableModel.fireTableDataChanged(); tableModel.fireTableDataChanged();
} }
/**
* Add listeners. Valid modifiers are CTRL and ALT and SHIFT.
*/
private void addListeners() { private void addListeners() {
selectionModel = actionTable.getSelectionModel(); selectionModel = actionTable.getSelectionModel();
selectionModel.addListSelectionListener(new TableSelectionListener()); selectionModel.addListSelectionListener(new TableSelectionListener());
@ -463,11 +461,6 @@ public class KeyBindingsPanel extends JPanel {
unappliedChanges = changes; unappliedChanges = changes;
} }
/**
* Get the action that is selected in the table.
*
* @return String
*/
private String getSelectedAction() { private String getSelectedAction() {
if (selectionModel.isSelectionEmpty()) { if (selectionModel.isSelectionEmpty()) {
return null; return null;
@ -477,9 +470,6 @@ public class KeyBindingsPanel extends JPanel {
return tableActions.get(modelRow).getFullName(); return tableActions.get(modelRow).getFullName();
} }
/**
* Add the action name to the list for the given keystroke.
*/
private void addToKeyMap(KeyStroke ks, String actionName) { private void addToKeyMap(KeyStroke ks, String actionName) {
if (ks == null) { if (ks == null) {
return; return;
@ -495,9 +485,6 @@ public class KeyBindingsPanel extends JPanel {
} }
} }
/**
* Remove the given actionName from from the list for the keystroke.
*/
private void removeFromKeyMap(KeyStroke ks, String actionName) { private void removeFromKeyMap(KeyStroke ks, String actionName) {
if (ks == null) { if (ks == null) {
return; return;
@ -512,11 +499,7 @@ public class KeyBindingsPanel extends JPanel {
} }
} }
/** private void showActionsMappedToKeyStroke(String ksName) {
* Display actions mapped to the given keystroke name.
* @param ksName name of Keystroke that has multiple actions mapped
*/
private void showActionMapped(String ksName) {
List<String> list = actionNamesByKeyStroke.get(ksName); List<String> list = actionNamesByKeyStroke.get(ksName);
if (list == null) { if (list == null) {
return; return;
@ -538,17 +521,10 @@ public class KeyBindingsPanel extends JPanel {
} }
} }
/**
* Clear the info panel.
*/
private void clearInfoPanel() { private void clearInfoPanel() {
updateInfoPanel(" "); updateInfoPanel(" ");
} }
/**
* Replace multiline label in the info panel.
* @param text new text to show
*/
private void updateInfoPanel(String text) { private void updateInfoPanel(String text) {
infoPanel.removeAll(); infoPanel.removeAll();
infoPanel.repaint(); infoPanel.repaint();
@ -559,8 +535,6 @@ public class KeyBindingsPanel extends JPanel {
validate(); validate();
} }
//////////////////////////////////////////////////////////////////////
private void processKeyBindingsFromOptions(Options keyBindingOptions) { private void processKeyBindingsFromOptions(Options keyBindingOptions) {
if (keyBindingOptions == null) { if (keyBindingOptions == null) {
return; return;
@ -578,7 +552,7 @@ public class KeyBindingsPanel extends JPanel {
while (iterator.hasNext()) { while (iterator.hasNext()) {
String name = iterator.next(); String name = iterator.next();
KeyStroke keyStroke = keyBindingsMap.get(name); KeyStroke keyStroke = keyBindingsMap.get(name);
keyStroke = KeyBindingData.validateKeyStroke(keyStroke); keyStroke = KeyBindingUtils.validateKeyStroke(keyStroke);
// prevent non-existing keybindings from being added to Ghidra (this can happen // prevent non-existing keybindings from being added to Ghidra (this can happen
// when actions exist in the imported bindings, but have been removed from // when actions exist in the imported bindings, but have been removed from
@ -619,7 +593,7 @@ public class KeyBindingsPanel extends JPanel {
if (selectedActionName != null) { if (selectedActionName != null) {
if (processKeyStroke(selectedActionName, ks)) { if (processKeyStroke(selectedActionName, ks)) {
String keyStrokeText = KeyEntryTextField.parseKeyStroke(ks); String keyStrokeText = KeyEntryTextField.parseKeyStroke(ks);
showActionMapped(keyStrokeText); showActionsMappedToKeyStroke(keyStrokeText);
tableModel.fireTableDataChanged(); tableModel.fireTableDataChanged();
} }
} }
@ -691,7 +665,7 @@ public class KeyBindingsPanel extends JPanel {
if (ks != null) { if (ks != null) {
ksName = KeyEntryTextField.parseKeyStroke(ks); ksName = KeyEntryTextField.parseKeyStroke(ks);
showActionMapped(ksName); showActionsMappedToKeyStroke(ksName);
} }
ksField.setText(ksName); ksField.setText(ksName);
@ -734,11 +708,18 @@ public class KeyBindingsPanel extends JPanel {
} }
return ""; return "";
case PLUGIN_NAME: case PLUGIN_NAME:
return action.getOwner(); return getOwner(action);
} }
return "Unknown Column!"; return "Unknown Column!";
} }
private String getOwner(DockingActionIf action) {
if (action instanceof SharedStubKeyBindingAction) {
return ((SharedStubKeyBindingAction) action).getOwnersDescription();
}
return action.getOwner();
}
@Override @Override
public List<DockingActionIf> getModelData() { public List<DockingActionIf> getModelData() {
return tableActions; return tableActions;

View file

@ -23,9 +23,9 @@ import javax.swing.KeyStroke;
import javax.swing.text.SimpleAttributeSet; import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants; import javax.swing.text.StyleConstants;
import docking.DockingKeyBindingAction;
import docking.action.DockingActionIf; import docking.action.DockingActionIf;
import docking.action.MenuData; import docking.action.MenuData;
import docking.actions.KeyBindingUtils;
import ghidra.framework.plugintool.PluginConfigurationModel; import ghidra.framework.plugintool.PluginConfigurationModel;
import ghidra.framework.plugintool.util.PluginDescription; import ghidra.framework.plugintool.util.PluginDescription;
import ghidra.framework.plugintool.util.PluginStatus; import ghidra.framework.plugintool.util.PluginStatus;
@ -201,7 +201,7 @@ class PluginDetailsPanel extends AbstractDetailsPanel {
buffer.append("<TD WIDTH=\"100\">"); buffer.append("<TD WIDTH=\"100\">");
KeyStroke keyBinding = dockableAction.getKeyBinding(); KeyStroke keyBinding = dockableAction.getKeyBinding();
if (keyBinding != null) { if (keyBinding != null) {
String keyStrokeString = DockingKeyBindingAction.parseKeyStroke(keyBinding); String keyStrokeString = KeyBindingUtils.parseKeyStroke(keyBinding);
insertHTMLString(keyStrokeString, locAttrSet, buffer); insertHTMLString(keyStrokeString, locAttrSet, buffer);
} }
else { else {

View file

@ -27,9 +27,11 @@ import javax.swing.border.Border;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import docking.*; import docking.DialogComponentProvider;
import docking.StatusBar;
import docking.action.DockingActionIf; import docking.action.DockingActionIf;
import docking.action.KeyEntryDialog; import docking.actions.KeyEntryDialog;
import docking.actions.ToolActions;
import docking.widgets.OptionDialog; import docking.widgets.OptionDialog;
import docking.widgets.table.GTable; import docking.widgets.table.GTable;
import generic.jar.ResourceFile; import generic.jar.ResourceFile;
@ -280,12 +282,10 @@ public class ToolScreenShots extends GhidraScreenShotGenerator {
public void testSetKeyBindings() { public void testSetKeyBindings() {
tool = env.launchDefaultTool(); tool = env.launchDefaultTool();
DockingWindowManager windowManager = tool.getWindowManager(); ToolActions toolActions = (ToolActions) getInstanceField("toolActions", tool);
ActionToGuiMapper actionMgr =
(ActionToGuiMapper) getInstanceField("actionManager", windowManager);
DockingActionIf action = getAction(tool, "FunctionPlugin", "Delete Function"); DockingActionIf action = getAction(tool, "FunctionPlugin", "Delete Function");
final KeyEntryDialog keyEntryDialog = new KeyEntryDialog(action, actionMgr); final KeyEntryDialog keyEntryDialog = new KeyEntryDialog(action, toolActions);
runSwing(() -> tool.showDialog(keyEntryDialog), false); runSwing(() -> tool.showDialog(keyEntryDialog), false);
captureDialog(); captureDialog();