1 /*
   2  * Copyright (c) 2005, 2017, 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 sun.awt.X11;
  27 
  28 import java.awt.*;
  29 import java.awt.event.*;
  30 import java.awt.peer.TrayIconPeer;
  31 import sun.awt.*;
  32 import java.awt.image.*;
  33 import java.security.AccessController;
  34 import java.security.PrivilegedAction;
  35 import java.lang.reflect.InvocationTargetException;
  36 import sun.util.logging.PlatformLogger;
  37 import java.awt.geom.AffineTransform;
  38 import sun.java2d.pipe.Region;
  39 
  40 public class XTrayIconPeer implements TrayIconPeer,
  41        InfoWindow.Balloon.LiveArguments,
  42        InfoWindow.Tooltip.LiveArguments
  43 {
  44     private static final PlatformLogger ctrLog = PlatformLogger.getLogger("sun.awt.X11.XTrayIconPeer.centering");
  45 
  46     TrayIcon target;
  47     TrayIconEventProxy eventProxy;
  48     XTrayIconEmbeddedFrame eframe;
  49     TrayIconCanvas canvas;
  50     InfoWindow.Balloon balloon;
  51     InfoWindow.Tooltip tooltip;
  52     PopupMenu popup;
  53     String tooltipString;
  54     boolean isTrayIconDisplayed;
  55     long eframeParentID;
  56     final XEventDispatcher parentXED, eframeXED;
  57 
  58     static final XEventDispatcher dummyXED = new XEventDispatcher() {
  59             public void dispatchEvent(XEvent ev) {}
  60         };
  61 
  62     volatile boolean isDisposed;
  63 
  64     boolean isParentWindowLocated;
  65     int old_x, old_y;
  66     int ex_width, ex_height;
  67 
  68     static final int TRAY_ICON_WIDTH = 24;
  69     static final int TRAY_ICON_HEIGHT = 24;
  70     int tray_icon_width = TRAY_ICON_WIDTH;
  71     int tray_icon_height = TRAY_ICON_HEIGHT;
  72 
  73     XTrayIconPeer(TrayIcon target)
  74       throws AWTException
  75     {
  76         this.target = target;
  77         AffineTransform tx = GraphicsEnvironment.getLocalGraphicsEnvironment().
  78                      getDefaultScreenDevice().getDefaultConfiguration().
  79                      getDefaultTransform();
  80         tray_icon_width = Region.clipScale(TRAY_ICON_WIDTH, tx.getScaleX());
  81         tray_icon_height = Region.clipScale(TRAY_ICON_HEIGHT, tx.getScaleY());
  82 
  83         eventProxy = new TrayIconEventProxy(this);
  84 
  85         canvas = new TrayIconCanvas(target, tray_icon_width, tray_icon_height);
  86 
  87         eframe = new XTrayIconEmbeddedFrame();
  88 
  89         eframe.setSize(tray_icon_width, tray_icon_height);
  90         eframe.add(canvas);
  91 
  92         // Fix for 6317038: as EmbeddedFrame is instance of Frame, it is blocked
  93         // by modal dialogs, but in the case of TrayIcon it shouldn't. So we
  94         // set ModalExclusion property on it.
  95         AccessController.doPrivileged(new PrivilegedAction<Object>() {
  96             public Object run() {
  97                 eframe.setModalExclusionType(Dialog.ModalExclusionType.TOOLKIT_EXCLUDE);
  98                 return null;
  99             }
 100         });
 101 
 102 
 103         if (XWM.getWMID() != XWM.METACITY_WM) {
 104             parentXED = dummyXED; // We don't like to leave it 'null'.
 105         } else {
 106             parentXED = new XEventDispatcher() {
 107                 // It's executed under AWTLock.
 108                 public void dispatchEvent(XEvent ev) {
 109                     if (isDisposed() || ev.get_type() != XConstants.ConfigureNotify) {
 110                         return;
 111                     }
 112 
 113                     XConfigureEvent ce = ev.get_xconfigure();
 114 
 115                     if (ctrLog.isLoggable(PlatformLogger.Level.FINE)) {
 116                         ctrLog.fine("ConfigureNotify on parent of {0}: {1}x{2}+{3}+{4} (old: {5}+{6})",
 117                                 XTrayIconPeer.this, ce.get_width(), ce.get_height(),
 118                                 ce.get_x(), ce.get_y(), old_x, old_y);
 119                     }
 120 
 121                     // A workaround for Gnome/Metacity (it doesn't affect the behaviour on KDE).
 122                     // On Metacity the EmbeddedFrame's parent window bounds are larger
 123                     // than TrayIcon size required (that is we need a square but a rectangle
 124                     // is provided by the Panel Notification Area). The parent's background color
 125                     // differs from the Panel's one. To hide the background we resize parent
 126                     // window so that it fits the EmbeddedFrame.
 127                     // However due to resizing the parent window it loses centering in the Panel.
 128                     // We center it when discovering that some of its side is of size greater
 129                     // than the fixed value. Centering is being done by "X" (when the parent's width
 130                     // is greater) and by "Y" (when the parent's height is greater).
 131 
 132                     // Actually we need this workaround until we could detect taskbar color.
 133 
 134                     if (ce.get_height() != tray_icon_height && ce.get_width() != tray_icon_width) {
 135 
 136                         // If both the height and the width differ from the fixed size then WM
 137                         // must level at least one side to the fixed size. For some reason it may take
 138                         // a few hops (even after reparenting) and we have to skip the intermediate ones.
 139                         if (ctrLog.isLoggable(PlatformLogger.Level.FINE)) {
 140                             ctrLog.fine("ConfigureNotify on parent of {0}. Skipping as intermediate resizing.",
 141                                     XTrayIconPeer.this);
 142                         }
 143                         return;
 144 
 145                     } else if (ce.get_height() > tray_icon_height) {
 146 
 147                         if (ctrLog.isLoggable(PlatformLogger.Level.FINE)) {
 148                             ctrLog.fine("ConfigureNotify on parent of {0}. Centering by \"Y\".",
 149                                     XTrayIconPeer.this);
 150                         }
 151 
 152                         XlibWrapper.XMoveResizeWindow(XToolkit.getDisplay(), eframeParentID,
 153                                                       ce.get_x(),
 154                                                       ce.get_y()+ce.get_height()/2-tray_icon_height/2,
 155                                                       tray_icon_width,
 156                                                       tray_icon_height);
 157                         ex_height = ce.get_height();
 158                         ex_width = 0;
 159 
 160                     } else if (ce.get_width() > tray_icon_width) {
 161 
 162                         if (ctrLog.isLoggable(PlatformLogger.Level.FINE)) {
 163                             ctrLog.fine("ConfigureNotify on parent of {0}. Centering by \"X\".",
 164                                     XTrayIconPeer.this);
 165                         }
 166 
 167                         XlibWrapper.XMoveResizeWindow(XToolkit.getDisplay(), eframeParentID,
 168                                                       ce.get_x()+ce.get_width()/2 - tray_icon_width/2,
 169                                                       ce.get_y(),
 170                                                       tray_icon_width,
 171                                                       tray_icon_height);
 172                         ex_width = ce.get_width();
 173                         ex_height = 0;
 174 
 175                     } else if (isParentWindowLocated && ce.get_x() != old_x && ce.get_y() != old_y) {
 176                         // If moving by both "X" and "Y".
 177                         // When some tray icon gets removed from the tray, a Java icon may be repositioned.
 178                         // In this case the parent window also lose centering. We have to restore it.
 179 
 180                         if (ex_height != 0) {
 181 
 182                             if (ctrLog.isLoggable(PlatformLogger.Level.FINE)) {
 183                                 ctrLog.fine("ConfigureNotify on parent of {0}. Move detected. Centering by \"Y\".",
 184                                         XTrayIconPeer.this);
 185                             }
 186 
 187                             XlibWrapper.XMoveWindow(XToolkit.getDisplay(), eframeParentID,
 188                                                     ce.get_x(),
 189                                                     ce.get_y() + ex_height/2 - tray_icon_height/2);
 190 
 191                         } else if (ex_width != 0) {
 192 
 193                             if (ctrLog.isLoggable(PlatformLogger.Level.FINE)) {
 194                                 ctrLog.fine("ConfigureNotify on parent of {0}. Move detected. Centering by \"X\".",
 195                                         XTrayIconPeer.this);
 196                             }
 197 
 198                             XlibWrapper.XMoveWindow(XToolkit.getDisplay(), eframeParentID,
 199                                                     ce.get_x() + ex_width/2 - tray_icon_width/2,
 200                                                     ce.get_y());
 201                         } else {
 202                             if (ctrLog.isLoggable(PlatformLogger.Level.FINE)) {
 203                                 ctrLog.fine("ConfigureNotify on parent of {0}. Move detected. Skipping.",
 204                                         XTrayIconPeer.this);
 205                             }
 206                         }
 207                     }
 208                     old_x = ce.get_x();
 209                     old_y = ce.get_y();
 210                     isParentWindowLocated = true;
 211                 }
 212             };
 213         }
 214         eframeXED = new XEventDispatcher() {
 215                 // It's executed under AWTLock.
 216                 XTrayIconPeer xtiPeer = XTrayIconPeer.this;
 217 
 218                 public void dispatchEvent(XEvent ev) {
 219                     if (isDisposed() || ev.get_type() != XConstants.ReparentNotify) {
 220                         return;
 221                     }
 222 
 223                     XReparentEvent re = ev.get_xreparent();
 224                     eframeParentID = re.get_parent();
 225                     if (eframeParentID == XToolkit.getDefaultRootWindow()) {
 226 
 227                         if (isTrayIconDisplayed) { // most likely Notification Area was removed
 228                             SunToolkit.executeOnEventHandlerThread(xtiPeer.target, new Runnable() {
 229                                     public void run() {
 230                                         SystemTray.getSystemTray().remove(xtiPeer.target);
 231                                     }
 232                                 });
 233                         }
 234                         return;
 235                     }
 236 
 237                     if (!isTrayIconDisplayed) {
 238                         addXED(eframeParentID, parentXED, XConstants.StructureNotifyMask);
 239 
 240                         isTrayIconDisplayed = true;
 241                         XToolkit.awtLockNotifyAll();
 242                     }
 243                 }
 244             };
 245 
 246         addXED(getWindow(), eframeXED, XConstants.StructureNotifyMask);
 247 
 248         XSystemTrayPeer.getPeerInstance().addTrayIcon(this); // throws AWTException
 249         // Wait till the EmbeddedFrame is reparented
 250         long start = System.currentTimeMillis();
 251         final long PERIOD = XToolkit.getTrayIconDisplayTimeout();
 252         XToolkit.awtLock();
 253         try {
 254             while (!isTrayIconDisplayed) {
 255                 try {
 256                     XToolkit.awtLockWait(PERIOD);
 257                 } catch (InterruptedException e) {
 258                     break;
 259                 }
 260                 if (System.currentTimeMillis() - start > PERIOD) {
 261                     break;
 262                 }
 263             }
 264         } finally {
 265             XToolkit.awtUnlock();
 266         }
 267 
 268         // This is unlikely to happen.
 269         if (!isTrayIconDisplayed || eframeParentID == 0 ||
 270             eframeParentID == XToolkit.getDefaultRootWindow())
 271         {
 272             throw new AWTException("TrayIcon couldn't be displayed.");
 273         }
 274 
 275         eframe.setVisible(true);
 276         updateImage();
 277 
 278         balloon = new InfoWindow.Balloon(eframe, target, this);
 279         tooltip = new InfoWindow.Tooltip(eframe, target, this);
 280 
 281         addListeners();
 282     }
 283 
 284     public void dispose() {
 285         if (SunToolkit.isDispatchThreadForAppContext(target)) {
 286             disposeOnEDT();
 287         } else {
 288             try {
 289                 SunToolkit.executeOnEDTAndWait(target, new Runnable() {
 290                         public void run() {
 291                             disposeOnEDT();
 292                         }
 293                     });
 294             } catch (InterruptedException ie) {
 295             } catch (InvocationTargetException ite) {}
 296         }
 297     }
 298 
 299     private void disposeOnEDT() {
 300         // All actions that is to be synchronized with disposal
 301         // should be executed either under AWTLock, or on EDT.
 302         // isDisposed value must be checked.
 303         XToolkit.awtLock();
 304         isDisposed = true;
 305         XToolkit.awtUnlock();
 306 
 307         removeXED(getWindow(), eframeXED);
 308         removeXED(eframeParentID, parentXED);
 309         removeListeners();
 310         eframe.realDispose();
 311         balloon.dispose();
 312         tooltip.dispose();
 313         isTrayIconDisplayed = false;
 314         canvas.dispose();
 315         canvas = null;
 316         popup = null;
 317         balloon = null;
 318         tooltip = null;
 319         XToolkit.targetDisposedPeer(target, this);
 320         target = null;
 321         eframe = null;
 322     }
 323 
 324     public static void suppressWarningString(Window w) {
 325         AWTAccessor.getWindowAccessor().setTrayIconWindow(w, true);
 326     }
 327 
 328     public void setToolTip(String tooltip) {
 329         tooltipString = tooltip;
 330     }
 331 
 332     public String getTooltipString() {
 333         return tooltipString;
 334     }
 335 
 336     public void updateImage() {
 337         Runnable r = new Runnable() {
 338                 public void run() {
 339                     canvas.updateImage(target.getImage());
 340                 }
 341             };
 342 
 343         if (!SunToolkit.isDispatchThreadForAppContext(target)) {
 344             SunToolkit.executeOnEventHandlerThread(target, r);
 345         } else {
 346             r.run();
 347         }
 348     }
 349 
 350     public void displayMessage(String caption, String text, String messageType) {
 351         Point loc = getLocationOnScreen();
 352         Rectangle screen = eframe.getGraphicsConfiguration().getBounds();
 353 
 354         // Check if the tray icon is in the bounds of a screen.
 355         if (!(loc.x < screen.x || loc.x >= screen.x + screen.width ||
 356               loc.y < screen.y || loc.y >= screen.y + screen.height))
 357         {
 358             balloon.display(caption, text, messageType);
 359         }
 360     }
 361 
 362     // It's synchronized with disposal by EDT.
 363     public void showPopupMenu(int x, int y) {
 364         if (isDisposed())
 365             return;
 366 
 367         assert SunToolkit.isDispatchThreadForAppContext(target);
 368 
 369         PopupMenu newPopup = target.getPopupMenu();
 370         if (popup != newPopup) {
 371             if (popup != null) {
 372                 eframe.remove(popup);
 373             }
 374             if (newPopup != null) {
 375                 eframe.add(newPopup);
 376             }
 377             popup = newPopup;
 378         }
 379 
 380         if (popup != null) {
 381             final XBaseWindow peer = AWTAccessor.getComponentAccessor()
 382                                                 .getPeer(eframe);
 383             Point loc = peer.toLocal(new Point(x, y));
 384             popup.show(eframe, loc.x, loc.y);
 385         }
 386     }
 387 
 388 
 389     // ******************************************************************
 390     // ******************************************************************
 391 
 392 
 393     private void addXED(long window, XEventDispatcher xed, long mask) {
 394         if (window == 0) {
 395             return;
 396         }
 397         XToolkit.awtLock();
 398         try {
 399             XlibWrapper.XSelectInput(XToolkit.getDisplay(), window, mask);
 400         } finally {
 401             XToolkit.awtUnlock();
 402         }
 403         XToolkit.addEventDispatcher(window, xed);
 404     }
 405 
 406     private void removeXED(long window, XEventDispatcher xed) {
 407         if (window == 0) {
 408             return;
 409         }
 410         XToolkit.awtLock();
 411         try {
 412             XToolkit.removeEventDispatcher(window, xed);
 413         } finally {
 414             XToolkit.awtUnlock();
 415         }
 416     }
 417 
 418     // Private method for testing purposes.
 419     private Point getLocationOnScreen() {
 420         return eframe.getLocationOnScreen();
 421     }
 422 
 423     public Rectangle getBounds() {
 424         Point loc = getLocationOnScreen();
 425         return new Rectangle(loc.x, loc.y, loc.x + tray_icon_width, loc.y + tray_icon_height);
 426     }
 427 
 428     void addListeners() {
 429         canvas.addMouseListener(eventProxy);
 430         canvas.addMouseMotionListener(eventProxy);
 431         eframe.addMouseListener(eventProxy);
 432     }
 433 
 434     void removeListeners() {
 435         canvas.removeMouseListener(eventProxy);
 436         canvas.removeMouseMotionListener(eventProxy);
 437         eframe.removeMouseListener(eventProxy);
 438     }
 439 
 440     long getWindow() {
 441         return AWTAccessor.getComponentAccessor()
 442                           .<XEmbeddedFramePeer>getPeer(eframe).getWindow();
 443     }
 444 
 445     public boolean isDisposed() {
 446         return isDisposed;
 447     }
 448 
 449     public String getActionCommand() {
 450         return target.getActionCommand();
 451     }
 452 
 453     static class TrayIconEventProxy implements MouseListener, MouseMotionListener {
 454         XTrayIconPeer xtiPeer;
 455 
 456         TrayIconEventProxy(XTrayIconPeer xtiPeer) {
 457             this.xtiPeer = xtiPeer;
 458         }
 459 
 460         public void handleEvent(MouseEvent e) {
 461             //prevent DRAG events from being posted with TrayIcon source(CR 6565779)
 462             if (e.getID() == MouseEvent.MOUSE_DRAGGED) {
 463                 return;
 464             }
 465 
 466             // Event handling is synchronized with disposal by EDT.
 467             if (xtiPeer.isDisposed()) {
 468                 return;
 469             }
 470             Point coord = XBaseWindow.toOtherWindow(xtiPeer.getWindow(),
 471                                                     XToolkit.getDefaultRootWindow(),
 472                                                     e.getX(), e.getY());
 473 
 474             if (e.isPopupTrigger()) {
 475                 xtiPeer.showPopupMenu(coord.x, coord.y);
 476             }
 477 
 478             e.translatePoint(coord.x - e.getX(), coord.y - e.getY());
 479             // This is a hack in order to set non-Component source to MouseEvent
 480             // instance.
 481             // In some cases this could lead to unpredictable result (e.g. when
 482             // other class tries to cast source field to Component).
 483             // We already filter DRAG events out (CR 6565779).
 484             e.setSource(xtiPeer.target);
 485             XToolkit.postEvent(XToolkit.targetToAppContext(e.getSource()), e);
 486         }
 487         @SuppressWarnings("deprecation")
 488         public void mouseClicked(MouseEvent e) {
 489             if ((e.getClickCount() == 1 || xtiPeer.balloon.isVisible()) &&
 490                 e.getButton() == MouseEvent.BUTTON1)
 491             {
 492                 ActionEvent aev = new ActionEvent(xtiPeer.target, ActionEvent.ACTION_PERFORMED,
 493                                                   xtiPeer.target.getActionCommand(), e.getWhen(),
 494                                                   e.getModifiers());
 495                 XToolkit.postEvent(XToolkit.targetToAppContext(aev.getSource()), aev);
 496             }
 497             if (xtiPeer.balloon.isVisible()) {
 498                 xtiPeer.balloon.hide();
 499             }
 500             handleEvent(e);
 501         }
 502         public void mouseEntered(MouseEvent e) {
 503             xtiPeer.tooltip.enter();
 504             handleEvent(e);
 505         }
 506         public void mouseExited(MouseEvent e) {
 507             xtiPeer.tooltip.exit();
 508             handleEvent(e);
 509         }
 510         public void mousePressed(MouseEvent e) {
 511             handleEvent(e);
 512         }
 513         public void mouseReleased(MouseEvent e) {
 514             handleEvent(e);
 515         }
 516         public void mouseDragged(MouseEvent e) {
 517             handleEvent(e);
 518         }
 519         public void mouseMoved(MouseEvent e) {
 520             handleEvent(e);
 521         }
 522     }
 523 
 524     // ***************************************
 525     // Special embedded frame for tray icon
 526     // ***************************************
 527 
 528     @SuppressWarnings("serial") // JDK-implementation class
 529     private static class XTrayIconEmbeddedFrame extends XEmbeddedFrame {
 530         public XTrayIconEmbeddedFrame(){
 531             super(XToolkit.getDefaultRootWindow(), true, true);
 532         }
 533 
 534         public boolean isUndecorated() {
 535             return true;
 536         }
 537 
 538         public boolean isResizable() {
 539             return false;
 540         }
 541 
 542         // embedded frame for tray icon shouldn't be disposed by anyone except tray icon
 543         public void dispose(){
 544         }
 545 
 546         public void realDispose(){
 547             super.dispose();
 548         }
 549     };
 550 
 551     // ***************************************
 552     // Classes for painting an image on canvas
 553     // ***************************************
 554 
 555     @SuppressWarnings("serial") // JDK-implementation class
 556     static class TrayIconCanvas extends IconCanvas {
 557         TrayIcon target;
 558         boolean autosize;
 559         final int width, height;
 560 
 561         TrayIconCanvas(TrayIcon target, int width, int height) {
 562             super(width, height);
 563             this.target = target;
 564             this.width = width;
 565             this.height = height;
 566         }
 567 
 568         // Invoke on EDT.
 569         protected void repaintImage(boolean doClear) {
 570             boolean old_autosize = autosize;
 571             int old_curW = curW, old_curH = curH;
 572             autosize = target.isImageAutoSize();
 573             AffineTransform tx = GraphicsEnvironment.getLocalGraphicsEnvironment().
 574                      getDefaultScreenDevice().getDefaultConfiguration().
 575                      getDefaultTransform();
 576             int imgWidth = Region.clipScale(image.getWidth(observer), tx.getScaleX());
 577             int imgHeight = Region.clipScale(image.getHeight(observer), tx.getScaleY());
 578 
 579             curW = autosize ? width : imgWidth;
 580             curH = autosize ? height : imgHeight;
 581 
 582             // update the size before repainting
 583             super.updateSize(curW, curH);
 584             super.repaintImage(doClear || (old_autosize != autosize) ||
 585                     (old_curW != curW || old_curH != curH));
 586         }
 587 
 588         public void dispose() {
 589             super.dispose();
 590             target = null;
 591         }
 592     }
 593 
 594     @SuppressWarnings("serial") // JDK-implementation class
 595     public static class IconCanvas extends Canvas {
 596         volatile Image image;
 597         IconObserver observer;
 598         int curW, curH;
 599 
 600         IconCanvas(int width, int height) {
 601             curW = width;
 602             curH = height;
 603         }
 604 
 605         public void updateSize(int width, int height) {
 606             curW = width;
 607             curH = height;
 608         }
 609 
 610         // Invoke on EDT.
 611         public void updateImage(Image image) {
 612             this.image = image;
 613             if (observer == null) {
 614                 observer = new IconObserver();
 615             }
 616             repaintImage(true);
 617         }
 618 
 619         public void dispose() {
 620             observer = null;
 621         }
 622 
 623         // Invoke on EDT.
 624         protected void repaintImage(boolean doClear) {
 625             Graphics g = getGraphics();
 626             if (g != null) {
 627                try {
 628                     if (isVisible()) {
 629                         if (doClear) {
 630                             update(g);
 631                         } else {
 632                             paint(g);
 633                         }
 634                     }
 635                 } finally {
 636                     g.dispose();
 637                 }
 638             }
 639         }
 640 
 641         // Invoke on EDT.
 642         public void paint(Graphics g) {
 643             if (g != null && curW > 0 && curH > 0) {
 644                 BufferedImage bufImage = new BufferedImage(curW, curH, BufferedImage.TYPE_INT_ARGB);
 645                 Graphics2D gr = bufImage.createGraphics();
 646                 if (gr != null) {
 647                     try {
 648                         gr.setColor(getBackground());
 649                         gr.fillRect(0, 0, curW, curH);
 650                         gr.drawImage(image, 0, 0, curW, curH, observer);
 651                         gr.dispose();
 652 
 653                         g.drawImage(bufImage, 0, 0, curW, curH, null);
 654                     } finally {
 655                         gr.dispose();
 656                     }
 657                 }
 658             }
 659         }
 660 
 661         class IconObserver implements ImageObserver {
 662             public boolean imageUpdate(final Image image, final int flags, int x, int y, int width, int height) {
 663                 if (image != IconCanvas.this.image || // if the image has been changed
 664                     !IconCanvas.this.isVisible())
 665                 {
 666                     return false;
 667                 }
 668                 if ((flags & (ImageObserver.FRAMEBITS | ImageObserver.ALLBITS |
 669                               ImageObserver.WIDTH | ImageObserver.HEIGHT)) != 0)
 670                 {
 671                     SunToolkit.executeOnEventHandlerThread(IconCanvas.this, new Runnable() {
 672                             public void run() {
 673                                 repaintImage(false);
 674                             }
 675                         });
 676                 }
 677                 return (flags & ImageObserver.ALLBITS) == 0;
 678             }
 679         }
 680     }
 681 }