mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 02:39:44 +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
|
@ -4,9 +4,9 @@
|
||||||
* 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.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@ -15,13 +15,15 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.app.plugin.core.datamgr;
|
package ghidra.app.plugin.core.datamgr;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.awt.datatransfer.Clipboard;
|
||||||
import java.util.List;
|
import java.awt.datatransfer.Transferable;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
import javax.swing.tree.TreePath;
|
import javax.swing.tree.TreePath;
|
||||||
|
|
||||||
import docking.widgets.tree.GTree;
|
import docking.widgets.tree.GTree;
|
||||||
import docking.widgets.tree.GTreeNode;
|
import docking.widgets.tree.GTreeNode;
|
||||||
|
import docking.widgets.tree.support.GTreeNodeTransferable;
|
||||||
import ghidra.app.context.ProgramActionContext;
|
import ghidra.app.context.ProgramActionContext;
|
||||||
import ghidra.app.plugin.core.datamgr.archive.BuiltInSourceArchive;
|
import ghidra.app.plugin.core.datamgr.archive.BuiltInSourceArchive;
|
||||||
import ghidra.app.plugin.core.datamgr.archive.ProjectArchive;
|
import ghidra.app.plugin.core.datamgr.archive.ProjectArchive;
|
||||||
|
@ -37,6 +39,8 @@ public class DataTypesActionContext extends ProgramActionContext implements Doma
|
||||||
private DataTypeArchiveGTree archiveGTree;
|
private DataTypeArchiveGTree archiveGTree;
|
||||||
private List<DomainFile> domainFiles;
|
private List<DomainFile> domainFiles;
|
||||||
|
|
||||||
|
private List<GTreeNode> clipboardNodes;
|
||||||
|
|
||||||
public DataTypesActionContext(DataTypesProvider provider, Program program,
|
public DataTypesActionContext(DataTypesProvider provider, Program program,
|
||||||
DataTypeArchiveGTree archiveGTree, GTreeNode clickedNode) {
|
DataTypeArchiveGTree archiveGTree, GTreeNode clickedNode) {
|
||||||
this(provider, program, archiveGTree, clickedNode, false);
|
this(provider, program, archiveGTree, clickedNode, false);
|
||||||
|
@ -44,13 +48,33 @@ public class DataTypesActionContext extends ProgramActionContext implements Doma
|
||||||
|
|
||||||
public DataTypesActionContext(DataTypesProvider provider, Program program,
|
public DataTypesActionContext(DataTypesProvider provider, Program program,
|
||||||
DataTypeArchiveGTree archiveGTree, GTreeNode clickedNode, boolean isToolbarAction) {
|
DataTypeArchiveGTree archiveGTree, GTreeNode clickedNode, boolean isToolbarAction) {
|
||||||
|
|
||||||
super(provider, program, archiveGTree);
|
super(provider, program, archiveGTree);
|
||||||
this.archiveGTree = archiveGTree;
|
this.archiveGTree = archiveGTree;
|
||||||
this.clickedNode = clickedNode;
|
this.clickedNode = clickedNode;
|
||||||
this.isToolbarAction = isToolbarAction;
|
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() {
|
public boolean isToolbarAction() {
|
||||||
return isToolbarAction;
|
return isToolbarAction;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
/* ###
|
/* ###
|
||||||
* 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.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@ -16,11 +15,7 @@
|
||||||
*/
|
*/
|
||||||
package ghidra.app.plugin.core.datamgr.actions;
|
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.Clipboard;
|
||||||
import java.awt.datatransfer.Transferable;
|
|
||||||
import java.awt.event.KeyEvent;
|
import java.awt.event.KeyEvent;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@ -28,7 +23,9 @@ import docking.ActionContext;
|
||||||
import docking.action.DockingAction;
|
import docking.action.DockingAction;
|
||||||
import docking.action.KeyBindingData;
|
import docking.action.KeyBindingData;
|
||||||
import docking.widgets.tree.GTreeNode;
|
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 {
|
public class ClearCutAction extends DockingAction {
|
||||||
private Clipboard clipboard;
|
private Clipboard clipboard;
|
||||||
|
@ -42,25 +39,35 @@ public class ClearCutAction extends DockingAction {
|
||||||
setEnabled(true);
|
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
|
@Override
|
||||||
public boolean isEnabledForContext(ActionContext context) {
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void actionPerformed(ActionContext context) {
|
public void actionPerformed(ActionContext context) {
|
||||||
Transferable transferable = clipboard.getContents(this);
|
DataTypesActionContext dtc = (DataTypesActionContext) context;
|
||||||
if (transferable instanceof GTreeNodeTransferable) {
|
List<GTreeNode> nodeList = dtc.getClipboardNodes();
|
||||||
GTreeNodeTransferable gtTransferable = (GTreeNodeTransferable) transferable;
|
DataTypeTreeNode node = (DataTypeTreeNode) nodeList.get(0);
|
||||||
List<GTreeNode> nodeList = gtTransferable.getAllData();
|
if (node.isCut()) {
|
||||||
if (nodeList.isEmpty()) {
|
clipboard.setContents(null, null);
|
||||||
return;
|
|
||||||
}
|
|
||||||
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) {
|
private void addKeyBindingAction(DockingActionIf action) {
|
||||||
|
|
||||||
DialogActionProxy proxy = new DialogActionProxy(action);
|
DialogActionProxy proxy = new DialogActionProxy(this, action);
|
||||||
keyBindingProxyActions.add(proxy);
|
keyBindingProxyActions.add(proxy);
|
||||||
|
|
||||||
// The tool will be null when clients add actions to this dialog before it has been shown.
|
// 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 {
|
private class DialogActionProxy extends DockingActionProxy {
|
||||||
|
|
||||||
public DialogActionProxy(DockingActionIf dockingAction) {
|
private DialogComponentProvider provider;
|
||||||
|
|
||||||
|
public DialogActionProxy(DialogComponentProvider provider, DockingActionIf dockingAction) {
|
||||||
super(dockingAction);
|
super(dockingAction);
|
||||||
|
this.provider = provider;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -1494,5 +1497,18 @@ public class DialogComponentProvider
|
||||||
public ToolBarData getToolBarData() {
|
public ToolBarData getToolBarData() {
|
||||||
return null;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,9 @@
|
||||||
* 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.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@ -31,7 +31,7 @@ public class DockingActionProxy
|
||||||
implements ToggleDockingActionIf, MultiActionDockingActionIf, PropertyChangeListener {
|
implements ToggleDockingActionIf, MultiActionDockingActionIf, PropertyChangeListener {
|
||||||
private WeakSet<PropertyChangeListener> propertyListeners =
|
private WeakSet<PropertyChangeListener> propertyListeners =
|
||||||
WeakDataStructureFactory.createSingleThreadAccessWeakSet();
|
WeakDataStructureFactory.createSingleThreadAccessWeakSet();
|
||||||
private final DockingActionIf dockingAction;
|
protected final DockingActionIf dockingAction;
|
||||||
|
|
||||||
public DockingActionProxy(DockingActionIf dockingAction) {
|
public DockingActionProxy(DockingActionIf dockingAction) {
|
||||||
this.dockingAction = dockingAction;
|
this.dockingAction = dockingAction;
|
||||||
|
|
|
@ -15,8 +15,7 @@
|
||||||
*/
|
*/
|
||||||
package docking;
|
package docking;
|
||||||
|
|
||||||
import java.awt.Toolkit;
|
import java.awt.Component;
|
||||||
import java.awt.event.ActionEvent;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import javax.swing.AbstractAction;
|
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
|
return true; // always enable; this is a internal action that cannot be disabled
|
||||||
}
|
}
|
||||||
|
|
||||||
public void reportNotEnabled() {
|
public abstract ExecutableAction getExecutableAction(Component focusOwner);
|
||||||
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 boolean isSystemKeybindingPrecedence() {
|
public boolean isSystemKeybindingPrecedence() {
|
||||||
return false;
|
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) {
|
protected ActionContext getLocalContext(ComponentProvider localProvider) {
|
||||||
if (localProvider == null) {
|
if (localProvider == null) {
|
||||||
return new DefaultActionContext();
|
return new DefaultActionContext();
|
||||||
|
@ -90,4 +68,5 @@ public abstract class DockingKeyBindingAction extends AbstractAction {
|
||||||
public List<DockingActionIf> getActions() {
|
public List<DockingActionIf> getActions() {
|
||||||
return List.of(dockingAction);
|
return List.of(dockingAction);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,9 @@
|
||||||
* 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.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@ -15,37 +15,24 @@
|
||||||
*/
|
*/
|
||||||
package docking;
|
package docking;
|
||||||
|
|
||||||
import docking.action.DockingActionIf;
|
import java.awt.Component;
|
||||||
import docking.action.ToggleDockingActionIf;
|
|
||||||
import generic.json.Json;
|
|
||||||
|
|
||||||
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;
|
public boolean isValid();
|
||||||
ActionContext context;
|
|
||||||
|
|
||||||
public ExecutableAction(DockingActionIf action, ActionContext context) {
|
public boolean isEnabled();
|
||||||
this.action = action;
|
|
||||||
this.context = context;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void execute() {
|
public void reportNotEnabled(Component focusOwner);
|
||||||
// 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);
|
public KeyBindingPrecedence getKeyBindingPrecedence();
|
||||||
}
|
|
||||||
|
|
||||||
public DockingActionIf getAction() {
|
public void execute();
|
||||||
return action;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return Json.toString(action);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.*;
|
||||||
import java.awt.event.KeyEvent;
|
import java.awt.event.KeyEvent;
|
||||||
import java.awt.event.KeyListener;
|
import java.awt.event.KeyListener;
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import javax.swing.text.JTextComponent;
|
import javax.swing.text.JTextComponent;
|
||||||
|
|
||||||
import docking.action.*;
|
|
||||||
import docking.actions.KeyBindingUtils;
|
import docking.actions.KeyBindingUtils;
|
||||||
import docking.menu.keys.MenuKeyProcessor;
|
import docking.menu.keys.MenuKeyProcessor;
|
||||||
import ghidra.util.bean.GGlassPane;
|
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
|
* <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.
|
* 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.
|
* Provides the current focus owner. This allows for dependency injection.
|
||||||
|
@ -119,75 +117,56 @@ public class KeyBindingOverrideKeyEventDispatcher implements KeyEventDispatcher
|
||||||
return true;
|
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)) {
|
if (MenuKeyProcessor.processMenuKeyEvent(event)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
DockingKeyBindingAction action = getDockingKeyBindingActionForEvent(event);
|
DockingKeyBindingAction action = getActionForEvent(event);
|
||||||
if (action == null) {
|
if (action == null) {
|
||||||
return false; // let the normal event flow continue
|
return false; // let the normal event flow continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// *Special*, System key bindings--these can always be processed and are a higher priority
|
// *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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// some known special cases that we don't wish to process
|
|
||||||
if (!isValidContextForAction(event, action)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (willBeHandledByTextComponent(event)) {
|
if (willBeHandledByTextComponent(event)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// no actions valid at all
|
if (!executableAction.isValid()) {
|
||||||
// return
|
// 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
|
||||||
// actions that are valid, but not enabled
|
// widgets.
|
||||||
|
|
||||||
// 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;
|
|
||||||
return false;
|
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.
|
// Process the key event in precedence order.
|
||||||
// If it processes the event at any given level, the short-circuit operator will kick out.
|
// 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
|
// Finally, if the exception statement is reached, then someone has added a new level
|
||||||
// of precedence that this algorithm has not taken into account!
|
// of precedence that this algorithm has not taken into account!
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
return processKeyListenerPrecedence(action, keyBindingPrecedence, event) ||
|
return processKeyListenerPrecedence(executableAction, event) ||
|
||||||
processComponentActionMapPrecedence(action, keyBindingPrecedence, event) ||
|
processComponentActionMapPrecedence(executableAction, event) ||
|
||||||
processActionAtPrecedence(DefaultLevel, keyBindingPrecedence, action, event) ||
|
processActionAtPrecedence(DefaultLevel, executableAction, event) ||
|
||||||
throwAssertException();
|
throwAssertException();
|
||||||
// @formatter:on
|
// @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).
|
* Returns true if the given key event should be blocked (i.e., not processed by us or Java).
|
||||||
*/
|
*/
|
||||||
|
@ -226,56 +205,13 @@ public class KeyBindingOverrideKeyEventDispatcher implements KeyEventDispatcher
|
||||||
private boolean actionInProgress(KeyEvent event) {
|
private boolean actionInProgress(KeyEvent event) {
|
||||||
boolean wasInProgress = inProgressAction != null;
|
boolean wasInProgress = inProgressAction != null;
|
||||||
if (event.getID() == KeyEvent.KEY_RELEASED) {
|
if (event.getID() == KeyEvent.KEY_RELEASED) {
|
||||||
DockingKeyBindingAction action = inProgressAction;
|
ExecutableAction action = inProgressAction;
|
||||||
inProgressAction = null;
|
inProgressAction = null;
|
||||||
KeyStroke keyStroke = KeyStroke.getKeyStrokeForEvent(event);
|
if (action != null) {
|
||||||
|
action.execute();
|
||||||
// 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);
|
|
||||||
|
|
||||||
}
|
|
||||||
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 wasInProgress;
|
||||||
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) {
|
private boolean isSettingKeyBindings(KeyEvent event) {
|
||||||
|
@ -370,7 +306,7 @@ public class KeyBindingOverrideKeyEventDispatcher implements KeyEventDispatcher
|
||||||
throw new AssertException("New precedence added to KeyBindingPrecedence?");
|
throw new AssertException("New precedence added to KeyBindingPrecedence?");
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean processSystemActionPrecedence(DockingKeyBindingAction action,
|
private boolean processSystemActionPrecedence(ExecutableAction executableAction,
|
||||||
KeyEvent event) {
|
KeyEvent event) {
|
||||||
|
|
||||||
if (isSettingKeyBindings(event)) {
|
if (isSettingKeyBindings(event)) {
|
||||||
|
@ -379,21 +315,17 @@ public class KeyBindingOverrideKeyEventDispatcher implements KeyEventDispatcher
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!action.isSystemKeybindingPrecedence()) {
|
KeyBindingPrecedence precedence = executableAction.getKeyBindingPrecedence();
|
||||||
|
if (precedence != SystemActionsLevel) {
|
||||||
return false;
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean processKeyListenerPrecedence(DockingKeyBindingAction action,
|
private boolean processKeyListenerPrecedence(ExecutableAction action, KeyEvent e) {
|
||||||
KeyBindingPrecedence keyBindingPrecedence, KeyEvent e) {
|
if (processActionAtPrecedence(KeyBindingPrecedence.KeyListenerLevel,
|
||||||
if (processActionAtPrecedence(KeyBindingPrecedence.KeyListenerLevel, keyBindingPrecedence,
|
|
||||||
action, e)) {
|
action, e)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -407,10 +339,8 @@ public class KeyBindingOverrideKeyEventDispatcher implements KeyEventDispatcher
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean processComponentActionMapPrecedence(DockingKeyBindingAction action,
|
private boolean processComponentActionMapPrecedence(ExecutableAction action, KeyEvent event) {
|
||||||
KeyBindingPrecedence keyBindingPrecedence, KeyEvent event) {
|
if (processActionAtPrecedence(ActionMapLevel, action, event)) {
|
||||||
|
|
||||||
if (processActionAtPrecedence(ActionMapLevel, keyBindingPrecedence, action, event)) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -422,11 +352,11 @@ public class KeyBindingOverrideKeyEventDispatcher implements KeyEventDispatcher
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean processActionAtPrecedence(KeyBindingPrecedence precedence,
|
private boolean processActionAtPrecedence(KeyBindingPrecedence keyBindingPrecedence,
|
||||||
KeyBindingPrecedence keyBindingPrecedence, DockingKeyBindingAction action,
|
ExecutableAction action, KeyEvent event) {
|
||||||
KeyEvent event) {
|
|
||||||
|
|
||||||
if (keyBindingPrecedence != precedence) {
|
KeyBindingPrecedence actionPrecedence = action.getKeyBindingPrecedence();
|
||||||
|
if (keyBindingPrecedence != actionPrecedence) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -506,7 +436,7 @@ public class KeyBindingOverrideKeyEventDispatcher implements KeyEventDispatcher
|
||||||
* @param event The key event to check.
|
* @param event The key event to check.
|
||||||
* @return An action, if one is available for the given key event, in the current context.
|
* @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();
|
DockingWindowManager activeManager = getActiveDockingWindowManager();
|
||||||
if (activeManager == null) {
|
if (activeManager == null) {
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -4,9 +4,9 @@
|
||||||
* 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.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@ -22,6 +22,7 @@ import java.util.List;
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
|
|
||||||
import docking.action.DockingActionIf;
|
import docking.action.DockingActionIf;
|
||||||
|
import docking.action.ToggleDockingActionIf;
|
||||||
import docking.event.mouse.GMouseListenerAdapter;
|
import docking.event.mouse.GMouseListenerAdapter;
|
||||||
import docking.widgets.label.GIconLabel;
|
import docking.widgets.label.GIconLabel;
|
||||||
import docking.widgets.label.GLabel;
|
import docking.widgets.label.GLabel;
|
||||||
|
@ -33,25 +34,27 @@ import docking.widgets.label.GLabel;
|
||||||
public class MultiActionDialog extends DialogComponentProvider {
|
public class MultiActionDialog extends DialogComponentProvider {
|
||||||
|
|
||||||
private String keystrokeName;
|
private String keystrokeName;
|
||||||
private List<ExecutableAction> list;
|
|
||||||
private JList<String> actionList;
|
private JList<String> actionList;
|
||||||
private DefaultListModel<String> listModel;
|
private DefaultListModel<String> listModel;
|
||||||
|
|
||||||
|
private List<DockingActionIf> actions;
|
||||||
|
private ActionContext context;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
* @param keystrokeName keystroke name
|
* @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);
|
super("Select Action", true);
|
||||||
this.keystrokeName = keystrokeName;
|
this.keystrokeName = keystrokeName;
|
||||||
|
this.context = context;
|
||||||
init();
|
init();
|
||||||
setActionList(list);
|
setActionList(actions);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* The callback method for when the "OK" button is pressed.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
protected void okCallback() {
|
protected void okCallback() {
|
||||||
maybeDoAction();
|
maybeDoAction();
|
||||||
|
@ -65,21 +68,23 @@ public class MultiActionDialog extends DialogComponentProvider {
|
||||||
|
|
||||||
close();
|
close();
|
||||||
|
|
||||||
ExecutableAction actionProxy = list.get(index);
|
DockingActionIf action = actions.get(index);
|
||||||
actionProxy.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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private void setActionList(List<DockingActionIf> actions) {
|
||||||
* Set the list of actions that are enabled
|
|
||||||
* @param list list of actions selected
|
|
||||||
*/
|
|
||||||
public void setActionList(List<ExecutableAction> list) {
|
|
||||||
okButton.setEnabled(false);
|
okButton.setEnabled(false);
|
||||||
this.list = list;
|
this.actions = actions;
|
||||||
listModel.clear();
|
listModel.clear();
|
||||||
for (int i = 0; i < list.size(); i++) {
|
for (DockingActionIf action : actions) {
|
||||||
ExecutableAction actionProxy = list.get(i);
|
|
||||||
DockingActionIf action = actionProxy.getAction();
|
|
||||||
listModel.addElement(action.getName() + " (" + action.getOwnerDescription() + ")");
|
listModel.addElement(action.getName() + " (" + action.getOwnerDescription() + ")");
|
||||||
}
|
}
|
||||||
actionList.setSelectedIndex(0);
|
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.*;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.help.UnsupportedOperationException;
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
|
|
||||||
import docking.*;
|
import docking.*;
|
||||||
|
@ -110,112 +111,115 @@ public class MultipleKeyAction extends DockingKeyBindingAction {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<DockingActionIf> getValidActions(Object source) {
|
public void actionPerformed(ActionEvent event) {
|
||||||
|
// A vestige from when we used to send this class through the Swing API. Execution is now
|
||||||
if (ignoreActionWhileMenuShowing()) {
|
// done on the ExecutableAction this class creates.
|
||||||
return List.of();
|
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private boolean ignoreActionWhileMenuShowing(ExecutableAction action) {
|
||||||
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
|
KeyBindingPrecedence precedence = action.getKeyBindingPrecedence();
|
||||||
if (ignoreActionWhileMenuShowing()) {
|
if (precedence == KeyBindingPrecedence.SystemActionsLevel) {
|
||||||
return;
|
// Allow system bindings through. This allows actions like Help to work for menus.
|
||||||
}
|
return false;
|
||||||
|
|
||||||
// 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!"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MenuSelectionManager menuManager = MenuSelectionManager.defaultManager();
|
MenuSelectionManager menuManager = MenuSelectionManager.defaultManager();
|
||||||
return menuManager.getSelectedPath().length != 0;
|
return menuManager.getSelectedPath().length != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<ExecutableAction> getValidContextActions(ActionContext localContext,
|
private ExecutableAction createNonDialogExecutableAction(ActionContext localContext,
|
||||||
Map<Class<? extends ActionContext>, ActionContext> contextMap) {
|
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
|
// 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) {
|
for (ActionData actionData : actions) {
|
||||||
if (actionData.isMyProvider(localContext)) {
|
if (!actionData.isMyProvider(localContext)) {
|
||||||
hasLocalActionsForKeyBinding = true;
|
continue;
|
||||||
if (isValidAndEnabled(actionData, localContext)) {
|
}
|
||||||
list.add(new ExecutableAction(actionData.action, localContext));
|
|
||||||
}
|
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) {
|
private void getLocalComponentActions(ActionContext localContext,
|
||||||
// At this point, we have local actions that may or may not be enabled. Return here
|
MultiExecutableAction multiAction) {
|
||||||
// so that any component specific actions found below will not interfere with the
|
|
||||||
// provider's local actions
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// 2) Check for actions local to the source component
|
|
||||||
//
|
|
||||||
for (ActionData actionData : actions) {
|
for (ActionData actionData : actions) {
|
||||||
if (!(actionData.action instanceof ComponentBasedDockingAction componentAction)) {
|
if (!(actionData.action instanceof ComponentBasedDockingAction componentAction)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (componentAction.isValidComponentContext(localContext)) {
|
if (!componentAction.isValidComponentContext(localContext)) {
|
||||||
hasLocalActionsForKeyBinding = true;
|
continue;
|
||||||
if (isValidAndEnabled(actionData, localContext)) {
|
}
|
||||||
list.add(new ExecutableAction(actionData.action, localContext));
|
|
||||||
}
|
multiAction.setContext(localContext);
|
||||||
|
multiAction.addValidAction(actionData.action);
|
||||||
|
|
||||||
|
if (isEnabled(actionData, localContext)) {
|
||||||
|
multiAction.addEnabledAction(actionData.action);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (hasLocalActionsForKeyBinding) {
|
private void getGlobalActions(ActionContext localContext,
|
||||||
// We have locals, ignore the globals. This prevents global actions from processing
|
MultiExecutableAction multiAction) {
|
||||||
// the given keybinding when a local action exits, regardless of enablement.
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// 3) Check for default context actions
|
|
||||||
//
|
|
||||||
for (ActionData actionData : actions) {
|
for (ActionData actionData : actions) {
|
||||||
if (!actionData.isGlobalAction()) {
|
if (!actionData.isGlobalAction()) {
|
||||||
continue;
|
continue;
|
||||||
|
@ -223,13 +227,25 @@ public class MultipleKeyAction extends DockingKeyBindingAction {
|
||||||
|
|
||||||
// When looking for context matches, we prefer local context, even though this
|
// 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
|
// is a 'global' action. This allows more specific context to be used when available
|
||||||
if (isValidAndEnabled(actionData, localContext)) {
|
if (!isValid(actionData, localContext)) {
|
||||||
list.add(new ExecutableAction(actionData.action, localContext));
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// this happens if we are in a dialog, default context is not used
|
multiAction.setContext(localContext);
|
||||||
if (contextMap == null) {
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -238,19 +254,33 @@ public class MultipleKeyAction extends DockingKeyBindingAction {
|
||||||
}
|
}
|
||||||
|
|
||||||
ActionContext defaultContext = contextMap.get(actionData.getContextType());
|
ActionContext defaultContext = contextMap.get(actionData.getContextType());
|
||||||
if (isValidAndEnabled(actionData, defaultContext)) {
|
if (!isValid(actionData, defaultContext)) {
|
||||||
list.add(new ExecutableAction(actionData.action, defaultContext));
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
multiAction.setContext(defaultContext);
|
||||||
|
multiAction.addValidAction(actionData.action);
|
||||||
|
|
||||||
|
if (isEnabled(actionData, defaultContext)) {
|
||||||
|
multiAction.addEnabledAction(actionData.action);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return list;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isValidAndEnabled(ActionData actionData, ActionContext context) {
|
private boolean isValid(ActionData actionData, ActionContext context) {
|
||||||
if (context == null) {
|
if (context == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
DockingActionIf a = actionData.action;
|
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
|
@Override
|
||||||
|
@ -259,65 +289,87 @@ public class MultipleKeyAction extends DockingKeyBindingAction {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public KeyBindingPrecedence getKeyBindingPrecedence() {
|
public ExecutableAction getExecutableAction(Component source) {
|
||||||
return geValidKeyBindingPrecedence(null);
|
ExecutableAction action = createExecutableAction(source);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
// If menu active, disable all default key bindings
|
||||||
* This is a special version of {@link #getKeyBindingPrecedence()} that allows the internal
|
if (ignoreActionWhileMenuShowing(action)) {
|
||||||
* key event processing to specify the source component when determining how precedence should
|
return new MultiExecutableAction();
|
||||||
* 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (validActions.size() != 1) {
|
return action;
|
||||||
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();
|
DockingWindowManager dwm = tool.getWindowManager();
|
||||||
Window window = getWindow(dwm, eventSource);
|
Window window = getWindow(dwm, eventSource);
|
||||||
if (window instanceof DockingDialog) {
|
if (window instanceof DockingDialog) {
|
||||||
return getDialogActions(window);
|
return createDialogActions(eventSource, window);
|
||||||
}
|
}
|
||||||
|
|
||||||
ComponentProvider localProvider = getProvider(dwm, eventSource);
|
ComponentProvider localProvider = getProvider(dwm, eventSource);
|
||||||
ActionContext localContext = getLocalContext(localProvider);
|
ActionContext localContext = getLocalContext(localProvider);
|
||||||
localContext.setSourceObject(eventSource);
|
localContext.setSourceObject(eventSource);
|
||||||
Map<Class<? extends ActionContext>, ActionContext> contextMap =
|
Map<Class<? extends ActionContext>, ActionContext> contextMap =
|
||||||
tool.getWindowManager().getDefaultActionContextMap();
|
dwm.getDefaultActionContextMap();
|
||||||
List<ExecutableAction> validActions = getValidContextActions(localContext, contextMap);
|
return createNonDialogExecutableAction(localContext, contextMap);
|
||||||
return validActions;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<ExecutableAction> getDialogActions(Window window) {
|
private ExecutableAction createDialogActions(Object eventSource, Window window) {
|
||||||
|
|
||||||
|
MultiExecutableAction multiAction = new MultiExecutableAction();
|
||||||
|
|
||||||
DockingDialog dockingDialog = (DockingDialog) window;
|
DockingDialog dockingDialog = (DockingDialog) window;
|
||||||
DialogComponentProvider provider = dockingDialog.getDialogComponent();
|
DialogComponentProvider provider = dockingDialog.getDialogComponent();
|
||||||
if (provider == null) {
|
if (provider == null) {
|
||||||
// this can happen if the dialog is closed during key event processing
|
// this can happen if the dialog is closed during key event processing
|
||||||
return Collections.emptyList();
|
return multiAction;
|
||||||
}
|
}
|
||||||
|
|
||||||
ActionContext context = provider.getActionContext(null);
|
ActionContext context = provider.getActionContext(null);
|
||||||
if (context == 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) {
|
private ComponentProvider getProvider(DockingWindowManager dwm, Object eventSource) {
|
||||||
|
@ -383,6 +435,9 @@ public class MultipleKeyAction extends DockingKeyBindingAction {
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isMyProvider(ActionContext localContext) {
|
boolean isMyProvider(ActionContext localContext) {
|
||||||
|
if (provider == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
ComponentProvider otherProvider = localContext.getComponentProvider();
|
ComponentProvider otherProvider = localContext.getComponentProvider();
|
||||||
return provider == otherProvider;
|
return provider == otherProvider;
|
||||||
}
|
}
|
||||||
|
@ -392,6 +447,142 @@ public class MultipleKeyAction extends DockingKeyBindingAction {
|
||||||
String providerString = provider == null ? "" : provider.toString() + " - ";
|
String providerString = provider == null ? "" : provider.toString() + " - ";
|
||||||
return providerString + action;
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,9 @@
|
||||||
* 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.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@ -15,6 +15,10 @@
|
||||||
*/
|
*/
|
||||||
package docking.action;
|
package docking.action;
|
||||||
|
|
||||||
|
import java.awt.Component;
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
|
|
||||||
|
import javax.help.UnsupportedOperationException;
|
||||||
import javax.swing.KeyStroke;
|
import javax.swing.KeyStroke;
|
||||||
|
|
||||||
import docking.*;
|
import docking.*;
|
||||||
|
@ -33,13 +37,29 @@ public class SystemKeyBindingAction extends DockingKeyBindingAction {
|
||||||
return dockingAction;
|
return dockingAction;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ActionContext getContext(Component focusOwner) {
|
||||||
|
ComponentProvider provider = tool.getActiveComponentProvider();
|
||||||
|
ActionContext context = getLocalContext(provider);
|
||||||
|
context.setSourceObject(focusOwner);
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isSystemKeybindingPrecedence() {
|
public boolean isSystemKeybindingPrecedence() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public KeyBindingPrecedence getKeyBindingPrecedence() {
|
public ExecutableAction getExecutableAction(Component focusOwner) {
|
||||||
return KeyBindingPrecedence.SystemActionsLevel;
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isValidContext(ActionContext context) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isEnabledForContext(ActionContext context) {
|
public boolean isEnabledForContext(ActionContext context) {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -1923,7 +1923,7 @@ public class GTree extends JPanel implements BusyListener {
|
||||||
@Override
|
@Override
|
||||||
public void actionPerformed(ActionContext context) {
|
public void actionPerformed(ActionContext context) {
|
||||||
|
|
||||||
GTree gTree = (GTree) context.getSourceComponent();
|
GTree gTree = getTree(context);
|
||||||
gTree.tree.isCopyFormatted = true;
|
gTree.tree.isCopyFormatted = true;
|
||||||
try {
|
try {
|
||||||
Action builtinCopyAction = TransferHandler.getCopyAction();
|
Action builtinCopyAction = TransferHandler.getCopyAction();
|
||||||
|
@ -1948,7 +1948,7 @@ public class GTree extends JPanel implements BusyListener {
|
||||||
GTreeAction activateFilterAction = new GTreeAction("Table/Tree Activate Filter", owner) {
|
GTreeAction activateFilterAction = new GTreeAction("Table/Tree Activate Filter", owner) {
|
||||||
@Override
|
@Override
|
||||||
public void actionPerformed(ActionContext context) {
|
public void actionPerformed(ActionContext context) {
|
||||||
GTree gTree = (GTree) context.getSourceComponent();
|
GTree gTree = getTree(context);
|
||||||
gTree.filterProvider.activate();
|
gTree.filterProvider.activate();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1966,7 +1966,7 @@ public class GTree extends JPanel implements BusyListener {
|
||||||
GTreeAction toggleFilterAction = new GTreeAction("Table/Tree Toggle Filter", owner) {
|
GTreeAction toggleFilterAction = new GTreeAction("Table/Tree Toggle Filter", owner) {
|
||||||
@Override
|
@Override
|
||||||
public void actionPerformed(ActionContext context) {
|
public void actionPerformed(ActionContext context) {
|
||||||
GTree gTree = (GTree) context.getSourceComponent();
|
GTree gTree = getTree(context);
|
||||||
gTree.filterProvider.toggleVisibility();
|
gTree.filterProvider.toggleVisibility();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue