From 462e15a291d0231fef82276132ad0c23d7397d80 Mon Sep 17 00:00:00 2001 From: nathan Date: Mon, 24 Nov 2014 17:40:06 +0100 Subject: [PATCH] Modified google code format --- src/dorkbox/util/tray/FailureCallback.java | 18 +- src/dorkbox/util/tray/SystemTray.java | 381 ++++++------ .../util/tray/SystemTrayMenuAction.java | 19 +- .../util/tray/SystemTrayMenuPopup.java | 87 +-- .../util/tray/linux/AppIndicatorTray.java | 346 +++++------ .../util/tray/linux/GtkSystemTray.java | 531 ++++++++-------- src/dorkbox/util/tray/linux/MenuEntry.java | 61 +- .../util/tray/swing/SwingSystemTray.java | 570 +++++++++--------- 8 files changed, 1059 insertions(+), 954 deletions(-) diff --git a/src/dorkbox/util/tray/FailureCallback.java b/src/dorkbox/util/tray/FailureCallback.java index 3405816..b677abd 100644 --- a/src/dorkbox/util/tray/FailureCallback.java +++ b/src/dorkbox/util/tray/FailureCallback.java @@ -1,6 +1,20 @@ +/* + * 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.util.tray; public interface FailureCallback { - - public void createTrayFailed(); + public void createTrayFailed(); } diff --git a/src/dorkbox/util/tray/SystemTray.java b/src/dorkbox/util/tray/SystemTray.java index db96529..581ec97 100644 --- a/src/dorkbox/util/tray/SystemTray.java +++ b/src/dorkbox/util/tray/SystemTray.java @@ -1,8 +1,20 @@ +/* + * 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.util.tray; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.io.File; import java.io.FileOutputStream; import java.io.IOException; @@ -18,6 +30,9 @@ import java.security.SecureRandom; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import dorkbox.util.NamedThreadFactory; import dorkbox.util.OS; import dorkbox.util.jna.linux.GtkSupport; @@ -31,197 +46,195 @@ import dorkbox.util.tray.swing.SwingSystemTray; */ public abstract class SystemTray { - private static final Charset UTF_8 = Charset.forName("UTF-8"); - private static MessageDigest digest; + private static final Charset UTF_8 = Charset.forName("UTF-8"); + private static MessageDigest digest; - protected static final Logger logger = LoggerFactory.getLogger(SystemTray.class); + protected static final Logger logger = LoggerFactory.getLogger(SystemTray.class); - /** - * Size of the icon * - */ - public static int ICON_SIZE = 22; + /** + * Size of the icon + */ + public static int ICON_SIZE = 22; - /** - * Location of the icon * - */ - public static String ICON_PATH = ""; + /** + * Location of the icon + */ + public static String ICON_PATH = ""; - private static final long runtimeRandom = new SecureRandom().nextLong(); - private static Class trayType; + private static final long runtimeRandom = new SecureRandom().nextLong(); + private static Class trayType; - static { - if (OS.isLinux()) { - GtkSupport.init(); - String getenv = System.getenv("XDG_CURRENT_DESKTOP"); - if (getenv != null && (getenv.equals("Unity") || getenv.equals("KDE"))) { - if (GtkSupport.isSupported) { - trayType = AppIndicatorTray.class; - } - } else { - if (GtkSupport.isSupported) { - trayType = GtkSystemTray.class; - } - } - } - - // this is windows OR mac - if (trayType == null && java.awt.SystemTray.isSupported()) { - trayType = SwingSystemTray.class; - } - - if (trayType == null) { - // unsupported tray - logger.error("Unsupported tray type!"); - } else { - try { - digest = MessageDigest.getInstance("MD5"); - } catch (NoSuchAlgorithmException e) { - logger.error("Unsupported hashing algorithm!"); - trayType = null; - } - } - } - - protected final ExecutorService - callbackExecutor = - Executors.newSingleThreadExecutor(new NamedThreadFactory("SysTrayExecutor", false)); - - protected volatile FailureCallback failureCallback; - protected volatile boolean active = false; - protected String appName; - - public static SystemTray create(String appName) { - if (trayType != null) { - try { - SystemTray newInstance = trayType.newInstance(); - if (newInstance != null) { - newInstance.setAppName(appName); - } - return newInstance; - } catch (Exception e) { - e.printStackTrace(); - } - } - - // unsupported - return null; - } - - private void setAppName(String appName) { - this.appName = appName; - } - - public abstract void createTray(String iconName); - - public void removeTray() { - SystemTray.this.callbackExecutor.shutdown(); - } - - public abstract void setStatus(String infoString, String iconName); - - public abstract void addMenuEntry(String menuText, SystemTrayMenuAction callback); - - public abstract void updateMenuEntry(String origMenuText, String newMenuText, SystemTrayMenuAction newCallback); - - - protected String iconPath(String fileName) { - // is file sitting on drive - File iconTest; - if (ICON_PATH.isEmpty()) { - iconTest = new File(fileName); - } else { - iconTest = new File(ICON_PATH, fileName); - } - if (iconTest.isFile() && iconTest.canRead()) { - return iconTest.getAbsolutePath(); - } else { - if (!ICON_PATH.isEmpty()) { - fileName = ICON_PATH + "/" + fileName; - } - - String extension = ""; - int dot = fileName.lastIndexOf('.'); - if (dot > -1) { - extension = fileName.substring(dot + 1); - } - - // maybe it's in somewhere else. - URL systemResource = ClassLoader.getSystemResource(fileName); - if (systemResource != null) { - // copy out to a temp file, as a hash of the file - String file = systemResource.getFile(); - byte[] bytes = file.getBytes(UTF_8); - File newFile; - String tempDir = System.getProperty("java.io.tmpdir"); - - // can be wimpy, only one at a time - synchronized (SystemTray.this) { - digest.reset(); - digest.update(bytes); - - // For KDE4, it must also be unique across runs - byte[] longBytes = new byte[8]; - ByteBuffer wrap = ByteBuffer.wrap(longBytes); - wrap.putLong(runtimeRandom); - digest.update(longBytes); - - byte[] hashBytes = digest.digest(); - String hash = new BigInteger(1, hashBytes).toString(32); - - newFile = new File(tempDir, hash + '.' + extension).getAbsoluteFile(); - newFile.deleteOnExit(); - } - - InputStream inStream = null; - OutputStream outStream = null; - - try { - inStream = systemResource.openStream(); - outStream = new FileOutputStream(newFile); - - byte[] buffer = new byte[2048]; - int read; - while ((read = inStream.read(buffer)) > 0) { - outStream.write(buffer, 0, read); - } - - return newFile.getAbsolutePath(); - } catch (IOException e) { - // Running from main line. - String message = "Unable to copy icon '" + fileName + "' to location: '" + newFile.getAbsolutePath() + "'"; - logger.error(message, e); - throw new RuntimeException(message); - } finally { - try { - if (inStream != null) { - inStream.close(); + static { + if (OS.isLinux()) { + GtkSupport.init(); + String getenv = System.getenv("XDG_CURRENT_DESKTOP"); + if (getenv != null && (getenv.equals("Unity") || getenv.equals("KDE"))) { + if (GtkSupport.isSupported) { + trayType = AppIndicatorTray.class; + } + } else { + if (GtkSupport.isSupported) { + trayType = GtkSystemTray.class; + } } - } catch (Exception ignored) { - } - try { - if (outStream != null) { - outStream.close(); - } - } catch (Exception ignored) { - } } - // appIndicator/gtk require strings - // swing version loads as an image - } + // this is windows OR mac + if (trayType == null && java.awt.SystemTray.isSupported()) { + trayType = SwingSystemTray.class; + } + + if (trayType == null) { + // unsupported tray + logger.error("Unsupported tray type!"); + } else { + try { + digest = MessageDigest.getInstance("MD5"); + } catch (NoSuchAlgorithmException e) { + logger.error("Unsupported hashing algorithm!"); + trayType = null; + } + } } - // Running from main line. - String message = "Unable to find icon '" + fileName + "'"; - logger.error(message); - throw new RuntimeException(message); - } + protected final ExecutorService callbackExecutor = Executors.newSingleThreadExecutor(new NamedThreadFactory("SysTrayExecutor", false)); - public final void setFailureCallback(FailureCallback failureCallback) { - this.failureCallback = failureCallback; - } + protected volatile FailureCallback failureCallback; + protected volatile boolean active = false; + protected String appName; - public final boolean isActive() { - return this.active; - } + public static SystemTray create(String appName) { + if (trayType != null) { + try { + SystemTray newInstance = trayType.newInstance(); + if (newInstance != null) { + newInstance.setAppName(appName); + } + return newInstance; + } catch (Exception e) { + e.printStackTrace(); + } + } + + // unsupported + return null; + } + + private void setAppName(String appName) { + this.appName = appName; + } + + public abstract void createTray(String iconName); + + public void removeTray() { + SystemTray.this.callbackExecutor.shutdown(); + } + + public abstract void setStatus(String infoString, String iconName); + + public abstract void addMenuEntry(String menuText, SystemTrayMenuAction callback); + + public abstract void updateMenuEntry(String origMenuText, String newMenuText, SystemTrayMenuAction newCallback); + + + protected String iconPath(String fileName) { + // is file sitting on drive + File iconTest; + if (ICON_PATH.isEmpty()) { + iconTest = new File(fileName); + } else { + iconTest = new File(ICON_PATH, fileName); + } + if (iconTest.isFile() && iconTest.canRead()) { + return iconTest.getAbsolutePath(); + } else { + if (!ICON_PATH.isEmpty()) { + fileName = ICON_PATH + "/" + fileName; + } + + String extension = ""; + int dot = fileName.lastIndexOf('.'); + if (dot > -1) { + extension = fileName.substring(dot + 1); + } + + // maybe it's in somewhere else. + URL systemResource = ClassLoader.getSystemResource(fileName); + if (systemResource != null) { + // copy out to a temp file, as a hash of the file + String file = systemResource.getFile(); + byte[] bytes = file.getBytes(UTF_8); + File newFile; + String tempDir = System.getProperty("java.io.tmpdir"); + + // can be wimpy, only one at a time + synchronized (SystemTray.this) { + digest.reset(); + digest.update(bytes); + + // For KDE4, it must also be unique across runs + byte[] longBytes = new byte[8]; + ByteBuffer wrap = ByteBuffer.wrap(longBytes); + wrap.putLong(runtimeRandom); + digest.update(longBytes); + + byte[] hashBytes = digest.digest(); + String hash = new BigInteger(1, hashBytes).toString(32); + + newFile = new File(tempDir, hash + '.' + extension).getAbsoluteFile(); + newFile.deleteOnExit(); + } + + InputStream inStream = null; + OutputStream outStream = null; + + try { + inStream = systemResource.openStream(); + outStream = new FileOutputStream(newFile); + + byte[] buffer = new byte[2048]; + int read; + while ((read = inStream.read(buffer)) > 0) { + outStream.write(buffer, 0, read); + } + + return newFile.getAbsolutePath(); + } catch (IOException e) { + // Running from main line. + String message = "Unable to copy icon '" + fileName + "' to location: '" + newFile.getAbsolutePath() + "'"; + logger.error(message, e); + throw new RuntimeException(message); + } finally { + try { + if (inStream != null) { + inStream.close(); + } + } catch (Exception ignored) { + } + try { + if (outStream != null) { + outStream.close(); + } + } catch (Exception ignored) { + } + } + + // appIndicator/gtk require strings + // swing version loads as an image + } + } + + // Running from main line. + String message = "Unable to find icon '" + fileName + "'"; + logger.error(message); + throw new RuntimeException(message); + } + + public final void setFailureCallback(FailureCallback failureCallback) { + this.failureCallback = failureCallback; + } + + public final boolean isActive() { + return this.active; + } } diff --git a/src/dorkbox/util/tray/SystemTrayMenuAction.java b/src/dorkbox/util/tray/SystemTrayMenuAction.java index d33d0ad..bd81311 100644 --- a/src/dorkbox/util/tray/SystemTrayMenuAction.java +++ b/src/dorkbox/util/tray/SystemTrayMenuAction.java @@ -1,7 +1,20 @@ +/* + * 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.util.tray; - public interface SystemTrayMenuAction { - - void onClick(SystemTray systemTray); + void onClick(SystemTray systemTray); } diff --git a/src/dorkbox/util/tray/SystemTrayMenuPopup.java b/src/dorkbox/util/tray/SystemTrayMenuPopup.java index df3cb1f..42e81d1 100644 --- a/src/dorkbox/util/tray/SystemTrayMenuPopup.java +++ b/src/dorkbox/util/tray/SystemTrayMenuPopup.java @@ -1,63 +1,66 @@ package dorkbox.util.tray; -import java.awt.*; +import java.awt.Dimension; +import java.awt.MouseInfo; +import java.awt.Point; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; -import javax.swing.*; +import javax.swing.JPopupMenu; +import javax.swing.SwingUtilities; import dorkbox.util.DelayTimer; public class SystemTrayMenuPopup extends JPopupMenu { + private static final long serialVersionUID = 1L; - private static final long serialVersionUID = 1L; + private DelayTimer timer; - private DelayTimer timer; + public SystemTrayMenuPopup() { + super(); - public SystemTrayMenuPopup() { - super(); + this.timer = new DelayTimer("PopupMenuHider", true, new DelayTimer.Callback() { + @Override + public void execute() { + SwingUtilities.invokeLater(new Runnable() { - this.timer = new DelayTimer("PopupMenuHider", true, new DelayTimer.Callback() { - @Override - public void execute() { - SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + Point location = MouseInfo.getPointerInfo().getLocation(); + Point locationOnScreen = getLocationOnScreen(); + Dimension size = getSize(); - @Override - public void run() { - Point location = MouseInfo.getPointerInfo().getLocation(); - Point locationOnScreen = getLocationOnScreen(); - Dimension size = getSize(); + if (location.x >= locationOnScreen.x && location.x < locationOnScreen.x + size.width + && location.y >= locationOnScreen.y && location.y < locationOnScreen.y + size.height) { - if (location.x >= locationOnScreen.x && location.x < locationOnScreen.x + size.width && - location.y >= locationOnScreen.y && location.y < locationOnScreen.y + size.height) { - SystemTrayMenuPopup.this.timer.delay(SystemTrayMenuPopup.this.timer.getDelay()); - } else { - setVisible(false); + SystemTrayMenuPopup.this.timer.delay(SystemTrayMenuPopup.this.timer.getDelay()); + } else { + setVisible(false); + } + } + }); } - } }); - } - }); - addMouseListener(new MouseAdapter() { - @Override - public void mouseExited(MouseEvent event) { - // wait before checking if mouse is still on the menu - SystemTrayMenuPopup.this.timer.delay(SystemTrayMenuPopup.this.timer.getDelay()); - } - }); - } - - - @Override - public void setVisible(boolean b) { - this.timer.cancel(); - - if (b) { - // if the mouse isn't inside the popup in 5 seconds, close the popup - this.timer.delay(5000L); + addMouseListener(new MouseAdapter() { + @Override + public void mouseExited(MouseEvent event) { + // wait before checking if mouse is still on the menu + SystemTrayMenuPopup.this.timer.delay(SystemTrayMenuPopup.this.timer.getDelay()); + } + }); } - super.setVisible(b); - } + + @Override + public void setVisible(boolean b) { + this.timer.cancel(); + + if (b) { + // if the mouse isn't inside the popup in 5 seconds, close the popup + this.timer.delay(5000L); + } + + super.setVisible(b); + } } diff --git a/src/dorkbox/util/tray/linux/AppIndicatorTray.java b/src/dorkbox/util/tray/linux/AppIndicatorTray.java index 116db5b..56f6d74 100644 --- a/src/dorkbox/util/tray/linux/AppIndicatorTray.java +++ b/src/dorkbox/util/tray/linux/AppIndicatorTray.java @@ -1,13 +1,28 @@ +/* + * 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.util.tray.linux; -import com.sun.jna.Pointer; - import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.CountDownLatch; +import com.sun.jna.Pointer; + import dorkbox.util.jna.linux.AppIndicator; import dorkbox.util.jna.linux.Gobject; import dorkbox.util.jna.linux.Gtk; @@ -15,7 +30,6 @@ import dorkbox.util.jna.linux.GtkSupport; import dorkbox.util.tray.SystemTray; import dorkbox.util.tray.SystemTrayMenuAction; - /** * Class for handling all system tray interactions. * @@ -26,204 +40,200 @@ import dorkbox.util.tray.SystemTrayMenuAction; * Lantern: https://github.com/getlantern/lantern/ Apache 2.0 License Copyright 2010 Brave New Software Project, Inc. */ public class AppIndicatorTray extends SystemTray { + private static final AppIndicator libappindicator = AppIndicator.INSTANCE; + private static final Gobject libgobject = Gobject.INSTANCE; + private static final Gtk libgtk = Gtk.INSTANCE; - private static final AppIndicator libappindicator = AppIndicator.INSTANCE; - private static final Gobject libgobject = Gobject.INSTANCE; - private static final Gtk libgtk = Gtk.INSTANCE; + private final CountDownLatch blockUntilStarted = new CountDownLatch(1); + private final Map menuEntries = new HashMap(2); - private final CountDownLatch blockUntilStarted = new CountDownLatch(1); - private final Map menuEntries = new HashMap(2); + private volatile AppIndicator.AppIndicatorInstanceStruct appIndicator; + private volatile Pointer menu; - private volatile AppIndicator.AppIndicatorInstanceStruct appIndicator; - private volatile Pointer menu; + private volatile Pointer connectionStatusItem; - private volatile Pointer connectionStatusItem; - - // need to hang on to these to prevent gc - private final List widgets = new ArrayList(4); + // need to hang on to these to prevent gc + private final List widgets = new ArrayList(4); - public AppIndicatorTray() { - } + public AppIndicatorTray() {} @Override - public void createTray(String iconName) { - this.appIndicator = - libappindicator - .app_indicator_new(this.appName, "indicator-messages-new", AppIndicator.CATEGORY_APPLICATION_STATUS); + public void createTray(String iconName) { + this.appIndicator = + libappindicator.app_indicator_new(this.appName, "indicator-messages-new", AppIndicator.CATEGORY_APPLICATION_STATUS); - /* basically a hack -- we should subclass the AppIndicator - type and override the fallback entry in the 'vtable', instead we just - hack the app indicator class itself. Not an issue unless we need other - appindicators. - */ - AppIndicator.AppIndicatorClassStruct - aiclass = - new AppIndicator.AppIndicatorClassStruct(this.appIndicator.parent.g_type_instance.g_class); + /* + * basically a hack -- we should subclass the AppIndicator type and override the fallback entry in the 'vtable', instead we just + * hack the app indicator class itself. Not an issue unless we need other appindicators. + */ + AppIndicator.AppIndicatorClassStruct aiclass = + new AppIndicator.AppIndicatorClassStruct(this.appIndicator.parent.g_type_instance.g_class); - aiclass.fallback = new AppIndicator.Fallback() { - @Override - public Pointer callback(final AppIndicator.AppIndicatorInstanceStruct self) { - AppIndicatorTray.this.callbackExecutor.execute(new Runnable() { - @Override - public void run() { - logger.warn("Failed to create appindicator system tray."); - if (AppIndicatorTray.this.failureCallback != null) { - AppIndicatorTray.this.failureCallback.createTrayFailed(); + aiclass.fallback = new AppIndicator.Fallback() { + @Override + public Pointer callback(final AppIndicator.AppIndicatorInstanceStruct self) { + AppIndicatorTray.this.callbackExecutor.execute(new Runnable() { + @Override + public void run() { + logger.warn("Failed to create appindicator system tray."); + + if (AppIndicatorTray.this.failureCallback != null) { + AppIndicatorTray.this.failureCallback.createTrayFailed(); + } + } + }); + return null; } - } - }); - return null; - } - }; - aiclass.write(); + }; + aiclass.write(); - this.menu = libgtk.gtk_menu_new(); - libappindicator.app_indicator_set_menu(this.appIndicator, this.menu); + this.menu = libgtk.gtk_menu_new(); + libappindicator.app_indicator_set_menu(this.appIndicator, this.menu); - libappindicator.app_indicator_set_icon_full(this.appIndicator, iconPath(iconName), this.appName); - libappindicator.app_indicator_set_status(this.appIndicator, AppIndicator.STATUS_ACTIVE); + libappindicator.app_indicator_set_icon_full(this.appIndicator, iconPath(iconName), this.appName); + libappindicator.app_indicator_set_status(this.appIndicator, AppIndicator.STATUS_ACTIVE); - if (!GtkSupport.usesSwtMainLoop) { - Thread gtkUpdateThread = new Thread() { - @Override - public void run() { - // notify our main thread to continue - AppIndicatorTray.this.blockUntilStarted.countDown(); + if (!GtkSupport.usesSwtMainLoop) { + Thread gtkUpdateThread = new Thread() { + @Override + public void run() { + // notify our main thread to continue + AppIndicatorTray.this.blockUntilStarted.countDown(); - try { - libgtk.gtk_main(); - } catch (Throwable t) { - logger.warn("Unable to run main loop", t); - } + try { + libgtk.gtk_main(); + } catch (Throwable t) { + logger.warn("Unable to run main loop", t); + } + } + }; + gtkUpdateThread.setName("GTK event loop"); + gtkUpdateThread.setDaemon(true); + gtkUpdateThread.start(); + } + + // we CANNOT continue until the GTK thread has started! (ignored if SWT is used) + try { + this.blockUntilStarted.await(); + this.active = true; + } catch (InterruptedException ignored) { } - }; - gtkUpdateThread.setName("GTK event loop"); - gtkUpdateThread.setDaemon(true); - gtkUpdateThread.start(); } - // we CANNOT continue until the GTK thread has started! (ignored if SWT is used) - try { - this.blockUntilStarted.await(); - this.active = true; - } catch (InterruptedException ignored) { - } - } + @Override + public void removeTray() { + for (Pointer widget : this.widgets) { + libgtk.gtk_widget_destroy(widget); + } - @Override - public void removeTray() { - for (Pointer widget : this.widgets) { - libgtk.gtk_widget_destroy(widget); + // this hides the indicator + libappindicator.app_indicator_set_status(this.appIndicator, AppIndicator.STATUS_PASSIVE); + this.appIndicator.write(); + Pointer p = this.appIndicator.getPointer(); + libgobject.g_object_unref(p); + + this.active = false; + + // GC it + this.appIndicator = null; + this.widgets.clear(); + + // unrefs the children too + libgobject.g_object_unref(this.menu); + this.menu = null; + + synchronized (this.menuEntries) { + this.menuEntries.clear(); + } + + this.connectionStatusItem = null; + + super.removeTray(); } - // this hides the indicator - libappindicator.app_indicator_set_status(this.appIndicator, AppIndicator.STATUS_PASSIVE); - this.appIndicator.write(); - Pointer p = this.appIndicator.getPointer(); - libgobject.g_object_unref(p); + @Override + public void setStatus(String infoString, String iconName) { + if (this.connectionStatusItem == null) { + this.connectionStatusItem = libgtk.gtk_menu_item_new_with_label(infoString); + this.widgets.add(this.connectionStatusItem); + libgtk.gtk_widget_set_sensitive(this.connectionStatusItem, Gtk.FALSE); + libgtk.gtk_menu_shell_append(this.menu, this.connectionStatusItem); + } else { + libgtk.gtk_menu_item_set_label(this.connectionStatusItem, infoString); + } - this.active = false; + libgtk.gtk_widget_show_all(this.connectionStatusItem); - // GC it - this.appIndicator = null; - this.widgets.clear(); - - // unrefs the children too - libgobject.g_object_unref(this.menu); - this.menu = null; - - synchronized (this.menuEntries) { - this.menuEntries.clear(); + libappindicator.app_indicator_set_icon_full(this.appIndicator, iconPath(iconName), this.appName); } - this.connectionStatusItem = null; + /** + * Will add a new menu entry, or update one if it already exists + */ + @Override + public void addMenuEntry(String menuText, final SystemTrayMenuAction callback) { + synchronized (this.menuEntries) { + MenuEntry menuEntry = this.menuEntries.get(menuText); - super.removeTray(); - } + if (menuEntry == null) { + Pointer dashboardItem = libgtk.gtk_menu_item_new_with_label(menuText); + Gobject.GCallback gtkCallback = new Gobject.GCallback() { + @Override + public void callback(Pointer instance, Pointer data) { + AppIndicatorTray.this.callbackExecutor.execute(new Runnable() { + @Override + public void run() { + callback.onClick(AppIndicatorTray.this); + } + }); + } + }; - @Override - public void setStatus(String infoString, String iconName) { - if (this.connectionStatusItem == null) { - this.connectionStatusItem = libgtk.gtk_menu_item_new_with_label(infoString); - this.widgets.add(this.connectionStatusItem); - libgtk.gtk_widget_set_sensitive(this.connectionStatusItem, Gtk.FALSE); - libgtk.gtk_menu_shell_append(this.menu, this.connectionStatusItem); - } else { - libgtk.gtk_menu_item_set_label(this.connectionStatusItem, infoString); + libgobject.g_signal_connect_data(dashboardItem, "activate", gtkCallback, null, null, 0); + libgtk.gtk_menu_shell_append(this.menu, dashboardItem); + libgtk.gtk_widget_show_all(dashboardItem); + + menuEntry = new MenuEntry(); + menuEntry.dashboardItem = dashboardItem; + + this.menuEntries.put(menuText, menuEntry); + } else { + updateMenuEntry(menuText, menuText, callback); + } + } } - libgtk.gtk_widget_show_all(this.connectionStatusItem); + /** + * Will update an already existing menu entry (or add a new one, if it doesn't exist) + */ + @Override + public void updateMenuEntry(String origMenuText, String newMenuText, final SystemTrayMenuAction newCallback) { + synchronized (this.menuEntries) { + MenuEntry menuEntry = this.menuEntries.get(origMenuText); - libappindicator.app_indicator_set_icon_full(this.appIndicator, iconPath(iconName), this.appName); - } + if (menuEntry != null) { + libgtk.gtk_menu_item_set_label(menuEntry.dashboardItem, newMenuText); - /** - * Will add a new menu entry, or update one if it already exists - */ - @Override - public void addMenuEntry(String menuText, final SystemTrayMenuAction callback) { - synchronized (this.menuEntries) { - MenuEntry menuEntry = this.menuEntries.get(menuText); + Gobject.GCallback gtkCallback = new Gobject.GCallback() { + @Override + public void callback(Pointer instance, Pointer data) { + AppIndicatorTray.this.callbackExecutor.execute(new Runnable() { + @Override + public void run() { + newCallback.onClick(AppIndicatorTray.this); + } + }); + } + }; - if (menuEntry == null) { - Pointer dashboardItem = libgtk.gtk_menu_item_new_with_label(menuText); - Gobject.GCallback gtkCallback = new Gobject.GCallback() { - @Override - public void callback(Pointer instance, Pointer data) { - AppIndicatorTray.this.callbackExecutor.execute(new Runnable() { - @Override - public void run() { - callback.onClick(AppIndicatorTray.this); - } - }); - } - }; + libgobject.g_signal_connect_data(menuEntry.dashboardItem, "activate", gtkCallback, null, null, 0); - libgobject.g_signal_connect_data(dashboardItem, "activate", gtkCallback, null, null, 0); - libgtk.gtk_menu_shell_append(this.menu, dashboardItem); - libgtk.gtk_widget_show_all(dashboardItem); - - menuEntry = new MenuEntry(); - menuEntry.dashboardItem = dashboardItem; - - this.menuEntries.put(menuText, menuEntry); - } else { - updateMenuEntry(menuText, menuText, callback); - } + libgtk.gtk_widget_show_all(menuEntry.dashboardItem); + } else { + addMenuEntry(origMenuText, newCallback); + } + } } - } - - /** - * Will update an already existing menu entry (or add a new one, if it doesn't exist) - */ - @Override - public void updateMenuEntry(String origMenuText, String newMenuText, final SystemTrayMenuAction newCallback) { - synchronized (this.menuEntries) { - MenuEntry menuEntry = this.menuEntries.get(origMenuText); - - if (menuEntry != null) { - libgtk.gtk_menu_item_set_label(menuEntry.dashboardItem, newMenuText); - - Gobject.GCallback gtkCallback = new Gobject.GCallback() { - @Override - public void callback(Pointer instance, Pointer data) { - AppIndicatorTray.this.callbackExecutor.execute(new Runnable() { - @Override - public void run() { - newCallback.onClick(AppIndicatorTray.this); - } - }); - } - }; - - libgobject.g_signal_connect_data(menuEntry.dashboardItem, "activate", gtkCallback, null, null, 0); - - libgtk.gtk_widget_show_all(menuEntry.dashboardItem); - } else { - addMenuEntry(origMenuText, newCallback); - } - } - } } diff --git a/src/dorkbox/util/tray/linux/GtkSystemTray.java b/src/dorkbox/util/tray/linux/GtkSystemTray.java index b65abca..d442bf9 100644 --- a/src/dorkbox/util/tray/linux/GtkSystemTray.java +++ b/src/dorkbox/util/tray/linux/GtkSystemTray.java @@ -1,8 +1,21 @@ +/* + * 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.util.tray.linux; -import com.sun.jna.Pointer; - -import java.awt.*; +import java.awt.Dimension; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.lang.reflect.InvocationTargetException; @@ -12,7 +25,10 @@ import java.util.List; import java.util.Map; import java.util.concurrent.CountDownLatch; -import javax.swing.*; +import javax.swing.JMenuItem; +import javax.swing.SwingUtilities; + +import com.sun.jna.Pointer; import dorkbox.util.jna.linux.Gobject; import dorkbox.util.jna.linux.Gtk; @@ -30,276 +46,275 @@ import dorkbox.util.tray.SystemTrayMenuPopup; */ public class GtkSystemTray extends SystemTray { - private static final Gobject libgobject = Gobject.INSTANCE; - private static final Gtk libgtk = Gtk.INSTANCE; + private static final Gobject libgobject = Gobject.INSTANCE; + private static final Gtk libgtk = Gtk.INSTANCE; - private final CountDownLatch blockUntilStarted = new CountDownLatch(1); - private final Map menuEntries = new HashMap(2); + private final CountDownLatch blockUntilStarted = new CountDownLatch(1); + private final Map menuEntries = new HashMap(2); - private volatile SystemTrayMenuPopup jmenu; - private volatile JMenuItem connectionStatusItem; + private volatile SystemTrayMenuPopup jmenu; + private volatile JMenuItem connectionStatusItem; - private volatile Pointer trayIcon; + private volatile Pointer trayIcon; - // need to hang on to these to prevent gc - private final List widgets = new ArrayList(4); + // need to hang on to these to prevent gc + private final List widgets = new ArrayList(4); - public GtkSystemTray() { - } + public GtkSystemTray() { + } - @Override - public void createTray(String iconName) { - this.trayIcon = libgtk.gtk_status_icon_new(); - libgtk.gtk_status_icon_set_from_file(this.trayIcon, iconPath(iconName)); - libgtk.gtk_status_icon_set_tooltip(this.trayIcon, this.appName); - libgtk.gtk_status_icon_set_visible(this.trayIcon, true); + @Override + public void createTray(String iconName) { + this.trayIcon = libgtk.gtk_status_icon_new(); + libgtk.gtk_status_icon_set_from_file(this.trayIcon, iconPath(iconName)); + libgtk.gtk_status_icon_set_tooltip(this.trayIcon, this.appName); + libgtk.gtk_status_icon_set_visible(this.trayIcon, true); - Gobject.GEventCallback gtkCallback = new Gobject.GEventCallback() { - @Override - public void callback(Pointer instance, final GdkEventButton event) { - // BUTTON_PRESS only - if (event.type == 4) { - SwingUtilities.invokeLater(new Runnable() { + Gobject.GEventCallback gtkCallback = new Gobject.GEventCallback() { + @Override + public void callback(Pointer instance, final GdkEventButton event) { + // BUTTON_PRESS only + if (event.type == 4) { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + if (GtkSystemTray.this.jmenu.isVisible()) { + GtkSystemTray.this.jmenu.setVisible(false); + } else { + int iconX = (int) (event.x_root - event.x); + int iconY = (int) (event.y_root - event.y); + // System.err.println("x: " + iconX + " y: " + iconY); + // System.err.println("x1: " + event.x_root + " y1: " + event.y_root); // relative to SCREEN + // System.err.println("x2: " + event.x + " y2: " + event.y); // relative to WINDOW + + Dimension size = GtkSystemTray.this.jmenu.getPreferredSize(); + + // do we open at top-right or top-left? + // we ASSUME monitor size is greater than 640x480 AND that our tray icon is IN THE CORNER SOMEWHERE + + // always put the menu in the middle + iconX -= size.width / 2; + + // y = 2 -> top + // y = 1068 -> bottom + if (iconY > 240) { + iconY -= size.height; + } else { + // have to account for the icon + iconY += ICON_SIZE; + } + + GtkSystemTray.this.jmenu.setInvoker(GtkSystemTray.this.jmenu); + GtkSystemTray.this.jmenu.setLocation(iconX, iconY); + GtkSystemTray.this.jmenu.setVisible(true); + } + } + }); + } + } + }; + // all the clicks. This is because native menu popups are a pain to figure out, so we cheat and use some java bits to do the popup + libgobject.g_signal_connect_data(this.trayIcon, "button_press_event", gtkCallback, null, null, 0); + + if (!GtkSupport.usesSwtMainLoop) { + Thread gtkUpdateThread = new Thread() { + @Override + public void run() { + // notify our main thread to continue + GtkSystemTray.this.blockUntilStarted.countDown(); + + try { + libgtk.gtk_main(); + } catch (Throwable t) { + logger.warn("Unable to run main loop", t); + } + } + }; + gtkUpdateThread.setName("GTK event loop"); + gtkUpdateThread.setDaemon(true); + gtkUpdateThread.start(); + } + + try { + SwingUtilities.invokeAndWait(new Runnable() { + @Override + public void run() { + GtkSystemTray.this.jmenu = new SystemTrayMenuPopup(); + } + }); + } catch (InvocationTargetException e) { + logger.error("Error creating tray menu", e); + } catch (InterruptedException e) { + logger.error("Error creating tray menu", e); + } + + // we CANNOT continue until the GTK thread has started! (ignored if SWT is used) + try { + this.blockUntilStarted.await(); + this.active = true; + } catch (InterruptedException ignored) { + } + } + + @Override + public void removeTray() { + for (Pointer widget : this.widgets) { + libgtk.gtk_widget_destroy(widget); + } + + // this hides the indicator + libgtk.gtk_status_icon_set_visible(this.trayIcon, false); + libgobject.g_object_unref(this.trayIcon); + + this.active = false; + + // GC it + this.trayIcon = null; + this.widgets.clear(); + + synchronized (this.menuEntries) { + this.menuEntries.clear(); + } + + this.jmenu.setVisible(false); + this.jmenu.setEnabled(false); + + this.jmenu = null; + this.connectionStatusItem = null; + + super.removeTray(); + } + + @Override + public void setStatus(final String infoString, String iconName) { + Runnable doRun = new Runnable() { @Override public void run() { - if (GtkSystemTray.this.jmenu.isVisible()) { - GtkSystemTray.this.jmenu.setVisible(false); - } else { - int iconX = (int) (event.x_root - event.x); - int iconY = (int) (event.y_root - event.y); -// System.err.println("x: " + iconX + " y: " + iconY); -// System.err.println("x1: " + event.x_root + " y1: " + event.y_root); // relative to SCREEN -// System.err.println("x2: " + event.x + " y2: " + event.y); // relative to WINDOW - - Dimension size = GtkSystemTray.this.jmenu.getPreferredSize(); - - // do we open at top-right or top-left? - // we ASSUME monitor size is greater than 640x480 AND that our tray icon is IN THE CORNER SOMEWHERE - - // always put the menu in the middle - iconX -= size.width / 2; - - // y = 2 -> top - // y = 1068 -> bottom - if (iconY > 240) { - iconY -= size.height; + if (GtkSystemTray.this.connectionStatusItem == null) { + GtkSystemTray.this.connectionStatusItem = new JMenuItem(infoString); + GtkSystemTray.this.connectionStatusItem.setEnabled(false); + GtkSystemTray.this.jmenu.add(GtkSystemTray.this.connectionStatusItem); } else { - // have to account for the icon - iconY += ICON_SIZE; + GtkSystemTray.this.connectionStatusItem.setText(infoString); } - - GtkSystemTray.this.jmenu.setInvoker(GtkSystemTray.this.jmenu); - GtkSystemTray.this.jmenu.setLocation(iconX, iconY); - GtkSystemTray.this.jmenu.setVisible(true); - } } - }); - } - } - }; - // all the clicks. This is because native menu popups are a pain to figure out, so we cheat and use some java bits to do the popup - libgobject.g_signal_connect_data(this.trayIcon, "button_press_event", gtkCallback, null, null, 0); + }; - if (!GtkSupport.usesSwtMainLoop) { - Thread gtkUpdateThread = new Thread() { - @Override - public void run() { - // notify our main thread to continue - GtkSystemTray.this.blockUntilStarted.countDown(); - - try { - libgtk.gtk_main(); - } catch (Throwable t) { - logger.warn("Unable to run main loop", t); - } - } - }; - gtkUpdateThread.setName("GTK event loop"); - gtkUpdateThread.setDaemon(true); - gtkUpdateThread.start(); - } - - try { - SwingUtilities.invokeAndWait(new Runnable() { - @Override - public void run() { - GtkSystemTray.this.jmenu = new SystemTrayMenuPopup(); - } - }); - } catch (InvocationTargetException e) { - logger.error("Error creating tray menu", e); - } catch (InterruptedException e) { - logger.error("Error creating tray menu", e); - } - - // we CANNOT continue until the GTK thread has started! (ignored if SWT is used) - try { - this.blockUntilStarted.await(); - this.active = true; - } catch (InterruptedException ignored) { - } - } - - @Override - public void removeTray() { - for (Pointer widget : this.widgets) { - libgtk.gtk_widget_destroy(widget); - } - - // this hides the indicator - libgtk.gtk_status_icon_set_visible(this.trayIcon, false); - libgobject.g_object_unref(this.trayIcon); - - this.active = false; - - // GC it - this.trayIcon = null; - this.widgets.clear(); - - synchronized (this.menuEntries) { - this.menuEntries.clear(); - } - - this.jmenu.setVisible(false); - this.jmenu.setEnabled(false); - - this.jmenu = null; - this.connectionStatusItem = null; - - super.removeTray(); - } - - @Override - public void setStatus(final String infoString, String iconName) { - Runnable doRun = new Runnable() { - @Override - public void run() { - if (GtkSystemTray.this.connectionStatusItem == null) { - GtkSystemTray.this.connectionStatusItem = new JMenuItem(infoString); - GtkSystemTray.this.connectionStatusItem.setEnabled(false); - GtkSystemTray.this.jmenu.add(GtkSystemTray.this.connectionStatusItem); + if (SwingUtilities.isEventDispatchThread()) { + doRun.run(); } else { - GtkSystemTray.this.connectionStatusItem.setText(infoString); - } - } - }; - - if (SwingUtilities.isEventDispatchThread()) { - doRun.run(); - } else { - try { - SwingUtilities.invokeAndWait(doRun); - } catch (InvocationTargetException e) { - logger.error("Error updating tray menu", e); - } catch (InterruptedException e) { - logger.error("Error updating tray menu", e); - } - } - - libgtk.gtk_status_icon_set_from_file(GtkSystemTray.this.trayIcon, iconPath(iconName)); - } - - /** - * Will add a new menu entry, or update one if it already exists - */ - @Override - public void addMenuEntry(final String menuText, final SystemTrayMenuAction callback) { - Runnable doRun = new Runnable() { - @Override - public void run() { - Map menuEntries2 = GtkSystemTray.this.menuEntries; - - synchronized (menuEntries2) { - JMenuItem menuEntry = menuEntries2.get(menuText); - - if (menuEntry == null) { - SystemTrayMenuPopup menu = GtkSystemTray.this.jmenu; - - menuEntry = new JMenuItem(menuText); - menuEntry.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - GtkSystemTray.this.callbackExecutor.execute(new Runnable() { - @Override - public void run() { - callback.onClick(GtkSystemTray.this); - } - }); - } - }); - menu.add(menuEntry); - - menuEntries2.put(menuText, menuEntry); - } else { - updateMenuEntry(menuText, menuText, callback); - } - } - } - }; - - if (SwingUtilities.isEventDispatchThread()) { - doRun.run(); - } else { - try { - SwingUtilities.invokeAndWait(doRun); - } catch (InvocationTargetException e) { - logger.error("Error updating tray menu", e); - } catch (InterruptedException e) { - logger.error("Error updating tray menu", e); - } - } - } - - /** - * Will update an already existing menu entry (or add a new one, if it doesn't exist) - */ - @Override - public void updateMenuEntry(final String origMenuText, final String newMenuText, - final SystemTrayMenuAction newCallback) { - Runnable doRun = new Runnable() { - @Override - public void run() { - Map menuEntries2 = GtkSystemTray.this.menuEntries; - - synchronized (menuEntries2) { - JMenuItem menuEntry = menuEntries2.get(origMenuText); - - if (menuEntry != null) { - ActionListener[] actionListeners = menuEntry.getActionListeners(); - for (ActionListener l : actionListeners) { - menuEntry.removeActionListener(l); + try { + SwingUtilities.invokeAndWait(doRun); + } catch (InvocationTargetException e) { + logger.error("Error updating tray menu", e); + } catch (InterruptedException e) { + logger.error("Error updating tray menu", e); } - - menuEntry.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - GtkSystemTray.this.callbackExecutor.execute(new Runnable() { - @Override - public void run() { - newCallback.onClick(GtkSystemTray.this); - } - }); - } - }); - menuEntry.setText(newMenuText); - menuEntry.revalidate(); - } else { - addMenuEntry(origMenuText, newCallback); - } } - } - }; - if (SwingUtilities.isEventDispatchThread()) { - doRun.run(); - } else { - try { - SwingUtilities.invokeAndWait(doRun); - } catch (InvocationTargetException e) { - logger.error("Error updating tray menu", e); - } catch (InterruptedException e) { - logger.error("Error updating tray menu", e); - } + libgtk.gtk_status_icon_set_from_file(GtkSystemTray.this.trayIcon, iconPath(iconName)); + } + + /** + * Will add a new menu entry, or update one if it already exists + */ + @Override + public void addMenuEntry(final String menuText, final SystemTrayMenuAction callback) { + Runnable doRun = new Runnable() { + @Override + public void run() { + Map menuEntries2 = GtkSystemTray.this.menuEntries; + + synchronized (menuEntries2) { + JMenuItem menuEntry = menuEntries2.get(menuText); + + if (menuEntry == null) { + SystemTrayMenuPopup menu = GtkSystemTray.this.jmenu; + + menuEntry = new JMenuItem(menuText); + menuEntry.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + GtkSystemTray.this.callbackExecutor.execute(new Runnable() { + @Override + public void run() { + callback.onClick(GtkSystemTray.this); + } + }); + } + }); + menu.add(menuEntry); + + menuEntries2.put(menuText, menuEntry); + } else { + updateMenuEntry(menuText, menuText, callback); + } + } + } + }; + + if (SwingUtilities.isEventDispatchThread()) { + doRun.run(); + } else { + try { + SwingUtilities.invokeAndWait(doRun); + } catch (InvocationTargetException e) { + logger.error("Error updating tray menu", e); + } catch (InterruptedException e) { + logger.error("Error updating tray menu", e); + } + } + } + + /** + * Will update an already existing menu entry (or add a new one, if it doesn't exist) + */ + @Override + public void updateMenuEntry(final String origMenuText, final String newMenuText, final SystemTrayMenuAction newCallback) { + Runnable doRun = new Runnable() { + @Override + public void run() { + Map menuEntries2 = GtkSystemTray.this.menuEntries; + + synchronized (menuEntries2) { + JMenuItem menuEntry = menuEntries2.get(origMenuText); + + if (menuEntry != null) { + ActionListener[] actionListeners = menuEntry.getActionListeners(); + for (ActionListener l : actionListeners) { + menuEntry.removeActionListener(l); + } + + menuEntry.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + GtkSystemTray.this.callbackExecutor.execute(new Runnable() { + @Override + public void run() { + newCallback.onClick(GtkSystemTray.this); + } + }); + } + }); + menuEntry.setText(newMenuText); + menuEntry.revalidate(); + } else { + addMenuEntry(origMenuText, newCallback); + } + } + } + }; + + if (SwingUtilities.isEventDispatchThread()) { + doRun.run(); + } else { + try { + SwingUtilities.invokeAndWait(doRun); + } catch (InvocationTargetException e) { + logger.error("Error updating tray menu", e); + } catch (InterruptedException e) { + logger.error("Error updating tray menu", e); + } + } } - } } diff --git a/src/dorkbox/util/tray/linux/MenuEntry.java b/src/dorkbox/util/tray/linux/MenuEntry.java index 8fd0167..e8b979c 100644 --- a/src/dorkbox/util/tray/linux/MenuEntry.java +++ b/src/dorkbox/util/tray/linux/MenuEntry.java @@ -1,3 +1,18 @@ +/* + * 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.util.tray.linux; import com.sun.jna.Pointer; @@ -7,31 +22,31 @@ import com.sun.jna.Pointer; */ class MenuEntry { - private final int hashCode; - public Pointer dashboardItem; + private final int hashCode; + public Pointer dashboardItem; - public MenuEntry() { - long time = System.nanoTime(); - this.hashCode = (int) (time ^ time >>> 32); - } + public MenuEntry() { + long time = System.nanoTime(); + this.hashCode = (int) (time ^ time >>> 32); + } - @Override - public int hashCode() { - return this.hashCode; - } + @Override + public int hashCode() { + return this.hashCode; + } - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + MenuEntry other = (MenuEntry) obj; + return this.hashCode == other.hashCode; } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - MenuEntry other = (MenuEntry) obj; - return this.hashCode == other.hashCode; - } } diff --git a/src/dorkbox/util/tray/swing/SwingSystemTray.java b/src/dorkbox/util/tray/swing/SwingSystemTray.java index d135011..0df939b 100644 --- a/src/dorkbox/util/tray/swing/SwingSystemTray.java +++ b/src/dorkbox/util/tray/swing/SwingSystemTray.java @@ -1,7 +1,30 @@ +/* + * 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.util.tray.swing; - -import java.awt.*; +import java.awt.AWTException; +import java.awt.Dimension; +import java.awt.GraphicsConfiguration; +import java.awt.GraphicsDevice; +import java.awt.GraphicsEnvironment; +import java.awt.Image; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.SystemTray; +import java.awt.TrayIcon; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; @@ -11,142 +34,142 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.Map; -import javax.swing.*; +import javax.swing.ImageIcon; +import javax.swing.JMenuItem; +import javax.swing.SwingUtilities; import dorkbox.util.tray.SystemTrayMenuAction; import dorkbox.util.tray.SystemTrayMenuPopup; - /** * Class for handling all system tray interaction, via SWING */ public class SwingSystemTray extends dorkbox.util.tray.SystemTray { - private final Map menuEntries = new HashMap(2); + private final Map menuEntries = new HashMap(2); - private volatile SystemTrayMenuPopup jmenu; - private volatile JMenuItem connectionStatusItem; + private volatile SystemTrayMenuPopup jmenu; + private volatile JMenuItem connectionStatusItem; - private volatile SystemTray tray; - private volatile TrayIcon trayIcon; + private volatile SystemTray tray; + private volatile TrayIcon trayIcon; - /** - * Creates a new system tray handler class. - */ - public SwingSystemTray() { - } + /** + * Creates a new system tray handler class. + */ + public SwingSystemTray() {} - @Override - public void removeTray() { - Runnable doRun = new Runnable() { - @Override - public void run() { - SwingSystemTray.this.tray.remove(SwingSystemTray.this.trayIcon); - SwingSystemTray.this.menuEntries.clear(); - } - }; - - if (SwingUtilities.isEventDispatchThread()) { - doRun.run(); - } else { - try { - SwingUtilities.invokeAndWait(doRun); - } catch (InvocationTargetException e) { - logger.error("Error updating tray menu", e); - } catch (InterruptedException e) { - logger.error("Error updating tray menu", e); - } - } - - super.removeTray(); - } - - @Override - public void createTray(final String iconName) { - Runnable doRun = new Runnable() { - @Override - public void run() { - SwingSystemTray.this.tray = SystemTray.getSystemTray(); - if (SwingSystemTray.this.tray == null) { - logger.warn("The system tray is not available"); - } else { - SwingSystemTray.this.jmenu = new SystemTrayMenuPopup(); - - Image trayImage = newImage(iconName); - SwingSystemTray.this.trayIcon = new TrayIcon(trayImage); - SwingSystemTray.this.trayIcon.setToolTip(SwingSystemTray.this.appName); - - SwingSystemTray.this.trayIcon.addMouseListener(new MouseAdapter() { + @Override + public void removeTray() { + Runnable doRun = new Runnable() { @Override - public void mousePressed(MouseEvent e) { - Dimension size = SwingSystemTray.this.jmenu.getPreferredSize(); - - Point point = e.getPoint(); - Rectangle bounds = getScreenBoundsAt(point); - - int x = point.x; - int y = point.y; - - if (y < bounds.y) { - y = bounds.y; - } else if (y > bounds.y + bounds.height) { - y = bounds.y + bounds.height + ICON_SIZE + 4; // 4 for padding - } - if (x < bounds.x) { - x = bounds.x; - } else if (x > bounds.x + bounds.width) { - x = bounds.x + bounds.width; - } - - if (x + size.width > bounds.x + bounds.width) { - // always put the menu in the middle - x = bounds.x + bounds.width - size.width; - } - if (y + size.height > bounds.y + bounds.height) { - y = bounds.y + bounds.height - size.height - ICON_SIZE - 4; // 4 for padding - } - - // do we open at top-right or top-left? - // we ASSUME monitor size is greater than 640x480 AND that our tray icon is IN THE CORNER SOMEWHERE - - // always put the menu in the middle - x -= size.width / 4; - - SwingSystemTray.this.jmenu.setInvoker(SwingSystemTray.this.jmenu); - SwingSystemTray.this.jmenu.setLocation(x, y); - SwingSystemTray.this.jmenu.setVisible(true); + public void run() { + SwingSystemTray.this.tray.remove(SwingSystemTray.this.trayIcon); + SwingSystemTray.this.menuEntries.clear(); } - }); + }; - try { - SwingSystemTray.this.tray.add(SwingSystemTray.this.trayIcon); - SwingSystemTray.this.active = true; - } catch (AWTException e) { - logger.error("TrayIcon could not be added.", e); - } + if (SwingUtilities.isEventDispatchThread()) { + doRun.run(); + } else { + try { + SwingUtilities.invokeAndWait(doRun); + } catch (InvocationTargetException e) { + logger.error("Error updating tray menu", e); + } catch (InterruptedException e) { + logger.error("Error updating tray menu", e); + } } - } - }; - if (SwingUtilities.isEventDispatchThread()) { - doRun.run(); - } else { - try { - SwingUtilities.invokeAndWait(doRun); - } catch (InvocationTargetException e) { - logger.error("Error creating tray menu", e); - } catch (InterruptedException e) { - logger.error("Error creating tray menu", e); - } + super.removeTray(); } - } - Image newImage(String name) { - String iconPath = iconPath(name); + @Override + public void createTray(final String iconName) { + Runnable doRun = new Runnable() { + @Override + public void run() { + SwingSystemTray.this.tray = SystemTray.getSystemTray(); + if (SwingSystemTray.this.tray == null) { + logger.warn("The system tray is not available"); + } else { + SwingSystemTray.this.jmenu = new SystemTrayMenuPopup(); - return new ImageIcon(iconPath).getImage().getScaledInstance(ICON_SIZE, ICON_SIZE, Image.SCALE_SMOOTH); - } + Image trayImage = newImage(iconName); + SwingSystemTray.this.trayIcon = new TrayIcon(trayImage); + SwingSystemTray.this.trayIcon.setToolTip(SwingSystemTray.this.appName); + + SwingSystemTray.this.trayIcon.addMouseListener(new MouseAdapter() { + @Override + public void mousePressed(MouseEvent e) { + Dimension size = SwingSystemTray.this.jmenu.getPreferredSize(); + + Point point = e.getPoint(); + Rectangle bounds = getScreenBoundsAt(point); + + int x = point.x; + int y = point.y; + + if (y < bounds.y) { + y = bounds.y; + } else if (y > bounds.y + bounds.height) { + y = bounds.y + bounds.height + ICON_SIZE + 4; // 4 for padding + } + if (x < bounds.x) { + x = bounds.x; + } else if (x > bounds.x + bounds.width) { + x = bounds.x + bounds.width; + } + + if (x + size.width > bounds.x + bounds.width) { + // always put the menu in the middle + x = bounds.x + bounds.width - size.width; + } + if (y + size.height > bounds.y + bounds.height) { + y = bounds.y + bounds.height - size.height - ICON_SIZE - 4; // 4 for padding + } + + // do we open at top-right or top-left? + // we ASSUME monitor size is greater than 640x480 AND that our tray icon is IN THE CORNER SOMEWHERE + + // always put the menu in the middle + x -= size.width / 4; + + SwingSystemTray.this.jmenu.setInvoker(SwingSystemTray.this.jmenu); + SwingSystemTray.this.jmenu.setLocation(x, y); + SwingSystemTray.this.jmenu.setVisible(true); + } + }); + + try { + SwingSystemTray.this.tray.add(SwingSystemTray.this.trayIcon); + SwingSystemTray.this.active = true; + } catch (AWTException e) { + logger.error("TrayIcon could not be added.", e); + } + } + } + }; + + if (SwingUtilities.isEventDispatchThread()) { + doRun.run(); + } else { + try { + SwingUtilities.invokeAndWait(doRun); + } catch (InvocationTargetException e) { + logger.error("Error creating tray menu", e); + } catch (InterruptedException e) { + logger.error("Error creating tray menu", e); + } + } + } + + Image newImage(String name) { + String iconPath = iconPath(name); + + return new ImageIcon(iconPath).getImage().getScaledInstance(ICON_SIZE, ICON_SIZE, Image.SCALE_SMOOTH); + } // public static Rectangle getSafeScreenBounds(Point pos) { // Rectangle bounds = getScreenBoundsAt(pos); @@ -170,175 +193,174 @@ public class SwingSystemTray extends dorkbox.util.tray.SystemTray { // return insets; // } - private static Rectangle getScreenBoundsAt(Point pos) { - GraphicsDevice gd = getGraphicsDeviceAt(pos); - Rectangle bounds = null; - if (gd != null) { - bounds = gd.getDefaultConfiguration().getBounds(); - } - - return bounds; - } - - private static GraphicsDevice getGraphicsDeviceAt(Point pos) { - GraphicsDevice device; - - GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); - GraphicsDevice lstGDs[] = ge.getScreenDevices(); - - ArrayList lstDevices = new ArrayList(lstGDs.length); - - for (GraphicsDevice gd : lstGDs) { - - GraphicsConfiguration gc = gd.getDefaultConfiguration(); - Rectangle screenBounds = gc.getBounds(); - - if (screenBounds.contains(pos)) { - lstDevices.add(gd); - } - } - - if (lstDevices.size() > 0) { - device = lstDevices.get(0); - } else { - device = ge.getDefaultScreenDevice(); - } - - return device; - } - - @Override - public void setStatus(final String infoString, final String iconName) { - Runnable doRun = new Runnable() { - @Override - public void run() { - if (SwingSystemTray.this.connectionStatusItem == null) { - SwingSystemTray.this.connectionStatusItem = new JMenuItem(infoString); - SwingSystemTray.this.connectionStatusItem.setEnabled(false); - SwingSystemTray.this.jmenu.add(SwingSystemTray.this.connectionStatusItem); - } else { - SwingSystemTray.this.connectionStatusItem.setText(infoString); + private static Rectangle getScreenBoundsAt(Point pos) { + GraphicsDevice gd = getGraphicsDeviceAt(pos); + Rectangle bounds = null; + if (gd != null) { + bounds = gd.getDefaultConfiguration().getBounds(); } - } - }; - if (SwingUtilities.isEventDispatchThread()) { - doRun.run(); - } else { - try { - SwingUtilities.invokeAndWait(doRun); - } catch (InvocationTargetException e) { - logger.error("Error updating tray menu", e); - } catch (InterruptedException e) { - logger.error("Error updating tray menu", e); - } + return bounds; } - Image trayImage = newImage(iconName); - SwingSystemTray.this.trayIcon.setImage(trayImage); - } + private static GraphicsDevice getGraphicsDeviceAt(Point pos) { + GraphicsDevice device; - /** - * Will add a new menu entry, or update one if it already exists - */ - @Override - public void addMenuEntry(final String menuText, final SystemTrayMenuAction callback) { - Runnable doRun = new Runnable() { - @Override - public void run() { - Map menuEntries2 = SwingSystemTray.this.menuEntries; + GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); + GraphicsDevice lstGDs[] = ge.getScreenDevices(); - synchronized (menuEntries2) { - JMenuItem menuEntry = menuEntries2.get(menuText); + ArrayList lstDevices = new ArrayList(lstGDs.length); - if (menuEntry == null) { - SystemTrayMenuPopup menu = SwingSystemTray.this.jmenu; + for (GraphicsDevice gd : lstGDs) { - menuEntry = new JMenuItem(menuText); - menuEntry.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - SwingSystemTray.this.callbackExecutor.execute(new Runnable() { - @Override - public void run() { - callback.onClick(SwingSystemTray.this); - } - }); - } - }); - menu.add(menuEntry); + GraphicsConfiguration gc = gd.getDefaultConfiguration(); + Rectangle screenBounds = gc.getBounds(); - menuEntries2.put(menuText, menuEntry); - } else { - updateMenuEntry(menuText, menuText, callback); - } - } - } - }; - - if (SwingUtilities.isEventDispatchThread()) { - doRun.run(); - } else { - try { - SwingUtilities.invokeAndWait(doRun); - } catch (InvocationTargetException e) { - logger.error("Error updating tray menu", e); - } catch (InterruptedException e) { - logger.error("Error updating tray menu", e); - } - } - } - - /** - * Will update an already existing menu entry (or add a new one, if it doesn't exist) - */ - @Override - public void updateMenuEntry(final String origMenuText, final String newMenuText, - final SystemTrayMenuAction newCallback) { - Runnable doRun = new Runnable() { - @Override - public void run() { - Map menuEntries2 = SwingSystemTray.this.menuEntries; - - synchronized (menuEntries2) { - JMenuItem menuEntry = menuEntries2.get(origMenuText); - - if (menuEntry != null) { - ActionListener[] actionListeners = menuEntry.getActionListeners(); - for (ActionListener l : actionListeners) { - menuEntry.removeActionListener(l); + if (screenBounds.contains(pos)) { + lstDevices.add(gd); } - - menuEntry.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - SwingSystemTray.this.callbackExecutor.execute(new Runnable() { - @Override - public void run() { - newCallback.onClick(SwingSystemTray.this); - } - }); - } - }); - menuEntry.setText(newMenuText); - menuEntry.revalidate(); - } else { - addMenuEntry(origMenuText, newCallback); - } } - } - }; - if (SwingUtilities.isEventDispatchThread()) { - doRun.run(); - } else { - try { - SwingUtilities.invokeAndWait(doRun); - } catch (InvocationTargetException e) { - logger.error("Error updating tray menu", e); - } catch (InterruptedException e) { - logger.error("Error updating tray menu", e); - } + if (lstDevices.size() > 0) { + device = lstDevices.get(0); + } else { + device = ge.getDefaultScreenDevice(); + } + + return device; + } + + @Override + public void setStatus(final String infoString, final String iconName) { + Runnable doRun = new Runnable() { + @Override + public void run() { + if (SwingSystemTray.this.connectionStatusItem == null) { + SwingSystemTray.this.connectionStatusItem = new JMenuItem(infoString); + SwingSystemTray.this.connectionStatusItem.setEnabled(false); + SwingSystemTray.this.jmenu.add(SwingSystemTray.this.connectionStatusItem); + } else { + SwingSystemTray.this.connectionStatusItem.setText(infoString); + } + } + }; + + if (SwingUtilities.isEventDispatchThread()) { + doRun.run(); + } else { + try { + SwingUtilities.invokeAndWait(doRun); + } catch (InvocationTargetException e) { + logger.error("Error updating tray menu", e); + } catch (InterruptedException e) { + logger.error("Error updating tray menu", e); + } + } + + Image trayImage = newImage(iconName); + SwingSystemTray.this.trayIcon.setImage(trayImage); + } + + /** + * Will add a new menu entry, or update one if it already exists + */ + @Override + public void addMenuEntry(final String menuText, final SystemTrayMenuAction callback) { + Runnable doRun = new Runnable() { + @Override + public void run() { + Map menuEntries2 = SwingSystemTray.this.menuEntries; + + synchronized (menuEntries2) { + JMenuItem menuEntry = menuEntries2.get(menuText); + + if (menuEntry == null) { + SystemTrayMenuPopup menu = SwingSystemTray.this.jmenu; + + menuEntry = new JMenuItem(menuText); + menuEntry.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + SwingSystemTray.this.callbackExecutor.execute(new Runnable() { + @Override + public void run() { + callback.onClick(SwingSystemTray.this); + } + }); + } + }); + menu.add(menuEntry); + + menuEntries2.put(menuText, menuEntry); + } else { + updateMenuEntry(menuText, menuText, callback); + } + } + } + }; + + if (SwingUtilities.isEventDispatchThread()) { + doRun.run(); + } else { + try { + SwingUtilities.invokeAndWait(doRun); + } catch (InvocationTargetException e) { + logger.error("Error updating tray menu", e); + } catch (InterruptedException e) { + logger.error("Error updating tray menu", e); + } + } + } + + /** + * Will update an already existing menu entry (or add a new one, if it doesn't exist) + */ + @Override + public void updateMenuEntry(final String origMenuText, final String newMenuText, final SystemTrayMenuAction newCallback) { + Runnable doRun = new Runnable() { + @Override + public void run() { + Map menuEntries2 = SwingSystemTray.this.menuEntries; + + synchronized (menuEntries2) { + JMenuItem menuEntry = menuEntries2.get(origMenuText); + + if (menuEntry != null) { + ActionListener[] actionListeners = menuEntry.getActionListeners(); + for (ActionListener l : actionListeners) { + menuEntry.removeActionListener(l); + } + + menuEntry.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + SwingSystemTray.this.callbackExecutor.execute(new Runnable() { + @Override + public void run() { + newCallback.onClick(SwingSystemTray.this); + } + }); + } + }); + menuEntry.setText(newMenuText); + menuEntry.revalidate(); + } else { + addMenuEntry(origMenuText, newCallback); + } + } + } + }; + + if (SwingUtilities.isEventDispatchThread()) { + doRun.run(); + } else { + try { + SwingUtilities.invokeAndWait(doRun); + } catch (InvocationTargetException e) { + logger.error("Error updating tray menu", e); + } catch (InterruptedException e) { + logger.error("Error updating tray menu", e); + } + } } - } }