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