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 }