GP-732 - Added key binding support for showing context menu

This commit is contained in:
emteere 2021-03-02 17:43:16 -05:00 committed by dragonmacher
parent 98d0cd9034
commit cffc1787ae
9 changed files with 179 additions and 33 deletions

View file

@ -15,7 +15,6 @@
*/
package docking;
import java.awt.event.MouseEvent;
import java.util.LinkedHashSet;
import java.util.Set;
@ -134,7 +133,7 @@ public class ActionToGuiMapper {
return menuGroupMap;
}
public void showPopupMenu(ComponentPlaceholder componentInfo, MouseEvent e) {
popupActionManager.popupMenu(componentInfo, e);
public void showPopupMenu(ComponentPlaceholder componentInfo, PopupMenuContext popupContext) {
popupActionManager.popupMenu(componentInfo, popupContext);
}
}

View file

@ -67,17 +67,17 @@ public class DockableComponent extends JPanel implements ContainerListener {
@Override
public void mousePressed(MouseEvent e) {
componentSelected((Component) e.getSource());
processPopupMouseEvent(e);
showContextMenu(e);
}
@Override
public void mouseReleased(MouseEvent e) {
processPopupMouseEvent(e);
showContextMenu(e);
}
@Override
public void mouseClicked(MouseEvent e) {
processPopupMouseEvent(e);
showContextMenu(e);
}
};
@ -146,24 +146,27 @@ public class DockableComponent extends JPanel implements ContainerListener {
return focusedComponent;
}
private void processPopupMouseEvent(final MouseEvent e) {
void showContextMenu(PopupMenuContext popupContext) {
actionMgr.showPopupMenu(placeholder, popupContext);
}
private void showContextMenu(MouseEvent e) {
Component component = e.getComponent();
if (component == null) {
return;
return; // not sure this can happen
}
// get the bounds to see if the clicked point is over the component
Rectangle bounds = component.getBounds(); // get bounds to get width and height
Rectangle bounds = component.getBounds();
if (component instanceof JComponent) {
((JComponent) component).computeVisibleRect(bounds);
}
Point point = e.getPoint();
boolean withinBounds = bounds.contains(point);
if (e.isPopupTrigger() && withinBounds) {
actionMgr.showPopupMenu(placeholder, e);
PopupMenuContext popupContext = new PopupMenuContext(e);
actionMgr.showPopupMenu(placeholder, popupContext);
}
}
@ -476,17 +479,11 @@ public class DockableComponent extends JPanel implements ContainerListener {
return null;
}
/**
* @see java.awt.event.ContainerListener#componentAdded(java.awt.event.ContainerEvent)
*/
@Override
public void componentAdded(ContainerEvent e) {
initializeComponents(e.getChild());
}
/**
* @see java.awt.event.ContainerListener#componentRemoved(java.awt.event.ContainerEvent)
*/
@Override
public void componentRemoved(ContainerEvent e) {
deinitializeComponents(e.getChild());

View file

@ -2172,6 +2172,34 @@ public class DockingWindowManager implements PropertyChangeListener, Placeholder
objectUnderMouse = null;
}
/**
* Shows a popup menu over the given component. If this given component is not part of the
* docking windows hierarchy, then no action is taken.
*
* @param component the component
*/
public static void showContextMenu(Component component) {
DockingWindowManager dwm = getInstance(component);
if (dwm == null) {
return;
}
DockableComponent dockableComponent = dwm.getDockableComponent(component);
if (dockableComponent == null) {
return;
}
Rectangle bounds = dockableComponent.getBounds();
bounds.x = 0;
bounds.y = 0;
int x = (int) bounds.getCenterX();
int y = (int) bounds.getCenterY();
PopupMenuContext popupContext = new PopupMenuContext(dockableComponent, new Point(x, y));
dockableComponent.showContextMenu(popupContext);
}
public void contextChanged(ComponentProvider provider) {
if (provider == null) {

View file

@ -16,6 +16,7 @@
package docking;
import java.awt.Component;
import java.awt.Point;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
@ -67,28 +68,27 @@ public class PopupActionManager implements PropertyChangeListener {
}
}
void popupMenu(ComponentPlaceholder info, MouseEvent e) {
void popupMenu(ComponentPlaceholder placeholder, PopupMenuContext popupContext) {
if (e.isConsumed()) {
return;
}
ComponentProvider popupProvider = info.getProvider();
ActionContext actionContext = popupProvider.getActionContext(e);
MouseEvent event = popupContext.getEvent();
ComponentProvider popupProvider = placeholder.getProvider();
ActionContext actionContext = popupProvider.getActionContext(event);
if (actionContext == null) {
actionContext = new ActionContext();
}
actionContext.setSourceObject(e.getSource());
actionContext.setMouseEvent(e);
actionContext.setSourceObject(popupContext.getSource());
actionContext.setMouseEvent(event);
Iterator<DockingActionIf> localActions = info.getActions();
Iterator<DockingActionIf> localActions = placeholder.getActions();
JPopupMenu popupMenu = createPopupMenu(localActions, actionContext);
if (popupMenu == null) {
return; // no matching actions
}
Component c = (Component) e.getSource();
popupMenu.show(c, e.getX(), e.getY());
Component c = popupContext.getComponent();
Point p = popupContext.getPoint();
popupMenu.show(c, p.x, p.y);
}
protected JPopupMenu createPopupMenu(Iterator<DockingActionIf> localActions,

View file

@ -0,0 +1,61 @@
/* ###
* 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 java.awt.Point;
import java.awt.event.MouseEvent;
import java.util.Objects;
/**
* A class that holds information used to show a popup menu
*/
public class PopupMenuContext {
private Component component;
private MouseEvent event;
private Point point;
PopupMenuContext(MouseEvent event) {
this.event = event;
this.component = Objects.requireNonNull(event.getComponent());
this.point = event.getPoint();
}
PopupMenuContext(Component component, Point point) {
this.component = Objects.requireNonNull(component);
this.point = point;
}
public MouseEvent getEvent() {
return event;
}
public Component getComponent() {
return component;
}
public Point getPoint() {
return new Point(point);
}
public Object getSource() {
if (event != null) {
return event.getSource();
}
return component;
}
}

View file

@ -17,8 +17,7 @@ package docking.action;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.HashMap;
import java.util.Map;
import java.util.*;
import javax.swing.Action;
import javax.swing.KeyStroke;
@ -61,6 +60,7 @@ public class KeyBindingsManager implements PropertyChangeListener {
public void addReservedAction(DockingActionIf action) {
KeyStroke keyBinding = action.getKeyBinding();
Objects.requireNonNull(keyBinding);
addReservedKeyBinding(action, keyBinding);
}

View file

@ -0,0 +1,52 @@
/* ###
* 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.action;
import java.awt.*;
import javax.swing.KeyStroke;
import docking.ActionContext;
import docking.DockingWindowManager;
/**
* An action to trigger a context menu over the focus owner. This allows context menus to be
* triggered from the keyboard.
*/
public class ShowContextMenuAction extends DockingAction {
public ShowContextMenuAction(KeyStroke keyStroke) {
super("Show Context Menu", DockingWindowManager.DOCKING_WINDOWS_OWNER);
setKeyBindingData(new KeyBindingData(keyStroke));
}
@Override
public void actionPerformed(ActionContext context) {
KeyboardFocusManager kfm = KeyboardFocusManager.getCurrentKeyboardFocusManager();
Window window = kfm.getActiveWindow();
if (window == null) {
return;
}
// use the focused component to determine what should get the context menu
Component focusOwner = kfm.getFocusOwner();
if (focusOwner != null) {
DockingWindowManager.showContextMenu(focusOwner);
}
}
}

View file

@ -91,6 +91,10 @@ public class ToolActions implements DockingToolActions, PropertyChangeListener {
keyBindingsManager.addReservedAction(new HelpAction(false, ReservedKeyBindings.HELP_KEY2));
keyBindingsManager.addReservedAction(
new HelpAction(true, ReservedKeyBindings.HELP_INFO_KEY));
keyBindingsManager.addReservedAction(
new ShowContextMenuAction(ReservedKeyBindings.CONTEXT_MENU_KEY1));
keyBindingsManager.addReservedAction(
new ShowContextMenuAction(ReservedKeyBindings.CONTEXT_MENU_KEY2));
// these are diagnostic
if (SystemUtilities.isInDevelopmentMode()) {

View file

@ -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.
@ -34,6 +33,11 @@ public class ReservedKeyBindings {
public static final KeyStroke HELP_INFO_KEY =
KeyStroke.getKeyStroke(KeyEvent.VK_F1, DockingUtils.CONTROL_KEY_MODIFIER_MASK);
public static final KeyStroke CONTEXT_MENU_KEY1 =
KeyStroke.getKeyStroke(KeyEvent.VK_F10, InputEvent.SHIFT_DOWN_MASK);
public static final KeyStroke CONTEXT_MENU_KEY2 =
KeyStroke.getKeyStroke(KeyEvent.VK_CONTEXT_MENU, 0);
public static final KeyStroke FOCUS_INFO_KEY =
KeyStroke.getKeyStroke(KeyEvent.VK_F2, DockingUtils.CONTROL_KEY_MODIFIER_MASK |
InputEvent.ALT_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK);
@ -50,7 +54,8 @@ public class ReservedKeyBindings {
code == KeyEvent.VK_CAPS_LOCK || code == KeyEvent.VK_TAB ||
HELP_KEY1.equals(keyStroke) || HELP_KEY2.equals(keyStroke) ||
HELP_INFO_KEY.equals(keyStroke) || UPDATE_KEY_BINDINGS_KEY.equals(keyStroke) ||
FOCUS_INFO_KEY.equals(keyStroke) || FOCUS_CYCLE_INFO_KEY.equals(keyStroke)) {
FOCUS_INFO_KEY.equals(keyStroke) || FOCUS_CYCLE_INFO_KEY.equals(keyStroke) ||
CONTEXT_MENU_KEY1.equals(keyStroke) || CONTEXT_MENU_KEY2.equals(keyStroke)) {
return true;
}