From 444562f3c625219d1fd5233c1deb93bc910a90f5 Mon Sep 17 00:00:00 2001 From: nathan Date: Sun, 14 Feb 2016 21:54:21 +0100 Subject: [PATCH] Added SWT compatible tray type (it is just a wrapper for SWT SystemTray) --- src/dorkbox/systemTray/SystemTray.java | 6 +- src/dorkbox/systemTray/swt/SwtMenuEntry.java | 167 +++++++++++++ src/dorkbox/systemTray/swt/SwtSystemTray.java | 234 ++++++++++++++++++ test/dorkbox/TestTraySwt.java | 28 +-- 4 files changed, 420 insertions(+), 15 deletions(-) create mode 100644 src/dorkbox/systemTray/swt/SwtMenuEntry.java create mode 100644 src/dorkbox/systemTray/swt/SwtSystemTray.java diff --git a/src/dorkbox/systemTray/SystemTray.java b/src/dorkbox/systemTray/SystemTray.java index 8426dff..826e3ec 100644 --- a/src/dorkbox/systemTray/SystemTray.java +++ b/src/dorkbox/systemTray/SystemTray.java @@ -21,6 +21,7 @@ import dorkbox.systemTray.linux.GtkSystemTray; import dorkbox.systemTray.linux.jna.AppIndicator; import dorkbox.systemTray.linux.jna.GtkSupport; import dorkbox.systemTray.swing.SwingSystemTray; +import dorkbox.systemTray.swt.SwtSystemTray; import dorkbox.util.OS; import dorkbox.util.Property; import dorkbox.util.process.ShellProcessBuilder; @@ -76,7 +77,10 @@ class SystemTray { // maybe we should load the SWT version? (SWT's use of GTK is incompatible with how we use GTK) if (isSwtLoaded) { - + try { + trayType = SwtSystemTray.class; + } catch (Throwable ignored) { + } } else { // Note: AppIndicators DO NOT support tooltips. We could try to create one, by creating a GTK widget and attaching it on diff --git a/src/dorkbox/systemTray/swt/SwtMenuEntry.java b/src/dorkbox/systemTray/swt/SwtMenuEntry.java new file mode 100644 index 0000000..abe727e --- /dev/null +++ b/src/dorkbox/systemTray/swt/SwtMenuEntry.java @@ -0,0 +1,167 @@ +/* + * 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.swt; + +import dorkbox.systemTray.ImageUtil; +import dorkbox.systemTray.MenuEntry; +import dorkbox.systemTray.SystemTray; +import dorkbox.systemTray.SystemTrayMenuAction; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.Menu; +import org.eclipse.swt.widgets.MenuItem; + +import java.io.InputStream; +import java.net.URL; + +class SwtMenuEntry implements MenuEntry { + private final Menu parent; + private final SystemTray systemTray; + private final MenuItem menuItem; + private final Listener selectionListener; + + private volatile String text; + String imagePath; + private Image image; + volatile SystemTrayMenuAction callback; + + public + SwtMenuEntry(final Menu parentMenu, + final String label, + final String imagePath, + final Image image, + final SystemTrayMenuAction callback, + final SystemTray systemTray) { + + this.parent = parentMenu; + this.text = label; + this.imagePath = imagePath; + this.image = image; + this.callback = callback; + this.systemTray = systemTray; + + menuItem = new MenuItem(parentMenu, SWT.PUSH); + menuItem.setText(label); + + selectionListener = new Listener() { + public + void handleEvent(Event event) { + handle(); + } + }; + menuItem.addListener(SWT.Selection, selectionListener); + + if (image != null && !image.isDisposed()) { + menuItem.setImage(image); + } + } + + private + void handle() { + SystemTrayMenuAction cb = this.callback; + if (cb != null) { + cb.onClick(systemTray, this); + } + } + + @Override + public + String getText() { + return text; + } + + @Override + public + void setText(final String newText) { + this.text = newText; + menuItem.setText(newText); + } + + private + void setImage_(final String imagePath) { + if (imagePath == null) { + menuItem.setImage(null); + image = null; + } + else { + image = new Image(parent.getShell().getDisplay(), imagePath); + menuItem.setImage(image); + } + } + + @Override + public + void setImage(final String imagePath) { + if (imagePath == null) { + setImage_(null); + } + else { + setImage_(ImageUtil.iconPath(imagePath)); + } + } + + @Override + public + void setImage(final URL imageUrl) { + if (imageUrl == null) { + setImage_(null); + } + else { + setImage_(ImageUtil.iconPath(imageUrl)); + } + } + + @Override + public + void setImage(final String cacheName, final InputStream imageStream) { + if (imageStream == null) { + setImage_(null); + } + else { + setImage_(ImageUtil.iconPath(cacheName, imageStream)); + } + } + + @Override + @Deprecated + public + void setImage(final InputStream imageStream) { + if (imageStream == null) { + setImage_(null); + } + else { + setImage_(ImageUtil.iconPathNoCache(imageStream)); + } + } + + @Override + public + void setCallback(final SystemTrayMenuAction callback) { + this.callback = callback; + } + + @Override + public + void remove() { + if (image != null) { + image.dispose(); + } + menuItem.dispose(); + } +} diff --git a/src/dorkbox/systemTray/swt/SwtSystemTray.java b/src/dorkbox/systemTray/swt/SwtSystemTray.java new file mode 100644 index 0000000..c2a8500 --- /dev/null +++ b/src/dorkbox/systemTray/swt/SwtSystemTray.java @@ -0,0 +1,234 @@ +/* + * 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.swt; + +import dorkbox.systemTray.ImageUtil; +import dorkbox.systemTray.MenuEntry; +import dorkbox.systemTray.SystemTrayMenuAction; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.Menu; +import org.eclipse.swt.widgets.MenuItem; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.TrayItem; + +import java.io.InputStream; +import java.net.URL; + +/** + * Class for handling all system tray interaction, via SWING + */ +public +class SwtSystemTray extends dorkbox.systemTray.SystemTray { +// volatile SwingSystemTrayMenuPopup menu; + volatile MenuItem connectionStatusItem; +// +// volatile SystemTray tray; +// volatile TrayIcon trayIcon; + + private final Display display; + private final Shell shell; + private final TrayItem tray; + + volatile boolean isActive = false; + private final Menu menu; + + /** + * Creates a new system tray handler class. + */ + public + SwtSystemTray() { + super(); + + display = Display.getCurrent(); + shell = display.getShells()[0]; + + tray = new TrayItem(display.getSystemTray(), SWT.NONE); + menu = new Menu(shell, SWT.POP_UP); + + tray.addListener(SWT.MenuDetect, new Listener() { + public void handleEvent(Event event) { + menu.setVisible(true); + } + }); + tray.addListener(SWT.Selection, new Listener() { + public void handleEvent(Event event) { + menu.setVisible(true); + } + }); + } + + + @Override + public + void shutdown() { + synchronized (this) { + for (MenuEntry menuEntry : menuEntries) { + menuEntry.remove(); + } + menuEntries.clear(); + + connectionStatusItem = null; + + tray.dispose(); + } + } + + @Override + public + void setStatus(final String infoString) { + synchronized (this) { + if (connectionStatusItem == null && infoString != null && !infoString.isEmpty()) { + deleteMenu(); + + connectionStatusItem = new MenuItem(menu, SWT.PUSH); + connectionStatusItem.setText(infoString); + connectionStatusItem.setEnabled(false); + + createMenu(); + } + else { + if (infoString == null || infoString.isEmpty()) { + // deletes the status entry only + connectionStatusItem.dispose(); + connectionStatusItem = null; + } + else { + connectionStatusItem.setText(infoString); + } + } + } + } + + /** + * Deletes the contents of the menu, and unreferences everything in it. + */ + private void deleteMenu() { + // have to remove status from menu + if (connectionStatusItem != null) { + connectionStatusItem.dispose(); + } + + synchronized (menuEntries) { + // have to remove all other menu entries + for (int i = 0; i < menuEntries.size(); i++) { + SwtMenuEntry menuEntry__ = (SwtMenuEntry) menuEntries.get(i); + menuEntry__.remove(); + } + } + } + + /** + * recreates the menu items + */ + private void createMenu() { + synchronized (menuEntries) { + // now add back other menu entries + final SwtMenuEntry[] swtMenuEntries = menuEntries.toArray(new SwtMenuEntry[0]); + menuEntries.clear(); + + for (int i = 0; i < swtMenuEntries.length; i++) { + SwtMenuEntry menuEntry__ = swtMenuEntries[i]; + addMenuEntry_(menuEntry__.getText(), menuEntry__.imagePath, menuEntry__.callback); + } + } + } + + @Override + protected + void setIcon_(final String iconPath) { + tray.setImage(new Image(display, iconPath)); + } + + /** + * Will add a new menu entry, or update one if it already exists + */ + private + void addMenuEntry_(final String menuText, final String imagePath, final SystemTrayMenuAction callback) { + if (menuText == null) { + throw new NullPointerException("Menu text cannot be null"); + } + + synchronized (this) { + synchronized (menuEntries) { + MenuEntry menuEntry = getMenuEntry(menuText); + + if (menuEntry != null) { + throw new IllegalArgumentException("Menu entry already exists for given label '" + menuText + "'"); + } + else { + final Image image; + if (imagePath != null) { + image = new Image(display, imagePath); + } else { + image = null; + } + + menuEntry = new SwtMenuEntry(menu, menuText, imagePath, image, callback, this); + menuEntries.add(menuEntry); + } + } + } + } + + @Override + public + void addMenuEntry(String menuText, final String imagePath, final SystemTrayMenuAction callback) { + if (imagePath == null) { + addMenuEntry_(menuText, null, callback); + } + else { + addMenuEntry_(menuText, ImageUtil.iconPath(imagePath), callback); + } + } + + @Override + public + void addMenuEntry(final String menuText, final URL imageUrl, final SystemTrayMenuAction callback) { + if (imageUrl == null) { + addMenuEntry_(menuText, null, callback); + } + else { + addMenuEntry_(menuText, ImageUtil.iconPath(imageUrl), callback); + } + } + + @Override + public + void addMenuEntry(final String menuText, final String cacheName, final InputStream imageStream, final SystemTrayMenuAction callback) { + if (imageStream == null) { + addMenuEntry_(menuText, null, callback); + } + else { + addMenuEntry_(menuText, ImageUtil.iconPath(cacheName, imageStream), callback); + } + } + + @Override + @Deprecated + public + void addMenuEntry(final String menuText, final InputStream imageStream, final SystemTrayMenuAction callback) { + if (imageStream == null) { + addMenuEntry_(menuText, null, callback); + } + else { + addMenuEntry_(menuText, ImageUtil.iconPathNoCache(imageStream), callback); + } + } +} diff --git a/test/dorkbox/TestTraySwt.java b/test/dorkbox/TestTraySwt.java index 4798472..b3d4c30 100644 --- a/test/dorkbox/TestTraySwt.java +++ b/test/dorkbox/TestTraySwt.java @@ -24,7 +24,6 @@ import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Text; -import java.io.File; import java.net.URL; /** @@ -42,12 +41,6 @@ class TestTraySwt { public static void main(String[] args) { - // ONLY if manually loading JNA jars. - // - // Not necessary if using the official JNA downloaded from https://github.com/twall/jna AND THAT JAR is on the classpath - // - System.load(new File("../../resources/Dependencies/jna/linux_64/libjna.so").getAbsolutePath()); //64bit linux library - new TestTraySwt(); } @@ -58,14 +51,12 @@ class TestTraySwt { public TestTraySwt() { Display display = new Display (); - Shell shell = new Shell(display); + final Shell shell = new Shell(display); Text helloWorldTest = new Text(shell, SWT.NONE); - helloWorldTest.setText("Hello World SWT"); + helloWorldTest.setText("Hello World SWT ................. "); helloWorldTest.pack(); - shell.pack(); - shell.open (); this.systemTray = SystemTray.getSystemTray(); if (systemTray == null) { @@ -111,13 +102,22 @@ class TestTraySwt { public void onClick(final SystemTray systemTray, final MenuEntry menuEntry) { systemTray.shutdown(); + shell.close(); // close down SWT shell //System.exit(0); not necessary if all non-daemon threads have stopped. } }); - while (!shell.isDisposed ()) { - if (!display.readAndDispatch ()) display.sleep (); + + + shell.pack(); + shell.open(); + + while (!shell.isDisposed()) { + if (!display.readAndDispatch()) { + display.sleep(); + } } - display.dispose (); + + display.dispose(); } }