0001: /*
0002: * Copyright 2005-2007 Sun Microsystems, Inc. All Rights Reserved.
0003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0004: *
0005: * This code is free software; you can redistribute it and/or modify it
0006: * under the terms of the GNU General Public License version 2 only, as
0007: * published by the Free Software Foundation. Sun designates this
0008: * particular file as subject to the "Classpath" exception as provided
0009: * by Sun in the LICENSE file that accompanied this code.
0010: *
0011: * This code is distributed in the hope that it will be useful, but WITHOUT
0012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0014: * version 2 for more details (a copy is included in the LICENSE file that
0015: * accompanied this code).
0016: *
0017: * You should have received a copy of the GNU General Public License version
0018: * 2 along with this work; if not, write to the Free Software Foundation,
0019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0020: *
0021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
0022: * CA 95054 USA or visit www.sun.com if you need additional information or
0023: * have any questions.
0024: */
0025:
0026: package sun.awt.X11;
0027:
0028: import java.awt.*;
0029: import java.awt.event.*;
0030: import java.awt.peer.TrayIconPeer;
0031: import sun.awt.*;
0032: import java.awt.image.*;
0033: import java.text.BreakIterator;
0034: import java.util.Vector;
0035: import java.lang.reflect.Field;
0036: import java.util.logging.Logger;
0037: import java.util.logging.Level;
0038: import java.util.AbstractQueue;
0039: import java.util.concurrent.ArrayBlockingQueue;
0040: import java.security.AccessController;
0041: import java.security.PrivilegedAction;
0042: import java.lang.reflect.InvocationTargetException;
0043:
0044: public class XTrayIconPeer implements TrayIconPeer {
0045: private static final Logger ctrLog = Logger
0046: .getLogger("sun.awt.X11.XTrayIconPeer.centering");
0047:
0048: TrayIcon target;
0049: TrayIconEventProxy eventProxy;
0050: XTrayIconEmbeddedFrame eframe;
0051: TrayIconCanvas canvas;
0052: Balloon balloon;
0053: Tooltip tooltip;
0054: PopupMenu popup;
0055: String tooltipString;
0056: boolean isTrayIconDisplayed;
0057: long eframeParentID;
0058: final XEventDispatcher parentXED, eframeXED;
0059:
0060: static final XEventDispatcher dummyXED = new XEventDispatcher() {
0061: public void dispatchEvent(XEvent ev) {
0062: }
0063: };
0064:
0065: volatile boolean isDisposed;
0066:
0067: boolean isParentWindowLocated;
0068: int old_x, old_y;
0069: int ex_width, ex_height;
0070:
0071: final static int TRAY_ICON_WIDTH = 24;
0072: final static int TRAY_ICON_HEIGHT = 24;
0073:
0074: XTrayIconPeer(TrayIcon target) throws AWTException {
0075: this .target = target;
0076:
0077: eventProxy = new TrayIconEventProxy(this );
0078:
0079: canvas = new TrayIconCanvas(target, TRAY_ICON_WIDTH,
0080: TRAY_ICON_HEIGHT);
0081:
0082: eframe = new XTrayIconEmbeddedFrame();
0083:
0084: eframe.setSize(TRAY_ICON_WIDTH, TRAY_ICON_HEIGHT);
0085: eframe.add(canvas);
0086:
0087: // Fix for 6317038: as EmbeddedFrame is instance of Frame, it is blocked
0088: // by modal dialogs, but in the case of TrayIcon it shouldn't. So we
0089: // set ModalExclusion property on it.
0090: AccessController.doPrivileged(new PrivilegedAction() {
0091: public Object run() {
0092: eframe
0093: .setModalExclusionType(Dialog.ModalExclusionType.TOOLKIT_EXCLUDE);
0094: return null;
0095: }
0096: });
0097:
0098: if (XWM.getWMID() != XWM.METACITY_WM) {
0099: parentXED = dummyXED; // We don't like to leave it 'null'.
0100:
0101: } else {
0102: parentXED = new XEventDispatcher() {
0103: // It's executed under AWTLock.
0104: public void dispatchEvent(XEvent ev) {
0105: if (isDisposed()
0106: || ev.get_type() != XlibWrapper.ConfigureNotify) {
0107: return;
0108: }
0109:
0110: XConfigureEvent ce = ev.get_xconfigure();
0111:
0112: ctrLog
0113: .log(
0114: Level.FINE,
0115: "ConfigureNotify on parent of {0}: {1}x{2}+{3}+{4} (old: {5}+{6})",
0116: new Object[] { XTrayIconPeer.this ,
0117: ce.get_width(),
0118: ce.get_height(),
0119: ce.get_x(), ce.get_y(),
0120: old_x, old_y });
0121:
0122: // A workaround for Gnome/Metacity (it doesn't affect the behaviour on KDE).
0123: // On Metacity the EmbeddedFrame's parent window bounds are larger
0124: // than TrayIcon size required (that is we need a square but a rectangle
0125: // is provided by the Panel Notification Area). The parent's background color
0126: // differs from the Panel's one. To hide the background we resize parent
0127: // window so that it fits the EmbeddedFrame.
0128: // However due to resizing the parent window it loses centering in the Panel.
0129: // We center it when discovering that some of its side is of size greater
0130: // than the fixed value. Centering is being done by "X" (when the parent's width
0131: // is greater) and by "Y" (when the parent's height is greater).
0132:
0133: // Actually we need this workaround until we could detect taskbar color.
0134:
0135: if (ce.get_height() != TRAY_ICON_HEIGHT
0136: && ce.get_width() != TRAY_ICON_WIDTH) {
0137:
0138: // If both the height and the width differ from the fixed size then WM
0139: // must level at least one side to the fixed size. For some reason it may take
0140: // a few hops (even after reparenting) and we have to skip the intermediate ones.
0141: ctrLog
0142: .log(
0143: Level.FINE,
0144: "ConfigureNotify on parent of {0}. Skipping as intermediate resizing.",
0145: XTrayIconPeer.this );
0146: return;
0147:
0148: } else if (ce.get_height() > TRAY_ICON_HEIGHT) {
0149:
0150: ctrLog
0151: .log(
0152: Level.FINE,
0153: "ConfigureNotify on parent of {0}. Centering by \"Y\".",
0154: XTrayIconPeer.this );
0155:
0156: XlibWrapper.XMoveResizeWindow(XToolkit
0157: .getDisplay(), eframeParentID, ce
0158: .get_x(), ce.get_y() + ce.get_height()
0159: / 2 - TRAY_ICON_HEIGHT / 2,
0160: TRAY_ICON_WIDTH, TRAY_ICON_HEIGHT);
0161: ex_height = ce.get_height();
0162: ex_width = 0;
0163:
0164: } else if (ce.get_width() > TRAY_ICON_WIDTH) {
0165:
0166: ctrLog
0167: .log(
0168: Level.FINE,
0169: "ConfigureNotify on parent of {0}. Centering by \"X\".",
0170: XTrayIconPeer.this );
0171:
0172: XlibWrapper.XMoveResizeWindow(XToolkit
0173: .getDisplay(), eframeParentID, ce
0174: .get_x()
0175: + ce.get_width()
0176: / 2
0177: - TRAY_ICON_WIDTH
0178: / 2, ce.get_y(), TRAY_ICON_WIDTH,
0179: TRAY_ICON_HEIGHT);
0180: ex_width = ce.get_width();
0181: ex_height = 0;
0182:
0183: } else if (isParentWindowLocated
0184: && ce.get_x() != old_x
0185: && ce.get_y() != old_y) {
0186: // If moving by both "X" and "Y".
0187: // When some tray icon gets removed from the tray, a Java icon may be repositioned.
0188: // In this case the parent window also lose centering. We have to restore it.
0189:
0190: if (ex_height != 0) {
0191:
0192: ctrLog
0193: .log(
0194: Level.FINE,
0195: "ConfigureNotify on parent of {0}. Move detected. Centering by \"Y\".",
0196: XTrayIconPeer.this );
0197:
0198: XlibWrapper.XMoveWindow(XToolkit
0199: .getDisplay(), eframeParentID, ce
0200: .get_x(), ce.get_y() + ex_height
0201: / 2 - TRAY_ICON_HEIGHT / 2);
0202:
0203: } else if (ex_width != 0) {
0204:
0205: ctrLog
0206: .log(
0207: Level.FINE,
0208: "ConfigureNotify on parent of {0}. Move detected. Centering by \"X\".",
0209: XTrayIconPeer.this );
0210:
0211: XlibWrapper.XMoveWindow(XToolkit
0212: .getDisplay(), eframeParentID, ce
0213: .get_x()
0214: + ex_width
0215: / 2
0216: - TRAY_ICON_WIDTH
0217: / 2, ce.get_y());
0218: } else {
0219: ctrLog
0220: .log(
0221: Level.FINE,
0222: "ConfigureNotify on parent of {0}. Move detected. Skipping.",
0223: XTrayIconPeer.this );
0224: }
0225: }
0226: old_x = ce.get_x();
0227: old_y = ce.get_y();
0228: isParentWindowLocated = true;
0229: }
0230: };
0231: }
0232: eframeXED = new XEventDispatcher() {
0233: // It's executed under AWTLock.
0234: XTrayIconPeer xtiPeer = XTrayIconPeer.this ;
0235:
0236: public void dispatchEvent(XEvent ev) {
0237: if (isDisposed()
0238: || ev.get_type() != XlibWrapper.ReparentNotify) {
0239: return;
0240: }
0241:
0242: XReparentEvent re = ev.get_xreparent();
0243: eframeParentID = re.get_parent();
0244:
0245: if (eframeParentID == XToolkit.getDefaultRootWindow()) {
0246:
0247: if (isTrayIconDisplayed) { // most likely Notification Area was removed
0248: SunToolkit.executeOnEventHandlerThread(
0249: xtiPeer.target, new Runnable() {
0250: public void run() {
0251: SystemTray.getSystemTray()
0252: .remove(xtiPeer.target);
0253: }
0254: });
0255: }
0256: return;
0257: }
0258:
0259: if (!isTrayIconDisplayed) {
0260: addXED(eframeParentID, parentXED,
0261: XlibWrapper.StructureNotifyMask);
0262:
0263: isTrayIconDisplayed = true;
0264: XToolkit.awtLockNotifyAll();
0265: }
0266: }
0267: };
0268:
0269: addXED(getWindow(), eframeXED, XlibWrapper.StructureNotifyMask);
0270:
0271: XSystemTrayPeer.getPeerInstance().addTrayIcon(this ); // throws AWTException
0272:
0273: // Wait till the EmbeddedFrame is reparented
0274: long start = System.currentTimeMillis();
0275: final long PERIOD = 2000L;
0276: XToolkit.awtLock();
0277: try {
0278: while (!isTrayIconDisplayed) {
0279: try {
0280: XToolkit.awtLockWait(PERIOD);
0281: } catch (InterruptedException e) {
0282: break;
0283: }
0284: if (System.currentTimeMillis() - start > PERIOD) {
0285: break;
0286: }
0287: }
0288: } finally {
0289: XToolkit.awtUnlock();
0290: }
0291:
0292: // This is unlikely to happen.
0293: if (!isTrayIconDisplayed || eframeParentID == 0
0294: || eframeParentID == XToolkit.getDefaultRootWindow()) {
0295: throw new AWTException("TrayIcon couldn't be displayed.");
0296: }
0297:
0298: eframe.setVisible(true);
0299: updateImage();
0300:
0301: balloon = new Balloon(this , eframe);
0302: tooltip = new Tooltip(this , eframe);
0303:
0304: addListeners();
0305: }
0306:
0307: public void dispose() {
0308: if (SunToolkit.isDispatchThreadForAppContext(target)) {
0309: disposeOnEDT();
0310: } else {
0311: try {
0312: SunToolkit.executeOnEDTAndWait(target, new Runnable() {
0313: public void run() {
0314: disposeOnEDT();
0315: }
0316: });
0317: } catch (InterruptedException ie) {
0318: } catch (InvocationTargetException ite) {
0319: }
0320: }
0321: }
0322:
0323: private void disposeOnEDT() {
0324: // All actions that is to be synchronized with disposal
0325: // should be executed either under AWTLock, or on EDT.
0326: // isDisposed value must be checked.
0327: XToolkit.awtLock();
0328: isDisposed = true;
0329: XToolkit.awtUnlock();
0330:
0331: removeXED(getWindow(), eframeXED);
0332: removeXED(eframeParentID, parentXED);
0333: eframe.realDispose();
0334: balloon.dispose();
0335: isTrayIconDisplayed = false;
0336: XToolkit.targetDisposedPeer(target, this );
0337: }
0338:
0339: public static void suppressWarningString(Window w) {
0340: WindowAccessor.setTrayIconWindow(w, true);
0341: }
0342:
0343: public void setToolTip(String tooltip) {
0344: tooltipString = tooltip;
0345: }
0346:
0347: public void updateImage() {
0348: Runnable r = new Runnable() {
0349: public void run() {
0350: canvas.updateImage(target.getImage());
0351: }
0352: };
0353:
0354: if (!SunToolkit.isDispatchThreadForAppContext(target)) {
0355: SunToolkit.executeOnEventHandlerThread(target, r);
0356: } else {
0357: r.run();
0358: }
0359: }
0360:
0361: public void displayMessage(String caption, String text,
0362: String messageType) {
0363: Point loc = getLocationOnScreen();
0364: Rectangle screen = eframe.getGraphicsConfiguration()
0365: .getBounds();
0366:
0367: // Check if the tray icon is in the bounds of a screen.
0368: if (!(loc.x < screen.x || loc.x >= screen.x + screen.width
0369: || loc.y < screen.y || loc.y >= screen.y
0370: + screen.height)) {
0371: balloon.display(caption, text, messageType);
0372: }
0373: }
0374:
0375: // It's synchronized with disposal by EDT.
0376: public void showPopupMenu(int x, int y) {
0377: if (isDisposed())
0378: return;
0379:
0380: assert SunToolkit.isDispatchThreadForAppContext(target);
0381:
0382: PopupMenu newPopup = target.getPopupMenu();
0383: if (popup != newPopup) {
0384: if (popup != null) {
0385: eframe.remove(popup);
0386: }
0387: if (newPopup != null) {
0388: eframe.add(newPopup);
0389: }
0390: popup = newPopup;
0391: }
0392:
0393: if (popup != null) {
0394: Point loc = ((XBaseWindow) eframe.getPeer())
0395: .toLocal(new Point(x, y));
0396: popup.show(eframe, loc.x, loc.y);
0397: }
0398: }
0399:
0400: // ******************************************************************
0401: // ******************************************************************
0402:
0403: private void addXED(long window, XEventDispatcher xed, long mask) {
0404: if (window == 0) {
0405: return;
0406: }
0407: XToolkit.awtLock();
0408: try {
0409: XlibWrapper.XSelectInput(XToolkit.getDisplay(), window,
0410: mask);
0411: } finally {
0412: XToolkit.awtUnlock();
0413: }
0414: XToolkit.addEventDispatcher(window, xed);
0415: }
0416:
0417: private void removeXED(long window, XEventDispatcher xed) {
0418: if (window == 0) {
0419: return;
0420: }
0421: XToolkit.awtLock();
0422: try {
0423: XToolkit.removeEventDispatcher(window, xed);
0424: } finally {
0425: XToolkit.awtUnlock();
0426: }
0427: }
0428:
0429: // Private method for testing purposes.
0430: private Point getLocationOnScreen() {
0431: return eframe.getLocationOnScreen();
0432: }
0433:
0434: private Rectangle getBounds() {
0435: Point loc = getLocationOnScreen();
0436: return new Rectangle(loc.x, loc.y, loc.x + TRAY_ICON_WIDTH,
0437: loc.y + TRAY_ICON_HEIGHT);
0438: }
0439:
0440: void addListeners() {
0441: canvas.addMouseListener(eventProxy);
0442: canvas.addMouseMotionListener(eventProxy);
0443: }
0444:
0445: long getWindow() {
0446: return ((XEmbeddedFramePeer) eframe.getPeer()).getWindow();
0447: }
0448:
0449: boolean isDisposed() {
0450: return isDisposed;
0451: }
0452:
0453: static class TrayIconEventProxy implements MouseListener,
0454: MouseMotionListener {
0455: XTrayIconPeer xtiPeer;
0456:
0457: TrayIconEventProxy(XTrayIconPeer xtiPeer) {
0458: this .xtiPeer = xtiPeer;
0459: }
0460:
0461: public void handleEvent(MouseEvent e) {
0462: // Event handling is synchronized with disposal by EDT.
0463: if (xtiPeer.isDisposed()) {
0464: return;
0465: }
0466: Point coord = XBaseWindow
0467: .toOtherWindow(xtiPeer.getWindow(), XToolkit
0468: .getDefaultRootWindow(), e.getX(), e.getY());
0469:
0470: if (e.isPopupTrigger()) {
0471: xtiPeer.showPopupMenu(coord.x, coord.y);
0472: }
0473:
0474: e.translatePoint(coord.x - e.getX(), coord.y - e.getY());
0475: e.setSource(xtiPeer.target);
0476: Toolkit.getDefaultToolkit().getSystemEventQueue()
0477: .postEvent(e);
0478: }
0479:
0480: public void mouseClicked(MouseEvent e) {
0481: if ((e.getClickCount() > 1 || xtiPeer.balloon.isVisible())
0482: && e.getButton() == MouseEvent.BUTTON1) {
0483: ActionEvent aev = new ActionEvent(xtiPeer.target,
0484: ActionEvent.ACTION_PERFORMED, xtiPeer.target
0485: .getActionCommand(), e.getWhen(), e
0486: .getModifiers());
0487: Toolkit.getDefaultToolkit().getSystemEventQueue()
0488: .postEvent(aev);
0489: }
0490: if (xtiPeer.balloon.isVisible()) {
0491: xtiPeer.balloon.hide();
0492: }
0493: handleEvent(e);
0494: }
0495:
0496: public void mouseEntered(MouseEvent e) {
0497: xtiPeer.tooltip.enter();
0498: handleEvent(e);
0499: }
0500:
0501: public void mouseExited(MouseEvent e) {
0502: xtiPeer.tooltip.exit();
0503: handleEvent(e);
0504: }
0505:
0506: public void mousePressed(MouseEvent e) {
0507: handleEvent(e);
0508: }
0509:
0510: public void mouseReleased(MouseEvent e) {
0511: handleEvent(e);
0512: }
0513:
0514: public void mouseDragged(MouseEvent e) {
0515: handleEvent(e);
0516: }
0517:
0518: public void mouseMoved(MouseEvent e) {
0519: handleEvent(e);
0520: }
0521: }
0522:
0523: static boolean isTrayIconStuffWindow(Window w) {
0524: return (w instanceof Tooltip) || (w instanceof Balloon)
0525: || (w instanceof XTrayIconEmbeddedFrame);
0526: }
0527:
0528: // ***************************************
0529: // Special embedded frame for tray icon
0530: // ***************************************
0531:
0532: private static class XTrayIconEmbeddedFrame extends XEmbeddedFrame {
0533: public XTrayIconEmbeddedFrame() {
0534: super (XToolkit.getDefaultRootWindow(), true, true);
0535: }
0536:
0537: public boolean isUndecorated() {
0538: return true;
0539: }
0540:
0541: public boolean isResizable() {
0542: return false;
0543: }
0544:
0545: // embedded frame for tray icon shouldn't be disposed by anyone except tray icon
0546: public void dispose() {
0547: }
0548:
0549: public void realDispose() {
0550: super .dispose();
0551: }
0552: };
0553:
0554: // ***************************************
0555: // Classes for painting an image on canvas
0556: // ***************************************
0557:
0558: static class TrayIconCanvas extends IconCanvas {
0559: TrayIcon target;
0560: boolean autosize;
0561:
0562: TrayIconCanvas(TrayIcon target, int width, int height) {
0563: super (width, height);
0564: this .target = target;
0565: }
0566:
0567: // Invoke on EDT.
0568: protected void repaintImage(boolean doClear) {
0569: boolean old_autosize = autosize;
0570: autosize = target.isImageAutoSize();
0571:
0572: curW = autosize ? width : image.getWidth(observer);
0573: curH = autosize ? height : image.getHeight(observer);
0574:
0575: super .repaintImage(doClear || (old_autosize != autosize));
0576: }
0577: }
0578:
0579: static class IconCanvas extends Canvas {
0580: volatile Image image;
0581: IconObserver observer;
0582: int width, height;
0583: int curW, curH;
0584:
0585: IconCanvas(int width, int height) {
0586: this .width = curW = width;
0587: this .height = curH = height;
0588: }
0589:
0590: // Invoke on EDT.
0591: public void updateImage(Image image) {
0592: this .image = image;
0593: if (observer == null) {
0594: observer = new IconObserver();
0595: }
0596: repaintImage(true);
0597: }
0598:
0599: // Invoke on EDT.
0600: protected void repaintImage(boolean doClear) {
0601: Graphics g = getGraphics();
0602: if (g != null) {
0603: try {
0604: if (isVisible()) {
0605: if (doClear) {
0606: update(g);
0607: } else {
0608: paint(g);
0609: }
0610: }
0611: } finally {
0612: g.dispose();
0613: }
0614: }
0615: }
0616:
0617: // Invoke on EDT.
0618: public void paint(Graphics g) {
0619: if (g != null && curW > 0 && curH > 0) {
0620: BufferedImage bufImage = new BufferedImage(curW, curH,
0621: BufferedImage.TYPE_INT_ARGB);
0622: Graphics2D gr = bufImage.createGraphics();
0623: if (gr != null) {
0624: try {
0625: gr.setColor(getBackground());
0626: gr.fillRect(0, 0, curW, curH);
0627: gr.drawImage(image, 0, 0, curW, curH, observer);
0628: gr.dispose();
0629:
0630: g.drawImage(bufImage, 0, 0, curW, curH, null);
0631: } finally {
0632: gr.dispose();
0633: }
0634: }
0635: }
0636: }
0637:
0638: class IconObserver implements ImageObserver {
0639: public boolean imageUpdate(final Image image,
0640: final int flags, int x, int y, int width, int height) {
0641: if (image != IconCanvas.this .image || // if the image has been changed
0642: !IconCanvas.this .isVisible()) {
0643: return false;
0644: }
0645: if ((flags & (ImageObserver.FRAMEBITS
0646: | ImageObserver.ALLBITS | ImageObserver.WIDTH | ImageObserver.HEIGHT)) != 0) {
0647: SunToolkit.executeOnEventHandlerThread(
0648: IconCanvas.this , new Runnable() {
0649: public void run() {
0650: repaintImage(false);
0651: }
0652: });
0653: }
0654: return (flags & ImageObserver.ALLBITS) == 0;
0655: }
0656: }
0657: }
0658:
0659: // ***************************************
0660: // Classes for toolitp and balloon windows
0661: // ***************************************
0662:
0663: static class Tooltip extends InfoWindow {
0664: XTrayIconPeer xtiPeer;
0665: Label textLabel = new Label("");
0666: Runnable starter = new Runnable() {
0667: public void run() {
0668: display();
0669: }
0670: };
0671:
0672: final static int TOOLTIP_SHOW_TIME = 10000;
0673: final static int TOOLTIP_START_DELAY_TIME = 1000;
0674: final static int TOOLTIP_MAX_LENGTH = 64;
0675: final static int TOOLTIP_MOUSE_CURSOR_INDENT = 5;
0676: final static Color TOOLTIP_BACKGROUND_COLOR = new Color(255,
0677: 255, 220);
0678: final static Font TOOLTIP_TEXT_FONT = XWindow.defaultFont;
0679:
0680: Tooltip(XTrayIconPeer xtiPeer, Frame parent) {
0681: super (parent, Color.black);
0682: this .xtiPeer = xtiPeer;
0683:
0684: suppressWarningString(this );
0685:
0686: setCloser(null, TOOLTIP_SHOW_TIME);
0687: textLabel.setBackground(TOOLTIP_BACKGROUND_COLOR);
0688: textLabel.setFont(TOOLTIP_TEXT_FONT);
0689: add(textLabel);
0690: }
0691:
0692: /*
0693: * WARNING: this method is executed on Toolkit thread!
0694: */
0695: void display() {
0696: String tip = xtiPeer.tooltipString;
0697: if (tip == null) {
0698: return;
0699: } else if (tip.length() > TOOLTIP_MAX_LENGTH) {
0700: textLabel.setText(tip.substring(0, TOOLTIP_MAX_LENGTH));
0701: } else {
0702: textLabel.setText(tip);
0703: }
0704:
0705: // Execute on EDT to avoid deadlock (see 6280857).
0706: SunToolkit.executeOnEventHandlerThread(xtiPeer.target,
0707: new Runnable() {
0708: public void run() {
0709: if (xtiPeer.isDisposed()) {
0710: return;
0711: }
0712: Point pointer = (Point) AccessController
0713: .doPrivileged(new PrivilegedAction() {
0714: public Object run() {
0715: if (!isPointerOverTrayIcon(xtiPeer
0716: .getBounds())) {
0717: return null;
0718: }
0719: return MouseInfo
0720: .getPointerInfo()
0721: .getLocation();
0722: }
0723: });
0724: if (pointer == null) {
0725: return;
0726: }
0727: show(new Point(pointer.x, pointer.y),
0728: TOOLTIP_MOUSE_CURSOR_INDENT);
0729: }
0730: });
0731: }
0732:
0733: void enter() {
0734: XToolkit.schedule(starter, TOOLTIP_START_DELAY_TIME);
0735: }
0736:
0737: void exit() {
0738: XToolkit.remove(starter);
0739: if (isVisible()) {
0740: hide();
0741: }
0742: }
0743:
0744: boolean isPointerOverTrayIcon(Rectangle trayRect) {
0745: Point p = MouseInfo.getPointerInfo().getLocation();
0746: return !(p.x < trayRect.x
0747: || p.x > (trayRect.x + trayRect.width)
0748: || p.y < trayRect.y || p.y > (trayRect.y + trayRect.height));
0749: }
0750: }
0751:
0752: static class Balloon extends InfoWindow {
0753: final static int BALLOON_SHOW_TIME = 10000;
0754: final static int BALLOON_TEXT_MAX_LENGTH = 256;
0755: final static int BALLOON_WORD_LINE_MAX_LENGTH = 16;
0756: final static int BALLOON_WORD_LINE_MAX_COUNT = 4;
0757: final static int BALLOON_ICON_WIDTH = 32;
0758: final static int BALLOON_ICON_HEIGHT = 32;
0759: final static int BALLOON_TRAY_ICON_INDENT = 0;
0760: final static Color BALLOON_CAPTION_BACKGROUND_COLOR = new Color(
0761: 200, 200, 255);
0762: final static Font BALLOON_CAPTION_FONT = new Font(Font.DIALOG,
0763: Font.BOLD, 12);
0764:
0765: XTrayIconPeer xtiPeer;
0766: Panel mainPanel = new Panel();
0767: Panel captionPanel = new Panel();
0768: Label captionLabel = new Label("");
0769: Button closeButton = new Button("X");
0770: Panel textPanel = new Panel();
0771: IconCanvas iconCanvas = new IconCanvas(BALLOON_ICON_WIDTH,
0772: BALLOON_ICON_HEIGHT);
0773: Label[] lineLabels = new Label[BALLOON_WORD_LINE_MAX_COUNT];
0774: ActionPerformer ap = new ActionPerformer();
0775:
0776: Image iconImage;
0777: Image errorImage;
0778: Image warnImage;
0779: Image infoImage;
0780: boolean gtkImagesLoaded;
0781:
0782: Displayer displayer = new Displayer();
0783:
0784: Balloon(final XTrayIconPeer xtiPeer, Frame parent) {
0785: super (parent, new Color(90, 80, 190));
0786: this .xtiPeer = xtiPeer;
0787:
0788: suppressWarningString(this );
0789:
0790: setCloser(new Runnable() {
0791: public void run() {
0792: if (textPanel != null) {
0793: textPanel.removeAll();
0794: textPanel.setSize(0, 0);
0795: iconCanvas.setSize(0, 0);
0796: XToolkit.awtLock();
0797: try {
0798: displayer.isDisplayed = false;
0799: XToolkit.awtLockNotifyAll();
0800: } finally {
0801: XToolkit.awtUnlock();
0802: }
0803: }
0804: }
0805: }, BALLOON_SHOW_TIME);
0806:
0807: add(mainPanel);
0808:
0809: captionLabel.setFont(BALLOON_CAPTION_FONT);
0810: captionLabel.addMouseListener(ap);
0811:
0812: captionPanel.setLayout(new BorderLayout());
0813: captionPanel.add(captionLabel, BorderLayout.WEST);
0814: captionPanel.add(closeButton, BorderLayout.EAST);
0815: captionPanel
0816: .setBackground(BALLOON_CAPTION_BACKGROUND_COLOR);
0817: captionPanel.addMouseListener(ap);
0818:
0819: closeButton.addActionListener(new ActionListener() {
0820: public void actionPerformed(ActionEvent e) {
0821: hide();
0822: }
0823: });
0824:
0825: mainPanel.setLayout(new BorderLayout());
0826: mainPanel.setBackground(Color.white);
0827: mainPanel.add(captionPanel, BorderLayout.NORTH);
0828: mainPanel.add(iconCanvas, BorderLayout.WEST);
0829: mainPanel.add(textPanel, BorderLayout.CENTER);
0830:
0831: iconCanvas.addMouseListener(ap);
0832:
0833: for (int i = 0; i < BALLOON_WORD_LINE_MAX_COUNT; i++) {
0834: lineLabels[i] = new Label();
0835: lineLabels[i].addMouseListener(ap);
0836: lineLabels[i].setBackground(Color.white);
0837: }
0838:
0839: displayer.start();
0840: }
0841:
0842: void display(String caption, String text, String messageType) {
0843: if (!gtkImagesLoaded) {
0844: loadGtkImages();
0845: }
0846: displayer.display(caption, text, messageType);
0847: }
0848:
0849: private void _display(String caption, String text,
0850: String messageType) {
0851: captionLabel.setText(caption);
0852:
0853: BreakIterator iter = BreakIterator.getWordInstance();
0854: if (text != null) {
0855: iter.setText(text);
0856: int start = iter.first(), end;
0857: int nLines = 0;
0858:
0859: do {
0860: end = iter.next();
0861:
0862: if (end == BreakIterator.DONE
0863: || text.substring(start, end).length() >= 50) {
0864: lineLabels[nLines].setText(text.substring(
0865: start, end == BreakIterator.DONE ? iter
0866: .last() : end));
0867: textPanel.add(lineLabels[nLines++]);
0868: start = end;
0869: }
0870: if (nLines == BALLOON_WORD_LINE_MAX_COUNT) {
0871: if (end != BreakIterator.DONE) {
0872: lineLabels[nLines - 1].setText(new String(
0873: lineLabels[nLines - 1].getText()
0874: + " ..."));
0875: }
0876: break;
0877: }
0878: } while (end != BreakIterator.DONE);
0879:
0880: textPanel.setLayout(new GridLayout(nLines, 1));
0881: }
0882:
0883: if ("ERROR".equals(messageType)) {
0884: iconImage = errorImage;
0885: } else if ("WARNING".equals(messageType)) {
0886: iconImage = warnImage;
0887: } else if ("INFO".equals(messageType)) {
0888: iconImage = infoImage;
0889: } else {
0890: iconImage = null;
0891: }
0892:
0893: if (iconImage != null) {
0894: Dimension tpSize = textPanel.getSize();
0895: iconCanvas
0896: .setSize(
0897: BALLOON_ICON_WIDTH,
0898: (BALLOON_ICON_HEIGHT > tpSize.height ? BALLOON_ICON_HEIGHT
0899: : tpSize.height));
0900: }
0901:
0902: SunToolkit.executeOnEventHandlerThread(xtiPeer.target,
0903: new Runnable() {
0904: public void run() {
0905: if (xtiPeer.isDisposed()) {
0906: return;
0907: }
0908: Point parLoc = getParent()
0909: .getLocationOnScreen();
0910: Dimension parSize = getParent().getSize();
0911: show(new Point(
0912: parLoc.x + parSize.width / 2,
0913: parLoc.y + parSize.height / 2),
0914: BALLOON_TRAY_ICON_INDENT);
0915: if (iconImage != null) {
0916: iconCanvas.updateImage(iconImage); // call it after the show(..) above
0917: }
0918: }
0919: });
0920: }
0921:
0922: public void dispose() {
0923: displayer.interrupt();
0924: super .dispose();
0925: }
0926:
0927: void loadGtkImages() {
0928: if (!gtkImagesLoaded) {
0929: errorImage = (Image) Toolkit.getDefaultToolkit()
0930: .getDesktopProperty(
0931: "gtk.icon.gtk-dialog-error.6.rtl");
0932: warnImage = (Image) Toolkit.getDefaultToolkit()
0933: .getDesktopProperty(
0934: "gtk.icon.gtk-dialog-warning.6.rtl");
0935: infoImage = (Image) Toolkit.getDefaultToolkit()
0936: .getDesktopProperty(
0937: "gtk.icon.gtk-dialog-info.6.rtl");
0938: gtkImagesLoaded = true;
0939: }
0940: }
0941:
0942: class ActionPerformer extends MouseAdapter {
0943: public void mouseClicked(MouseEvent e) {
0944: // hide the balloon by any click
0945: hide();
0946: if (e.getButton() == MouseEvent.BUTTON1) {
0947: ActionEvent aev = new ActionEvent(xtiPeer.target,
0948: ActionEvent.ACTION_PERFORMED,
0949: xtiPeer.target.getActionCommand(), e
0950: .getWhen(), e.getModifiers());
0951: Toolkit.getDefaultToolkit().getSystemEventQueue()
0952: .postEvent(aev);
0953: }
0954: }
0955: }
0956:
0957: class Displayer extends Thread {
0958: final int MAX_CONCURRENT_MSGS = 10;
0959:
0960: ArrayBlockingQueue<Message> messageQueue = new ArrayBlockingQueue<Message>(
0961: MAX_CONCURRENT_MSGS);
0962: boolean isDisplayed;
0963:
0964: Displayer() {
0965: setDaemon(true);
0966: }
0967:
0968: public void run() {
0969: while (true) {
0970: Message msg = null;
0971: try {
0972: msg = (Message) messageQueue.take();
0973: } catch (InterruptedException e) {
0974: return;
0975: }
0976:
0977: /*
0978: * Wait till the previous message is displayed if any
0979: */
0980: XToolkit.awtLock();
0981: try {
0982: while (isDisplayed) {
0983: try {
0984: XToolkit.awtLockWait();
0985: } catch (InterruptedException e) {
0986: return;
0987: }
0988: }
0989: isDisplayed = true;
0990: } finally {
0991: XToolkit.awtUnlock();
0992: }
0993: _display(msg.caption, msg.text, msg.messageType);
0994: }
0995: }
0996:
0997: void display(String caption, String text, String messageType) {
0998: messageQueue.offer(new Message(caption, text,
0999: messageType));
1000: }
1001: }
1002:
1003: class Message {
1004: String caption, text, messageType;
1005:
1006: Message(String caption, String text, String messageType) {
1007: this .caption = caption;
1008: this .text = text;
1009: this .messageType = messageType;
1010: }
1011: }
1012: }
1013:
1014: static class InfoWindow extends Window {
1015: Container container;
1016: Closer closer;
1017:
1018: InfoWindow(Frame parent, Color borderColor) {
1019: super (parent);
1020: container = new Container() {
1021: public Insets getInsets() {
1022: return new Insets(1, 1, 1, 1);
1023: }
1024: };
1025: setLayout(new BorderLayout());
1026: setBackground(borderColor);
1027: add(container, BorderLayout.CENTER);
1028: container.setLayout(new BorderLayout());
1029:
1030: closer = new Closer();
1031: }
1032:
1033: public Component add(Component c) {
1034: container.add(c, BorderLayout.CENTER);
1035: return c;
1036: }
1037:
1038: void setCloser(Runnable action, int time) {
1039: closer.set(action, time);
1040: }
1041:
1042: // Must be executed on EDT.
1043: protected void show(Point corner, int indent) {
1044: assert SunToolkit
1045: .isDispatchThreadForAppContext(InfoWindow.this );
1046:
1047: pack();
1048:
1049: Dimension size = getSize();
1050: // TODO: When 6356322 is fixed we should get screen bounds in
1051: // this way: eframe.getGraphicsConfiguration().getBounds().
1052: Dimension scrSize = Toolkit.getDefaultToolkit()
1053: .getScreenSize();
1054:
1055: if (corner.x < scrSize.width / 2
1056: && corner.y < scrSize.height / 2) { // 1st square
1057: setLocation(corner.x + indent, corner.y + indent);
1058:
1059: } else if (corner.x >= scrSize.width / 2
1060: && corner.y < scrSize.height / 2) { // 2nd square
1061: setLocation(corner.x - indent - size.width, corner.y
1062: + indent);
1063:
1064: } else if (corner.x < scrSize.width / 2
1065: && corner.y >= scrSize.height / 2) { // 3rd square
1066: setLocation(corner.x + indent, corner.y - indent
1067: - size.height);
1068:
1069: } else if (corner.x >= scrSize.width / 2
1070: && corner.y >= scrSize.height / 2) { // 4th square
1071: setLocation(corner.x - indent - size.width, corner.y
1072: - indent - size.height);
1073: }
1074:
1075: InfoWindow.super .show();
1076: InfoWindow.this .closer.schedule();
1077: }
1078:
1079: public void hide() {
1080: closer.close();
1081: }
1082:
1083: class Closer implements Runnable {
1084: Runnable action;
1085: int time;
1086:
1087: public void run() {
1088: doClose();
1089: }
1090:
1091: void set(Runnable action, int time) {
1092: this .action = action;
1093: this .time = time;
1094: }
1095:
1096: void schedule() {
1097: XToolkit.schedule(this , time);
1098: }
1099:
1100: void close() {
1101: XToolkit.remove(this );
1102: doClose();
1103: }
1104:
1105: // WARNING: this method may be executed on Toolkit thread.
1106: private void doClose() {
1107: SunToolkit.executeOnEventHandlerThread(InfoWindow.this ,
1108: new Runnable() {
1109: public void run() {
1110: InfoWindow.super.hide();
1111: invalidate();
1112: if (action != null) {
1113: action.run();
1114: }
1115: }
1116: });
1117: }
1118: }
1119: }
1120: }
|