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