1 /*
   2  * Copyright 2005-2008 Sun Microsystems, Inc.  All Rights Reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Sun designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Sun in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
  22  * CA 95054 USA or visit www.sun.com if you need additional information or
  23  * have any questions.
  24  */
  25 
  26 package java.awt;
  27 
  28 import java.util.Vector;
  29 import java.awt.peer.SystemTrayPeer;
  30 import java.beans.PropertyChangeListener;
  31 import java.beans.PropertyChangeSupport;
  32 import sun.awt.AppContext;
  33 import sun.awt.SunToolkit;
  34 import sun.awt.HeadlessToolkit;
  35 import sun.security.util.SecurityConstants;
  36 
  37 /**
  38  * The <code>SystemTray</code> class represents the system tray for a
  39  * desktop.  On Microsoft Windows it is referred to as the "Taskbar
  40  * Status Area", on Gnome it is referred to as the "Notification
  41  * Area", on KDE it is referred to as the "System Tray".  The system
  42  * tray is shared by all applications running on the desktop.
  43  *
  44  * <p> On some platforms the system tray may not be present or may not
  45  * be supported, in this case {@link SystemTray#getSystemTray()}
  46  * throws {@link UnsupportedOperationException}.  To detect whether the
  47  * system tray is supported, use {@link SystemTray#isSupported}.
  48  *
  49  * <p>The <code>SystemTray</code> may contain one or more {@link
  50  * TrayIcon TrayIcons}, which are added to the tray using the {@link
  51  * #add} method, and removed when no longer needed, using the
  52  * {@link #remove}.  <code>TrayIcon</code> consists of an
  53  * image, a popup menu and a set of associated listeners.  Please see
  54  * the {@link TrayIcon} class for details.
  55  *
  56  * <p>Every Java application has a single <code>SystemTray</code>
  57  * instance that allows the app to interface with the system tray of
  58  * the desktop while the app is running.  The <code>SystemTray</code>
  59  * instance can be obtained from the {@link #getSystemTray} method.
  60  * An application may not create its own instance of
  61  * <code>SystemTray</code>.
  62  *
  63  * <p>The following code snippet demonstrates how to access
  64  * and customize the system tray:
  65  * <code>
  66  * <pre>
  67  *     {@link TrayIcon} trayIcon = null;
  68  *     if (SystemTray.isSupported()) {
  69  *         // get the SystemTray instance
  70  *         SystemTray tray = SystemTray.{@link #getSystemTray};
  71  *         // load an image
  72  *         {@link java.awt.Image} image = {@link java.awt.Toolkit#getImage(String) Toolkit.getDefaultToolkit().getImage}(...);
  73  *         // create a action listener to listen for default action executed on the tray icon
  74  *         {@link java.awt.event.ActionListener} listener = new {@link java.awt.event.ActionListener ActionListener}() {
  75  *             public void {@link java.awt.event.ActionListener#actionPerformed actionPerformed}({@link java.awt.event.ActionEvent} e) {
  76  *                 // execute default action of the application
  77  *                 // ...
  78  *             }
  79  *         };
  80  *         // create a popup menu
  81  *         {@link java.awt.PopupMenu} popup = new {@link java.awt.PopupMenu#PopupMenu PopupMenu}();
  82  *         // create menu item for the default action
  83  *         MenuItem defaultItem = new MenuItem(...);
  84  *         defaultItem.addActionListener(listener);
  85  *         popup.add(defaultItem);
  86  *         /// ... add other items
  87  *         // construct a TrayIcon
  88  *         trayIcon = new {@link TrayIcon#TrayIcon(java.awt.Image, String, java.awt.PopupMenu) TrayIcon}(image, "Tray Demo", popup);
  89  *         // set the TrayIcon properties
  90  *         trayIcon.{@link TrayIcon#addActionListener(java.awt.event.ActionListener) addActionListener}(listener);
  91  *         // ...
  92  *         // add the tray image
  93  *         try {
  94  *             tray.{@link SystemTray#add(TrayIcon) add}(trayIcon);
  95  *         } catch (AWTException e) {
  96  *             System.err.println(e);
  97  *         }
  98  *         // ...
  99  *     } else {
 100  *         // disable tray option in your application or
 101  *         // perform other actions
 102  *         ...
 103  *     }
 104  *     // ...
 105  *     // some time later
 106  *     // the application state has changed - update the image
 107  *     if (trayIcon != null) {
 108  *         trayIcon.{@link TrayIcon#setImage(java.awt.Image) setImage}(updatedImage);
 109  *     }
 110  *     // ...
 111  * </pre>
 112  * </code>
 113  *
 114  * @since 1.6
 115  * @see TrayIcon
 116  *
 117  * @author Bino George
 118  * @author Denis Mikhalkin
 119  * @author Sharon Zakhour
 120  * @author Anton Tarasov
 121  */
 122 public class SystemTray {
 123     private static SystemTray systemTray;
 124     private int currentIconID = 0; // each TrayIcon added gets a unique ID
 125 
 126     transient private SystemTrayPeer peer;
 127 
 128     private static final TrayIcon[] EMPTY_TRAY_ARRAY = new TrayIcon[0];
 129 
 130     /**
 131      * Private <code>SystemTray</code> constructor.
 132      *
 133      */
 134     private SystemTray() {
 135         addNotify();
 136     }
 137 
 138     /**
 139      * Gets the <code>SystemTray</code> instance that represents the
 140      * desktop's tray area.  This always returns the same instance per
 141      * application.  On some platforms the system tray may not be
 142      * supported.  You may use the {@link #isSupported} method to
 143      * check if the system tray is supported.
 144      *
 145      * <p>If a SecurityManager is installed, the AWTPermission
 146      * {@code accessSystemTray} must be granted in order to get the
 147      * {@code SystemTray} instance. Otherwise this method will throw a
 148      * SecurityException.
 149      *
 150      * @return the <code>SystemTray</code> instance that represents
 151      * the desktop's tray area
 152      * @throws UnsupportedOperationException if the system tray isn't
 153      * supported by the current platform
 154      * @throws HeadlessException if
 155      * <code>GraphicsEnvironment.isHeadless()</code> returns <code>true</code>
 156      * @throws SecurityException if {@code accessSystemTray} permission
 157      * is not granted
 158      * @see #add(TrayIcon)
 159      * @see TrayIcon
 160      * @see #isSupported
 161      * @see SecurityManager#checkPermission
 162      * @see AWTPermission
 163      */
 164     public static SystemTray getSystemTray() {
 165         checkSystemTrayAllowed();
 166         if (GraphicsEnvironment.isHeadless()) {
 167             throw new HeadlessException();
 168         }
 169 
 170         initializeSystemTrayIfNeeded();
 171 
 172         if (!isSupported()) {
 173             throw new UnsupportedOperationException(
 174                 "The system tray is not supported on the current platform.");
 175         }
 176 
 177         return systemTray;
 178     }
 179 
 180     /**
 181      * Returns whether the system tray is supported on the current
 182      * platform.  In addition to displaying the tray icon, minimal
 183      * system tray support includes either a popup menu (see {@link
 184      * TrayIcon#setPopupMenu(PopupMenu)}) or an action event (see
 185      * {@link TrayIcon#addActionListener(ActionListener)}).
 186      *
 187      * <p>Developers should not assume that all of the system tray
 188      * functionality is supported.  To guarantee that the tray icon's
 189      * default action is always accessible, add the default action to
 190      * both the action listener and the popup menu.  See the {@link
 191      * SystemTray example} for an example of how to do this.
 192      *
 193      * <p><b>Note</b>: When implementing <code>SystemTray</code> and
 194      * <code>TrayIcon</code> it is <em>strongly recommended</em> that
 195      * you assign different gestures to the popup menu and an action
 196      * event.  Overloading a gesture for both purposes is confusing
 197      * and may prevent the user from accessing one or the other.
 198      *
 199      * @see #getSystemTray
 200      * @return <code>false</code> if no system tray access is supported; this
 201      * method returns <code>true</code> if the minimal system tray access is
 202      * supported but does not guarantee that all system tray
 203      * functionality is supported for the current platform
 204      */
 205     public static boolean isSupported() {
 206         Toolkit toolkit = Toolkit.getDefaultToolkit();
 207         if (toolkit instanceof SunToolkit) {
 208             // connecting tray to native resource
 209             initializeSystemTrayIfNeeded();
 210             return ((SunToolkit)toolkit).isTraySupported();
 211         } else if (toolkit instanceof HeadlessToolkit) {
 212             // skip initialization as the init routine
 213             // throws HeadlessException
 214             return ((HeadlessToolkit)toolkit).isTraySupported();
 215         } else {
 216             return false;
 217         }
 218     }
 219 
 220     /**
 221      * Adds a <code>TrayIcon</code> to the <code>SystemTray</code>.
 222      * The tray icon becomes visible in the system tray once it is
 223      * added.  The order in which icons are displayed in a tray is not
 224      * specified - it is platform and implementation-dependent.
 225      *
 226      * <p> All icons added by the application are automatically
 227      * removed from the <code>SystemTray</code> upon application exit
 228      * and also when the desktop system tray becomes unavailable.
 229      *
 230      * @param trayIcon the <code>TrayIcon</code> to be added
 231      * @throws NullPointerException if <code>trayIcon</code> is
 232      * <code>null</code>
 233      * @throws IllegalArgumentException if the same instance of
 234      * a <code>TrayIcon</code> is added more than once
 235      * @throws AWTException if the desktop system tray is missing
 236      * @see #remove(TrayIcon)
 237      * @see #getSystemTray
 238      * @see TrayIcon
 239      * @see java.awt.Image
 240      */
 241     public void add(TrayIcon trayIcon) throws AWTException {
 242         if (trayIcon == null) {
 243             throw new NullPointerException("adding null TrayIcon");
 244         }
 245         TrayIcon[] oldArray = null, newArray = null;
 246         Vector<TrayIcon> icons = null;
 247         synchronized (this) {
 248             oldArray = systemTray.getTrayIcons();
 249             icons = (Vector<TrayIcon>)AppContext.getAppContext().get(TrayIcon.class);
 250             if (icons == null) {
 251                 icons = new Vector<TrayIcon>(3);
 252                 AppContext.getAppContext().put(TrayIcon.class, icons);
 253 
 254             } else if (icons.contains(trayIcon)) {
 255                 throw new IllegalArgumentException("adding TrayIcon that is already added");
 256             }
 257             icons.add(trayIcon);
 258             newArray = systemTray.getTrayIcons();
 259 
 260             trayIcon.setID(++currentIconID);
 261         }
 262         try {
 263             trayIcon.addNotify();
 264         } catch (AWTException e) {
 265             icons.remove(trayIcon);
 266             throw e;
 267         }
 268         firePropertyChange("trayIcons", oldArray, newArray);
 269     }
 270 
 271     /**
 272      * Removes the specified <code>TrayIcon</code> from the
 273      * <code>SystemTray</code>.
 274      *
 275      * <p> All icons added by the application are automatically
 276      * removed from the <code>SystemTray</code> upon application exit
 277      * and also when the desktop system tray becomes unavailable.
 278      *
 279      * <p> If <code>trayIcon</code> is <code>null</code> or was not
 280      * added to the system tray, no exception is thrown and no action
 281      * is performed.
 282      *
 283      * @param trayIcon the <code>TrayIcon</code> to be removed
 284      * @see #add(TrayIcon)
 285      * @see TrayIcon
 286      */
 287     public void remove(TrayIcon trayIcon) {
 288         if (trayIcon == null) {
 289             return;
 290         }
 291         TrayIcon[] oldArray = null, newArray = null;
 292         synchronized (this) {
 293             oldArray = systemTray.getTrayIcons();
 294             Vector<TrayIcon> icons = (Vector<TrayIcon>)AppContext.getAppContext().get(TrayIcon.class);
 295             // TrayIcon with no peer is not contained in the array.
 296             if (icons == null || !icons.remove(trayIcon)) {
 297                 return;
 298             }
 299             trayIcon.removeNotify();
 300             newArray = systemTray.getTrayIcons();
 301         }
 302         firePropertyChange("trayIcons", oldArray, newArray);
 303     }
 304 
 305     /**
 306      * Returns an array of all icons added to the tray by this
 307      * application.  You can't access the icons added by another
 308      * application.  Some browsers partition applets in different
 309      * code bases into separate contexts, and establish walls between
 310      * these contexts.  In such a scenario, only the tray icons added
 311      * from this context will be returned.
 312      *
 313      * <p> The returned array is a copy of the actual array and may be
 314      * modified in any way without affecting the system tray.  To
 315      * remove a <code>TrayIcon</code> from the
 316      * <code>SystemTray</code>, use the {@link
 317      * #remove(TrayIcon)} method.
 318      *
 319      * @return an array of all tray icons added to this tray, or an
 320      * empty array if none has been added
 321      * @see #add(TrayIcon)
 322      * @see TrayIcon
 323      */
 324     public TrayIcon[] getTrayIcons() {
 325         Vector<TrayIcon> icons = (Vector<TrayIcon>)AppContext.getAppContext().get(TrayIcon.class);
 326         if (icons != null) {
 327             return (TrayIcon[])icons.toArray(new TrayIcon[icons.size()]);
 328         }
 329         return EMPTY_TRAY_ARRAY;
 330     }
 331 
 332     /**
 333      * Returns the size, in pixels, of the space that a tray icon will
 334      * occupy in the system tray.  Developers may use this methods to
 335      * acquire the preferred size for the image property of a tray icon
 336      * before it is created.  For convenience, there is a similar
 337      * method {@link TrayIcon#getSize} in the <code>TrayIcon</code> class.
 338      *
 339      * @return the default size of a tray icon, in pixels
 340      * @see TrayIcon#setImageAutoSize(boolean)
 341      * @see java.awt.Image
 342      * @see TrayIcon#getSize()
 343      */
 344     public Dimension getTrayIconSize() {
 345         return peer.getTrayIconSize();
 346     }
 347 
 348     /**
 349      * Adds a {@code PropertyChangeListener} to the list of listeners for the
 350      * specific property. The following properties are currently supported:
 351      * <p> </p>
 352      * <table border=1 summary="SystemTray properties">
 353      * <tr>
 354      *    <th>Property</th>
 355      *    <th>Description</th>
 356      * </tr>
 357      * <tr>
 358      *    <td>{@code trayIcons}</td>
 359      *    <td>The {@code SystemTray}'s array of {@code TrayIcon} objects.
 360      *        The array is accessed via the {@link #getTrayIcons} method.<br>
 361      *        This property is changed when a tray icon is added to (or removed
 362      *        from) the system tray.<br> For example, this property is changed
 363      *        when the system tray becomes unavailable on the desktop<br>
 364      *        and the tray icons are automatically removed.</td>
 365      * </tr>
 366      * <tr>
 367      *    <td>{@code systemTray}</td>
 368      *    <td>This property contains {@code SystemTray} instance when the system tray
 369      *        is available or <code>null</code> otherwise.<br> This property is changed
 370      *        when the system tray becomes available or unavailable on the desktop.<br>
 371      *        The property is accessed by the {@link #getSystemTray} method.</td>
 372      * </tr>
 373      * </table>
 374      * <p> </p>
 375      * The {@code listener} listens to property changes only in this context.
 376      * <p>
 377      * If {@code listener} is {@code null}, no exception is thrown
 378      * and no action is performed.
 379      *
 380      * @param propertyName the specified property
 381      * @param listener the property change listener to be added
 382      *
 383      * @see #removePropertyChangeListener
 384      * @see #getPropertyChangeListeners
 385      */
 386     public synchronized void addPropertyChangeListener(String propertyName,
 387                                                        PropertyChangeListener listener)
 388     {
 389         if (listener == null) {
 390             return;
 391         }
 392         getCurrentChangeSupport().addPropertyChangeListener(propertyName, listener);
 393     }
 394 
 395     /**
 396      * Removes a {@code PropertyChangeListener} from the listener list
 397      * for a specific property.
 398      * <p>
 399      * The {@code PropertyChangeListener} must be from this context.
 400      * <p>
 401      * If {@code propertyName} or {@code listener} is {@code null} or invalid,
 402      * no exception is thrown and no action is taken.
 403      *
 404      * @param propertyName the specified property
 405      * @param listener the PropertyChangeListener to be removed
 406      *
 407      * @see #addPropertyChangeListener
 408      * @see #getPropertyChangeListeners
 409      */
 410     public synchronized void removePropertyChangeListener(String propertyName,
 411                                                           PropertyChangeListener listener)
 412     {
 413         if (listener == null) {
 414             return;
 415         }
 416         getCurrentChangeSupport().removePropertyChangeListener(propertyName, listener);
 417     }
 418 
 419     /**
 420      * Returns an array of all the listeners that have been associated
 421      * with the named property.
 422      * <p>
 423      * Only the listeners in this context are returned.
 424      *
 425      * @param propertyName the specified property
 426      * @return all of the {@code PropertyChangeListener}s associated with
 427      *         the named property; if no such listeners have been added or
 428      *         if {@code propertyName} is {@code null} or invalid, an empty
 429      *         array is returned
 430      *
 431      * @see #addPropertyChangeListener
 432      * @see #removePropertyChangeListener
 433      */
 434     public synchronized PropertyChangeListener[] getPropertyChangeListeners(String propertyName) {
 435         return getCurrentChangeSupport().getPropertyChangeListeners(propertyName);
 436     }
 437 
 438 
 439     // ***************************************************************
 440     // ***************************************************************
 441 
 442 
 443     /**
 444      * Support for reporting bound property changes for Object properties.
 445      * This method can be called when a bound property has changed and it will
 446      * send the appropriate PropertyChangeEvent to any registered
 447      * PropertyChangeListeners.
 448      *
 449      * @param propertyName the property whose value has changed
 450      * @param oldValue the property's previous value
 451      * @param newValue the property's new value
 452      */
 453     private void firePropertyChange(String propertyName,
 454                                     Object oldValue, Object newValue)
 455     {
 456         if (oldValue != null && newValue != null && oldValue.equals(newValue)) {
 457             return;
 458         }
 459         getCurrentChangeSupport().firePropertyChange(propertyName, oldValue, newValue);
 460     }
 461 
 462     /**
 463      * Returns the current PropertyChangeSupport instance for the
 464      * calling thread's context.
 465      *
 466      * @return this thread's context's PropertyChangeSupport
 467      */
 468     private synchronized PropertyChangeSupport getCurrentChangeSupport() {
 469         PropertyChangeSupport changeSupport =
 470             (PropertyChangeSupport)AppContext.getAppContext().get(SystemTray.class);
 471 
 472         if (changeSupport == null) {
 473             changeSupport = new PropertyChangeSupport(this);
 474             AppContext.getAppContext().put(SystemTray.class, changeSupport);
 475         }
 476         return changeSupport;
 477     }
 478 
 479     synchronized void addNotify() {
 480         if (peer == null) {
 481             Toolkit toolkit = Toolkit.getDefaultToolkit();
 482             if (toolkit instanceof SunToolkit) {
 483                 peer = ((SunToolkit)Toolkit.getDefaultToolkit()).createSystemTray(this);
 484             } else if (toolkit instanceof HeadlessToolkit) {
 485                 peer = ((HeadlessToolkit)Toolkit.getDefaultToolkit()).createSystemTray(this);
 486             }
 487         }
 488     }
 489 
 490     static void checkSystemTrayAllowed() {
 491         SecurityManager security = System.getSecurityManager();
 492         if (security != null) {
 493             security.checkPermission(SecurityConstants.AWT.ACCESS_SYSTEM_TRAY_PERMISSION);
 494         }
 495     }
 496 
 497     private static void initializeSystemTrayIfNeeded() {
 498         synchronized (SystemTray.class) {
 499             if (systemTray == null) {
 500                 systemTray = new SystemTray();
 501             }
 502         }
 503     }
 504 }