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