diff --git a/Ghidra/Framework/Docking/src/main/java/docking/ActionToGuiMapper.java b/Ghidra/Framework/Docking/src/main/java/docking/ActionToGuiMapper.java index b048f0cf42..87d7fa763b 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/ActionToGuiMapper.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/ActionToGuiMapper.java @@ -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); } } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/DockableComponent.java b/Ghidra/Framework/Docking/src/main/java/docking/DockableComponent.java index 55538b0f9e..c0963c8883 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/DockableComponent.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/DockableComponent.java @@ -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()); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/DockingWindowManager.java b/Ghidra/Framework/Docking/src/main/java/docking/DockingWindowManager.java index 4b7d9b4476..873c75d020 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/DockingWindowManager.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/DockingWindowManager.java @@ -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) { diff --git a/Ghidra/Framework/Docking/src/main/java/docking/PopupActionManager.java b/Ghidra/Framework/Docking/src/main/java/docking/PopupActionManager.java index af93a7677a..5a712f2b76 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/PopupActionManager.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/PopupActionManager.java @@ -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 localActions = info.getActions(); + Iterator 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 localActions, diff --git a/Ghidra/Framework/Docking/src/main/java/docking/PopupMenuContext.java b/Ghidra/Framework/Docking/src/main/java/docking/PopupMenuContext.java new file mode 100644 index 0000000000..16c87cbd7f --- /dev/null +++ b/Ghidra/Framework/Docking/src/main/java/docking/PopupMenuContext.java @@ -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; + } +} diff --git a/Ghidra/Framework/Docking/src/main/java/docking/action/KeyBindingsManager.java b/Ghidra/Framework/Docking/src/main/java/docking/action/KeyBindingsManager.java index f29071f815..e433c7705a 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/action/KeyBindingsManager.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/action/KeyBindingsManager.java @@ -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); } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/action/ShowContextMenuAction.java b/Ghidra/Framework/Docking/src/main/java/docking/action/ShowContextMenuAction.java new file mode 100644 index 0000000000..469e521057 --- /dev/null +++ b/Ghidra/Framework/Docking/src/main/java/docking/action/ShowContextMenuAction.java @@ -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); + } + } + +} diff --git a/Ghidra/Framework/Docking/src/main/java/docking/actions/ToolActions.java b/Ghidra/Framework/Docking/src/main/java/docking/actions/ToolActions.java index 592d545f81..bec2cc41cb 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/actions/ToolActions.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/actions/ToolActions.java @@ -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()) { diff --git a/Ghidra/Framework/Docking/src/main/java/ghidra/util/ReservedKeyBindings.java b/Ghidra/Framework/Docking/src/main/java/ghidra/util/ReservedKeyBindings.java index c081d5ff99..025c116c48 100644 --- a/Ghidra/Framework/Docking/src/main/java/ghidra/util/ReservedKeyBindings.java +++ b/Ghidra/Framework/Docking/src/main/java/ghidra/util/ReservedKeyBindings.java @@ -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; }