From 0e5d52352b68937ce07fd72c16d0fd2cd4359076 Mon Sep 17 00:00:00 2001 From: nathan Date: Fri, 17 Feb 2017 15:50:42 +0100 Subject: [PATCH] Updated Swing menu item behavior to permit customization. --- README.md | 74 +++++++----- SystemTray.iml | 3 +- src/dorkbox/systemTray/SystemTray.java | 9 +- .../systemTray/swingUI/AdjustedJMenu.java | 47 -------- .../systemTray/swingUI/AdjustedJMenuItem.java | 40 ------- .../systemTray/swingUI/DefaultMenuItemUI.java | 109 ++++++++++++++++++ .../swingUI/DefaultPopupMenuUI.java | 109 ++++++++++++++++++ .../swingUI/DefaultSeparatorUI.java | 109 ++++++++++++++++++ src/dorkbox/systemTray/swingUI/SwingMenu.java | 46 ++++++-- .../systemTray/swingUI/SwingMenuItem.java | 14 ++- .../swingUI/SwingMenuItemCheckbox.java | 14 ++- .../swingUI/SwingMenuItemSeparator.java | 6 + .../swingUI/SwingMenuItemStatus.java | 15 ++- .../systemTray/swingUI/SwingUIFactory.java | 62 ++++++++++ .../systemTray/swingUI/_SwingTray.java | 2 +- test/dorkbox/CustomSwingUI.java | 104 +++++++++++++++++ test/dorkbox/TestTray.java | 2 + test/dorkbox/TestTrayJavaFX.java | 2 + test/dorkbox/TestTraySwt.java | 1 + 19 files changed, 628 insertions(+), 140 deletions(-) delete mode 100644 src/dorkbox/systemTray/swingUI/AdjustedJMenu.java delete mode 100644 src/dorkbox/systemTray/swingUI/AdjustedJMenuItem.java create mode 100644 src/dorkbox/systemTray/swingUI/DefaultMenuItemUI.java create mode 100644 src/dorkbox/systemTray/swingUI/DefaultPopupMenuUI.java create mode 100644 src/dorkbox/systemTray/swingUI/DefaultSeparatorUI.java create mode 100644 src/dorkbox/systemTray/swingUI/SwingUIFactory.java create mode 100644 test/dorkbox/CustomSwingUI.java diff --git a/README.md b/README.md index 7aa9ca2..22943d6 100644 --- a/README.md +++ b/README.md @@ -22,11 +22,12 @@ This library provides **OS Native** menus and **Swing/AWT** menus, depending on The following unique problems are also solved by this library: 1. *Sun/Oracle* system-tray icons on gnu/linux **do not** support images with transparent backgrounds -2. *Sun/Oracle* system-tray and *SWT* system-tray implementations **do not** support app-indicators, which are necessary on different distributions of gnu/linux and unix. +2. *Sun/Oracle* system-tray and *SWT* system-tray implementations **do not** support app-indicators, which are necessary on different distributions of gnu/linux and unix 3. *Sun/Oracle* system-tray menus on Windows **look absolutely horrid** 4. *Sun/Oracle* system-tray icons on Windows are **hard-coded** to a max size of 24x24 (it was last updated in *2006*) 5. *Sun/Oracle* system-tray menus on MacOS **do not** always respond to both mouse buttons, where Apple menus do 6. MacOS and Windows *native* menus **do not** support images attached to menu entries +7. Windows menus **do not** support a different L&F from applications @@ -50,7 +51,7 @@ Problems and Restrictions - **ToolTips** The maximum length is 64 characters long, and it is not supported on all Operating Systems and Desktop Environments. Please note that **Ubuntu** does not always support this! - - **Linux/Unix Menus** Some linux environments only support right-click to display the menu, and it is not possible to change the behavior. + - **Linux/Unix Menus** Some linux environments only support right-click to display the menu, and it is not possible to change the behavior. Compatibility Matrix ------------------ @@ -134,27 +135,31 @@ SystemTray.ENABLE_SHUTDOWN_HOOK (type boolean, default value 'true') SystemTray.AUTO_FIX_INCONSISTENCIES (type boolean, default value 'true') - - Allows the SystemTray logic to resolve various OS inconsistencies for the SystemTray in different combinations + - Allows the SystemTray logic to resolve various OS inconsistencies for the SystemTray in different combinations +SystemTray.SWING_UI (type SwingUIFactory, default value 'null') + - Allows the developer to provide a custom look and feel for the Swing UI, if defined. See the test example for specific use. + + SystemTray.DEBUG (type boolean, default value 'false') - - This property is provided for debugging any errors in the logic used to determine the system-tray type and initialization feedback. + - This property is provided for debugging any errors in the logic used to determine the system-tray type and initialization feedback. Extension.ENABLE_EXTENSION_INSTALL (type boolean, default value 'true') - - Permit the StatusTray icon to be displayed next to the clock by installing an extension. By default, gnome - places the icon in the "notification drawer", which is a collapsible menu (usually) at the bottom left corner - of the screen. This should be set to false if you want to preserve the default Desktop Environment UI preferences. - Additionally, Arch Linux is the only exception to this rule where it does not install the extension, so TopIcons is - necessary for placing the icon near the clock. + - Permit the StatusTray icon to be displayed next to the clock by installing an extension. By default, gnome + places the icon in the "notification drawer", which is a collapsible menu (usually) at the bottom left corner + of the screen. This should be set to false if you want to preserve the default Desktop Environment UI preferences. + Additionally, Arch Linux is the only exception to this rule where it does not install the extension, so TopIcons is + necessary for placing the icon near the clock. Extension.ENABLE_SHELL_RESTART (type boolean, default value 'true') - - Permit the gnome-shell to be restarted when the extension is installed. + - Permit the gnome-shell to be restarted when the extension is installed. Extension.SHELL_RESTART_COMMAND (type String, default value 'nome-shell --replace &') - - Command to restart the gnome-shell. It is recommended to start it in the background (hence '&') + - Command to restart the gnome-shell. It is recommended to start it in the background (hence '&') ``` @@ -163,26 +168,30 @@ Extension.SHELL_RESTART_COMMAND (type String, default value 'nome-shell --rep The test application is [on GitHub](https://github.com/dorkbox/SystemTray/blob/master/test/dorkbox/TestTray.java), and a *simple* example is as follows: ``` - this.systemTray = SystemTray.get(); - if (systemTray == null) { - throw new RuntimeException("Unable to load SystemTray!"); - } + SystemTray.SWING_UI = new CustomSwingUI(); - try { - this.systemTray.setImage("grey_icon.png"); - } catch (IOException e) { - e.printStackTrace(); - } - - this.systemTray.setStatus("Not Running"); - - this.systemTray.addEntry("Quit", new SystemTrayMenuAction() { + SystemTray systemTray = SystemTray.get(); + if (systemTray == null) { + throw new RuntimeException("Unable to load SystemTray!"); + } + + try { + systemTray.setImage("grey_icon.png"); + } catch (IOException e) { + e.printStackTrace(); + } + + systemTray.setStatus("Not Running"); + + + systemTray.getMenu().add(new MenuItem("Quit", new ActionListener() { @Override public - void onClick(final SystemTray systemTray, final Menu parent, final Entry entry) { - System.exit(0); + void actionPerformed(final ActionEvent e) { + systemTray.shutdown(); + //System.exit(0); not necessary if all non-daemon threads have stopped. } - }); + })).setShortcut('q'); // case does not matter ```     @@ -229,18 +238,21 @@ ISSUES:     -

We now release to maven!

+

Release Notes

This project **includes** some utility classes, which are an extremely small subset of a much larger library; including only what is *necessary* for this particular project to function. Additionally this project is **kept in sync** with the utilities library, so "jar hell" is not an issue, and the latest release will always include the same utility files as all other projects in the dorkbox repository at that time. - Please note that the utility classes have their source code included in the release, and eventually the entire utility library will be provided as a dorkbox repository. -``` + Please note that the utility classes have their source code included in the release, and also on [GitHub](https://github.com/dorkbox/Utilities) repository. + + +
Maven Info
+```` com.dorkbox SystemTray 2.20 -``` +```` Or if you don't want to use Maven, you can access the latest files and source-code directly from here: diff --git a/SystemTray.iml b/SystemTray.iml index 6be8b8f..9c38ed0 100755 --- a/SystemTray.iml +++ b/SystemTray.iml @@ -28,10 +28,9 @@ + - - diff --git a/src/dorkbox/systemTray/SystemTray.java b/src/dorkbox/systemTray/SystemTray.java index 83a9c8b..b7edfa3 100644 --- a/src/dorkbox/systemTray/SystemTray.java +++ b/src/dorkbox/systemTray/SystemTray.java @@ -45,6 +45,7 @@ import dorkbox.systemTray.nativeUI.NativeUI; import dorkbox.systemTray.nativeUI._AppIndicatorNativeTray; import dorkbox.systemTray.nativeUI._AwtTray; import dorkbox.systemTray.nativeUI._GtkStatusIconNativeTray; +import dorkbox.systemTray.swingUI.SwingUIFactory; import dorkbox.systemTray.swingUI._SwingTray; import dorkbox.systemTray.util.ImageUtils; import dorkbox.systemTray.util.JavaFX; @@ -127,7 +128,7 @@ class SystemTray { *

* This is an advanced feature, and it is recommended to leave at AutoDetect. */ - public static TrayType FORCE_TRAY_TYPE = TrayType.AutoDetect; + public static TrayType FORCE_TRAY_TYPE = TrayType.Swing; @Property /** @@ -146,6 +147,12 @@ class SystemTray { */ public static boolean AUTO_FIX_INCONSISTENCIES = true; + @Property + /** + * Allows the developer to provide a custom look and feel for the Swing UI, if defined. See the test example for specific use. + */ + public static SwingUIFactory SWING_UI = null; + @Property /** * This property is provided for debugging any errors in the logic used to determine the system-tray type. diff --git a/src/dorkbox/systemTray/swingUI/AdjustedJMenu.java b/src/dorkbox/systemTray/swingUI/AdjustedJMenu.java deleted file mode 100644 index dfb1049..0000000 --- a/src/dorkbox/systemTray/swingUI/AdjustedJMenu.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2016 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.swingUI; - -import java.awt.Insets; - -import javax.swing.JMenu; -import javax.swing.JPopupMenu; -import javax.swing.border.EmptyBorder; - -import dorkbox.systemTray.util.ImageUtils; - -class AdjustedJMenu extends JMenu { - AdjustedJMenu() { - super(); - setFont(ImageUtils.ENTRY_FONT); - } - - @Override - public - Insets getMargin() { - Insets margin = super.getMargin(); - if (margin != null) { - margin.set(2, -2, 2, 4); - - // makes sure the popup menu is created. - JPopupMenu popupMenu = getPopupMenu(); - // now adjust the border of it - popupMenu.setBorder(new EmptyBorder(1, 1, 1, 1)); - } - - return margin; - } -} diff --git a/src/dorkbox/systemTray/swingUI/AdjustedJMenuItem.java b/src/dorkbox/systemTray/swingUI/AdjustedJMenuItem.java deleted file mode 100644 index cdd6ffb..0000000 --- a/src/dorkbox/systemTray/swingUI/AdjustedJMenuItem.java +++ /dev/null @@ -1,40 +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.swingUI; - -import java.awt.Insets; - -import javax.swing.JMenuItem; - -import dorkbox.systemTray.util.ImageUtils; - -class AdjustedJMenuItem extends JMenuItem { - - AdjustedJMenuItem() { - super(); - setFont(ImageUtils.ENTRY_FONT); - } - - @Override - public - Insets getMargin() { - Insets margin = super.getMargin(); - if (margin != null) { - margin.set(2, -2, 2, 4); - } - return margin; - } -} diff --git a/src/dorkbox/systemTray/swingUI/DefaultMenuItemUI.java b/src/dorkbox/systemTray/swingUI/DefaultMenuItemUI.java new file mode 100644 index 0000000..98d1f4a --- /dev/null +++ b/src/dorkbox/systemTray/swingUI/DefaultMenuItemUI.java @@ -0,0 +1,109 @@ +/* + * Copyright 2017 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.swingUI; + +import java.awt.Component; +import java.awt.Dimension; +import java.awt.Graphics; + +import javax.accessibility.Accessible; +import javax.swing.JComponent; +import javax.swing.UIManager; +import javax.swing.plaf.ComponentUI; +import javax.swing.plaf.MenuItemUI; + +public +class DefaultMenuItemUI extends MenuItemUI { + private final ComponentUI ui; + + public + DefaultMenuItemUI(final JComponent jComponent) { + this.ui = UIManager.getDefaults() + .getUI(jComponent); + } + + @Override + public + void installUI(final JComponent c) { + ui.installUI(c); + } + + @Override + public + void uninstallUI(final JComponent c) { + ui.uninstallUI(c); + } + + @Override + public + void paint(final Graphics g, final JComponent c) { + ui.paint(g, c); + } + + @Override + public + void update(final Graphics g, final JComponent c) { + ui.update(g, c); + } + + @Override + public + Dimension getPreferredSize(final JComponent c) { + return ui.getPreferredSize(c); + } + + @Override + public + Dimension getMinimumSize(final JComponent c) { + return ui.getMinimumSize(c); + } + + @Override + public + Dimension getMaximumSize(final JComponent c) { + return ui.getMaximumSize(c); + } + + @Override + public + boolean contains(final JComponent c, final int x, final int y) { + return ui.contains(c, x, y); + } + + @Override + public + int getBaseline(final JComponent c, final int width, final int height) { + return ui.getBaseline(c, width, height); + } + + @Override + public + Component.BaselineResizeBehavior getBaselineResizeBehavior(final JComponent c) { + return ui.getBaselineResizeBehavior(c); + } + + @Override + public + int getAccessibleChildrenCount(final JComponent c) { + return ui.getAccessibleChildrenCount(c); + } + + @Override + public + Accessible getAccessibleChild(final JComponent c, final int i) { + return ui.getAccessibleChild(c, i); + } +} diff --git a/src/dorkbox/systemTray/swingUI/DefaultPopupMenuUI.java b/src/dorkbox/systemTray/swingUI/DefaultPopupMenuUI.java new file mode 100644 index 0000000..a8d7058 --- /dev/null +++ b/src/dorkbox/systemTray/swingUI/DefaultPopupMenuUI.java @@ -0,0 +1,109 @@ +/* + * Copyright 2017 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.swingUI; + +import java.awt.Component; +import java.awt.Dimension; +import java.awt.Graphics; + +import javax.accessibility.Accessible; +import javax.swing.JComponent; +import javax.swing.UIManager; +import javax.swing.plaf.ComponentUI; +import javax.swing.plaf.PopupMenuUI; + +public +class DefaultPopupMenuUI extends PopupMenuUI { + private final ComponentUI ui; + + public + DefaultPopupMenuUI(final JComponent jComponent) { + this.ui = UIManager.getDefaults() + .getUI(jComponent); + } + + @Override + public + void installUI(final JComponent c) { + ui.installUI(c); + } + + @Override + public + void uninstallUI(final JComponent c) { + ui.uninstallUI(c); + } + + @Override + public + void paint(final Graphics g, final JComponent c) { + ui.paint(g, c); + } + + @Override + public + void update(final Graphics g, final JComponent c) { + ui.update(g, c); + } + + @Override + public + Dimension getPreferredSize(final JComponent c) { + return ui.getPreferredSize(c); + } + + @Override + public + Dimension getMinimumSize(final JComponent c) { + return ui.getMinimumSize(c); + } + + @Override + public + Dimension getMaximumSize(final JComponent c) { + return ui.getMaximumSize(c); + } + + @Override + public + boolean contains(final JComponent c, final int x, final int y) { + return ui.contains(c, x, y); + } + + @Override + public + int getBaseline(final JComponent c, final int width, final int height) { + return ui.getBaseline(c, width, height); + } + + @Override + public + Component.BaselineResizeBehavior getBaselineResizeBehavior(final JComponent c) { + return ui.getBaselineResizeBehavior(c); + } + + @Override + public + int getAccessibleChildrenCount(final JComponent c) { + return ui.getAccessibleChildrenCount(c); + } + + @Override + public + Accessible getAccessibleChild(final JComponent c, final int i) { + return ui.getAccessibleChild(c, i); + } +} diff --git a/src/dorkbox/systemTray/swingUI/DefaultSeparatorUI.java b/src/dorkbox/systemTray/swingUI/DefaultSeparatorUI.java new file mode 100644 index 0000000..bac77a9 --- /dev/null +++ b/src/dorkbox/systemTray/swingUI/DefaultSeparatorUI.java @@ -0,0 +1,109 @@ +/* + * Copyright 2017 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.swingUI; + +import java.awt.Component; +import java.awt.Dimension; +import java.awt.Graphics; + +import javax.accessibility.Accessible; +import javax.swing.JComponent; +import javax.swing.UIManager; +import javax.swing.plaf.ComponentUI; +import javax.swing.plaf.SeparatorUI; + +public +class DefaultSeparatorUI extends SeparatorUI { + private final ComponentUI ui; + + public + DefaultSeparatorUI(final JComponent jComponent) { + this.ui = UIManager.getDefaults() + .getUI(jComponent); + } + + @Override + public + void installUI(final JComponent c) { + ui.installUI(c); + } + + @Override + public + void uninstallUI(final JComponent c) { + ui.uninstallUI(c); + } + + @Override + public + void paint(final Graphics g, final JComponent c) { + ui.paint(g, c); + } + + @Override + public + void update(final Graphics g, final JComponent c) { + ui.update(g, c); + } + + @Override + public + Dimension getPreferredSize(final JComponent c) { + return ui.getPreferredSize(c); + } + + @Override + public + Dimension getMinimumSize(final JComponent c) { + return ui.getMinimumSize(c); + } + + @Override + public + Dimension getMaximumSize(final JComponent c) { + return ui.getMaximumSize(c); + } + + @Override + public + boolean contains(final JComponent c, final int x, final int y) { + return ui.contains(c, x, y); + } + + @Override + public + int getBaseline(final JComponent c, final int width, final int height) { + return ui.getBaseline(c, width, height); + } + + @Override + public + Component.BaselineResizeBehavior getBaselineResizeBehavior(final JComponent c) { + return ui.getBaselineResizeBehavior(c); + } + + @Override + public + int getAccessibleChildrenCount(final JComponent c) { + return ui.getAccessibleChildrenCount(c); + } + + @Override + public + Accessible getAccessibleChild(final JComponent c, final int i) { + return ui.getAccessibleChild(c, i); + } +} diff --git a/src/dorkbox/systemTray/swingUI/SwingMenu.java b/src/dorkbox/systemTray/swingUI/SwingMenu.java index fd3e379..38ba38e 100644 --- a/src/dorkbox/systemTray/swingUI/SwingMenu.java +++ b/src/dorkbox/systemTray/swingUI/SwingMenu.java @@ -19,6 +19,8 @@ import java.io.File; import javax.swing.ImageIcon; import javax.swing.JComponent; +import javax.swing.JMenu; +import javax.swing.JPopupMenu; import dorkbox.systemTray.Checkbox; import dorkbox.systemTray.Entry; @@ -26,7 +28,9 @@ import dorkbox.systemTray.Menu; import dorkbox.systemTray.MenuItem; import dorkbox.systemTray.Separator; import dorkbox.systemTray.Status; +import dorkbox.systemTray.SystemTray; import dorkbox.systemTray.peer.MenuPeer; +import dorkbox.systemTray.util.ImageUtils; import dorkbox.util.SwingUtil; // this is a weird composite class, because it must be a Menu, but ALSO a Entry -- so it has both (and duplicate code) @@ -38,15 +42,33 @@ class SwingMenu implements MenuPeer { // This is NOT a copy constructor! @SuppressWarnings("IncompleteCopyConstructor") - SwingMenu(final SwingMenu parent) { + SwingMenu(final SwingMenu parent, final Menu entry) { this.parent = parent; if (parent == null) { - this._native = new TrayPopup(); + TrayPopup trayPopup = new TrayPopup(); + // this is before setUI, so that users can customize the font if they want + if (ImageUtils.ENTRY_FONT != null) { + trayPopup.setFont(ImageUtils.ENTRY_FONT); + } + if (SystemTray.SWING_UI != null) { + trayPopup.setUI(SystemTray.SWING_UI.getMenuUI(trayPopup, null)); + } + this._native = trayPopup; } else { - this._native = new AdjustedJMenu(); - parent._native.add(this._native); + JMenu jMenu = new JMenu(); + JPopupMenu popupMenu = jMenu.getPopupMenu(); // ensure the popup menu is created + + // this is before setUI, so that users can customize the font if they want + jMenu.setFont(ImageUtils.ENTRY_FONT); + if (SystemTray.SWING_UI != null) { + jMenu.setUI(SystemTray.SWING_UI.getItemUI(jMenu, entry)); + popupMenu.setUI(SystemTray.SWING_UI.getMenuUI(popupMenu, entry)); + } + + this._native = jMenu; + parent._native.add(jMenu); } } @@ -59,7 +81,7 @@ class SwingMenu implements MenuPeer { public void run() { if (entry instanceof Menu) { - SwingMenu swingMenu = new SwingMenu(SwingMenu.this); + SwingMenu swingMenu = new SwingMenu(SwingMenu.this, (Menu) entry); ((Menu) entry).bind(swingMenu, parentMenu, parentMenu.getSystemTray()); } else if (entry instanceof Separator) { @@ -67,15 +89,15 @@ class SwingMenu implements MenuPeer { entry.bind(item, parentMenu, parentMenu.getSystemTray()); } else if (entry instanceof Checkbox) { - SwingMenuItemCheckbox item = new SwingMenuItemCheckbox(SwingMenu.this); + SwingMenuItemCheckbox item = new SwingMenuItemCheckbox(SwingMenu.this, entry); ((Checkbox) entry).bind(item, parentMenu, parentMenu.getSystemTray()); } else if (entry instanceof Status) { - SwingMenuItemStatus item = new SwingMenuItemStatus(SwingMenu.this); + SwingMenuItemStatus item = new SwingMenuItemStatus(SwingMenu.this, entry); ((Status) entry).bind(item, parentMenu, parentMenu.getSystemTray()); } else if (entry instanceof MenuItem) { - SwingMenuItem item = new SwingMenuItem(SwingMenu.this); + SwingMenuItem item = new SwingMenuItem(SwingMenu.this, entry); ((MenuItem) entry).bind(item, parentMenu, parentMenu.getSystemTray()); } } @@ -93,10 +115,10 @@ class SwingMenu implements MenuPeer { File imageFile = menuItem.getImage(); if (imageFile != null) { ImageIcon origIcon = new ImageIcon(imageFile.getAbsolutePath()); - ((AdjustedJMenu) _native).setIcon(origIcon); + ((JMenu) _native).setIcon(origIcon); } else { - ((AdjustedJMenu) _native).setIcon(null); + ((JMenu) _native).setIcon(null); } } }); @@ -124,7 +146,7 @@ class SwingMenu implements MenuPeer { @Override public void run() { - ((AdjustedJMenu) _native).setText(menuItem.getText()); + ((JMenu) _native).setText(menuItem.getText()); } }); } @@ -147,7 +169,7 @@ class SwingMenu implements MenuPeer { @Override public void run() { - ((AdjustedJMenu) _native).setMnemonic(vKey); + ((JMenu) _native).setMnemonic(vKey); } }); } diff --git a/src/dorkbox/systemTray/swingUI/SwingMenuItem.java b/src/dorkbox/systemTray/swingUI/SwingMenuItem.java index d592c9c..3ad1d98 100644 --- a/src/dorkbox/systemTray/swingUI/SwingMenuItem.java +++ b/src/dorkbox/systemTray/swingUI/SwingMenuItem.java @@ -22,6 +22,7 @@ import java.io.File; import javax.swing.ImageIcon; import javax.swing.JMenuItem; +import dorkbox.systemTray.Entry; import dorkbox.systemTray.MenuItem; import dorkbox.systemTray.SystemTray; import dorkbox.systemTray.peer.MenuItemPeer; @@ -34,14 +35,23 @@ class SwingMenuItem implements MenuItemPeer { private static ImageIcon transparentIcon; private final SwingMenu parent; - private final JMenuItem _native = new AdjustedJMenuItem(); + private final JMenuItem _native = new JMenuItem(); private volatile ActionListener swingCallback; // this is ALWAYS called on the EDT. - SwingMenuItem(final SwingMenu parent) { + SwingMenuItem(final SwingMenu parent, Entry entry) { this.parent = parent; + + // this is before setUI, so that users can customize the font if they want + if (ImageUtils.ENTRY_FONT != null) { + _native.setFont(ImageUtils.ENTRY_FONT); + } + if (SystemTray.SWING_UI != null) { + _native.setUI(SystemTray.SWING_UI.getItemUI(_native, entry)); + } + parent._native.add(_native); if (transparentIcon == null) { diff --git a/src/dorkbox/systemTray/swingUI/SwingMenuItemCheckbox.java b/src/dorkbox/systemTray/swingUI/SwingMenuItemCheckbox.java index 75965ae..e277d16 100644 --- a/src/dorkbox/systemTray/swingUI/SwingMenuItemCheckbox.java +++ b/src/dorkbox/systemTray/swingUI/SwingMenuItemCheckbox.java @@ -23,6 +23,7 @@ import javax.swing.ImageIcon; import javax.swing.JMenuItem; import dorkbox.systemTray.Checkbox; +import dorkbox.systemTray.Entry; import dorkbox.systemTray.SystemTray; import dorkbox.systemTray.peer.CheckboxPeer; import dorkbox.systemTray.util.ImageUtils; @@ -31,7 +32,7 @@ import dorkbox.util.SwingUtil; class SwingMenuItemCheckbox implements CheckboxPeer { private final SwingMenu parent; - private final JMenuItem _native = new AdjustedJMenuItem(); + private final JMenuItem _native = new JMenuItem(); // these have to be volatile, because they can be changed from any thread private volatile ActionListener callback; @@ -41,8 +42,17 @@ class SwingMenuItemCheckbox implements CheckboxPeer { private static ImageIcon uncheckedIcon; // this is ALWAYS called on the EDT. - SwingMenuItemCheckbox(final SwingMenu parent) { + SwingMenuItemCheckbox(final SwingMenu parent, final Entry entry) { this.parent = parent; + + // this is before setUI, so that users can customize the font if they want + if (ImageUtils.ENTRY_FONT != null) { + _native.setFont(ImageUtils.ENTRY_FONT); + } + if (SystemTray.SWING_UI != null) { + _native.setUI(SystemTray.SWING_UI.getItemUI(_native, entry)); + } + parent._native.add(_native); if (checkedIcon == null) { diff --git a/src/dorkbox/systemTray/swingUI/SwingMenuItemSeparator.java b/src/dorkbox/systemTray/swingUI/SwingMenuItemSeparator.java index baabda4..330273e 100644 --- a/src/dorkbox/systemTray/swingUI/SwingMenuItemSeparator.java +++ b/src/dorkbox/systemTray/swingUI/SwingMenuItemSeparator.java @@ -17,6 +17,7 @@ package dorkbox.systemTray.swingUI; import javax.swing.JSeparator; +import dorkbox.systemTray.SystemTray; import dorkbox.systemTray.peer.EntryPeer; import dorkbox.util.SwingUtil; @@ -28,6 +29,11 @@ class SwingMenuItemSeparator implements EntryPeer { // this is ALWAYS called on the EDT. SwingMenuItemSeparator(final SwingMenu parent) { this.parent = parent; + + if (SystemTray.SWING_UI != null) { + _native.setUI(SystemTray.SWING_UI.getSeparatorUI(_native)); + } + parent._native.add(_native); } diff --git a/src/dorkbox/systemTray/swingUI/SwingMenuItemStatus.java b/src/dorkbox/systemTray/swingUI/SwingMenuItemStatus.java index fd5fc08..61ba2a6 100644 --- a/src/dorkbox/systemTray/swingUI/SwingMenuItemStatus.java +++ b/src/dorkbox/systemTray/swingUI/SwingMenuItemStatus.java @@ -19,19 +19,30 @@ import java.awt.Font; import javax.swing.JMenuItem; +import dorkbox.systemTray.Entry; import dorkbox.systemTray.Status; +import dorkbox.systemTray.SystemTray; import dorkbox.systemTray.peer.StatusPeer; +import dorkbox.systemTray.util.ImageUtils; import dorkbox.util.SwingUtil; class SwingMenuItemStatus implements StatusPeer { private final SwingMenu parent; - private final JMenuItem _native = new AdjustedJMenuItem(); + private final JMenuItem _native = new JMenuItem(); // this is ALWAYS called on the EDT. - SwingMenuItemStatus(final SwingMenu parent) { + SwingMenuItemStatus(final SwingMenu parent, final Entry entry) { this.parent = parent; + // this is before setUI, so that users can customize the font if they want + if (ImageUtils.ENTRY_FONT != null) { + _native.setFont(ImageUtils.ENTRY_FONT); + } + if (SystemTray.SWING_UI != null) { + _native.setUI(SystemTray.SWING_UI.getItemUI(_native, entry)); + } + // status is ALWAYS at 0 index... parent._native.add(_native, 0); diff --git a/src/dorkbox/systemTray/swingUI/SwingUIFactory.java b/src/dorkbox/systemTray/swingUI/SwingUIFactory.java new file mode 100644 index 0000000..e7e066b --- /dev/null +++ b/src/dorkbox/systemTray/swingUI/SwingUIFactory.java @@ -0,0 +1,62 @@ +/* + * Copyright 2017 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.swingUI; + +import javax.swing.JMenuItem; +import javax.swing.JPopupMenu; +import javax.swing.JSeparator; +import javax.swing.plaf.MenuItemUI; +import javax.swing.plaf.PopupMenuUI; +import javax.swing.plaf.SeparatorUI; + +import dorkbox.systemTray.Entry; +import dorkbox.systemTray.Menu; + +/** + * Factory to allow for Look & Feel of the Swing UI components in the SystemTray. Provide an implementation to customize the Swing L&F. + */ +public +interface SwingUIFactory { + + /** + * Allows one to specify the Look & Feel of the menus (The main SystemTray and sub-menus) + * + * @param jPopupMenu the swing JPopupMenu that is displayed when one clicks on the System Tray icon + * @param entry the entry which is bound to the menu, or null if it is the main SystemTray menu. + * + * @return the UI used to customize the Look & Feel of the SystemTray menu + sub-menus + */ + PopupMenuUI getMenuUI(JPopupMenu jPopupMenu, Menu entry); + + /** + * Allows one to specify the Look & Feel of a menu entry + * + * @param jMenuItem the swing JMenuItem that is displayed in the menu + * @param entry the entry which is bound to the JMenuItem. + * + * @return the UI used to customize the Look & Feel of the menu entry + */ + MenuItemUI getItemUI(JMenuItem jMenuItem, Entry entry); + + /** + * Allows one to specify the Look & Feel of a menu separator entry + * + * @param jSeparator the swing JSeparator that is displayed in the menu + * + * @return the UI used to customize the Look & Feel of a menu separator entry + */ + SeparatorUI getSeparatorUI(JSeparator jSeparator); +} diff --git a/src/dorkbox/systemTray/swingUI/_SwingTray.java b/src/dorkbox/systemTray/swingUI/_SwingTray.java index 4cb7f3a..61cb590 100644 --- a/src/dorkbox/systemTray/swingUI/_SwingTray.java +++ b/src/dorkbox/systemTray/swingUI/_SwingTray.java @@ -62,7 +62,7 @@ class _SwingTray extends Tray implements SwingUI { _SwingTray.this.tray = SystemTray.getSystemTray(); // we override various methods, because each tray implementation is SLIGHTLY different. This allows us customization. - final SwingMenu swingMenu = new SwingMenu(null) { + final SwingMenu swingMenu = new SwingMenu(null, null) { @Override public void setEnabled(final MenuItem menuItem) { diff --git a/test/dorkbox/CustomSwingUI.java b/test/dorkbox/CustomSwingUI.java new file mode 100644 index 0000000..297287d --- /dev/null +++ b/test/dorkbox/CustomSwingUI.java @@ -0,0 +1,104 @@ +/* + * Copyright 2017 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; + +import java.awt.Insets; + +import javax.swing.JComponent; +import javax.swing.JMenuItem; +import javax.swing.JPopupMenu; +import javax.swing.JSeparator; +import javax.swing.border.EmptyBorder; +import javax.swing.plaf.MenuItemUI; +import javax.swing.plaf.PopupMenuUI; +import javax.swing.plaf.SeparatorUI; + +import dorkbox.systemTray.Entry; +import dorkbox.systemTray.Menu; +import dorkbox.systemTray.swingUI.DefaultMenuItemUI; +import dorkbox.systemTray.swingUI.DefaultPopupMenuUI; +import dorkbox.systemTray.swingUI.DefaultSeparatorUI; +import dorkbox.systemTray.swingUI.SwingUIFactory; + +/** + * Factory to allow for Look & Feel of the Swing UI components in the SystemTray. + * + * This implementation is provided as an example of what looks reasonable on our systems for Nimbus. Naturally, everyone will have + * different systems and thus will want to change this based on their own, specified Swing L&F. + */ +public +class CustomSwingUI implements SwingUIFactory { + + /** + * Allows one to specify the Look & Feel of the menus (The main SystemTray and sub-menus) + * + * @param jPopupMenu the swing JPopupMenu that is displayed when one clicks on the System Tray icon + * @param entry the entry which is bound to the menu, or null if it is the main SystemTray menu. + * + * @return the UI used to customize the Look & Feel of the SystemTray menu + sub-menus + */ + @Override + public + PopupMenuUI getMenuUI(final JPopupMenu jPopupMenu, final Menu entry) { + return new DefaultPopupMenuUI(jPopupMenu) { + @Override + public + void installUI(final JComponent c) { + super.installUI(c); + + // borderUI resource border type will get changed internally! + // setBorder(new BorderUIResource.EmptyBorderUIResource(0, 0, 0, 0)); + c.setBorder(new EmptyBorder(1, 1, 1, 1)); + } + }; + } + + /** + * Allows one to specify the Look & Feel of a menu entry + * + * @param jMenuItem the swing JMenuItem that is displayed in the menu + * @param entry the entry which is bound to the JMenuItem. + * + * @return the UI used to customize the Look & Feel of the menu entry + */ + @Override + public + MenuItemUI getItemUI(final JMenuItem jMenuItem, final Entry entry) { + return new DefaultMenuItemUI(jMenuItem) { + @Override + public + void installUI(final JComponent c) { + super.installUI(c); + + ((JMenuItem) c).setMargin(new Insets(2, -2, 2, 4)); + c.setBorder(new EmptyBorder(1, 1, 1, 1)); + } + }; + } + + /** + * Allows one to specify the Look & Feel of a menu separator entry + * + * @param jSeparator the swing JSeparator that is displayed in the menu + * + * @return the UI used to customize the Look & Feel of a menu separator entry + */ + @Override + public + SeparatorUI getSeparatorUI(final JSeparator jSeparator) { + return new DefaultSeparatorUI(jSeparator); + } +} diff --git a/test/dorkbox/TestTray.java b/test/dorkbox/TestTray.java index e74abe5..5ad81e0 100644 --- a/test/dorkbox/TestTray.java +++ b/test/dorkbox/TestTray.java @@ -56,6 +56,8 @@ class TestTray { public TestTray() { + SystemTray.SWING_UI = new CustomSwingUI(); + this.systemTray = SystemTray.get(); if (systemTray == null) { throw new RuntimeException("Unable to load SystemTray!"); diff --git a/test/dorkbox/TestTrayJavaFX.java b/test/dorkbox/TestTrayJavaFX.java index 00dc3a1..c58ec00 100644 --- a/test/dorkbox/TestTrayJavaFX.java +++ b/test/dorkbox/TestTrayJavaFX.java @@ -114,6 +114,8 @@ class TestTrayJavaFX { primaryStage.show(); + SystemTray.SWING_UI = new CustomSwingUI(); + this.systemTray = SystemTray.get(); if (systemTray == null) { throw new RuntimeException("Unable to load SystemTray!"); diff --git a/test/dorkbox/TestTraySwt.java b/test/dorkbox/TestTraySwt.java index 097af6e..d1ce107 100644 --- a/test/dorkbox/TestTraySwt.java +++ b/test/dorkbox/TestTraySwt.java @@ -72,6 +72,7 @@ class TestTraySwt { helloWorldTest.setText("Hello World SWT ................. "); helloWorldTest.pack(); + SystemTray.SWING_UI = new CustomSwingUI(); this.systemTray = SystemTray.get(); if (systemTray == null) {