001 /*
002 * Copyright 2005-2006 Sun Microsystems, Inc. All Rights Reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation. Sun designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Sun in the LICENSE file that accompanied this code.
010 *
011 * This code is distributed in the hope that it will be useful, but WITHOUT
012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014 * version 2 for more details (a copy is included in the LICENSE file that
015 * accompanied this code).
016 *
017 * You should have received a copy of the GNU General Public License version
018 * 2 along with this work; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020 *
021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022 * CA 95054 USA or visit www.sun.com if you need additional information or
023 * have any questions.
024 */
025
026 package java.awt;
027
028 import java.util.Vector;
029 import java.awt.peer.SystemTrayPeer;
030 import java.beans.PropertyChangeListener;
031 import java.beans.PropertyChangeSupport;
032 import sun.awt.AppContext;
033 import sun.awt.SunToolkit;
034 import sun.awt.HeadlessToolkit;
035 import sun.security.util.SecurityConstants;
036
037 /**
038 * The <code>SystemTray</code> class represents the system tray for a
039 * desktop. On Microsoft Windows it is referred to as the "Taskbar
040 * Status Area", on Gnome it is referred to as the "Notification
041 * Area", on KDE it is referred to as the "System Tray". The system
042 * tray is shared by all applications running on the desktop.
043 *
044 * <p> On some platforms the system tray may not be present or may not
045 * be supported, in this case {@link SystemTray#getSystemTray()}
046 * throws {@link UnsupportedOperationException}. To detect whether the
047 * system tray is supported, use {@link SystemTray#isSupported}.
048 *
049 * <p>The <code>SystemTray</code> may contain one or more {@link
050 * TrayIcon TrayIcons}, which are added to the tray using the {@link
051 * #add} method, and removed when no longer needed, using the
052 * {@link #remove}. <code>TrayIcon</code> consists of an
053 * image, a popup menu and a set of associated listeners. Please see
054 * the {@link TrayIcon} class for details.
055 *
056 * <p>Every Java application has a single <code>SystemTray</code>
057 * instance that allows the app to interface with the system tray of
058 * the desktop while the app is running. The <code>SystemTray</code>
059 * instance can be obtained from the {@link #getSystemTray} method.
060 * An application may not create its own instance of
061 * <code>SystemTray</code>.
062 *
063 * <p>The following code snippet demonstrates how to access
064 * and customize the system tray:
065 * <code>
066 * <pre>
067 * {@link TrayIcon} trayIcon = null;
068 * if (SystemTray.isSupported()) {
069 * // get the SystemTray instance
070 * SystemTray tray = SystemTray.{@link #getSystemTray};
071 * // load an image
072 * {@link java.awt.Image} image = {@link java.awt.Toolkit#getImage(String) Toolkit.getDefaultToolkit().getImage}(...);
073 * // create a action listener to listen for default action executed on the tray icon
074 * {@link java.awt.event.ActionListener} listener = new {@link java.awt.event.ActionListener ActionListener}() {
075 * public void {@link java.awt.event.ActionListener#actionPerformed actionPerformed}({@link java.awt.event.ActionEvent} e) {
076 * // execute default action of the application
077 * // ...
078 * }
079 * };
080 * // create a popup menu
081 * {@link java.awt.PopupMenu} popup = new {@link java.awt.PopupMenu#PopupMenu PopupMenu}();
082 * // create menu item for the default action
083 * MenuItem defaultItem = new MenuItem(...);
084 * defaultItem.addActionListener(listener);
085 * popup.add(defaultItem);
086 * /// ... add other items
087 * // construct a TrayIcon
088 * trayIcon = new {@link TrayIcon#TrayIcon(java.awt.Image, String, java.awt.PopupMenu) TrayIcon}(image, "Tray Demo", popup);
089 * // set the TrayIcon properties
090 * trayIcon.{@link TrayIcon#addActionListener(java.awt.event.ActionListener) addActionListener}(listener);
091 * // ...
092 * // add the tray image
093 * try {
094 * tray.{@link SystemTray#add(TrayIcon) add}(trayIcon);
095 * } catch (AWTException e) {
096 * System.err.println(e);
097 * }
098 * // ...
099 * } 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 /**
129 * Private <code>SystemTray</code> constructor.
130 *
131 */
132 private SystemTray() {
133 addNotify();
134 }
135
136 /**
137 * Gets the <code>SystemTray</code> instance that represents the
138 * desktop's tray area. This always returns the same instance per
139 * application. On some platforms the system tray may not be
140 * supported. You may use the {@link #isSupported} method to
141 * check if the system tray is supported.
142 *
143 * <p>If a SecurityManager is installed, the AWTPermission
144 * {@code accessSystemTray} must be granted in order to get the
145 * {@code SystemTray} instance. Otherwise this method will throw a
146 * SecurityException.
147 *
148 * @return the <code>SystemTray</code> instance that represents
149 * the desktop's tray area
150 * @throws UnsupportedOperationException if the system tray isn't
151 * supported by the current platform
152 * @throws HeadlessException if
153 * <code>GraphicsEnvironment.isHeadless()</code> returns <code>true</code>
154 * @throws SecurityException if {@code accessSystemTray} permission
155 * is not granted
156 * @see #add(TrayIcon)
157 * @see TrayIcon
158 * @see #isSupported
159 * @see SecurityManager#checkPermission
160 * @see AWTPermission
161 */
162 public static SystemTray getSystemTray() {
163 checkSystemTrayAllowed();
164 if (GraphicsEnvironment.isHeadless()) {
165 throw new HeadlessException();
166 }
167 if (!isSupported()) {
168 throw new UnsupportedOperationException(
169 "The system tray is not supported on the current platform.");
170 }
171
172 synchronized (SystemTray.class) {
173 if (systemTray == null) {
174 systemTray = new SystemTray();
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 if (Toolkit.getDefaultToolkit() instanceof SunToolkit) {
207
208 return ((SunToolkit) Toolkit.getDefaultToolkit())
209 .isTraySupported();
210
211 } else if (Toolkit.getDefaultToolkit() instanceof HeadlessToolkit) {
212
213 return ((HeadlessToolkit) Toolkit.getDefaultToolkit())
214 .isTraySupported();
215 }
216 return false;
217 }
218
219 /**
220 * Adds a <code>TrayIcon</code> to the <code>SystemTray</code>.
221 * The tray icon becomes visible in the system tray once it is
222 * added. The order in which icons are displayed in a tray is not
223 * specified - it is platform and implementation-dependent.
224 *
225 * <p> All icons added by the application are automatically
226 * removed from the <code>SystemTray</code> upon application exit
227 * and also when the desktop system tray becomes unavailable.
228 *
229 * @param trayIcon the <code>TrayIcon</code> to be added
230 * @throws NullPointerException if <code>trayIcon</code> is
231 * <code>null</code>
232 * @throws IllegalArgumentException if the same instance of
233 * a <code>TrayIcon</code> is added more than once
234 * @throws AWTException if the desktop system tray is missing
235 * @see #remove(TrayIcon)
236 * @see #getSystemTray
237 * @see TrayIcon
238 * @see java.awt.Image
239 */
240 public void add(TrayIcon trayIcon) throws AWTException {
241 if (trayIcon == null) {
242 throw new NullPointerException("adding null TrayIcon");
243 }
244 TrayIcon[] oldArray = null, newArray = null;
245 Vector<TrayIcon> icons = null;
246 synchronized (this ) {
247 oldArray = systemTray.getTrayIcons();
248 icons = (Vector<TrayIcon>) AppContext.getAppContext().get(
249 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(
256 "adding TrayIcon that is already added");
257 }
258 icons.add(trayIcon);
259 newArray = systemTray.getTrayIcons();
260
261 trayIcon.setID(++currentIconID);
262 }
263 try {
264 trayIcon.addNotify();
265 } catch (AWTException e) {
266 icons.remove(trayIcon);
267 throw e;
268 }
269 firePropertyChange("trayIcons", oldArray, newArray);
270 }
271
272 /**
273 * Removes the specified <code>TrayIcon</code> from the
274 * <code>SystemTray</code>.
275 *
276 * <p> All icons added by the application are automatically
277 * removed from the <code>SystemTray</code> upon application exit
278 * and also when the desktop system tray becomes unavailable.
279 *
280 * <p> If <code>trayIcon</code> is <code>null</code> or was not
281 * added to the system tray, no exception is thrown and no action
282 * is performed.
283 *
284 * @param trayIcon the <code>TrayIcon</code> to be removed
285 * @see #add(TrayIcon)
286 * @see TrayIcon
287 */
288 public void remove(TrayIcon trayIcon) {
289 if (trayIcon == null) {
290 return;
291 }
292 TrayIcon[] oldArray = null, newArray = null;
293 synchronized (this ) {
294 oldArray = systemTray.getTrayIcons();
295 Vector<TrayIcon> icons = (Vector<TrayIcon>) AppContext
296 .getAppContext().get(TrayIcon.class);
297 // TrayIcon with no peer is not contained in the array.
298 if (icons == null || !icons.remove(trayIcon)) {
299 return;
300 }
301 trayIcon.removeNotify();
302 newArray = systemTray.getTrayIcons();
303 }
304 firePropertyChange("trayIcons", oldArray, newArray);
305 }
306
307 /**
308 * Returns an array of all icons added to the tray by this
309 * application. You can't access the icons added by another
310 * application. Some browsers partition applets in different
311 * code bases into separate contexts, and establish walls between
312 * these contexts. In such a scenario, only the tray icons added
313 * from this context will be returned.
314 *
315 * <p> The returned array is a copy of the actual array and may be
316 * modified in any way without affecting the system tray. To
317 * remove a <code>TrayIcon</code> from the
318 * <code>SystemTray</code>, use the {@link
319 * #remove(TrayIcon)} method.
320 *
321 * @return an array of all tray icons added to this tray, or an
322 * empty array if none has been added
323 * @see #add(TrayIcon)
324 * @see TrayIcon
325 */
326 public TrayIcon[] getTrayIcons() {
327 Vector<TrayIcon> icons = (Vector<TrayIcon>) AppContext
328 .getAppContext().get(TrayIcon.class);
329 if (icons != null) {
330 return (TrayIcon[]) icons
331 .toArray(new TrayIcon[icons.size()]);
332 }
333 return new TrayIcon[0];
334 }
335
336 /**
337 * Returns the size, in pixels, of the space that a tray icon will
338 * occupy in the system tray. Developers may use this methods to
339 * acquire the preferred size for the image property of a tray icon
340 * before it is created. For convenience, there is a similar
341 * method {@link TrayIcon#getSize} in the <code>TrayIcon</code> class.
342 *
343 * @return the default size of a tray icon, in pixels
344 * @see TrayIcon#setImageAutoSize(boolean)
345 * @see java.awt.Image
346 * @see TrayIcon#getSize()
347 */
348 public Dimension getTrayIconSize() {
349 return peer.getTrayIconSize();
350 }
351
352 /**
353 * Adds a {@code PropertyChangeListener} to the listener list for a
354 * specific property. Currently supported property:
355 * <ul>
356 * <li>{@code trayIcons}<p>
357 * <p>
358 * This {@code SystemTray}'s array of {@code TrayIcon}s.
359 * The array is accessed via {@link SystemTray#getTrayIcons}.<br>
360 * This property is changed when a {@code TrayIcon} is added to
361 * (or removed from) the {@code SystemTray}.<br> For example, this property
362 * is changed when the native {@code SystemTray} becomes unavailable on the
363 * desktop<br> and the {@code TrayIcon}s are automatically removed.</li>
364 * </ul>
365 * <p>
366 * The {@code listener} listens to property changes only in this context.
367 * <p>
368 * If {@code listener} is {@code null}, no exception is thrown
369 * and no action is performed.
370 *
371 * @param propertyName the specified property
372 * @param listener the property change listener to be added
373 *
374 * @see #removePropertyChangeListener
375 * @see #getPropertyChangeListeners
376 */
377 public synchronized void addPropertyChangeListener(
378 String propertyName, PropertyChangeListener listener) {
379 if (listener == null) {
380 return;
381 }
382 getCurrentChangeSupport().addPropertyChangeListener(
383 propertyName, listener);
384 }
385
386 /**
387 * Removes a {@code PropertyChangeListener} from the listener list
388 * for a specific property.
389 * <p>
390 * The {@code PropertyChangeListener} must be from this context.
391 * <p>
392 * If {@code propertyName} or {@code listener} is {@code null} or invalid,
393 * no exception is thrown and no action is taken.
394 *
395 * @param propertyName the specified property
396 * @param listener the PropertyChangeListener to be removed
397 *
398 * @see #addPropertyChangeListener
399 * @see #getPropertyChangeListeners
400 */
401 public synchronized void removePropertyChangeListener(
402 String propertyName, PropertyChangeListener listener) {
403 if (listener == null) {
404 return;
405 }
406 getCurrentChangeSupport().removePropertyChangeListener(
407 propertyName, listener);
408 }
409
410 /**
411 * Returns an array of all the listeners that have been associated
412 * with the named property.
413 * <p>
414 * Only the listeners in this context are returned.
415 *
416 * @param propertyName the specified property
417 * @return all of the {@code PropertyChangeListener}s associated with
418 * the named property; if no such listeners have been added or
419 * if {@code propertyName} is {@code null} or invalid, an empty
420 * array is returned
421 *
422 * @see #addPropertyChangeListener
423 * @see #removePropertyChangeListener
424 */
425 public synchronized PropertyChangeListener[] getPropertyChangeListeners(
426 String propertyName) {
427 return getCurrentChangeSupport().getPropertyChangeListeners(
428 propertyName);
429 }
430
431 // ***************************************************************
432 // ***************************************************************
433
434 /**
435 * Support for reporting bound property changes for Object properties.
436 * This method can be called when a bound property has changed and it will
437 * send the appropriate PropertyChangeEvent to any registered
438 * PropertyChangeListeners.
439 *
440 * @param propertyName the property whose value has changed
441 * @param oldValue the property's previous value
442 * @param newValue the property's new value
443 */
444 private void firePropertyChange(String propertyName,
445 Object oldValue, Object newValue) {
446 if (oldValue != null && newValue != null
447 && oldValue.equals(newValue)) {
448 return;
449 }
450 getCurrentChangeSupport().firePropertyChange(propertyName,
451 oldValue, newValue);
452 }
453
454 /**
455 * Returns the current PropertyChangeSupport instance for the
456 * calling thread's context.
457 *
458 * @return this thread's context's PropertyChangeSupport
459 */
460 private synchronized PropertyChangeSupport getCurrentChangeSupport() {
461 PropertyChangeSupport changeSupport = (PropertyChangeSupport) AppContext
462 .getAppContext().get(SystemTray.class);
463
464 if (changeSupport == null) {
465 changeSupport = new PropertyChangeSupport(this );
466 AppContext.getAppContext().put(SystemTray.class,
467 changeSupport);
468 }
469 return changeSupport;
470 }
471
472 synchronized void addNotify() {
473 if (peer == null) {
474 peer = ((SunToolkit) Toolkit.getDefaultToolkit())
475 .createSystemTray(this );
476 }
477 }
478
479 static void checkSystemTrayAllowed() {
480 SecurityManager security = System.getSecurityManager();
481 if (security != null) {
482 security
483 .checkPermission(SecurityConstants.ACCESS_SYSTEM_TRAY_PERMISSION);
484 }
485 }
486 }
|