mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-09-28 17:11:11 -09:00
GP-6628 - Key Bindings - Updated the options UI to show unregistered key bindings
This commit is contained in:
@@ -979,7 +979,6 @@ src/main/resources/images/hoverOff.gif||GHIDRA||||END|
|
||||
src/main/resources/images/hoverOn.gif||GHIDRA||||END|
|
||||
src/main/resources/images/icon_link.gif||FAMFAMFAM Mini Icons - Public Domain|||famfamfam mini icon set|END|
|
||||
src/main/resources/images/imported_bookmark.gif||GHIDRA||||END|
|
||||
src/main/resources/images/katomic.png||Nuvola Icons - LGPL 2.1|||Nuvola icon set|END|
|
||||
src/main/resources/images/key.png||FAMFAMFAM Icons - CC 2.5|||silk|END|
|
||||
src/main/resources/images/kmessedwords.png||Nuvola Icons - LGPL 2.1||||END|
|
||||
src/main/resources/images/label.png||GHIDRA||||END|
|
||||
|
||||
@@ -126,13 +126,28 @@
|
||||
</OL>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<P><IMG alt="" border="0" src="help/shared/note.yellow.png"> When a key is mapped to multiple
|
||||
actions, and more than one of these actions is valid in the current context (i.e., the action
|
||||
is enabled), then a dialog is displayed for you to choose what action you want to
|
||||
perform.</P>
|
||||
|
||||
<P>To avoid the extra step of choosing the action from the dialog, do not map the same key to
|
||||
actions that are applicable in the same context.</P>
|
||||
<BLOCKQUOTE>
|
||||
<P><IMG alt="" border="0" src="help/shared/note.yellow.png"> When a key is mapped to multiple
|
||||
actions, and more than one of these actions is valid in the current context (i.e., the action
|
||||
is enabled), then a dialog is displayed for you to choose what action you want to
|
||||
perform.</P>
|
||||
|
||||
<P>To avoid the extra step of choosing the action from the dialog, do not map the same key to
|
||||
actions that are applicable in the same context.</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P><IMG alt="" border="0" src="help/shared/note.yellow.png">Unregistered action key
|
||||
bindings will be displayed in a lighter color in the table. These values can still
|
||||
be changed.</P>
|
||||
|
||||
<P>Unregistered actions may appear if plugins have been removed. They may also appear
|
||||
for dialogs or components that have not yet been created in the system. Because
|
||||
some actions are registred on-demand, not all actions in the system will appear in
|
||||
this table. Once an action UI has been shown in a given tool session, then its
|
||||
actions appear in this table for the remainder of the tool session.
|
||||
</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H3>Remove a Key Binding</H3>
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ import org.junit.*;
|
||||
|
||||
import docking.*;
|
||||
import docking.action.DockingActionIf;
|
||||
import docking.actions.ActionBindingsDescriptor;
|
||||
import docking.actions.KeyBindingUtils;
|
||||
import docking.options.editor.*;
|
||||
import docking.tool.ToolConstants;
|
||||
@@ -887,10 +888,10 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
|
||||
JTable table = (JTable) getInstanceField("actionTable", panel);
|
||||
@SuppressWarnings("unchecked")
|
||||
RowObjectFilterModel<DockingActionIf> model =
|
||||
(RowObjectFilterModel<DockingActionIf>) table.getModel();
|
||||
RowObjectFilterModel<ActionBindingsDescriptor> model =
|
||||
(RowObjectFilterModel<ActionBindingsDescriptor>) table.getModel();
|
||||
|
||||
DockingActionIf rowValue = model.getModelData().get(row);
|
||||
ActionBindingsDescriptor rowValue = model.getModelData().get(row);
|
||||
|
||||
String keyBindingColumnValue =
|
||||
(String) model.getColumnValueForRow(rowValue, 1 /* key binding column */);
|
||||
@@ -917,10 +918,10 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
|
||||
JTable table = (JTable) getInstanceField("actionTable", panel);
|
||||
@SuppressWarnings("unchecked")
|
||||
RowObjectFilterModel<DockingActionIf> model =
|
||||
(RowObjectFilterModel<DockingActionIf>) table.getModel();
|
||||
RowObjectFilterModel<ActionBindingsDescriptor> model =
|
||||
(RowObjectFilterModel<ActionBindingsDescriptor>) table.getModel();
|
||||
|
||||
DockingActionIf rowValue = model.getModelData().get(row);
|
||||
ActionBindingsDescriptor rowValue = model.getModelData().get(row);
|
||||
|
||||
String keyBindingColumnValue =
|
||||
(String) model.getColumnValueForRow(rowValue, 1 /* key binding column */);
|
||||
@@ -1036,14 +1037,14 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
private int selectRowForAction(KeyBindingsPanel panel, String actionName, String actionOwner) {
|
||||
final JTable table = (JTable) getInstanceField("actionTable", panel);
|
||||
@SuppressWarnings("unchecked")
|
||||
final RowObjectFilterModel<DockingActionIf> model =
|
||||
(RowObjectFilterModel<DockingActionIf>) table.getModel();
|
||||
final RowObjectFilterModel<ActionBindingsDescriptor> model =
|
||||
(RowObjectFilterModel<ActionBindingsDescriptor>) table.getModel();
|
||||
|
||||
int actionRow = -1;
|
||||
List<DockingActionIf> modelData = model.getModelData();
|
||||
List<ActionBindingsDescriptor> modelData = model.getModelData();
|
||||
int rowCount = modelData.size();
|
||||
for (int i = 0; i < rowCount; i++) {
|
||||
DockingActionIf rowData = modelData.get(i);
|
||||
ActionBindingsDescriptor rowData = modelData.get(i);
|
||||
String rowActionName =
|
||||
(String) model.getColumnValueForRow(rowData, 0 /* action name column */);
|
||||
if (rowActionName.equals(actionName)) {
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package docking.actions;
|
||||
|
||||
import docking.action.DockingActionIf;
|
||||
|
||||
/**
|
||||
* An interface that allows the {@link KeyBindingsModel} API to provide key and mouse binding information
|
||||
* to clients, without having to have a registered action to provide the information. Action
|
||||
* descriptions will be loaded from the saved tool options. If no plugin has registered an action
|
||||
* for the current tool session, then the an unregistered action descriptor will get created when
|
||||
* editing key and mouse bindings via the options UI.
|
||||
*/
|
||||
public interface ActionBindingsDescriptor {
|
||||
|
||||
/**
|
||||
* {@return the action name without the owner}
|
||||
*/
|
||||
public String getName();
|
||||
|
||||
/**
|
||||
* {@return the full action name in the format: 'Name (Owner)'}
|
||||
*/
|
||||
public String getFullName();
|
||||
|
||||
/**
|
||||
* {@return the owner name(s) of the action}
|
||||
*/
|
||||
public String getOwnerDescription();
|
||||
|
||||
/**
|
||||
* {@return the action description or a blank string}
|
||||
*/
|
||||
public String getDescription();
|
||||
|
||||
/**
|
||||
* {@return a string that shows all key and mouse binding info for the action}
|
||||
*/
|
||||
public String getBindingText();
|
||||
|
||||
/**
|
||||
* The action for the binding. This will be an arbitrary action for shared bindings.
|
||||
* This will be null if this class represents an unregistered action.
|
||||
*
|
||||
* @return an action or null
|
||||
*/
|
||||
public DockingActionIf getRepresentativeAction();
|
||||
|
||||
/**
|
||||
* {@return true if a plugin has registered an action for this descriptor; false is no action
|
||||
* has been registered}
|
||||
*/
|
||||
public boolean isRegistered();
|
||||
}
|
||||
@@ -17,6 +17,7 @@ package docking.actions;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.swing.KeyStroke;
|
||||
|
||||
@@ -30,31 +31,31 @@ import gui.event.MouseBinding;
|
||||
import util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* An object that maps actions to key strokes and mouse bindings.
|
||||
* An object that maps actions to key strokes and mouse bindings. This class loads key bindings
|
||||
* from tool options. Clients can use this model to manage the values of each binding until the
|
||||
* user is done editing the bindings. When the client is finished, the bindings changes can be:
|
||||
* applied, ignored or restored to default settings.
|
||||
* <p>
|
||||
* This class knows how to load all system actions and how to load any key and mouse bindings for
|
||||
* those actions from the tool's options. Clients can make changes to the state of this class that
|
||||
* can then be applied to the system by calling {@link #applyChanges()}.
|
||||
*/
|
||||
public class KeyBindings {
|
||||
public class KeyBindingsModel {
|
||||
|
||||
private Tool tool;
|
||||
private ToolOptions keyBindingOptions;
|
||||
|
||||
// allows clients to populate a table of all actions
|
||||
private List<DockingActionIf> uniqueActions = new ArrayList<>();
|
||||
|
||||
// allows clients to know if a given key stroke or mouse binding is in use
|
||||
private Map<KeyStroke, List<String>> actionNamesByKeyStroke = new HashMap<>();
|
||||
private Map<MouseBinding, String> actionNameByMouseBinding = new HashMap<>();
|
||||
|
||||
// tracks all changes to an action's key stroke and mouse bindings, which allows us to apply
|
||||
// and restore options values
|
||||
private Map<String, ActionKeyBindingState> actionInfoByFullName = new HashMap<>();
|
||||
private Map<String, KeyBindingState> actionInfoByFullName = new HashMap<>();
|
||||
|
||||
private String longestActionName = "";
|
||||
|
||||
public KeyBindings(Tool tool) {
|
||||
public KeyBindingsModel(Tool tool) {
|
||||
this.tool = tool;
|
||||
|
||||
keyBindingOptions = tool.getOptions(DockingToolConstants.KEY_BINDINGS);
|
||||
@@ -62,15 +63,19 @@ public class KeyBindings {
|
||||
init();
|
||||
}
|
||||
|
||||
public List<DockingActionIf> getUniqueActions() {
|
||||
return Collections.unmodifiableList(uniqueActions);
|
||||
public List<ActionBindingsDescriptor> getActionBindings() {
|
||||
|
||||
return actionInfoByFullName.values()
|
||||
.stream()
|
||||
.map(state -> (ActionBindingsDescriptor) state)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/* used for testing */
|
||||
public Map<String, KeyStroke> getKeyStrokesByFullActionName() {
|
||||
Map<String, KeyStroke> result = new HashMap<>();
|
||||
Set<Entry<String, ActionKeyBindingState>> entries = actionInfoByFullName.entrySet();
|
||||
for (Entry<String, ActionKeyBindingState> entry : entries) {
|
||||
Set<Entry<String, KeyBindingState>> entries = actionInfoByFullName.entrySet();
|
||||
for (Entry<String, KeyBindingState> entry : entries) {
|
||||
String key = entry.getKey();
|
||||
KeyStroke value = entry.getValue().getCurrentKeyStroke();
|
||||
result.put(key, value);
|
||||
@@ -83,12 +88,12 @@ public class KeyBindings {
|
||||
}
|
||||
|
||||
public KeyStroke getKeyStroke(String fullName) {
|
||||
ActionKeyBindingState info = actionInfoByFullName.get(fullName);
|
||||
KeyBindingState info = actionInfoByFullName.get(fullName);
|
||||
return info.getCurrentKeyStroke();
|
||||
}
|
||||
|
||||
public MouseBinding getMouseBinding(String fullName) {
|
||||
ActionKeyBindingState info = actionInfoByFullName.get(fullName);
|
||||
KeyBindingState info = actionInfoByFullName.get(fullName);
|
||||
return info.getCurrentMouseBinding();
|
||||
}
|
||||
|
||||
@@ -114,11 +119,9 @@ public class KeyBindings {
|
||||
sb.append(" ");
|
||||
|
||||
String name = names.get(i);
|
||||
ActionKeyBindingState info = actionInfoByFullName.get(name);
|
||||
DockingActionIf action = info.getRepresentativeAction();
|
||||
String shortName = action.getName();
|
||||
sb.append(shortName);
|
||||
sb.append(" (").append(action.getOwnerDescription()).append(')');
|
||||
KeyBindingState state = actionInfoByFullName.get(name);
|
||||
String fullName = state.getFullName();
|
||||
sb.append(fullName);
|
||||
if (i < names.size() - 1) {
|
||||
sb.append("\n");
|
||||
}
|
||||
@@ -155,7 +158,7 @@ public class KeyBindings {
|
||||
actionNameByMouseBinding.put(newBinding, fullName);
|
||||
}
|
||||
|
||||
ActionKeyBindingState info = actionInfoByFullName.get(fullName);
|
||||
KeyBindingState info = actionInfoByFullName.get(fullName);
|
||||
info.setCurrentMouseBinding(newBinding);
|
||||
return true;
|
||||
}
|
||||
@@ -174,14 +177,14 @@ public class KeyBindings {
|
||||
}
|
||||
addActionKeyStroke(fullName, newKs);
|
||||
|
||||
ActionKeyBindingState info = actionInfoByFullName.get(fullName);
|
||||
KeyBindingState info = actionInfoByFullName.get(fullName);
|
||||
info.setCurrentKeyStroke(newKs);
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean removeKeyStroke(String fullName) {
|
||||
|
||||
ActionKeyBindingState info = actionInfoByFullName.get(fullName);
|
||||
KeyBindingState info = actionInfoByFullName.get(fullName);
|
||||
if (info == null) {
|
||||
return false; // not sure if this can happen
|
||||
}
|
||||
@@ -201,7 +204,7 @@ public class KeyBindings {
|
||||
* system started.
|
||||
*/
|
||||
public void restoreOptions() {
|
||||
for (ActionKeyBindingState info : actionInfoByFullName.values()) {
|
||||
for (KeyBindingState info : actionInfoByFullName.values()) {
|
||||
info.restore(keyBindingOptions);
|
||||
}
|
||||
}
|
||||
@@ -210,8 +213,8 @@ public class KeyBindings {
|
||||
* Cancels any pending changes that have not yet been applied.
|
||||
*/
|
||||
public void cancelChanges() {
|
||||
for (ActionKeyBindingState info : actionInfoByFullName.values()) {
|
||||
info.cancelChanges();
|
||||
for (KeyBindingState info : actionInfoByFullName.values()) {
|
||||
info.clearChanges();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,7 +222,7 @@ public class KeyBindings {
|
||||
* Applies any pending changes.
|
||||
*/
|
||||
public void applyChanges() {
|
||||
for (ActionKeyBindingState info : actionInfoByFullName.values()) {
|
||||
for (KeyBindingState info : actionInfoByFullName.values()) {
|
||||
info.apply(keyBindingOptions);
|
||||
}
|
||||
}
|
||||
@@ -242,6 +245,7 @@ public class KeyBindings {
|
||||
|
||||
actionInfoByFullName = new HashMap<>();
|
||||
|
||||
Set<String> registeredNames = new HashSet<>();
|
||||
Map<String, List<DockingActionIf>> actionsByFullName =
|
||||
KeyBindingUtils.getAllActionsByFullName(tool);
|
||||
Set<Entry<String, List<DockingActionIf>>> entries = actionsByFullName.entrySet();
|
||||
@@ -252,6 +256,8 @@ public class KeyBindings {
|
||||
String fullName = entry.getKey();
|
||||
ActionTrigger trigger = keyBindingOptions.getActionTrigger(fullName, null);
|
||||
|
||||
registeredNames.add(fullName);
|
||||
|
||||
KeyStroke ks = null;
|
||||
MouseBinding mb = null;
|
||||
|
||||
@@ -260,18 +266,45 @@ public class KeyBindings {
|
||||
mb = trigger.getMouseBinding();
|
||||
}
|
||||
|
||||
ActionKeyBindingState info = new ActionKeyBindingState(actions, ks, mb);
|
||||
actionInfoByFullName.put(fullName, info);
|
||||
|
||||
uniqueActions.add(info.getRepresentativeAction());
|
||||
ActionKeyBindingState state = new ActionKeyBindingState(actions, ks, mb);
|
||||
actionInfoByFullName.put(fullName, state);
|
||||
|
||||
addActionKeyStroke(fullName, ks);
|
||||
|
||||
String shortName = info.getShortName();
|
||||
String shortName = state.getName();
|
||||
if (shortName.length() > longestActionName.length()) {
|
||||
longestActionName = shortName;
|
||||
}
|
||||
}
|
||||
|
||||
// ask options for unregistered key binding options
|
||||
List<String> allNamesList = keyBindingOptions.getOptionNames();
|
||||
Set<String> unregisteredNames = new HashSet<>(allNamesList);
|
||||
unregisteredNames.removeAll(registeredNames);
|
||||
|
||||
for (String fullName : unregisteredNames) {
|
||||
KeyStroke ks = null;
|
||||
MouseBinding mb = null;
|
||||
|
||||
ActionTrigger trigger = keyBindingOptions.getActionTrigger(fullName, null);
|
||||
if (trigger != null) {
|
||||
ks = trigger.getKeyStroke();
|
||||
mb = trigger.getMouseBinding();
|
||||
}
|
||||
|
||||
UnregisteredActionKeyBindingState state =
|
||||
new UnregisteredActionKeyBindingState(fullName, ks, mb);
|
||||
actionInfoByFullName.put(fullName, state);
|
||||
|
||||
addActionKeyStroke(fullName, ks);
|
||||
|
||||
int description = fullName.indexOf("(");
|
||||
String shortName = fullName.substring(0, description).trim();
|
||||
if (shortName.length() > longestActionName.length()) {
|
||||
longestActionName = shortName;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void addActionKeyStroke(String actionName, KeyStroke ks) {
|
||||
@@ -289,62 +322,74 @@ public class KeyBindings {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A class to store current and original values for key strokes and mouse bindings. This is
|
||||
* used to apply changes and restore default values.
|
||||
*/
|
||||
private class ActionKeyBindingState {
|
||||
private abstract class KeyBindingState implements ActionBindingsDescriptor {
|
||||
|
||||
private List<DockingActionIf> actions = new ArrayList<>();
|
||||
private KeyStroke originalKeyStroke;
|
||||
private KeyStroke currentKeyStroke;
|
||||
private MouseBinding originalMouseBinding;
|
||||
private MouseBinding currentMouseBinding;
|
||||
KeyStroke originalKeyStroke;
|
||||
KeyStroke currentKeyStroke;
|
||||
MouseBinding originalMouseBinding;
|
||||
MouseBinding currentMouseBinding;
|
||||
|
||||
ActionKeyBindingState(List<DockingActionIf> actions, KeyStroke ks, MouseBinding mb) {
|
||||
this.actions.addAll(actions);
|
||||
KeyBindingState(KeyStroke ks, MouseBinding mb) {
|
||||
this.originalKeyStroke = ks;
|
||||
this.currentKeyStroke = ks;
|
||||
this.originalMouseBinding = mb;
|
||||
this.currentMouseBinding = mb;
|
||||
}
|
||||
|
||||
public DockingActionIf getRepresentativeAction() {
|
||||
// pick one action, they are all conceptually the same
|
||||
return actions.get(0);
|
||||
abstract KeyBindingData getCurrentKeyBindingData();
|
||||
|
||||
// restores the key/mouse binding options to their default values
|
||||
abstract void restore(ToolOptions options);
|
||||
|
||||
KeyBindingData getOriginalKeyBindingData() {
|
||||
|
||||
if (originalKeyStroke == null && originalMouseBinding == null) {
|
||||
return null; // the key binding data does not exist or has been cleared
|
||||
}
|
||||
|
||||
ActionTrigger trigger = new ActionTrigger(originalKeyStroke, originalMouseBinding);
|
||||
return new KeyBindingData(trigger);
|
||||
}
|
||||
|
||||
String getShortName() {
|
||||
// pick one action, they are all conceptually the same
|
||||
return actions.get(0).getName();
|
||||
@Override
|
||||
public String getBindingText() {
|
||||
String text = "";
|
||||
String fullName = getFullName();
|
||||
KeyStroke ks = getKeyStroke(fullName);
|
||||
if (ks != null) {
|
||||
text += KeyBindingUtils.parseKeyStroke(ks);
|
||||
}
|
||||
|
||||
MouseBinding mb = getMouseBinding(fullName);
|
||||
if (mb != null) {
|
||||
text += " (" + mb.getDisplayText() + ")";
|
||||
}
|
||||
|
||||
return text.trim();
|
||||
}
|
||||
|
||||
String getFullName() {
|
||||
return getRepresentativeAction().getFullName();
|
||||
}
|
||||
|
||||
public MouseBinding getCurrentMouseBinding() {
|
||||
MouseBinding getCurrentMouseBinding() {
|
||||
return currentMouseBinding;
|
||||
}
|
||||
|
||||
public void setCurrentMouseBinding(MouseBinding newMouseBinding) {
|
||||
void setCurrentMouseBinding(MouseBinding newMouseBinding) {
|
||||
this.currentMouseBinding = newMouseBinding;
|
||||
}
|
||||
|
||||
public KeyStroke getCurrentKeyStroke() {
|
||||
KeyStroke getCurrentKeyStroke() {
|
||||
return currentKeyStroke;
|
||||
}
|
||||
|
||||
public void setCurrentKeyStroke(KeyStroke newKeyStroke) {
|
||||
void setCurrentKeyStroke(KeyStroke newKeyStroke) {
|
||||
this.currentKeyStroke = newKeyStroke;
|
||||
}
|
||||
|
||||
public void cancelChanges() {
|
||||
void clearChanges() {
|
||||
currentKeyStroke = originalKeyStroke;
|
||||
currentMouseBinding = originalMouseBinding;
|
||||
}
|
||||
|
||||
public void apply(ToolOptions keyStrokeOptions) {
|
||||
void apply(ToolOptions keyStrokeOptions) {
|
||||
if (!hasChanged()) {
|
||||
return;
|
||||
}
|
||||
@@ -353,19 +398,17 @@ public class KeyBindings {
|
||||
apply(keyStrokeOptions, kbd);
|
||||
}
|
||||
|
||||
private void apply(ToolOptions keyStrokeOptions, KeyBindingData keyBinding) {
|
||||
|
||||
void apply(ToolOptions keyStrokeOptions, KeyBindingData keyBinding) {
|
||||
String fullName = getFullName();
|
||||
if (keyBinding == null) {
|
||||
// no bindings; bindings have been cleared
|
||||
for (DockingActionIf action : actions) {
|
||||
action.setUnvalidatedKeyBindingData(null);
|
||||
}
|
||||
keyStrokeOptions.setActionTrigger(fullName, null);
|
||||
currentKeyStroke = null;
|
||||
currentMouseBinding = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// 1) update the options with the new value
|
||||
ActionTrigger newTrigger = keyBinding.getActionTrigger();
|
||||
String fullName = getFullName();
|
||||
keyStrokeOptions.setActionTrigger(fullName, newTrigger);
|
||||
|
||||
// 2) update our state so the UI shows the new value
|
||||
@@ -373,12 +416,7 @@ public class KeyBindings {
|
||||
currentMouseBinding = newTrigger.getMouseBinding();
|
||||
}
|
||||
|
||||
private boolean hasChanged() {
|
||||
return !Objects.equals(originalKeyStroke, currentKeyStroke) ||
|
||||
!Objects.equals(originalMouseBinding, currentMouseBinding);
|
||||
}
|
||||
|
||||
private boolean matches(KeyBindingData kbData) {
|
||||
boolean matches(KeyBindingData kbData) {
|
||||
|
||||
if (CollectionUtils.isAllNull(kbData, currentKeyStroke, currentMouseBinding)) {
|
||||
return true;
|
||||
@@ -397,7 +435,137 @@ public class KeyBindings {
|
||||
return Objects.equals(otherMb, currentMouseBinding);
|
||||
}
|
||||
|
||||
private KeyBindingData getCurrentKeyBindingData() {
|
||||
boolean hasChanged() {
|
||||
return !Objects.equals(originalKeyStroke, currentKeyStroke) ||
|
||||
!Objects.equals(originalMouseBinding, currentMouseBinding);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents info for an action that is known by the options, but has not been registered.
|
||||
*/
|
||||
private class UnregisteredActionKeyBindingState extends KeyBindingState {
|
||||
|
||||
private String fullName;
|
||||
private String actionName;
|
||||
private String ownerName;
|
||||
|
||||
UnregisteredActionKeyBindingState(String fullName, KeyStroke ks, MouseBinding mb) {
|
||||
super(ks, mb);
|
||||
this.fullName = fullName;
|
||||
|
||||
int descriptionIndex = fullName.indexOf("(");
|
||||
this.actionName = fullName.substring(0, descriptionIndex).trim();
|
||||
|
||||
int lastParen = fullName.lastIndexOf(")");
|
||||
this.ownerName = fullName.substring(descriptionIndex + 1, lastParen).trim();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return actionName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFullName() {
|
||||
return fullName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getOwnerDescription() {
|
||||
return ownerName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return ""; // no action; no description
|
||||
}
|
||||
|
||||
@Override
|
||||
public DockingActionIf getRepresentativeAction() {
|
||||
return null; // no action
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRegistered() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
KeyBindingData getCurrentKeyBindingData() {
|
||||
if (currentKeyStroke == null && currentMouseBinding == null) {
|
||||
return null; // no bindings or the values have been cleared
|
||||
}
|
||||
|
||||
ActionTrigger trigger = new ActionTrigger(currentKeyStroke, currentMouseBinding);
|
||||
return new KeyBindingData(trigger);
|
||||
}
|
||||
|
||||
@Override
|
||||
void restore(ToolOptions options) {
|
||||
|
||||
if (!hasChanged()) {
|
||||
return;
|
||||
}
|
||||
|
||||
KeyBindingData defaultBinding = getOriginalKeyBindingData();
|
||||
|
||||
clearChanges();
|
||||
|
||||
if (!matches(defaultBinding)) {
|
||||
apply(options, defaultBinding);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A class to store current and original values for key strokes and mouse bindings. This is
|
||||
* used to apply changes and restore default values.
|
||||
*/
|
||||
private class ActionKeyBindingState extends KeyBindingState {
|
||||
|
||||
private List<DockingActionIf> actions = new ArrayList<>();
|
||||
|
||||
ActionKeyBindingState(List<DockingActionIf> actions, KeyStroke ks, MouseBinding mb) {
|
||||
super(ks, mb);
|
||||
this.actions.addAll(actions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
// pick one action, they are all conceptually the same
|
||||
return getRepresentativeAction().getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFullName() {
|
||||
return getRepresentativeAction().getFullName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getOwnerDescription() {
|
||||
return getRepresentativeAction().getOwnerDescription();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return getRepresentativeAction().getDescription();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DockingActionIf getRepresentativeAction() {
|
||||
// pick one action, they are all conceptually the same
|
||||
return actions.get(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRegistered() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
KeyBindingData getCurrentKeyBindingData() {
|
||||
|
||||
if (currentKeyStroke == null && currentMouseBinding == null) {
|
||||
return null; // the key binding data does not exist or has been cleared
|
||||
@@ -410,11 +578,12 @@ public class KeyBindings {
|
||||
}
|
||||
|
||||
// restores the options to their default values
|
||||
public void restore(ToolOptions options) {
|
||||
@Override
|
||||
void restore(ToolOptions options) {
|
||||
DockingActionIf action = getRepresentativeAction();
|
||||
KeyBindingData defaultBinding = action.getDefaultKeyBindingData();
|
||||
|
||||
cancelChanges();
|
||||
clearChanges();
|
||||
|
||||
if (!matches(defaultBinding)) {
|
||||
apply(options, defaultBinding);
|
||||
@@ -37,7 +37,7 @@ import resources.Icons;
|
||||
*/
|
||||
public class KeyEntryDialog extends DialogComponentProvider {
|
||||
|
||||
private KeyBindings keyBindings;
|
||||
private KeyBindingsModel keyBindings;
|
||||
private ToolActions toolActions;
|
||||
private DockingActionIf action;
|
||||
|
||||
@@ -54,7 +54,7 @@ public class KeyEntryDialog extends DialogComponentProvider {
|
||||
this.action = action;
|
||||
this.toolActions = (ToolActions) tool.getToolActions();
|
||||
|
||||
this.keyBindings = new KeyBindings(tool);
|
||||
this.keyBindings = new KeyBindingsModel(tool);
|
||||
|
||||
setUpAttributes();
|
||||
createPanel();
|
||||
|
||||
@@ -8,6 +8,9 @@ color.fg.extensionpanel.path = color.palette.blue
|
||||
color.fg.extensionpanel.details.title = color.palette.maroon
|
||||
color.fg.extensionpanel.details.version = color.palette.blue
|
||||
|
||||
color.fg.options.keybindings.table.unregistered = color.palette.lightgray
|
||||
color.fg.options.keybindings.table.unregistered.selected.unfocused = color.palette.gray
|
||||
|
||||
color.fg.pluginpanel.name = color.fg
|
||||
color.fg.pluginpanel.description = color.palette.gray
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ import docking.tool.util.DockingToolConstants;
|
||||
import docking.widgets.*;
|
||||
import docking.widgets.MultiLineLabel.VerticalAlignment;
|
||||
import docking.widgets.table.*;
|
||||
import generic.theme.GColor;
|
||||
import generic.theme.Gui;
|
||||
import ghidra.framework.options.*;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
@@ -66,10 +67,10 @@ public class KeyBindingsPanel extends JPanel {
|
||||
private KeyBindingsTableModel tableModel;
|
||||
private ActionBindingListener actionBindingListener = new ActionBindingListener();
|
||||
private ActionBindingPanel actionBindingPanel;
|
||||
private GTableFilterPanel<DockingActionIf> tableFilterPanel;
|
||||
private GTableFilterPanel<ActionBindingsDescriptor> tableFilterPanel;
|
||||
private EmptyBorderButton helpButton;
|
||||
|
||||
private KeyBindings keyBindings;
|
||||
private KeyBindingsModel keyBindings;
|
||||
private boolean unappliedChanges;
|
||||
|
||||
private PluginTool tool;
|
||||
@@ -82,7 +83,7 @@ public class KeyBindingsPanel extends JPanel {
|
||||
public KeyBindingsPanel(PluginTool tool) {
|
||||
this.tool = tool;
|
||||
|
||||
this.keyBindings = new KeyBindings(tool);
|
||||
this.keyBindings = new KeyBindingsModel(tool);
|
||||
|
||||
createPanelComponents();
|
||||
|
||||
@@ -147,7 +148,7 @@ public class KeyBindingsPanel extends JPanel {
|
||||
gettingStartedPanel = new JPanel();
|
||||
activeActionPanel = createActiveActionPanel();
|
||||
|
||||
tableModel = new KeyBindingsTableModel(new ArrayList<>(keyBindings.getUniqueActions()));
|
||||
tableModel = new KeyBindingsTableModel(new ArrayList<>(keyBindings.getActionBindings()));
|
||||
actionTable = new GTable(tableModel);
|
||||
|
||||
JScrollPane actionsScroller = new JScrollPane(actionTable);
|
||||
@@ -156,6 +157,8 @@ public class KeyBindingsPanel extends JPanel {
|
||||
actionTable.setHTMLRenderingEnabled(true);
|
||||
actionTable.getSelectionModel().addListSelectionListener(new TableSelectionListener());
|
||||
|
||||
actionTable.setDefaultRenderer(String.class, new KeyBindingsRenderer());
|
||||
|
||||
adjustTableColumns();
|
||||
|
||||
// middle panel - filter field and import/export buttons
|
||||
@@ -206,9 +209,9 @@ public class KeyBindingsPanel extends JPanel {
|
||||
helpButton = new EmptyBorderButton(Icons.HELP_ICON);
|
||||
helpButton.setEnabled(false);
|
||||
helpButton.addActionListener(e -> {
|
||||
DockingActionIf action = getSelectedAction();
|
||||
ActionBindingsDescriptor binding = getSelectedBinding();
|
||||
HelpService hs = Help.getHelpService();
|
||||
hs.showHelp(action, false, KeyBindingsPanel.this);
|
||||
hs.showHelp(binding, false, KeyBindingsPanel.this);
|
||||
});
|
||||
|
||||
JPanel statusPanel = new JPanel();
|
||||
@@ -372,7 +375,7 @@ public class KeyBindingsPanel extends JPanel {
|
||||
unappliedChanges = changes;
|
||||
}
|
||||
|
||||
private DockingActionIf getSelectedAction() {
|
||||
private ActionBindingsDescriptor getSelectedBinding() {
|
||||
if (actionTable.getSelectedRowCount() == 0) {
|
||||
return null;
|
||||
}
|
||||
@@ -381,7 +384,7 @@ public class KeyBindingsPanel extends JPanel {
|
||||
}
|
||||
|
||||
private String getSelectedActionName() {
|
||||
DockingActionIf action = getSelectedAction();
|
||||
ActionBindingsDescriptor action = getSelectedBinding();
|
||||
if (action == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -457,21 +460,22 @@ public class KeyBindingsPanel extends JPanel {
|
||||
private void updateKeyStroke(KeyStroke ks) {
|
||||
clearInfoPanel();
|
||||
|
||||
DockingActionIf action = getSelectedAction();
|
||||
if (action == null) {
|
||||
ActionBindingsDescriptor binding = getSelectedBinding();
|
||||
if (binding == null) {
|
||||
statusLabel.setText(GETTING_STARTED_MESSAGE);
|
||||
return;
|
||||
}
|
||||
|
||||
DockingActionIf dockingAction = binding.getRepresentativeAction();
|
||||
ToolActions toolActions = (ToolActions) tool.getToolActions();
|
||||
String errorMessage = toolActions.validateActionKeyBinding(action, ks);
|
||||
String errorMessage = toolActions.validateActionKeyBinding(dockingAction, ks);
|
||||
if (errorMessage != null) {
|
||||
actionBindingPanel.clearKeyStroke();
|
||||
statusLabel.setText(errorMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
String selectedActionName = action.getFullName();
|
||||
String selectedActionName = binding.getFullName();
|
||||
if (setActionKeyStroke(selectedActionName, ks)) {
|
||||
showActionsMappedToKeyStroke(ks);
|
||||
fireRowChanged();
|
||||
@@ -483,13 +487,13 @@ public class KeyBindingsPanel extends JPanel {
|
||||
|
||||
clearInfoPanel();
|
||||
|
||||
DockingActionIf action = getSelectedAction();
|
||||
if (action == null) {
|
||||
ActionBindingsDescriptor binding = getSelectedBinding();
|
||||
if (binding == null) {
|
||||
statusLabel.setText(GETTING_STARTED_MESSAGE);
|
||||
return;
|
||||
}
|
||||
|
||||
String selectedActionName = action.getFullName();
|
||||
String selectedActionName = binding.getFullName();
|
||||
if (setMouseBinding(selectedActionName, mb)) {
|
||||
fireRowChanged();
|
||||
changesMade(true);
|
||||
@@ -573,8 +577,8 @@ public class KeyBindingsPanel extends JPanel {
|
||||
|
||||
helpButton.setEnabled(false);
|
||||
|
||||
DockingActionIf action = getSelectedAction();
|
||||
if (action == null) {
|
||||
ActionBindingsDescriptor binding = getSelectedBinding();
|
||||
if (binding == null) {
|
||||
swapView(gettingStartedPanel);
|
||||
|
||||
statusLabel.setText(GETTING_STARTED_MESSAGE);
|
||||
@@ -599,48 +603,64 @@ public class KeyBindingsPanel extends JPanel {
|
||||
MouseBinding mb = keyBindings.getMouseBinding(fullActionName);
|
||||
actionBindingPanel.setKeyBindingData(ks, mb);
|
||||
|
||||
String description = action.getDescription();
|
||||
String description = binding.getDescription();
|
||||
if (StringUtils.isBlank(description)) {
|
||||
description = action.getName();
|
||||
description = binding.getName();
|
||||
}
|
||||
|
||||
// Not sure why we escape the html here. Probably just to be safe.
|
||||
statusLabel.setText("<html>" + description);
|
||||
helpButton.setToolTipText("Help for " + action.getName());
|
||||
helpButton.setToolTipText("Help for " + binding.getName());
|
||||
}
|
||||
}
|
||||
|
||||
private static GColor COLOR_FG_UNREGISTERED =
|
||||
new GColor("color.fg.options.keybindings.table.unregistered");
|
||||
private static GColor COLOR_FG_UNREGISTERED_SELECTED_UNFOCUSED =
|
||||
new GColor("color.fg.options.keybindings.table.unregistered.selected.unfocused");
|
||||
|
||||
private class KeyBindingsRenderer extends GTableCellRenderer {
|
||||
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
||||
Component renderer = super.getTableCellRendererComponent(data);
|
||||
ActionBindingsDescriptor action = (ActionBindingsDescriptor) data.getRowObject();
|
||||
if (!action.isRegistered()) {
|
||||
|
||||
boolean selected = data.isSelected();
|
||||
boolean focused = actionTable.isFocusOwner();
|
||||
setForeground(COLOR_FG_UNREGISTERED);
|
||||
|
||||
if (!focused && selected) {
|
||||
// Selected and not focused; light gray background on some LaFs. Update the
|
||||
// foreground to stand out against that color.
|
||||
setForeground(COLOR_FG_UNREGISTERED_SELECTED_UNFOCUSED);
|
||||
}
|
||||
}
|
||||
|
||||
return renderer;
|
||||
}
|
||||
}
|
||||
|
||||
private class KeyBindingsTableModel
|
||||
extends GDynamicColumnTableModel<DockingActionIf, Object> {
|
||||
extends GDynamicColumnTableModel<ActionBindingsDescriptor, Object> {
|
||||
|
||||
private List<DockingActionIf> actions;
|
||||
private List<ActionBindingsDescriptor> actions;
|
||||
|
||||
public KeyBindingsTableModel(List<DockingActionIf> actions) {
|
||||
public KeyBindingsTableModel(List<ActionBindingsDescriptor> actions) {
|
||||
super(new ServiceProviderStub());
|
||||
this.actions = actions;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TableColumnDescriptor<DockingActionIf> createTableColumnDescriptor() {
|
||||
TableColumnDescriptor<DockingActionIf> descriptor = new TableColumnDescriptor<>();
|
||||
protected TableColumnDescriptor<ActionBindingsDescriptor> createTableColumnDescriptor() {
|
||||
TableColumnDescriptor<ActionBindingsDescriptor> descriptor =
|
||||
new TableColumnDescriptor<>();
|
||||
descriptor.addVisibleColumn("Action Name", String.class, a -> a.getName(), 1, true);
|
||||
descriptor.addVisibleColumn("Key Binding", String.class, a -> {
|
||||
String text = "";
|
||||
String fullName = a.getFullName();
|
||||
KeyStroke ks = keyBindings.getKeyStroke(fullName);
|
||||
if (ks != null) {
|
||||
text += KeyBindingUtils.parseKeyStroke(ks);
|
||||
}
|
||||
|
||||
MouseBinding mb = keyBindings.getMouseBinding(fullName);
|
||||
if (mb != null) {
|
||||
text += " (" + mb.getDisplayText() + ")";
|
||||
}
|
||||
|
||||
return text.trim();
|
||||
});
|
||||
descriptor.addVisibleColumn("Key Binding", String.class, a -> a.getBindingText());
|
||||
descriptor.addVisibleColumn("Owner", String.class, a -> a.getOwnerDescription());
|
||||
descriptor.addHiddenColumn("Description", String.class, a -> a.getDescription());
|
||||
descriptor.addHiddenColumn("Registered?", Boolean.class, a -> a.isRegistered());
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
@@ -650,7 +670,7 @@ public class KeyBindingsPanel extends JPanel {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DockingActionIf> getModelData() {
|
||||
public List<ActionBindingsDescriptor> getModelData() {
|
||||
return actions;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user