diff --git a/Ghidra/Framework/Docking/src/main/java/docking/DockableComponent.java b/Ghidra/Framework/Docking/src/main/java/docking/DockableComponent.java index 826f4456d5..cd6cd48777 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/DockableComponent.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/DockableComponent.java @@ -4,9 +4,9 @@ * 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. @@ -21,9 +21,10 @@ import java.awt.event.*; import javax.swing.*; +import org.apache.commons.lang3.ArrayUtils; + import docking.action.DockingActionIf; -import ghidra.util.CascadedDropTarget; -import ghidra.util.HelpLocation; +import ghidra.util.*; import help.HelpService; /** @@ -150,6 +151,10 @@ public class DockableComponent extends JPanel implements ContainerListener { return; } + if (!e.isPopupTrigger()) { + return; + } + Component component = e.getComponent(); if (component == null) { return; // not sure this can happen @@ -162,11 +167,31 @@ public class DockableComponent extends JPanel implements ContainerListener { } Point point = e.getPoint(); - boolean withinBounds = bounds.contains(point); - if (e.isPopupTrigger() && withinBounds) { + if (!bounds.contains(point)) { + return; + } + + // + // Consume the event so that Java UI listeners do not process it. This fixes issues with + // UI classes (e.g., listeners change table selection). We want to run this code later to + // allow trailing application mouse listeners to have a chance to update the context. If + // the delayed nature causes any timing issues, then we will need a more robust way of + // registering mouse listeners to work around this issue. + // + e.consume(); + Swing.runLater(() -> { + + MenuSelectionManager msm = MenuSelectionManager.defaultManager(); + MenuElement[] selectedPath = msm.getSelectedPath(); + if (!ArrayUtils.isEmpty(selectedPath)) { + // This means that a menu is open. This can happen if a mouse listener further down + // the listener list has shown a popup. In that case, do not show the context menu. + return; + } + PopupMenuContext popupContext = new PopupMenuContext(e); actionMgr.showPopupMenu(placeholder, popupContext); - } + }); } @Override @@ -328,8 +353,27 @@ public class DockableComponent extends JPanel implements ContainerListener { } if (comp.isFocusable()) { - comp.removeMouseListener(popupListener); - comp.addMouseListener(popupListener); + installPopupListenerFirst(comp); + } + } + + /** + * Remove and re-add all mouse listeners so our popup listener can go first. This allows our + * popup listener to consume the event, preventing Java UI listeners from changing the table + * selection when the user is performing a Ctrl-Mouse click on the Mac. + * + * @param comp the component + */ + private void installPopupListenerFirst(Component comp) { + comp.removeMouseListener(popupListener); + MouseListener[] listeners = comp.getMouseListeners(); + for (MouseListener l : listeners) { + comp.removeMouseListener(l); + } + + comp.addMouseListener(popupListener); + for (MouseListener l : listeners) { + comp.addMouseListener(l); } }