1 /*
   2  * Copyright (c) 2003, 2014, 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 package sun.awt.X11;
  26 
  27 import java.awt.*;
  28 import java.awt.image.*;
  29 
  30 import sun.awt.IconInfo;
  31 import sun.awt.image.ToolkitImage;
  32 import sun.awt.image.ImageRepresentation;
  33 
  34 import sun.util.logging.PlatformLogger;
  35 
  36 public class XIconWindow extends XBaseWindow {
  37     private static final PlatformLogger log = PlatformLogger.getLogger("sun.awt.X11.XIconWindow");
  38     XDecoratedPeer parent;
  39     Dimension size;
  40     long iconPixmap = 0;
  41     long iconMask = 0;
  42     int iconWidth = 0;
  43     int iconHeight = 0;
  44     XIconWindow(XDecoratedPeer parent) {
  45         super(new XCreateWindowParams(new Object[] {
  46             PARENT, parent,
  47             DELAYED, Boolean.TRUE}));
  48     }
  49 
  50     void instantPreInit(XCreateWindowParams params) {
  51         super.instantPreInit(params);
  52         this.parent = (XDecoratedPeer)params.get(PARENT);
  53     }
  54 
  55     /**
  56      * @return array of XIconsSize structures, caller must free this array after use.
  57      */
  58     private XIconSize[] getIconSizes() {
  59         XToolkit.awtLock();
  60         try {
  61             AwtGraphicsConfigData adata = parent.getGraphicsConfigurationData();
  62             final long screen = adata.get_awt_visInfo().get_screen();
  63             final long display = XToolkit.getDisplay();
  64 
  65             if (log.isLoggable(PlatformLogger.Level.FINEST)) {
  66                 log.finest(adata.toString());
  67             }
  68 
  69             long status =
  70                 XlibWrapper.XGetIconSizes(display, XToolkit.getDefaultRootWindow(),
  71                                           XlibWrapper.larg1, XlibWrapper.iarg1);
  72             if (status == 0) {
  73                 return null;
  74             }
  75             int count = Native.getInt(XlibWrapper.iarg1);
  76             long sizes_ptr = Native.getLong(XlibWrapper.larg1); // XIconSize*
  77             if (log.isLoggable(PlatformLogger.Level.FINEST)) {
  78                 log.finest("count = {1}, sizes_ptr = {0}", Long.valueOf(sizes_ptr), Integer.valueOf(count));
  79             }
  80             XIconSize[] res = new XIconSize[count];
  81             for (int i = 0; i < count; i++, sizes_ptr += XIconSize.getSize()) {
  82                 res[i] = new XIconSize(sizes_ptr);
  83                 if (log.isLoggable(PlatformLogger.Level.FINEST)) {
  84                     log.finest("sizes_ptr[{1}] = {0}", res[i], Integer.valueOf(i));
  85                 }
  86             }
  87             return res;
  88         } finally {
  89             XToolkit.awtUnlock();
  90         }
  91     }
  92 
  93     private Dimension calcIconSize(int widthHint, int heightHint) {
  94         if (XWM.getWMID() == XWM.ICE_WM) {
  95             // ICE_WM has a bug - it only displays icons of the size
  96             // 16x16, while reporting 32x32 in its size list
  97             log.finest("Returning ICE_WM icon size: 16x16");
  98             return new Dimension(16, 16);
  99         }
 100 
 101         XIconSize[] sizeList = getIconSizes();
 102         if (log.isLoggable(PlatformLogger.Level.FINEST)) {
 103             log.finest("Icon sizes: {0}", (Object[]) sizeList);
 104         }
 105         if (sizeList == null) {
 106             // No icon sizes so we simply fall back to 16x16
 107             return new Dimension(16, 16);
 108         }
 109         boolean found = false;
 110         int dist = 0xffffffff, newDist, diff = 0, closestHeight, closestWidth;
 111         int saveWidth = 0, saveHeight = 0;
 112         for (int i = 0; i < sizeList.length; i++) {
 113             if (widthHint >= sizeList[i].get_min_width() &&
 114                 widthHint <= sizeList[i].get_max_width() &&
 115                 heightHint >= sizeList[i].get_min_height() &&
 116                 heightHint <= sizeList[i].get_max_height()) {
 117                 found = true;
 118                 if ((((widthHint-sizeList[i].get_min_width())
 119                       % sizeList[i].get_width_inc()) == 0) &&
 120                     (((heightHint-sizeList[i].get_min_height())
 121                       % sizeList[i].get_height_inc()) ==0)) {
 122                     /* Found an exact match */
 123                     saveWidth = widthHint;
 124                     saveHeight = heightHint;
 125                     dist = 0;
 126                     break;
 127                 }
 128                 diff = widthHint - sizeList[i].get_min_width();
 129                 if (diff == 0) {
 130                     closestWidth = widthHint;
 131                 } else {
 132                     diff = diff%sizeList[i].get_width_inc();
 133                     closestWidth = widthHint - diff;
 134                 }
 135                 diff = heightHint - sizeList[i].get_min_height();
 136                 if (diff == 0) {
 137                     closestHeight = heightHint;
 138                 } else {
 139                     diff = diff%sizeList[i].get_height_inc();
 140                     closestHeight = heightHint - diff;
 141                 }
 142                 newDist = closestWidth*closestWidth +
 143                     closestHeight*closestHeight;
 144                 if (dist > newDist) {
 145                     saveWidth = closestWidth;
 146                     saveHeight = closestHeight;
 147                     dist = newDist;
 148                 }
 149             }
 150         }
 151         if (log.isLoggable(PlatformLogger.Level.FINEST)) {
 152             log.finest("found=" + found);
 153         }
 154         if (!found) {
 155             if (log.isLoggable(PlatformLogger.Level.FINEST)) {
 156                 log.finest("widthHint=" + widthHint + ", heightHint=" + heightHint
 157                            + ", saveWidth=" + saveWidth + ", saveHeight=" + saveHeight
 158                            + ", max_width=" + sizeList[0].get_max_width()
 159                            + ", max_height=" + sizeList[0].get_max_height()
 160                            + ", min_width=" + sizeList[0].get_min_width()
 161                            + ", min_height=" + sizeList[0].get_min_height());
 162             }
 163 
 164             if (widthHint  > sizeList[0].get_max_width() ||
 165                 heightHint > sizeList[0].get_max_height())
 166             {
 167                 // Icon image too big
 168                 /* determine which way to scale */
 169                 int wdiff = widthHint - sizeList[0].get_max_width();
 170                 int hdiff = heightHint - sizeList[0].get_max_height();
 171                 if (log.isLoggable(PlatformLogger.Level.FINEST)) {
 172                     log.finest("wdiff=" + wdiff + ", hdiff=" + hdiff);
 173                 }
 174                 if (wdiff >= hdiff) { /* need to scale width more  */
 175                     saveWidth = sizeList[0].get_max_width();
 176                     saveHeight =
 177                         (int)(((double)sizeList[0].get_max_width()/widthHint) * heightHint);
 178                 } else {
 179                     saveWidth =
 180                         (int)(((double)sizeList[0].get_max_height()/heightHint) * widthHint);
 181                     saveHeight = sizeList[0].get_max_height();
 182                 }
 183             } else if (widthHint  < sizeList[0].get_min_width() ||
 184                        heightHint < sizeList[0].get_min_height())
 185             {
 186                 // Icon image too small
 187                 saveWidth = (sizeList[0].get_min_width()+sizeList[0].get_max_width())/2;
 188                 saveHeight = (sizeList[0].get_min_height()+sizeList[0].get_max_height())/2;
 189             } else {
 190                 // Icon image fits within right size
 191                 saveWidth = widthHint;
 192                 saveHeight = widthHint;
 193             }
 194         }
 195 
 196         XToolkit.awtLock();
 197         try {
 198             XlibWrapper.XFree(sizeList[0].pData);
 199         } finally {
 200             XToolkit.awtUnlock();
 201         }
 202 
 203         if (log.isLoggable(PlatformLogger.Level.FINEST)) {
 204             log.finest("return " + saveWidth + "x" + saveHeight);
 205         }
 206         return new Dimension(saveWidth, saveHeight);
 207     }
 208 
 209     /**
 210      * @return preffered icon size calculated from specific icon
 211      */
 212     Dimension getIconSize(int widthHint, int heightHint) {
 213         if (size == null) {
 214             size = calcIconSize(widthHint, heightHint);
 215         }
 216         return size;
 217     }
 218 
 219    /**
 220     * This function replaces iconPixmap handle with new image
 221     * It does not replace window's hints, so it should be
 222     * called only from setIconImage()
 223     */
 224    void replaceImage(Image img)
 225     {
 226         if (parent == null) {
 227             return;
 228         }
 229         //Prepare image
 230         //create new buffered image of desired size
 231         //in current window's color model
 232         BufferedImage bi = null;
 233         if (img != null && iconWidth != 0 && iconHeight != 0) {
 234             GraphicsConfiguration defaultGC = parent.getGraphicsConfiguration().getDevice().getDefaultConfiguration();
 235             ColorModel model = defaultGC.getColorModel();
 236             WritableRaster raster = model.createCompatibleWritableRaster(iconWidth, iconHeight);
 237             bi = new BufferedImage(model, raster, model.isAlphaPremultiplied(), null);
 238             Graphics g = bi.getGraphics();
 239             try {
 240                 //We need to draw image on SystemColors.window
 241                 //for using as iconWindow's background
 242                 g.setColor(SystemColor.window);
 243                 g.fillRect(0, 0, iconWidth, iconHeight);
 244                 if (g instanceof Graphics2D) {
 245                     ((Graphics2D)g).setComposite(AlphaComposite.Src);
 246                 }
 247                 g.drawImage(img, 0, 0, iconWidth, iconHeight, null);
 248             } finally {
 249                 g.dispose();
 250             }
 251         }
 252         //create pixmap
 253         XToolkit.awtLock();
 254         try {
 255             if (iconPixmap != 0) {
 256                 XlibWrapper.XFreePixmap(XToolkit.getDisplay(), iconPixmap);
 257                 iconPixmap = 0;
 258                 log.finest("Freed previous pixmap");
 259             }
 260             if (bi == null || iconWidth == 0 || iconHeight == 0) {
 261                 return;  //The iconPixmap is 0 now, we have done everything
 262             }
 263             AwtGraphicsConfigData adata = parent.getGraphicsConfigurationData();
 264             awtImageData awtImage = adata.get_awtImage(0);
 265             XVisualInfo visInfo = adata.get_awt_visInfo();
 266             iconPixmap = XlibWrapper.XCreatePixmap(XToolkit.getDisplay(),
 267                                                    XlibWrapper.RootWindow(XToolkit.getDisplay(), visInfo.get_screen()),
 268                                                    iconWidth,
 269                                                    iconHeight,
 270                                                    awtImage.get_Depth()
 271                                                    );
 272             if (iconPixmap == 0) {
 273                 log.finest("Can't create new pixmap for icon");
 274                 return; //Can't do nothing
 275             }
 276             //Transform image data
 277             long bytes = 0;
 278             DataBuffer srcBuf = bi.getData().getDataBuffer();
 279             if (srcBuf instanceof DataBufferByte) {
 280                 byte[] buf = ((DataBufferByte)srcBuf).getData();
 281                 ColorData cdata = adata.get_color_data(0);
 282                 int num_colors = cdata.get_awt_numICMcolors();
 283                 for (int i = 0; i < buf.length; i++) {
 284                     int b = Byte.toUnsignedInt(buf[i]);
 285                     buf[i] = (b >= num_colors) ?
 286                         0 : cdata.get_awt_icmLUT2Colors(b);
 287                 }
 288                 bytes = Native.toData(buf);
 289             } else if (srcBuf instanceof DataBufferInt) {
 290                 bytes = Native.toData(((DataBufferInt)srcBuf).getData());
 291             } else if (srcBuf instanceof DataBufferUShort) {
 292                 bytes = Native.toData(((DataBufferUShort)srcBuf).getData());
 293             } else {
 294                 throw new IllegalArgumentException("Unknown data buffer: " + srcBuf);
 295             }
 296             int bpp = awtImage.get_wsImageFormat().get_bits_per_pixel();
 297             int slp =awtImage.get_wsImageFormat().get_scanline_pad();
 298             int bpsl = paddedwidth(iconWidth*bpp, slp) >> 3;
 299             if (((bpsl << 3) / bpp) < iconWidth) {
 300                 log.finest("Image format doesn't fit to icon width");
 301                 return;
 302             }
 303             long dst = XlibWrapper.XCreateImage(XToolkit.getDisplay(),
 304                                                 visInfo.get_visual(),
 305                                                 awtImage.get_Depth(),
 306                                                 XConstants.ZPixmap,
 307                                                 0,
 308                                                 bytes,
 309                                                 iconWidth,
 310                                                 iconHeight,
 311                                                 32,
 312                                                 bpsl);
 313             if (dst == 0) {
 314                 log.finest("Can't create XImage for icon");
 315                 XlibWrapper.XFreePixmap(XToolkit.getDisplay(), iconPixmap);
 316                 iconPixmap = 0;
 317                 return;
 318             } else {
 319                 log.finest("Created XImage for icon");
 320             }
 321             long gc = XlibWrapper.XCreateGC(XToolkit.getDisplay(), iconPixmap, 0, 0);
 322             if (gc == 0) {
 323                 log.finest("Can't create GC for pixmap");
 324                 XlibWrapper.XFreePixmap(XToolkit.getDisplay(), iconPixmap);
 325                 iconPixmap = 0;
 326                 return;
 327             } else {
 328                 log.finest("Created GC for pixmap");
 329             }
 330             try {
 331                 XlibWrapper.XPutImage(XToolkit.getDisplay(), iconPixmap, gc,
 332                                       dst, 0, 0, 0, 0, iconWidth, iconHeight);
 333             } finally {
 334                 XlibWrapper.XFreeGC(XToolkit.getDisplay(), gc);
 335             }
 336         } finally {
 337             XToolkit.awtUnlock();
 338         }
 339     }
 340 
 341    /**
 342     * This function replaces iconPixmap handle with new image
 343     * It does not replace window's hints, so it should be
 344     * called only from setIconImage()
 345     */
 346     void replaceMask(Image img) {
 347         if (parent == null) {
 348             return;
 349         }
 350         //Prepare image
 351         BufferedImage bi = null;
 352         if (img != null && iconWidth != 0 && iconHeight != 0) {
 353             bi = new BufferedImage(iconWidth, iconHeight, BufferedImage.TYPE_INT_ARGB);
 354             Graphics g = bi.getGraphics();
 355             try {
 356                 g.drawImage(img, 0, 0, iconWidth, iconHeight, null);
 357             } finally {
 358                 g.dispose();
 359             }
 360         }
 361         //create mask
 362         XToolkit.awtLock();
 363         try {
 364             if (iconMask != 0) {
 365                 XlibWrapper.XFreePixmap(XToolkit.getDisplay(), iconMask);
 366                 iconMask = 0;
 367                 log.finest("Freed previous mask");
 368             }
 369             if (bi == null || iconWidth == 0 || iconHeight == 0) {
 370                 return;  //The iconMask is 0 now, we have done everything
 371             }
 372             AwtGraphicsConfigData adata = parent.getGraphicsConfigurationData();
 373             awtImageData awtImage = adata.get_awtImage(0);
 374             XVisualInfo visInfo = adata.get_awt_visInfo();
 375             ColorModel cm = bi.getColorModel();
 376             DataBuffer srcBuf = bi.getRaster().getDataBuffer();
 377             int sidx = 0;//index of source element
 378             int bpl = (iconWidth + 7) >> 3;//bytes per line
 379             byte[] destBuf = new byte[bpl * iconHeight];
 380             int didx = 0;//index of destination element
 381             for (int i = 0; i < iconHeight; i++) {
 382                 int dbit = 0;//index of current bit
 383                 int cv = 0;
 384                 for (int j = 0; j < iconWidth; j++) {
 385                     if (cm.getAlpha(srcBuf.getElem(sidx)) != 0 ) {
 386                         cv = cv + (1 << dbit);
 387                     }
 388                     dbit++;
 389                     if (dbit == 8) {
 390                         destBuf[didx] = (byte)cv;
 391                         cv = 0;
 392                         dbit = 0;
 393                         didx++;
 394                     }
 395                     sidx++;
 396                 }
 397             }
 398             iconMask = XlibWrapper.XCreateBitmapFromData(XToolkit.getDisplay(),
 399                 XlibWrapper.RootWindow(XToolkit.getDisplay(), visInfo.get_screen()),
 400                 Native.toData(destBuf),
 401                 iconWidth, iconHeight);
 402         } finally {
 403             XToolkit.awtUnlock();
 404         }
 405     }
 406 
 407     /**
 408      * Sets icon image by selecting one of the images from the list.
 409      * The selected image is the one having the best matching size.
 410      */
 411     void setIconImages(java.util.List<IconInfo> icons) {
 412         if (icons == null || icons.size() == 0) return;
 413 
 414         int minDiff = Integer.MAX_VALUE;
 415         Image min = null;
 416         for (IconInfo iconInfo : icons) {
 417             if (iconInfo.isValid()) {
 418                 Image image = iconInfo.getImage();
 419                 Dimension dim = calcIconSize(image.getWidth(null), image.getHeight(null));
 420                 int widthDiff = Math.abs(dim.width - image.getWidth(null));
 421                 int heightDiff = Math.abs(image.getHeight(null) - dim.height);
 422 
 423                 // "=" below allows to select the best matching icon
 424                 if (minDiff >= (widthDiff + heightDiff)) {
 425                     minDiff = (widthDiff + heightDiff);
 426                     min = image;
 427                 }
 428             }
 429         }
 430         if (min != null) {
 431             if (log.isLoggable(PlatformLogger.Level.FINER)) {
 432                 log.finer("Icon: {0}x{1}", min.getWidth(null), min.getHeight(null));
 433             }
 434             setIconImage(min);
 435         }
 436     }
 437 
 438     void setIconImage(Image img) {
 439         if (img == null) {
 440             //if image is null, reset to default image
 441             replaceImage(null);
 442             replaceMask(null);
 443         } else {
 444             //get image size
 445             int width;
 446             int height;
 447             if (img instanceof ToolkitImage) {
 448                 ImageRepresentation ir = ((ToolkitImage)img).getImageRep();
 449                 ir.reconstruct(ImageObserver.ALLBITS);
 450                 width = ir.getWidth();
 451                 height = ir.getHeight();
 452             }
 453             else {
 454                 width = img.getWidth(null);
 455                 height = img.getHeight(null);
 456             }
 457             Dimension iconSize = getIconSize(width, height);
 458             if (iconSize != null) {
 459                 if (log.isLoggable(PlatformLogger.Level.FINEST)) {
 460                     log.finest("Icon size: {0}", iconSize);
 461                 }
 462                 iconWidth = iconSize.width;
 463                 iconHeight = iconSize.height;
 464             } else {
 465                 log.finest("Error calculating image size");
 466                 iconWidth = 0;
 467                 iconHeight = 0;
 468             }
 469             replaceImage(img);
 470             replaceMask(img);
 471         }
 472         //create icon window and set XWMHints
 473         XToolkit.awtLock();
 474         try {
 475             AwtGraphicsConfigData adata = parent.getGraphicsConfigurationData();
 476             awtImageData awtImage = adata.get_awtImage(0);
 477             XVisualInfo visInfo = adata.get_awt_visInfo();
 478             XWMHints hints = parent.getWMHints();
 479             window = hints.get_icon_window();
 480             if (window == 0) {
 481                 log.finest("Icon window wasn't set");
 482                 XCreateWindowParams params = getDelayedParams();
 483                 params.add(BORDER_PIXEL, Long.valueOf(XToolkit.getAwtDefaultFg()));
 484                 params.add(BACKGROUND_PIXMAP, iconPixmap);
 485                 params.add(COLORMAP, adata.get_awt_cmap());
 486                 params.add(DEPTH, awtImage.get_Depth());
 487                 params.add(VISUAL_CLASS, XConstants.InputOutput);
 488                 params.add(VISUAL, visInfo.get_visual());
 489                 params.add(VALUE_MASK, XConstants.CWBorderPixel | XConstants.CWColormap | XConstants.CWBackPixmap);
 490                 params.add(PARENT_WINDOW, XlibWrapper.RootWindow(XToolkit.getDisplay(), visInfo.get_screen()));
 491                 params.add(BOUNDS, new Rectangle(0, 0, iconWidth, iconHeight));
 492                 params.remove(DELAYED);
 493                 init(params);
 494                 if (getWindow() == 0) {
 495                     log.finest("Can't create new icon window");
 496                 } else {
 497                     log.finest("Created new icon window");
 498                 }
 499             }
 500             if (getWindow() != 0) {
 501                 XlibWrapper.XSetWindowBackgroundPixmap(XToolkit.getDisplay(), getWindow(), iconPixmap);
 502                 XlibWrapper.XClearWindow(XToolkit.getDisplay(), getWindow());
 503             }
 504             // Provide both pixmap and window, WM or Taskbar will use the one they find more appropriate
 505             long newFlags = hints.get_flags() | XUtilConstants.IconPixmapHint | XUtilConstants.IconMaskHint;
 506             if (getWindow()  != 0) {
 507                 newFlags |= XUtilConstants.IconWindowHint;
 508             }
 509             hints.set_flags(newFlags);
 510             hints.set_icon_pixmap(iconPixmap);
 511             hints.set_icon_mask(iconMask);
 512             hints.set_icon_window(getWindow());
 513             XlibWrapper.XSetWMHints(XToolkit.getDisplay(), parent.getShell(), hints.pData);
 514             log.finest("Set icon window hint");
 515         } finally {
 516             XToolkit.awtUnlock();
 517         }
 518     }
 519 
 520     static int paddedwidth(int number, int boundary)
 521     {
 522         return (((number) + ((boundary) - 1)) & (~((boundary) - 1)));
 523     }
 524 }