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: }
|