mirror of https://github.com/weisJ/darklaf.git
Browse Source
Removed old MouseGrabber from some remaining listener lists it was added to.pull/154/head
weisj
5 years ago
4 changed files with 408 additions and 294 deletions
@ -0,0 +1,298 @@ |
|||||||
|
/* |
||||||
|
* MIT License |
||||||
|
* |
||||||
|
* Copyright (c) 2020 Jannis Weis |
||||||
|
* |
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy |
||||||
|
* of this software and associated documentation files (the "Software"), to deal |
||||||
|
* in the Software without restriction, including without limitation the rights |
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||||
|
* copies of the Software, and to permit persons to whom the Software is |
||||||
|
* furnished to do so, subject to the following conditions: |
||||||
|
* |
||||||
|
* The above copyright notice and this permission notice shall be included in all |
||||||
|
* copies or substantial portions of the Software. |
||||||
|
* |
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||||
|
* SOFTWARE. |
||||||
|
* |
||||||
|
*/ |
||||||
|
package com.github.weisj.darklaf.ui.popupmenu; |
||||||
|
|
||||||
|
import java.applet.Applet; |
||||||
|
import java.awt.*; |
||||||
|
import java.awt.event.*; |
||||||
|
import java.security.PrivilegedAction; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
import javax.swing.*; |
||||||
|
import javax.swing.event.ChangeEvent; |
||||||
|
import javax.swing.event.ChangeListener; |
||||||
|
|
||||||
|
import sun.awt.SunToolkit; |
||||||
|
|
||||||
|
import com.github.weisj.darklaf.components.OverlayScrollPane; |
||||||
|
import com.github.weisj.darklaf.util.DarkUIUtil; |
||||||
|
|
||||||
|
public class MouseGrabber implements ChangeListener, AWTEventListener, ComponentListener, WindowListener { |
||||||
|
|
||||||
|
Window grabbedWindow; |
||||||
|
MenuElement[] lastPathSelected; |
||||||
|
|
||||||
|
public MouseGrabber() { |
||||||
|
install(); |
||||||
|
} |
||||||
|
|
||||||
|
public void install() { |
||||||
|
MenuSelectionManager msm = MenuSelectionManager.defaultManager(); |
||||||
|
msm.addChangeListener(this); |
||||||
|
this.lastPathSelected = msm.getSelectedPath(); |
||||||
|
if (this.lastPathSelected.length != 0) { |
||||||
|
grabWindow(this.lastPathSelected); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
protected void grabWindow(final MenuElement[] newPath) { |
||||||
|
// A grab needs to be added
|
||||||
|
final Toolkit tk = Toolkit.getDefaultToolkit(); |
||||||
|
java.security.AccessController.doPrivileged((PrivilegedAction<Object>) () -> { |
||||||
|
tk.addAWTEventListener(MouseGrabber.this, |
||||||
|
AWTEvent.MOUSE_EVENT_MASK |
||||||
|
| AWTEvent.MOUSE_MOTION_EVENT_MASK |
||||||
|
| AWTEvent.MOUSE_WHEEL_EVENT_MASK |
||||||
|
| AWTEvent.WINDOW_EVENT_MASK |
||||||
|
| SunToolkit.GRAB_EVENT_MASK); |
||||||
|
return null; |
||||||
|
}); |
||||||
|
|
||||||
|
Component invoker = newPath[0].getComponent(); |
||||||
|
if (invoker instanceof JPopupMenu) { |
||||||
|
invoker = ((JPopupMenu) invoker).getInvoker(); |
||||||
|
} |
||||||
|
grabbedWindow = DarkUIUtil.getWindow(invoker); |
||||||
|
if (grabbedWindow != null) { |
||||||
|
if (tk instanceof SunToolkit) { |
||||||
|
((SunToolkit) tk).grab(grabbedWindow); |
||||||
|
} else { |
||||||
|
grabbedWindow.addComponentListener(this); |
||||||
|
grabbedWindow.addWindowListener(this); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public void uninstall() { |
||||||
|
MenuSelectionManager.defaultManager().removeChangeListener(this); |
||||||
|
ungrabWindow(); |
||||||
|
} |
||||||
|
|
||||||
|
protected void ungrabWindow() { |
||||||
|
final Toolkit tk = Toolkit.getDefaultToolkit(); |
||||||
|
// The grab should be removed
|
||||||
|
java.security.AccessController.doPrivileged((PrivilegedAction<Object>) () -> { |
||||||
|
tk.removeAWTEventListener(MouseGrabber.this); |
||||||
|
return null; |
||||||
|
}); |
||||||
|
realUngrabWindow(); |
||||||
|
} |
||||||
|
|
||||||
|
protected void realUngrabWindow() { |
||||||
|
Toolkit tk = Toolkit.getDefaultToolkit(); |
||||||
|
if (grabbedWindow != null) { |
||||||
|
if (tk instanceof SunToolkit) { |
||||||
|
((SunToolkit) tk).ungrab(grabbedWindow); |
||||||
|
} else { |
||||||
|
grabbedWindow.removeComponentListener(this); |
||||||
|
grabbedWindow.removeWindowListener(this); |
||||||
|
} |
||||||
|
grabbedWindow = null; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public void stateChanged(final ChangeEvent e) { |
||||||
|
MenuSelectionManager msm = MenuSelectionManager.defaultManager(); |
||||||
|
MenuElement[] p = msm.getSelectedPath(); |
||||||
|
|
||||||
|
if (lastPathSelected.length == 0 && p.length != 0) { |
||||||
|
grabWindow(p); |
||||||
|
} |
||||||
|
|
||||||
|
if (lastPathSelected.length != 0 && p.length == 0) { |
||||||
|
ungrabWindow(); |
||||||
|
} |
||||||
|
|
||||||
|
lastPathSelected = p; |
||||||
|
repaintIfNecessary(e); |
||||||
|
} |
||||||
|
|
||||||
|
protected void repaintIfNecessary(final ChangeEvent e) { |
||||||
|
Object source = e.getSource(); |
||||||
|
if (source instanceof MenuSelectionManager) { |
||||||
|
MenuSelectionManager manager = (MenuSelectionManager) source; |
||||||
|
MenuElement[] path = manager.getSelectedPath(); |
||||||
|
if (path != null && path.length > 0) { |
||||||
|
Component comp = path[0].getComponent(); |
||||||
|
if (comp.isVisible()) { |
||||||
|
OverlayScrollPane sp = DarkUIUtil.getParentOfType(OverlayScrollPane.class, comp); |
||||||
|
if (sp != null) sp.repaint(comp.getBounds()); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public void eventDispatched(final AWTEvent ev) { |
||||||
|
if (ev instanceof sun.awt.UngrabEvent) { |
||||||
|
// Popup should be canceled in case of ungrab event
|
||||||
|
cancelPopupMenu(); |
||||||
|
return; |
||||||
|
} |
||||||
|
if (!(ev instanceof MouseEvent)) { |
||||||
|
// We are interested in MouseEvents only
|
||||||
|
return; |
||||||
|
} |
||||||
|
MouseEvent me = (MouseEvent) ev; |
||||||
|
Component src = me.getComponent(); |
||||||
|
// If the scroll is done inside a combobox, menuitem,
|
||||||
|
// or inside a Popup#HeavyWeightWindow or inside a frame
|
||||||
|
// popup should not close which is the standard behaviour
|
||||||
|
switch (me.getID()) {/* |
||||||
|
* Changed here: Make doNotCancelPopup accessible to all component. |
||||||
|
* Allows for more versatile PopupMenus. |
||||||
|
*/ |
||||||
|
case MouseEvent.MOUSE_PRESSED : |
||||||
|
if (isInPopup(src) || |
||||||
|
(src instanceof JMenu && ((JMenu) src).isSelected())) { |
||||||
|
return; |
||||||
|
} |
||||||
|
if (!(src instanceof JComponent) || |
||||||
|
!DarkPopupMenuUI.HIDE_POPUP_VALUE.equals(((JComponent) src).getClientProperty( |
||||||
|
DarkPopupMenuUI.KEY_DO_NOT_CANCEL_POPUP))) { |
||||||
|
// Cancel popup only if this property was not set.
|
||||||
|
// If this property is set to TRUE component wants
|
||||||
|
// to deal with this event by himself.
|
||||||
|
cancelPopupMenu(); |
||||||
|
// Ask UIManager about should we consume event that closes
|
||||||
|
// popup. This made to match native apps behaviour.
|
||||||
|
boolean consumeEvent = UIManager.getBoolean("PopupMenu.consumeEventOnClose"); |
||||||
|
// Consume the event so that normal processing stops.
|
||||||
|
if (consumeEvent && !(src instanceof MenuElement)) { |
||||||
|
me.consume(); |
||||||
|
} |
||||||
|
} |
||||||
|
break; |
||||||
|
case MouseEvent.MOUSE_RELEASED : |
||||||
|
if (!(src instanceof MenuElement)) { |
||||||
|
// Do not forward event to MSM, let component handle it
|
||||||
|
if (isInPopup(src)) { |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
if (src instanceof JMenu || !(src instanceof JMenuItem)) { |
||||||
|
MenuSelectionManager.defaultManager().processMouseEvent(me); |
||||||
|
} |
||||||
|
break; |
||||||
|
case MouseEvent.MOUSE_DRAGGED : |
||||||
|
if (!(src instanceof MenuElement)) { |
||||||
|
// For the MOUSE_DRAGGED event the src is
|
||||||
|
// the Component in which mouse button was pressed.
|
||||||
|
// If the src is in popupMenu,
|
||||||
|
// do not forward event to MSM, let component handle it.
|
||||||
|
if (isInPopup(src)) { |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
MenuSelectionManager.defaultManager().processMouseEvent(me); |
||||||
|
break; |
||||||
|
case MouseEvent.MOUSE_WHEEL : |
||||||
|
if (isInPopup(src) |
||||||
|
|| ((src instanceof JComboBox) && ((JComboBox<?>) src).isPopupVisible()) |
||||||
|
|| ((src instanceof JWindow) && src.isVisible()) |
||||||
|
|| ((src instanceof JMenuItem) && src.isVisible()) |
||||||
|
|| (src instanceof JFrame) |
||||||
|
|| (src instanceof JDialog)) { |
||||||
|
return; |
||||||
|
} |
||||||
|
cancelPopupMenu(); |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
protected void cancelPopupMenu() { |
||||||
|
// We should ungrab window if a user code throws
|
||||||
|
// an unexpected runtime exception. See 6495920.
|
||||||
|
try { |
||||||
|
// 4234793: This action should call firePopupMenuCanceled but it's
|
||||||
|
// a protected method. The real solution could be to make
|
||||||
|
// firePopupMenuCanceled public and call it directly.
|
||||||
|
List<JPopupMenu> popups = DarkPopupMenuUI.getPopups(); |
||||||
|
for (JPopupMenu popup : popups) { |
||||||
|
popup.putClientProperty("JPopupMenu.firePopupMenuCanceled", Boolean.TRUE); |
||||||
|
} |
||||||
|
MenuSelectionManager.defaultManager().clearSelectedPath(); |
||||||
|
} catch (RuntimeException | Error ex) { |
||||||
|
realUngrabWindow(); |
||||||
|
throw ex; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@SuppressWarnings("deprecation") |
||||||
|
protected boolean isInPopup(final Component src) { |
||||||
|
for (Component c = src; c != null; c = c.getParent()) { |
||||||
|
if (c instanceof Applet || c instanceof Window) { |
||||||
|
break; |
||||||
|
} else if (c instanceof JPopupMenu) { |
||||||
|
return true; |
||||||
|
} else if (c instanceof JComponent |
||||||
|
&& Boolean.TRUE.equals(((JComponent) c).getClientProperty( |
||||||
|
DarkPopupMenuUI.KEY_DO_NOT_CANCEL_ON_SCROLL))) { |
||||||
|
/* |
||||||
|
* Change here: allows scrollable components that contain the popupMenu. |
||||||
|
*/ |
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
public void componentResized(final ComponentEvent e) { |
||||||
|
cancelPopupMenu(); |
||||||
|
} |
||||||
|
|
||||||
|
public void componentMoved(final ComponentEvent e) { |
||||||
|
cancelPopupMenu(); |
||||||
|
} |
||||||
|
|
||||||
|
public void componentShown(final ComponentEvent e) { |
||||||
|
cancelPopupMenu(); |
||||||
|
} |
||||||
|
|
||||||
|
public void componentHidden(final ComponentEvent e) { |
||||||
|
cancelPopupMenu(); |
||||||
|
} |
||||||
|
|
||||||
|
public void windowOpened(final WindowEvent e) {} |
||||||
|
|
||||||
|
public void windowClosing(final WindowEvent e) { |
||||||
|
cancelPopupMenu(); |
||||||
|
} |
||||||
|
|
||||||
|
public void windowClosed(final WindowEvent e) { |
||||||
|
cancelPopupMenu(); |
||||||
|
} |
||||||
|
|
||||||
|
public void windowIconified(final WindowEvent e) { |
||||||
|
cancelPopupMenu(); |
||||||
|
} |
||||||
|
|
||||||
|
public void windowDeiconified(final WindowEvent e) {} |
||||||
|
|
||||||
|
public void windowActivated(final WindowEvent e) {} |
||||||
|
|
||||||
|
public void windowDeactivated(final WindowEvent e) { |
||||||
|
cancelPopupMenu(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,107 @@ |
|||||||
|
/* |
||||||
|
* MIT License |
||||||
|
* |
||||||
|
* Copyright (c) 2020 Jannis Weis |
||||||
|
* |
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy |
||||||
|
* of this software and associated documentation files (the "Software"), to deal |
||||||
|
* in the Software without restriction, including without limitation the rights |
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||||
|
* copies of the Software, and to permit persons to whom the Software is |
||||||
|
* furnished to do so, subject to the following conditions: |
||||||
|
* |
||||||
|
* The above copyright notice and this permission notice shall be included in all |
||||||
|
* copies or substantial portions of the Software. |
||||||
|
* |
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||||
|
* SOFTWARE. |
||||||
|
* |
||||||
|
*/ |
||||||
|
package com.github.weisj.darklaf.ui.popupmenu; |
||||||
|
|
||||||
|
import java.awt.*; |
||||||
|
import java.awt.event.AWTEventListener; |
||||||
|
import java.awt.event.ComponentListener; |
||||||
|
import java.awt.event.WindowListener; |
||||||
|
import java.security.PrivilegedAction; |
||||||
|
|
||||||
|
import javax.swing.*; |
||||||
|
import javax.swing.event.ChangeListener; |
||||||
|
|
||||||
|
import com.github.weisj.darklaf.util.DarkUIUtil; |
||||||
|
|
||||||
|
public class MouseGrabberUtil { |
||||||
|
|
||||||
|
private MouseGrabberUtil() { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
private static MouseGrabber mouseGrabber; |
||||||
|
|
||||||
|
public static void uninstallMouseGrabber() { |
||||||
|
if (mouseGrabber != null) { |
||||||
|
mouseGrabber.uninstall(); |
||||||
|
mouseGrabber = null; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public static void installMouseGrabber() { |
||||||
|
if (mouseGrabber == null) { |
||||||
|
uninstallOldMouseGrabber(getOldMouseGrabber()); |
||||||
|
mouseGrabber = new MouseGrabber(); |
||||||
|
mouseGrabber.install(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public static ChangeListener getOldMouseGrabber() { |
||||||
|
MenuSelectionManager menuSelectionManager = MenuSelectionManager.defaultManager(); |
||||||
|
for (ChangeListener listener : menuSelectionManager.getChangeListeners()) { |
||||||
|
if (listener == null) continue; |
||||||
|
Class<?> listenerClass = listener.getClass(); |
||||||
|
if (listenerClass == null) continue; |
||||||
|
Class<?> enclosingClass = listenerClass.getEnclosingClass(); |
||||||
|
if (enclosingClass == null) continue; |
||||||
|
if (listenerClass.getName().endsWith("MouseGrabber") |
||||||
|
&& enclosingClass.getName().endsWith("BasicPopupMenuUI")) { |
||||||
|
return listener; |
||||||
|
} |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This Method is responsible for removing the old MouseGrabber from the AppContext, to be able to add our own |
||||||
|
* implementation for it that is a bit more generous with closing the popup. |
||||||
|
*/ |
||||||
|
public static void uninstallOldMouseGrabber(final ChangeListener oldMouseGrabber) { |
||||||
|
if (oldMouseGrabber == null) return; |
||||||
|
MenuSelectionManager menuSelectionManager = MenuSelectionManager.defaultManager(); |
||||||
|
menuSelectionManager.removeChangeListener(oldMouseGrabber); |
||||||
|
if (oldMouseGrabber instanceof AWTEventListener) { |
||||||
|
Toolkit tk = Toolkit.getDefaultToolkit(); |
||||||
|
java.security.AccessController.doPrivileged((PrivilegedAction<Object>) () -> { |
||||||
|
tk.removeAWTEventListener((AWTEventListener) oldMouseGrabber); |
||||||
|
return null; |
||||||
|
}); |
||||||
|
} |
||||||
|
MenuElement[] path = menuSelectionManager.getSelectedPath(); |
||||||
|
if (path.length != 0 && path[0] != null) { |
||||||
|
Component invoker = path[0].getComponent(); |
||||||
|
if (invoker instanceof JPopupMenu) { |
||||||
|
invoker = ((JPopupMenu) invoker).getInvoker(); |
||||||
|
} |
||||||
|
Window grabbedWindow = DarkUIUtil.getWindow(invoker); |
||||||
|
if (oldMouseGrabber instanceof WindowListener) { |
||||||
|
grabbedWindow.removeWindowListener((WindowListener) oldMouseGrabber); |
||||||
|
} |
||||||
|
if (oldMouseGrabber instanceof ComponentListener) { |
||||||
|
grabbedWindow.removeComponentListener((ComponentListener) oldMouseGrabber); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue