mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-04 18:29:37 +02:00
Update key processing to have the notion of both valid and enabled
This commit is contained in:
parent
13834fabaa
commit
d59d6e7d92
14 changed files with 606 additions and 339 deletions
|
@ -15,13 +15,15 @@
|
|||
*/
|
||||
package ghidra.app.plugin.core.datamgr;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.awt.datatransfer.Clipboard;
|
||||
import java.awt.datatransfer.Transferable;
|
||||
import java.util.*;
|
||||
|
||||
import javax.swing.tree.TreePath;
|
||||
|
||||
import docking.widgets.tree.GTree;
|
||||
import docking.widgets.tree.GTreeNode;
|
||||
import docking.widgets.tree.support.GTreeNodeTransferable;
|
||||
import ghidra.app.context.ProgramActionContext;
|
||||
import ghidra.app.plugin.core.datamgr.archive.BuiltInSourceArchive;
|
||||
import ghidra.app.plugin.core.datamgr.archive.ProjectArchive;
|
||||
|
@ -37,6 +39,8 @@ public class DataTypesActionContext extends ProgramActionContext implements Doma
|
|||
private DataTypeArchiveGTree archiveGTree;
|
||||
private List<DomainFile> domainFiles;
|
||||
|
||||
private List<GTreeNode> clipboardNodes;
|
||||
|
||||
public DataTypesActionContext(DataTypesProvider provider, Program program,
|
||||
DataTypeArchiveGTree archiveGTree, GTreeNode clickedNode) {
|
||||
this(provider, program, archiveGTree, clickedNode, false);
|
||||
|
@ -44,13 +48,33 @@ public class DataTypesActionContext extends ProgramActionContext implements Doma
|
|||
|
||||
public DataTypesActionContext(DataTypesProvider provider, Program program,
|
||||
DataTypeArchiveGTree archiveGTree, GTreeNode clickedNode, boolean isToolbarAction) {
|
||||
|
||||
super(provider, program, archiveGTree);
|
||||
this.archiveGTree = archiveGTree;
|
||||
this.clickedNode = clickedNode;
|
||||
this.isToolbarAction = isToolbarAction;
|
||||
}
|
||||
|
||||
public List<GTreeNode> getClipboardNodes() {
|
||||
if (clipboardNodes != null) {
|
||||
return clipboardNodes;
|
||||
}
|
||||
|
||||
// cache, since this could be slow
|
||||
DataTypesProvider dtProvider = (DataTypesProvider) getComponentProvider();
|
||||
DataTypeManagerPlugin plugin = dtProvider.getPlugin();
|
||||
Clipboard clipboard = plugin.getClipboard();
|
||||
Transferable transferable = clipboard.getContents(this);
|
||||
if (transferable instanceof GTreeNodeTransferable) {
|
||||
GTreeNodeTransferable gtTransferable = (GTreeNodeTransferable) transferable;
|
||||
clipboardNodes = gtTransferable.getAllData();
|
||||
}
|
||||
|
||||
if (clipboardNodes == null) {
|
||||
clipboardNodes = Collections.emptyList();
|
||||
}
|
||||
return clipboardNodes;
|
||||
}
|
||||
|
||||
public boolean isToolbarAction() {
|
||||
return isToolbarAction;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
* REVIEWED: YES
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -16,11 +15,7 @@
|
|||
*/
|
||||
package ghidra.app.plugin.core.datamgr.actions;
|
||||
|
||||
import ghidra.app.plugin.core.datamgr.DataTypeManagerPlugin;
|
||||
import ghidra.app.plugin.core.datamgr.tree.DataTypeTreeNode;
|
||||
|
||||
import java.awt.datatransfer.Clipboard;
|
||||
import java.awt.datatransfer.Transferable;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.util.List;
|
||||
|
||||
|
@ -28,7 +23,9 @@ import docking.ActionContext;
|
|||
import docking.action.DockingAction;
|
||||
import docking.action.KeyBindingData;
|
||||
import docking.widgets.tree.GTreeNode;
|
||||
import docking.widgets.tree.support.GTreeNodeTransferable;
|
||||
import ghidra.app.plugin.core.datamgr.DataTypeManagerPlugin;
|
||||
import ghidra.app.plugin.core.datamgr.DataTypesActionContext;
|
||||
import ghidra.app.plugin.core.datamgr.tree.DataTypeTreeNode;
|
||||
|
||||
public class ClearCutAction extends DockingAction {
|
||||
private Clipboard clipboard;
|
||||
|
@ -42,26 +39,36 @@ public class ClearCutAction extends DockingAction {
|
|||
setEnabled(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValidContext(ActionContext context) {
|
||||
|
||||
//
|
||||
// This action is particular about when it is valid. This is so that it does not interfere
|
||||
// with Escape key presses for the parent window, except when this action has work to do.
|
||||
//
|
||||
if (!(context instanceof DataTypesActionContext dtc)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !dtc.getClipboardNodes().isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabledForContext(ActionContext context) {
|
||||
// If we are valid, then we are enabled (see isValidContext()). Most actions are always
|
||||
// valid, but only sometimes enabled. We use the valid check to remove ourselves completely
|
||||
// from the workflow. But, if we are valid, then we are also enabled.
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionContext context) {
|
||||
Transferable transferable = clipboard.getContents(this);
|
||||
if (transferable instanceof GTreeNodeTransferable) {
|
||||
GTreeNodeTransferable gtTransferable = (GTreeNodeTransferable) transferable;
|
||||
List<GTreeNode> nodeList = gtTransferable.getAllData();
|
||||
if (nodeList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
DataTypesActionContext dtc = (DataTypesActionContext) context;
|
||||
List<GTreeNode> nodeList = dtc.getClipboardNodes();
|
||||
DataTypeTreeNode node = (DataTypeTreeNode) nodeList.get(0);
|
||||
if (node.isCut()) {
|
||||
clipboard.setContents(null, null);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1327,7 +1327,7 @@ public class DialogComponentProvider
|
|||
|
||||
private void addKeyBindingAction(DockingActionIf action) {
|
||||
|
||||
DialogActionProxy proxy = new DialogActionProxy(action);
|
||||
DialogActionProxy proxy = new DialogActionProxy(this, action);
|
||||
keyBindingProxyActions.add(proxy);
|
||||
|
||||
// The tool will be null when clients add actions to this dialog before it has been shown.
|
||||
|
@ -1481,8 +1481,11 @@ public class DialogComponentProvider
|
|||
*/
|
||||
private class DialogActionProxy extends DockingActionProxy {
|
||||
|
||||
public DialogActionProxy(DockingActionIf dockingAction) {
|
||||
private DialogComponentProvider provider;
|
||||
|
||||
public DialogActionProxy(DialogComponentProvider provider, DockingActionIf dockingAction) {
|
||||
super(dockingAction);
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1494,5 +1497,18 @@ public class DialogComponentProvider
|
|||
public ToolBarData getToolBarData() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabledForContext(ActionContext context) {
|
||||
if (context instanceof DialogActionContext dialogContext) {
|
||||
DialogComponentProvider contextProvider =
|
||||
dialogContext.getDialogComponentProvider();
|
||||
if (provider != contextProvider) {
|
||||
return false;
|
||||
}
|
||||
return dockingAction.isEnabledForContext(context);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ public class DockingActionProxy
|
|||
implements ToggleDockingActionIf, MultiActionDockingActionIf, PropertyChangeListener {
|
||||
private WeakSet<PropertyChangeListener> propertyListeners =
|
||||
WeakDataStructureFactory.createSingleThreadAccessWeakSet();
|
||||
private final DockingActionIf dockingAction;
|
||||
protected final DockingActionIf dockingAction;
|
||||
|
||||
public DockingActionProxy(DockingActionIf dockingAction) {
|
||||
this.dockingAction = dockingAction;
|
||||
|
|
|
@ -15,8 +15,7 @@
|
|||
*/
|
||||
package docking;
|
||||
|
||||
import java.awt.Toolkit;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.Component;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.AbstractAction;
|
||||
|
@ -47,33 +46,12 @@ public abstract class DockingKeyBindingAction extends AbstractAction {
|
|||
return true; // always enable; this is a internal action that cannot be disabled
|
||||
}
|
||||
|
||||
public void reportNotEnabled() {
|
||||
String name = dockingAction.getName();
|
||||
String ksText = KeyBindingUtils.parseKeyStroke(keyStroke);
|
||||
String message = "Action '%s' (%s) not currently enabled".formatted(name, ksText);
|
||||
tool.setStatusInfo(message, true);
|
||||
Toolkit.getDefaultToolkit().beep();
|
||||
}
|
||||
|
||||
public abstract KeyBindingPrecedence getKeyBindingPrecedence();
|
||||
public abstract ExecutableAction getExecutableAction(Component focusOwner);
|
||||
|
||||
public boolean isSystemKeybindingPrecedence() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(final ActionEvent e) {
|
||||
tool.setStatusInfo("");
|
||||
ComponentProvider provider = tool.getActiveComponentProvider();
|
||||
ActionContext context = getLocalContext(provider);
|
||||
context.setSourceObject(e.getSource());
|
||||
dockingAction.actionPerformed(context);
|
||||
}
|
||||
|
||||
public List<DockingActionIf> getValidActions(Object source) {
|
||||
return getActions(); // the action for this class is always enabled and valid
|
||||
}
|
||||
|
||||
protected ActionContext getLocalContext(ComponentProvider localProvider) {
|
||||
if (localProvider == null) {
|
||||
return new DefaultActionContext();
|
||||
|
@ -90,4 +68,5 @@ public abstract class DockingKeyBindingAction extends AbstractAction {
|
|||
public List<DockingActionIf> getActions() {
|
||||
return List.of(dockingAction);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -15,37 +15,24 @@
|
|||
*/
|
||||
package docking;
|
||||
|
||||
import docking.action.DockingActionIf;
|
||||
import docking.action.ToggleDockingActionIf;
|
||||
import generic.json.Json;
|
||||
import java.awt.Component;
|
||||
|
||||
public class ExecutableAction {
|
||||
/**
|
||||
* A class used by the {@link KeyBindingOverrideKeyEventDispatcher}. It represents an action and
|
||||
* the context in which that action should operate if {@link #execute()} is called. This class is
|
||||
* created for each keystroke that maps to a tool action.
|
||||
* <p>
|
||||
* This is not meant to be used outside of this API.
|
||||
*/
|
||||
public interface ExecutableAction {
|
||||
|
||||
DockingActionIf action;
|
||||
ActionContext context;
|
||||
public boolean isValid();
|
||||
|
||||
public ExecutableAction(DockingActionIf action, ActionContext context) {
|
||||
this.action = action;
|
||||
this.context = context;
|
||||
}
|
||||
public boolean isEnabled();
|
||||
|
||||
public void execute() {
|
||||
// Toggle actions do not toggle its state directly therefor we have to do it for
|
||||
// them before we execute the action.
|
||||
if (action instanceof ToggleDockingActionIf) {
|
||||
ToggleDockingActionIf toggleAction = (ToggleDockingActionIf) action;
|
||||
toggleAction.setSelected(!toggleAction.isSelected());
|
||||
}
|
||||
public void reportNotEnabled(Component focusOwner);
|
||||
|
||||
action.actionPerformed(context);
|
||||
}
|
||||
public KeyBindingPrecedence getKeyBindingPrecedence();
|
||||
|
||||
public DockingActionIf getAction() {
|
||||
return action;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return Json.toString(action);
|
||||
}
|
||||
public void execute();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
/* ###
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* A class to track an action's precedence and enablement
|
||||
* @param precedence the precedence
|
||||
* @param isValid true if valid
|
||||
* @param isEnabled true if enabled
|
||||
*/
|
||||
public record KbEnabledState(KeyBindingPrecedence precedence, boolean isValid,
|
||||
boolean isEnabled) {
|
||||
|
||||
public KbEnabledState {
|
||||
if (!isValid && isEnabled) {
|
||||
throw new IllegalArgumentException("Cannot be enable if not also valid");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -20,12 +20,10 @@ import static docking.KeyBindingPrecedence.*;
|
|||
import java.awt.*;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.awt.event.KeyListener;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.text.JTextComponent;
|
||||
|
||||
import docking.action.*;
|
||||
import docking.actions.KeyBindingUtils;
|
||||
import docking.menu.keys.MenuKeyProcessor;
|
||||
import ghidra.util.bean.GGlassPane;
|
||||
|
@ -57,7 +55,7 @@ public class KeyBindingOverrideKeyEventDispatcher implements KeyEventDispatcher
|
|||
* <b>Posterity Note:</b> While debugging we will not get a KeyEvent.KEY_RELEASED event if
|
||||
* the focus changes from the application to the debugger tool.
|
||||
*/
|
||||
private DockingKeyBindingAction inProgressAction;
|
||||
private ExecutableAction inProgressAction;
|
||||
|
||||
/**
|
||||
* Provides the current focus owner. This allows for dependency injection.
|
||||
|
@ -119,75 +117,56 @@ public class KeyBindingOverrideKeyEventDispatcher implements KeyEventDispatcher
|
|||
return true;
|
||||
}
|
||||
|
||||
// Special case for when we use one of our built-in menu navigation actions. We ignore
|
||||
// docking actions if a menu is open (this is done in action.getEnabledState()).
|
||||
if (MenuKeyProcessor.processMenuKeyEvent(event)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
DockingKeyBindingAction action = getDockingKeyBindingActionForEvent(event);
|
||||
DockingKeyBindingAction action = getActionForEvent(event);
|
||||
if (action == null) {
|
||||
return false; // let the normal event flow continue
|
||||
}
|
||||
|
||||
// *Special*, System key bindings--these can always be processed and are a higher priority
|
||||
if (processSystemActionPrecedence(action, event)) {
|
||||
Component focusOwner = focusProvider.getFocusOwner();
|
||||
ExecutableAction executableAction = action.getExecutableAction(focusOwner);
|
||||
if (processSystemActionPrecedence(executableAction, event)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// some known special cases that we don't wish to process
|
||||
if (!isValidContextForAction(event, action)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (willBeHandledByTextComponent(event)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// no actions valid at all
|
||||
// return
|
||||
|
||||
// actions that are valid, but not enabled
|
||||
|
||||
// also, is applicable:
|
||||
// isValidContext();
|
||||
// is action for active local for provider;
|
||||
// is focused
|
||||
|
||||
// actions that are enabled
|
||||
|
||||
KeyBindingPrecedence keyBindingPrecedence = getValidKeyBindingPrecedence(action);
|
||||
if (keyBindingPrecedence == null) {
|
||||
// Note: we used to return false here. Returning false allows Java to handle a given
|
||||
// key stroke when our actions are disabled. We have decided it is simpler to
|
||||
// always consume a given key stroke when we have actions registered. This
|
||||
// prevents inconsistent action firing between Ghidra and Java, depending upon
|
||||
// Ghidra's action enablement. If we find a case that is broken by this change,
|
||||
// then we will need a more robust solution here.
|
||||
// action.reportNotEnabled();
|
||||
// return true;
|
||||
if (!executableAction.isValid()) {
|
||||
// The action is not currently valid for the given focus owner. Let all key strokes go
|
||||
// to Java when we have no valid context. This allows keys like Escape to work on Java
|
||||
// widgets.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!executableAction.isEnabled()) {
|
||||
// The action is valid, but is not enabled. In this case, we do not want Java to get
|
||||
// the event. Instead, let the user know that they cannot perform the requested action.
|
||||
// We keep the action from Java in this case to keep the event processing consistent
|
||||
// whether or not the action is enabled.
|
||||
executableAction.reportNotEnabled(focusOwner);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Process the key event in precedence order.
|
||||
// If it processes the event at any given level, the short-circuit operator will kick out.
|
||||
// Finally, if the exception statement is reached, then someone has added a new level
|
||||
// of precedence that this algorithm has not taken into account!
|
||||
// @formatter:off
|
||||
return processKeyListenerPrecedence(action, keyBindingPrecedence, event) ||
|
||||
processComponentActionMapPrecedence(action, keyBindingPrecedence, event) ||
|
||||
processActionAtPrecedence(DefaultLevel, keyBindingPrecedence, action, event) ||
|
||||
return processKeyListenerPrecedence(executableAction, event) ||
|
||||
processComponentActionMapPrecedence(executableAction, event) ||
|
||||
processActionAtPrecedence(DefaultLevel, executableAction, event) ||
|
||||
throwAssertException();
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
private KeyBindingPrecedence getValidKeyBindingPrecedence(DockingKeyBindingAction action) {
|
||||
|
||||
if (action instanceof MultipleKeyAction) {
|
||||
MultipleKeyAction multiAction = (MultipleKeyAction) action;
|
||||
return multiAction.geValidKeyBindingPrecedence(focusProvider.getFocusOwner());
|
||||
}
|
||||
return action.getKeyBindingPrecedence();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given key event should be blocked (i.e., not processed by us or Java).
|
||||
*/
|
||||
|
@ -226,58 +205,15 @@ public class KeyBindingOverrideKeyEventDispatcher implements KeyEventDispatcher
|
|||
private boolean actionInProgress(KeyEvent event) {
|
||||
boolean wasInProgress = inProgressAction != null;
|
||||
if (event.getID() == KeyEvent.KEY_RELEASED) {
|
||||
DockingKeyBindingAction action = inProgressAction;
|
||||
ExecutableAction action = inProgressAction;
|
||||
inProgressAction = null;
|
||||
KeyStroke keyStroke = KeyStroke.getKeyStrokeForEvent(event);
|
||||
|
||||
// note: this call has no effect if 'action' is null
|
||||
Object source = event.getSource();
|
||||
int modifiersEx = event.getModifiersEx();
|
||||
SwingUtilities.notifyAction(action, keyStroke, event, source, modifiersEx);
|
||||
|
||||
if (action != null) {
|
||||
action.execute();
|
||||
}
|
||||
}
|
||||
return wasInProgress;
|
||||
}
|
||||
|
||||
private boolean isValidContextForAction(KeyEvent event, DockingKeyBindingAction kbAction) {
|
||||
Window activeWindow = focusProvider.getActiveWindow();
|
||||
if (!(activeWindow instanceof DockingDialog dialog)) {
|
||||
return true; // allow all non-dialog windows to process events
|
||||
}
|
||||
|
||||
// The choice to ignore modal dialogs was made long ago. We cannot remember why the
|
||||
// choice was made, but speculate that odd things can happen when keybindings are
|
||||
// processed with modal dialogs open. For now, do not let key bindings get processed
|
||||
// for modal dialogs. This can be changed in the future if needed.
|
||||
if (!dialog.isModal()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Allow modal dialogs to process their own actions
|
||||
DialogComponentProvider provider = dialog.getComponent();
|
||||
List<DockingActionIf> actions = kbAction.getValidActions(event.getSource());
|
||||
if (actions.isEmpty()) {
|
||||
return false; // no actions; not a valid key stroke for this dialog
|
||||
}
|
||||
for (DockingActionIf action : actions) {
|
||||
if (!isAllowedDialogAction(provider, action)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true; // all actions belong to the active dialog; this is a valid action
|
||||
}
|
||||
|
||||
private boolean isAllowedDialogAction(DialogComponentProvider provider,
|
||||
DockingActionIf action) {
|
||||
|
||||
if (action instanceof ComponentBasedDockingAction) {
|
||||
return true; // these actions work on low-level components, which may live in dialogs
|
||||
}
|
||||
|
||||
return provider.isDialogKeyBindingAction(action);
|
||||
}
|
||||
|
||||
private boolean isSettingKeyBindings(KeyEvent event) {
|
||||
Component destination = event.getComponent();
|
||||
if (destination == null) {
|
||||
|
@ -370,7 +306,7 @@ public class KeyBindingOverrideKeyEventDispatcher implements KeyEventDispatcher
|
|||
throw new AssertException("New precedence added to KeyBindingPrecedence?");
|
||||
}
|
||||
|
||||
private boolean processSystemActionPrecedence(DockingKeyBindingAction action,
|
||||
private boolean processSystemActionPrecedence(ExecutableAction executableAction,
|
||||
KeyEvent event) {
|
||||
|
||||
if (isSettingKeyBindings(event)) {
|
||||
|
@ -379,21 +315,17 @@ public class KeyBindingOverrideKeyEventDispatcher implements KeyEventDispatcher
|
|||
return false;
|
||||
}
|
||||
|
||||
if (!action.isSystemKeybindingPrecedence()) {
|
||||
KeyBindingPrecedence precedence = executableAction.getKeyBindingPrecedence();
|
||||
if (precedence != SystemActionsLevel) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (inProgressAction != null) {
|
||||
inProgressAction = executableAction; // this will be handled on the release
|
||||
return true;
|
||||
}
|
||||
|
||||
inProgressAction = action; // this will be handled on the release
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean processKeyListenerPrecedence(DockingKeyBindingAction action,
|
||||
KeyBindingPrecedence keyBindingPrecedence, KeyEvent e) {
|
||||
if (processActionAtPrecedence(KeyBindingPrecedence.KeyListenerLevel, keyBindingPrecedence,
|
||||
private boolean processKeyListenerPrecedence(ExecutableAction action, KeyEvent e) {
|
||||
if (processActionAtPrecedence(KeyBindingPrecedence.KeyListenerLevel,
|
||||
action, e)) {
|
||||
return true;
|
||||
}
|
||||
|
@ -407,10 +339,8 @@ public class KeyBindingOverrideKeyEventDispatcher implements KeyEventDispatcher
|
|||
return false;
|
||||
}
|
||||
|
||||
private boolean processComponentActionMapPrecedence(DockingKeyBindingAction action,
|
||||
KeyBindingPrecedence keyBindingPrecedence, KeyEvent event) {
|
||||
|
||||
if (processActionAtPrecedence(ActionMapLevel, keyBindingPrecedence, action, event)) {
|
||||
private boolean processComponentActionMapPrecedence(ExecutableAction action, KeyEvent event) {
|
||||
if (processActionAtPrecedence(ActionMapLevel, action, event)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -422,11 +352,11 @@ public class KeyBindingOverrideKeyEventDispatcher implements KeyEventDispatcher
|
|||
return false;
|
||||
}
|
||||
|
||||
private boolean processActionAtPrecedence(KeyBindingPrecedence precedence,
|
||||
KeyBindingPrecedence keyBindingPrecedence, DockingKeyBindingAction action,
|
||||
KeyEvent event) {
|
||||
private boolean processActionAtPrecedence(KeyBindingPrecedence keyBindingPrecedence,
|
||||
ExecutableAction action, KeyEvent event) {
|
||||
|
||||
if (keyBindingPrecedence != precedence) {
|
||||
KeyBindingPrecedence actionPrecedence = action.getKeyBindingPrecedence();
|
||||
if (keyBindingPrecedence != actionPrecedence) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -506,7 +436,7 @@ public class KeyBindingOverrideKeyEventDispatcher implements KeyEventDispatcher
|
|||
* @param event The key event to check.
|
||||
* @return An action, if one is available for the given key event, in the current context.
|
||||
*/
|
||||
private DockingKeyBindingAction getDockingKeyBindingActionForEvent(KeyEvent event) {
|
||||
private DockingKeyBindingAction getActionForEvent(KeyEvent event) {
|
||||
DockingWindowManager activeManager = getActiveDockingWindowManager();
|
||||
if (activeManager == null) {
|
||||
return null;
|
||||
|
|
|
@ -22,6 +22,7 @@ import java.util.List;
|
|||
import javax.swing.*;
|
||||
|
||||
import docking.action.DockingActionIf;
|
||||
import docking.action.ToggleDockingActionIf;
|
||||
import docking.event.mouse.GMouseListenerAdapter;
|
||||
import docking.widgets.label.GIconLabel;
|
||||
import docking.widgets.label.GLabel;
|
||||
|
@ -33,25 +34,27 @@ import docking.widgets.label.GLabel;
|
|||
public class MultiActionDialog extends DialogComponentProvider {
|
||||
|
||||
private String keystrokeName;
|
||||
private List<ExecutableAction> list;
|
||||
private JList<String> actionList;
|
||||
private DefaultListModel<String> listModel;
|
||||
|
||||
private List<DockingActionIf> actions;
|
||||
private ActionContext context;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param keystrokeName keystroke name
|
||||
* @param list list of actions
|
||||
* @param actions list of actions
|
||||
* @param context the context
|
||||
*/
|
||||
public MultiActionDialog(String keystrokeName, List<ExecutableAction> list) {
|
||||
public MultiActionDialog(String keystrokeName, List<DockingActionIf> actions,
|
||||
ActionContext context) {
|
||||
super("Select Action", true);
|
||||
this.keystrokeName = keystrokeName;
|
||||
this.context = context;
|
||||
init();
|
||||
setActionList(list);
|
||||
setActionList(actions);
|
||||
}
|
||||
|
||||
/**
|
||||
* The callback method for when the "OK" button is pressed.
|
||||
*/
|
||||
@Override
|
||||
protected void okCallback() {
|
||||
maybeDoAction();
|
||||
|
@ -65,21 +68,23 @@ public class MultiActionDialog extends DialogComponentProvider {
|
|||
|
||||
close();
|
||||
|
||||
ExecutableAction actionProxy = list.get(index);
|
||||
actionProxy.execute();
|
||||
DockingActionIf action = actions.get(index);
|
||||
|
||||
// Toggle actions do not toggle its state directly therefor we have to do it for
|
||||
// them before we execute the action.
|
||||
if (action instanceof ToggleDockingActionIf) {
|
||||
ToggleDockingActionIf toggleAction = (ToggleDockingActionIf) action;
|
||||
toggleAction.setSelected(!toggleAction.isSelected());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the list of actions that are enabled
|
||||
* @param list list of actions selected
|
||||
*/
|
||||
public void setActionList(List<ExecutableAction> list) {
|
||||
action.actionPerformed(context);
|
||||
}
|
||||
|
||||
private void setActionList(List<DockingActionIf> actions) {
|
||||
okButton.setEnabled(false);
|
||||
this.list = list;
|
||||
this.actions = actions;
|
||||
listModel.clear();
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
ExecutableAction actionProxy = list.get(i);
|
||||
DockingActionIf action = actionProxy.getAction();
|
||||
for (DockingActionIf action : actions) {
|
||||
listModel.addElement(action.getName() + " (" + action.getOwnerDescription() + ")");
|
||||
}
|
||||
actionList.setSelectedIndex(0);
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package docking;
|
||||
|
||||
import java.awt.Component;
|
||||
|
||||
import javax.help.UnsupportedOperationException;
|
||||
|
||||
import docking.action.DockingActionIf;
|
||||
import docking.action.ToggleDockingActionIf;
|
||||
|
||||
public class SystemExecutableAction implements ExecutableAction {
|
||||
private DockingActionIf action;
|
||||
private ActionContext context;
|
||||
|
||||
public SystemExecutableAction(DockingActionIf action, ActionContext context) {
|
||||
this.action = action;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportNotEnabled(Component focusOwner) {
|
||||
// we are always enabled
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyBindingPrecedence getKeyBindingPrecedence() {
|
||||
return action.getKeyBindingData().getKeyBindingPrecedence();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
// Toggle actions do not toggle its state directly therefor we have to do it for
|
||||
// them before we execute the action.
|
||||
if (action instanceof ToggleDockingActionIf) {
|
||||
ToggleDockingActionIf toggleAction = (ToggleDockingActionIf) action;
|
||||
toggleAction.setSelected(!toggleAction.isSelected());
|
||||
}
|
||||
|
||||
action.actionPerformed(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return action.getFullName();
|
||||
}
|
||||
}
|
|
@ -20,6 +20,7 @@ import java.awt.event.ActionEvent;
|
|||
import java.util.*;
|
||||
import java.util.List;
|
||||
|
||||
import javax.help.UnsupportedOperationException;
|
||||
import javax.swing.*;
|
||||
|
||||
import docking.*;
|
||||
|
@ -110,112 +111,115 @@ public class MultipleKeyAction extends DockingKeyBindingAction {
|
|||
}
|
||||
|
||||
@Override
|
||||
public List<DockingActionIf> getValidActions(Object source) {
|
||||
|
||||
if (ignoreActionWhileMenuShowing()) {
|
||||
return List.of();
|
||||
public void actionPerformed(ActionEvent event) {
|
||||
// A vestige from when we used to send this class through the Swing API. Execution is now
|
||||
// done on the ExecutableAction this class creates.
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
List<DockingActionIf> validActions = new ArrayList<>();
|
||||
List<ExecutableAction> proxyActions = getActionsForCurrentOrDefaultContext(source);
|
||||
for (ExecutableAction proxy : proxyActions) {
|
||||
DockingActionIf action = proxy.getAction();
|
||||
validActions.add(action);
|
||||
}
|
||||
return validActions;
|
||||
}
|
||||
private boolean ignoreActionWhileMenuShowing(ExecutableAction action) {
|
||||
|
||||
@Override
|
||||
public void actionPerformed(final ActionEvent event) {
|
||||
// Build list of actions which are valid in current context
|
||||
List<ExecutableAction> list = getActionsForCurrentOrDefaultContext(event.getSource());
|
||||
|
||||
// If menu active, disable all key bindings
|
||||
if (ignoreActionWhileMenuShowing()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If more than one action, prompt user for selection
|
||||
if (list.size() > 1) {
|
||||
// popup dialog to show multiple actions
|
||||
MultiActionDialog dialog =
|
||||
new MultiActionDialog(KeyBindingUtils.parseKeyStroke(keyStroke), list);
|
||||
|
||||
// doing the show in an invoke later seems to fix a strange swing bug that lock up
|
||||
// the program if you tried to invoke a new action too quickly after invoking
|
||||
// it the first time
|
||||
Swing.runLater(() -> DockingWindowManager.showDialog(dialog));
|
||||
}
|
||||
else if (list.size() == 1) {
|
||||
ExecutableAction actionProxy = list.get(0);
|
||||
tool.setStatusInfo("");
|
||||
actionProxy.execute();
|
||||
}
|
||||
else {
|
||||
String name = (String) getValue(Action.NAME);
|
||||
tool.setStatusInfo("Action (" + name + ") not valid in this context!", true);
|
||||
Toolkit.getDefaultToolkit().beep();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean ignoreActionWhileMenuShowing() {
|
||||
if (getKeyBindingPrecedence() == KeyBindingPrecedence.SystemActionsLevel) {
|
||||
return false; // allow system bindings through "no matter what!"
|
||||
KeyBindingPrecedence precedence = action.getKeyBindingPrecedence();
|
||||
if (precedence == KeyBindingPrecedence.SystemActionsLevel) {
|
||||
// Allow system bindings through. This allows actions like Help to work for menus.
|
||||
return false;
|
||||
}
|
||||
|
||||
MenuSelectionManager menuManager = MenuSelectionManager.defaultManager();
|
||||
return menuManager.getSelectedPath().length != 0;
|
||||
}
|
||||
|
||||
private List<ExecutableAction> getValidContextActions(ActionContext localContext,
|
||||
private ExecutableAction createNonDialogExecutableAction(ActionContext localContext,
|
||||
Map<Class<? extends ActionContext>, ActionContext> contextMap) {
|
||||
List<ExecutableAction> list = new ArrayList<>();
|
||||
boolean hasLocalActionsForKeyBinding = false;
|
||||
|
||||
MultiExecutableAction multiAction = new MultiExecutableAction();
|
||||
|
||||
//
|
||||
// 1) Prefer local actions for the active provider
|
||||
//
|
||||
getLocalContextActions(localContext, multiAction);
|
||||
if (multiAction.isValid()) {
|
||||
// At this point, we have local docking actions that may or may not be enabled. Exit
|
||||
// so that any component specific actions or global found below will not interfere with
|
||||
// the provider's local actions
|
||||
return multiAction;
|
||||
}
|
||||
|
||||
//
|
||||
// 2) Check for actions local to the source component (e.g., GTable and GTree)
|
||||
//
|
||||
getLocalComponentActions(localContext, multiAction);
|
||||
if (multiAction.isValid()) {
|
||||
// At this point, we have local component actions that may or may not be enabled. Exit
|
||||
// so that any global actions found below will not interfere with these component
|
||||
// actions.
|
||||
return multiAction;
|
||||
}
|
||||
|
||||
//
|
||||
// 3) Check for global actions using the current context
|
||||
//
|
||||
getGlobalActions(localContext, multiAction);
|
||||
if (multiAction.isValid()) {
|
||||
// We have found global actions that are valid for the current local context. Do not
|
||||
// also look for global actions that work for the default context.
|
||||
return multiAction;
|
||||
}
|
||||
|
||||
//
|
||||
// 4) Check for global actions using the default context. This is a final fallback to allow
|
||||
// global actions to work that are unrelated to the current active component's context.
|
||||
//
|
||||
getGlobalDefaultContextActions(contextMap, multiAction);
|
||||
return multiAction;
|
||||
}
|
||||
|
||||
private void getLocalContextActions(ActionContext localContext,
|
||||
MultiExecutableAction multiAction) {
|
||||
|
||||
for (ActionData actionData : actions) {
|
||||
if (actionData.isMyProvider(localContext)) {
|
||||
hasLocalActionsForKeyBinding = true;
|
||||
if (isValidAndEnabled(actionData, localContext)) {
|
||||
list.add(new ExecutableAction(actionData.action, localContext));
|
||||
if (!actionData.isMyProvider(localContext)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isValid(actionData, localContext)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
multiAction.setLocal(true);
|
||||
multiAction.setContext(localContext);
|
||||
multiAction.addValidAction(actionData.action);
|
||||
|
||||
if (isEnabled(actionData, localContext)) {
|
||||
multiAction.addEnabledAction(actionData.action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasLocalActionsForKeyBinding) {
|
||||
// At this point, we have local actions that may or may not be enabled. Return here
|
||||
// so that any component specific actions found below will not interfere with the
|
||||
// provider's local actions
|
||||
return list;
|
||||
}
|
||||
private void getLocalComponentActions(ActionContext localContext,
|
||||
MultiExecutableAction multiAction) {
|
||||
|
||||
//
|
||||
// 2) Check for actions local to the source component
|
||||
//
|
||||
for (ActionData actionData : actions) {
|
||||
if (!(actionData.action instanceof ComponentBasedDockingAction componentAction)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (componentAction.isValidComponentContext(localContext)) {
|
||||
hasLocalActionsForKeyBinding = true;
|
||||
if (isValidAndEnabled(actionData, localContext)) {
|
||||
list.add(new ExecutableAction(actionData.action, localContext));
|
||||
if (!componentAction.isValidComponentContext(localContext)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
multiAction.setContext(localContext);
|
||||
multiAction.addValidAction(actionData.action);
|
||||
|
||||
if (isEnabled(actionData, localContext)) {
|
||||
multiAction.addEnabledAction(actionData.action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasLocalActionsForKeyBinding) {
|
||||
// We have locals, ignore the globals. This prevents global actions from processing
|
||||
// the given keybinding when a local action exits, regardless of enablement.
|
||||
return list;
|
||||
}
|
||||
private void getGlobalActions(ActionContext localContext,
|
||||
MultiExecutableAction multiAction) {
|
||||
|
||||
//
|
||||
// 3) Check for default context actions
|
||||
//
|
||||
for (ActionData actionData : actions) {
|
||||
if (!actionData.isGlobalAction()) {
|
||||
continue;
|
||||
|
@ -223,13 +227,25 @@ public class MultipleKeyAction extends DockingKeyBindingAction {
|
|||
|
||||
// When looking for context matches, we prefer local context, even though this
|
||||
// is a 'global' action. This allows more specific context to be used when available
|
||||
if (isValidAndEnabled(actionData, localContext)) {
|
||||
list.add(new ExecutableAction(actionData.action, localContext));
|
||||
if (!isValid(actionData, localContext)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// this happens if we are in a dialog, default context is not used
|
||||
if (contextMap == null) {
|
||||
multiAction.setContext(localContext);
|
||||
multiAction.addValidAction(actionData.action);
|
||||
|
||||
if (isEnabled(actionData, localContext)) {
|
||||
multiAction.addEnabledAction(actionData.action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void getGlobalDefaultContextActions(
|
||||
Map<Class<? extends ActionContext>, ActionContext> contextMap,
|
||||
MultiExecutableAction multiAction) {
|
||||
|
||||
for (ActionData actionData : actions) {
|
||||
if (!actionData.isGlobalAction()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -238,19 +254,33 @@ public class MultipleKeyAction extends DockingKeyBindingAction {
|
|||
}
|
||||
|
||||
ActionContext defaultContext = contextMap.get(actionData.getContextType());
|
||||
if (isValidAndEnabled(actionData, defaultContext)) {
|
||||
list.add(new ExecutableAction(actionData.action, defaultContext));
|
||||
}
|
||||
}
|
||||
return list;
|
||||
if (!isValid(actionData, defaultContext)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
private boolean isValidAndEnabled(ActionData actionData, ActionContext context) {
|
||||
multiAction.setContext(defaultContext);
|
||||
multiAction.addValidAction(actionData.action);
|
||||
|
||||
if (isEnabled(actionData, defaultContext)) {
|
||||
multiAction.addEnabledAction(actionData.action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isValid(ActionData actionData, ActionContext context) {
|
||||
if (context == null) {
|
||||
return false;
|
||||
}
|
||||
DockingActionIf a = actionData.action;
|
||||
return a.isValidContext(context) && a.isEnabledForContext(context);
|
||||
return a.isValidContext(context);
|
||||
}
|
||||
|
||||
private boolean isEnabled(ActionData actionData, ActionContext context) {
|
||||
if (context == null) {
|
||||
return false;
|
||||
}
|
||||
DockingActionIf a = actionData.action;
|
||||
return a.isEnabledForContext(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -259,65 +289,87 @@ public class MultipleKeyAction extends DockingKeyBindingAction {
|
|||
}
|
||||
|
||||
@Override
|
||||
public KeyBindingPrecedence getKeyBindingPrecedence() {
|
||||
return geValidKeyBindingPrecedence(null);
|
||||
public ExecutableAction getExecutableAction(Component source) {
|
||||
ExecutableAction action = createExecutableAction(source);
|
||||
|
||||
// If menu active, disable all default key bindings
|
||||
if (ignoreActionWhileMenuShowing(action)) {
|
||||
return new MultiExecutableAction();
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a special version of {@link #getKeyBindingPrecedence()} that allows the internal
|
||||
* key event processing to specify the source component when determining how precedence should
|
||||
* be established for the actions contained herein.
|
||||
* @param source the component; may be null
|
||||
* @return the precedence; may be null
|
||||
*/
|
||||
public KeyBindingPrecedence geValidKeyBindingPrecedence(Component source) {
|
||||
|
||||
List<ExecutableAction> validActions = getActionsForCurrentOrDefaultContext(source);
|
||||
if (validActions.isEmpty()) {
|
||||
return null; // a signal that no actions are valid for the current context
|
||||
return action;
|
||||
}
|
||||
|
||||
if (validActions.size() != 1) {
|
||||
return KeyBindingPrecedence.DefaultLevel;
|
||||
}
|
||||
|
||||
ExecutableAction actionProxy = validActions.get(0);
|
||||
DockingActionIf action = actionProxy.getAction();
|
||||
return action.getKeyBindingData().getKeyBindingPrecedence();
|
||||
}
|
||||
|
||||
private List<ExecutableAction> getActionsForCurrentOrDefaultContext(Object eventSource) {
|
||||
private ExecutableAction createExecutableAction(Object eventSource) {
|
||||
|
||||
DockingWindowManager dwm = tool.getWindowManager();
|
||||
Window window = getWindow(dwm, eventSource);
|
||||
if (window instanceof DockingDialog) {
|
||||
return getDialogActions(window);
|
||||
return createDialogActions(eventSource, window);
|
||||
}
|
||||
|
||||
ComponentProvider localProvider = getProvider(dwm, eventSource);
|
||||
ActionContext localContext = getLocalContext(localProvider);
|
||||
localContext.setSourceObject(eventSource);
|
||||
Map<Class<? extends ActionContext>, ActionContext> contextMap =
|
||||
tool.getWindowManager().getDefaultActionContextMap();
|
||||
List<ExecutableAction> validActions = getValidContextActions(localContext, contextMap);
|
||||
return validActions;
|
||||
dwm.getDefaultActionContextMap();
|
||||
return createNonDialogExecutableAction(localContext, contextMap);
|
||||
}
|
||||
|
||||
private List<ExecutableAction> getDialogActions(Window window) {
|
||||
private ExecutableAction createDialogActions(Object eventSource, Window window) {
|
||||
|
||||
MultiExecutableAction multiAction = new MultiExecutableAction();
|
||||
|
||||
DockingDialog dockingDialog = (DockingDialog) window;
|
||||
DialogComponentProvider provider = dockingDialog.getDialogComponent();
|
||||
if (provider == null) {
|
||||
// this can happen if the dialog is closed during key event processing
|
||||
return Collections.emptyList();
|
||||
return multiAction;
|
||||
}
|
||||
|
||||
ActionContext context = provider.getActionContext(null);
|
||||
if (context == null) {
|
||||
return Collections.emptyList();
|
||||
return multiAction;
|
||||
}
|
||||
|
||||
List<ExecutableAction> validActions = getValidContextActions(context, null);
|
||||
return validActions;
|
||||
//
|
||||
// 1) Check for local actions
|
||||
//
|
||||
// Note: dialog key binding actions are proxy actions that get added to the tool as global
|
||||
// actions. Thus, there are no 'local' actions for the dialog.
|
||||
|
||||
//
|
||||
// 2) Check for actions local to the source component (e.g., GTable and GTree)
|
||||
//
|
||||
getLocalComponentActions(context, multiAction);
|
||||
if (multiAction.isValid()) {
|
||||
// At this point, we have local component actions that may or may not be enabled. Exit
|
||||
// so that any global actions found below will not interfere with these component
|
||||
// actions.
|
||||
return multiAction;
|
||||
}
|
||||
|
||||
//
|
||||
// 3) Check for global actions using the current context. As noted above, at the time of
|
||||
// writing, dialog actions are all registered at the global level.
|
||||
//
|
||||
getGlobalActions(context, multiAction);
|
||||
|
||||
// The choice to ignore global actions for modal dialogs was made long ago. We cannot
|
||||
// remember why the choice was made, but speculate that odd things can happen when
|
||||
// keybindings are processed with modal dialogs open. For now, do not let non-dialog
|
||||
// actions get processed for modal dialogs. This can be changed in the future if needed.
|
||||
if (provider.isModal()) {
|
||||
multiAction.filterAndKeepOnlyDialogActions(provider);
|
||||
}
|
||||
|
||||
// Note: we currently do not use *default* global actions in dialogs. It is not clear if
|
||||
// this decision was intentional.
|
||||
// if (!provider.isModal()) {
|
||||
// getGlobalDefaultContextActions(...);
|
||||
// }
|
||||
|
||||
return multiAction;
|
||||
}
|
||||
|
||||
private ComponentProvider getProvider(DockingWindowManager dwm, Object eventSource) {
|
||||
|
@ -383,6 +435,9 @@ public class MultipleKeyAction extends DockingKeyBindingAction {
|
|||
}
|
||||
|
||||
boolean isMyProvider(ActionContext localContext) {
|
||||
if (provider == null) {
|
||||
return false;
|
||||
}
|
||||
ComponentProvider otherProvider = localContext.getComponentProvider();
|
||||
return provider == otherProvider;
|
||||
}
|
||||
|
@ -392,6 +447,142 @@ public class MultipleKeyAction extends DockingKeyBindingAction {
|
|||
String providerString = provider == null ? "" : provider.toString() + " - ";
|
||||
return providerString + action;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An extension of {@link ExecutableAction} that itself contains 0 or more
|
||||
* {@link ExecutableAction}s. This class is used to create a snapshot of all actions valid and
|
||||
* enabled for a given keystroke.
|
||||
*/
|
||||
private class MultiExecutableAction implements ExecutableAction {
|
||||
|
||||
private List<DockingActionIf> validActions = new ArrayList<>();
|
||||
private List<DockingActionIf> enabledActions = new ArrayList<>();
|
||||
|
||||
private ActionContext context;
|
||||
private boolean isLocalAction;
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
|
||||
if (enabledActions.size() == 1) {
|
||||
DockingActionIf action = enabledActions.get(0);
|
||||
tool.setStatusInfo("");
|
||||
|
||||
// Toggle actions do not toggle its state directly therefor we have to do it for
|
||||
// them before we execute the action.
|
||||
if (action instanceof ToggleDockingActionIf) {
|
||||
ToggleDockingActionIf toggleAction = (ToggleDockingActionIf) action;
|
||||
toggleAction.setSelected(!toggleAction.isSelected());
|
||||
}
|
||||
|
||||
action.actionPerformed(context);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// If more than one action, prompt user to choose from multiple actions
|
||||
MultiActionDialog dialog =
|
||||
new MultiActionDialog(KeyBindingUtils.parseKeyStroke(keyStroke), enabledActions,
|
||||
context);
|
||||
|
||||
// doing the show in an invoke later seems to fix a strange swing bug that lock up
|
||||
// the program if you tried to invoke a new action too quickly after invoking
|
||||
// it the first time
|
||||
Swing.runLater(() -> DockingWindowManager.showDialog(dialog));
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyBindingPrecedence getKeyBindingPrecedence() {
|
||||
KeyBindingPrecedence precedence = KeyBindingPrecedence.DefaultLevel;
|
||||
if (enabledActions.size() == 1) {
|
||||
DockingActionIf action = enabledActions.get(0);
|
||||
precedence = action.getKeyBindingData().getKeyBindingPrecedence();
|
||||
}
|
||||
return precedence;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid() {
|
||||
return !validActions.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return !enabledActions.isEmpty();
|
||||
}
|
||||
|
||||
void setLocal(boolean isLocal) {
|
||||
this.isLocalAction = isLocal;
|
||||
}
|
||||
|
||||
void setContext(ActionContext context) {
|
||||
if (this.context != null && this.context != context) {
|
||||
throw new IllegalArgumentException("Context cannot be changed once set");
|
||||
}
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
void addValidAction(DockingActionIf a) {
|
||||
validActions.add(a);
|
||||
}
|
||||
|
||||
void addEnabledAction(DockingActionIf a) {
|
||||
enabledActions.add(a);
|
||||
}
|
||||
|
||||
/**
|
||||
* Keeps only those actions in the list that are owned by the given dialog provider
|
||||
* @param provider the provider
|
||||
*/
|
||||
void filterAndKeepOnlyDialogActions(DialogComponentProvider provider) {
|
||||
|
||||
Iterator<DockingActionIf> it = validActions.iterator();
|
||||
while (it.hasNext()) {
|
||||
DockingActionIf action = it.next();
|
||||
if (!provider.isDialogKeyBindingAction(action)) {
|
||||
it.remove();
|
||||
enabledActions.remove(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String getContextText(Component focusOwner) {
|
||||
DockingWindowManager dwm = tool.getWindowManager();
|
||||
Window window = getWindow(dwm, focusOwner);
|
||||
if (window instanceof DockingDialog) {
|
||||
return "in this dialog";
|
||||
}
|
||||
|
||||
if (!isLocalAction) {
|
||||
// no need to warn about global/default actions, as that may be annoying when the
|
||||
// keystrokes bubble up to the global level
|
||||
return null;
|
||||
}
|
||||
|
||||
ComponentProvider provider = context.getComponentProvider();
|
||||
if (provider != null) {
|
||||
return "in " + provider.getName();
|
||||
}
|
||||
|
||||
return "for context";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportNotEnabled(Component focusOwner) {
|
||||
|
||||
String contextText = getContextText(focusOwner);
|
||||
if (contextText == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
DockingActionIf action = validActions.get(0);
|
||||
String actionName = action.getName();
|
||||
String ksText = KeyBindingUtils.parseKeyStroke(keyStroke);
|
||||
String message =
|
||||
"'%s' (%s) not currently enabled %s".formatted(actionName, ksText, contextText);
|
||||
tool.setStatusInfo(message, true);
|
||||
Toolkit.getDefaultToolkit().beep();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,10 @@
|
|||
*/
|
||||
package docking.action;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.awt.event.ActionEvent;
|
||||
|
||||
import javax.help.UnsupportedOperationException;
|
||||
import javax.swing.KeyStroke;
|
||||
|
||||
import docking.*;
|
||||
|
@ -33,13 +37,29 @@ public class SystemKeyBindingAction extends DockingKeyBindingAction {
|
|||
return dockingAction;
|
||||
}
|
||||
|
||||
private ActionContext getContext(Component focusOwner) {
|
||||
ComponentProvider provider = tool.getActiveComponentProvider();
|
||||
ActionContext context = getLocalContext(provider);
|
||||
context.setSourceObject(focusOwner);
|
||||
return context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSystemKeybindingPrecedence() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyBindingPrecedence getKeyBindingPrecedence() {
|
||||
return KeyBindingPrecedence.SystemActionsLevel;
|
||||
public ExecutableAction getExecutableAction(Component focusOwner) {
|
||||
ActionContext context = getContext(focusOwner);
|
||||
return new SystemExecutableAction(dockingAction, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
// A vestige from when we used to send this class through the Swing API. Execution is now
|
||||
// done on the ExecutableAction this class creates.
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -258,6 +258,11 @@ public class SharedStubKeyBindingAction extends DockingAction implements Options
|
|||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValidContext(ActionContext context) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabledForContext(ActionContext context) {
|
||||
return false;
|
||||
|
|
|
@ -1923,7 +1923,7 @@ public class GTree extends JPanel implements BusyListener {
|
|||
@Override
|
||||
public void actionPerformed(ActionContext context) {
|
||||
|
||||
GTree gTree = (GTree) context.getSourceComponent();
|
||||
GTree gTree = getTree(context);
|
||||
gTree.tree.isCopyFormatted = true;
|
||||
try {
|
||||
Action builtinCopyAction = TransferHandler.getCopyAction();
|
||||
|
@ -1948,7 +1948,7 @@ public class GTree extends JPanel implements BusyListener {
|
|||
GTreeAction activateFilterAction = new GTreeAction("Table/Tree Activate Filter", owner) {
|
||||
@Override
|
||||
public void actionPerformed(ActionContext context) {
|
||||
GTree gTree = (GTree) context.getSourceComponent();
|
||||
GTree gTree = getTree(context);
|
||||
gTree.filterProvider.activate();
|
||||
}
|
||||
};
|
||||
|
@ -1966,7 +1966,7 @@ public class GTree extends JPanel implements BusyListener {
|
|||
GTreeAction toggleFilterAction = new GTreeAction("Table/Tree Toggle Filter", owner) {
|
||||
@Override
|
||||
public void actionPerformed(ActionContext context) {
|
||||
GTree gTree = (GTree) context.getSourceComponent();
|
||||
GTree gTree = getTree(context);
|
||||
gTree.filterProvider.toggleVisibility();
|
||||
}
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue