From 000d069bfcf61d8df60c5f05552348fde8a2f1af Mon Sep 17 00:00:00 2001 From: nathan Date: Sat, 8 Oct 2016 21:47:53 +0200 Subject: [PATCH] windows/mac swing menus working with keyboard navigation/mnemonics --- .../systemTray/swing/AdjustedJMenu.java | 27 +-- src/dorkbox/systemTray/swing/SwingMenu.java | 41 +++-- .../swing/SwingSystemTrayMenuPopup.java | 164 ------------------ src/dorkbox/systemTray/util/ImageUtils.java | 18 +- 4 files changed, 36 insertions(+), 214 deletions(-) delete mode 100644 src/dorkbox/systemTray/swing/SwingSystemTrayMenuPopup.java diff --git a/src/dorkbox/systemTray/swing/AdjustedJMenu.java b/src/dorkbox/systemTray/swing/AdjustedJMenu.java index 86dcec3..f85831e 100644 --- a/src/dorkbox/systemTray/swing/AdjustedJMenu.java +++ b/src/dorkbox/systemTray/swing/AdjustedJMenu.java @@ -23,11 +23,7 @@ import javax.swing.border.EmptyBorder; class AdjustedJMenu extends JMenu { - // only necessary in linux - private final SwingSystemTrayMenuPopup mainPopup; - - AdjustedJMenu(final SwingSystemTrayMenuPopup mainPopup) { - this.mainPopup = mainPopup; + AdjustedJMenu() { } @Override @@ -45,25 +41,4 @@ class AdjustedJMenu extends JMenu { return margin; } - - @Override - public - void setPopupMenuVisible(final boolean visible) { - if (mainPopup != null) { - mainPopup.track(getPopupMenu(), visible); - } - - super.setPopupMenuVisible(visible); - } - - @Override - public - void removeNotify() { - if (mainPopup != null) { - // have to make sure that when we are removed, we remove ourself from the tracker - mainPopup.track(getPopupMenu(), false); - } - - super.removeNotify(); - } } diff --git a/src/dorkbox/systemTray/swing/SwingMenu.java b/src/dorkbox/systemTray/swing/SwingMenu.java index 658ece3..2ce0b20 100644 --- a/src/dorkbox/systemTray/swing/SwingMenu.java +++ b/src/dorkbox/systemTray/swing/SwingMenu.java @@ -16,6 +16,8 @@ package dorkbox.systemTray.swing; +import static dorkbox.systemTray.swing.SwingEntry.getVkKey; + import java.io.File; import java.io.InputStream; import java.net.URL; @@ -30,11 +32,9 @@ import dorkbox.systemTray.MenuEntry; import dorkbox.systemTray.SystemTray; import dorkbox.systemTray.SystemTrayMenuAction; import dorkbox.systemTray.util.ImageUtils; -import dorkbox.util.OS; import dorkbox.util.SwingUtil; // this is a weird composite class, because it must be a Menu, but ALSO a MenuEntry -- so it has both -public class SwingMenu extends Menu implements MenuEntry { volatile JComponent _native; @@ -49,7 +49,6 @@ class SwingMenu extends Menu implements MenuEntry { * @param parent * the parent of this menu, null if the parent is the system tray */ - public SwingMenu(final SystemTray systemTray, final Menu parent) { super(systemTray, parent); @@ -59,21 +58,12 @@ class SwingMenu extends Menu implements MenuEntry { public void run() { if (parent != null) { - if (OS.isWindows()) { - _native = new AdjustedJMenu(null); - } - else { - _native = new AdjustedJMenu((SwingSystemTrayMenuPopup)((SwingMenu) systemTray.getMenu())._native); - } - + // when we are a sub-menu + _native = new AdjustedJMenu(); ((SwingMenu) parent)._native.add(_native); } else { // when we are the system tray - if (OS.isWindows()) { - _native = new SwingSystemTrayMenuWindowsPopup(); - } else { - _native = new SwingSystemTrayMenuPopup(); - } + _native = new SwingSystemTrayMenuWindowsPopup(); } } }); @@ -204,11 +194,13 @@ class SwingMenu extends Menu implements MenuEntry { // always called in the EDT + private void renderText(final String text) { ((JMenuItem) _native).setText(text); } @SuppressWarnings("Duplicates") + private void setImage_(final File imageFile) { hasLegitIcon = imageFile != null; @@ -313,6 +305,22 @@ class SwingMenu extends Menu implements MenuEntry { void setCallback(final SystemTrayMenuAction callback) { } + @Override + public + void setShortcut(final char key) { + if (_native instanceof JMenuItem) { + // yikes... + final int vKey = getVkKey(key); + dispatch(new Runnable() { + @Override + public + void run() { + ((JMenuItem) _native).setMnemonic(vKey); + } + }); + } + } + @Override public final void remove() { @@ -324,9 +332,6 @@ class SwingMenu extends Menu implements MenuEntry { if (_native instanceof SwingSystemTrayMenuWindowsPopup) { ((SwingSystemTrayMenuWindowsPopup) _native).close(); } - else if (_native instanceof SwingSystemTrayMenuPopup) { - ((SwingSystemTrayMenuPopup) _native).close(); - } SwingMenu parent = (SwingMenu) getParent(); if (parent != null) { diff --git a/src/dorkbox/systemTray/swing/SwingSystemTrayMenuPopup.java b/src/dorkbox/systemTray/swing/SwingSystemTrayMenuPopup.java deleted file mode 100644 index 68412bf..0000000 --- a/src/dorkbox/systemTray/swing/SwingSystemTrayMenuPopup.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright 2014 dorkbox, llc - * - * 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 dorkbox.systemTray.swing; - -import java.awt.Dimension; -import java.awt.MouseInfo; -import java.awt.Point; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.util.ArrayList; -import java.util.List; - -import javax.swing.JPopupMenu; -import javax.swing.border.EmptyBorder; - -import dorkbox.util.DelayTimer; -import dorkbox.util.Property; -import dorkbox.util.SwingUtil; - -/** - * This custom popup is required, because we cannot close this popup by clicking OUTSIDE the popup. For whatever reason, that does not - * work, so we must implement an "auto-hide" feature that checks if our mouse is still inside a menu every POPUP_HIDE_DELAY seconds - */ -public -class SwingSystemTrayMenuPopup extends JPopupMenu { - private static final long serialVersionUID = 1L; - - @Property - /** Customize the delay (for hiding the popup) when the cursor is "moused out" of the popup menu */ - public static long POPUP_HIDE_DELAY = 1000L; - - @Property - /** Customize the minimum amount of movement needed to cause the popup-delay to hide the popup */ - public static int MOVEMENT_DELTA = 20; - - private DelayTimer timer; - - protected volatile Point mouseClickLocation = null; - - // keep track of what popup is showing so we can check mouse bounds on that popup - private final List trackedMenus = new ArrayList(4); - - // NOTE: we can use the "hidden dialog" focus window trick... only on windows and mac - // private JDialog hiddenDialog; - - SwingSystemTrayMenuPopup() { - super(); - setFocusable(true); -// setBorder(new BorderUIResource.EmptyBorderUIResource(0, 0, 0, 0)); // borderUI resource border type will get changed! - setBorder(new EmptyBorder(1, 1, 1, 1)); - trackedMenus.add(this); - - this.timer = new DelayTimer("PopupMenuHider", true, new Runnable() { - @Override - public - void run() { - SwingUtil.invokeLater(new Runnable() { - @Override - public - void run() { - if (!isVisible()) { - return; - } - - Point location = MouseInfo.getPointerInfo() - .getLocation(); - - // are we inside one of our tracked menus (the root menu is included) - synchronized (trackedMenus) { - for (JPopupMenu trackedMenu : trackedMenus) { - Point menuLocation = trackedMenu.getLocationOnScreen(); - Dimension size = trackedMenu.getSize(); - - if (location.x >= menuLocation.x && location.x < menuLocation.x + size.width && - location.y >= menuLocation.y && location.y < menuLocation.y + size.height - ) { - - // restart the timer - SwingSystemTrayMenuPopup.this.timer.delay(POPUP_HIDE_DELAY); - return; - } - } - } - - - // has the mouse pointer moved > delta pixels from it's original location (when the tray icon was clicked)? - if (mouseClickLocation != null && - location.x >= mouseClickLocation.x - MOVEMENT_DELTA && location.x < mouseClickLocation.x + MOVEMENT_DELTA && - location.y >= mouseClickLocation.y - MOVEMENT_DELTA && location.y < mouseClickLocation.y + MOVEMENT_DELTA - ) { - - // restart the timer - SwingSystemTrayMenuPopup.this.timer.delay(POPUP_HIDE_DELAY); - return; - } - - // else, we hide it - setVisible(false); - } - }); - } - }); - - addMouseListener(new MouseAdapter() { - @Override - public - void mouseExited(MouseEvent event) { - // wait before checking if mouse is still on the menu - SwingSystemTrayMenuPopup.this.timer.delay(SwingSystemTrayMenuPopup.this.timer.getDelay()); - } - }); - } - - @Override - public - void setVisible(boolean makeVisible) { - // only allow java to close this popup if our timer closed it - this.timer.cancel(); - - if (makeVisible) { - mouseClickLocation = MouseInfo.getPointerInfo().getLocation(); - - // if the mouse isn't inside the popup in x seconds, close the popup - this.timer.delay(POPUP_HIDE_DELAY); - } - - super.setVisible(makeVisible); - } - - - public - void track(final JPopupMenu menu, final boolean visible) { - if (visible) { - synchronized (trackedMenus) { - trackedMenus.add(menu); - } - } else { - synchronized (trackedMenus) { - trackedMenus.remove(menu); - } - } - - // restart the timer - SwingSystemTrayMenuPopup.this.timer.delay(POPUP_HIDE_DELAY); - } - - public - void close() { - this.timer.cancel(); - } -} diff --git a/src/dorkbox/systemTray/util/ImageUtils.java b/src/dorkbox/systemTray/util/ImageUtils.java index ef550b9..3f48e3b 100644 --- a/src/dorkbox/systemTray/util/ImageUtils.java +++ b/src/dorkbox/systemTray/util/ImageUtils.java @@ -233,13 +233,8 @@ class ImageUtils { // make sure the directory exists newFile.getParentFile().mkdirs(); - BufferedImage image = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB); - Graphics2D g2d = image.createGraphics(); - g2d.setColor(new Color(0,0,0,0)); - g2d.fillRect(0, 0, size, size); - g2d.dispose(); - try { + BufferedImage image = getTransparentImageAsImage(size); ImageIO.write(image, "png", newFile); } catch (IOException e) { e.printStackTrace(); @@ -248,6 +243,17 @@ class ImageUtils { return newFile; } + public static + BufferedImage getTransparentImageAsImage(final int size) { + BufferedImage image = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB); + Graphics2D g2d = image.createGraphics(); + g2d.setColor(new Color(0,0,0,0)); + g2d.fillRect(0, 0, size, size); + g2d.dispose(); + + return image; + } + private static File getErrorImage(final String cacheName) { try {