1 /*
2  * Copyright (c) 2005, 2018, 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.windows;
27 
28 import java.awt.Graphics2D;
29 import java.awt.AWTEvent;
30 import java.awt.Frame;
31 import java.awt.GraphicsEnvironment;
32 import java.awt.PopupMenu;
33 import java.awt.Point;
34 import java.awt.TrayIcon;
35 import java.awt.Image;
36 import java.awt.geom.AffineTransform;
37 import java.awt.peer.TrayIconPeer;
38 import java.awt.image.*;
39 
40 import sun.awt.AWTAccessor;
41 import sun.awt.SunToolkit;
42 import sun.awt.image.IntegerComponentRaster;
43 import sun.java2d.pipe.Region;
44 
45 final class WTrayIconPeer extends WObjectPeer implements TrayIconPeer {
46     static final int TRAY_ICON_WIDTH = 16;
47     static final int TRAY_ICON_HEIGHT = 16;
48     static final int TRAY_ICON_MASK_SIZE = (TRAY_ICON_WIDTH * TRAY_ICON_HEIGHT) / 8;
49 
50     IconObserver observer = new IconObserver();
51     boolean firstUpdate = true;
52     Frame popupParent = new Frame("PopupMessageWindow");
53     PopupMenu popup;
54 
55     @Override
disposeImpl()56     protected void disposeImpl() {
57         if (popupParent != null) {
58             popupParent.dispose();
59         }
60         popupParent.dispose();
61         _dispose();
62         WToolkit.targetDisposedPeer(target, this);
63     }
64 
WTrayIconPeer(TrayIcon target)65     WTrayIconPeer(TrayIcon target) {
66         this.target = target;
67         popupParent.addNotify();
68         create();
69         updateImage();
70     }
71 
72     @Override
updateImage()73     public void updateImage() {
74         Image image = ((TrayIcon)target).getImage();
75         if (image != null) {
76             updateNativeImage(image);
77         }
78     }
79 
80     @Override
setToolTip(String tooltip)81     public native void setToolTip(String tooltip);
82 
83     @Override
showPopupMenu(final int x, final int y)84     public synchronized void showPopupMenu(final int x, final int y) {
85         if (isDisposed())
86             return;
87 
88         SunToolkit.executeOnEventHandlerThread(target, () -> {
89             PopupMenu newPopup = ((TrayIcon)target).getPopupMenu();
90             if (popup != newPopup) {
91                 if (popup != null) {
92                     popupParent.remove(popup);
93                 }
94                 if (newPopup != null) {
95                     popupParent.add(newPopup);
96                 }
97                 popup = newPopup;
98             }
99             if (popup != null) {
100                 WPopupMenuPeer peer = AWTAccessor.getMenuComponentAccessor()
101                                                  .getPeer(popup);
102                 peer.show(popupParent, new Point(x, y));
103             }
104         });
105     }
106 
107     @Override
displayMessage(String caption, String text, String messageType)108     public void displayMessage(String caption, String text, String messageType) {
109         // The situation when both caption and text are null is processed in the shared code.
110         if (caption == null) {
111             caption = "";
112         }
113         if (text == null) {
114             text = "";
115         }
116         _displayMessage(caption, text, messageType);
117     }
118 
119 
120     // ***********************************************
121     // ***********************************************
122 
123 
updateNativeImage(Image image)124     synchronized void updateNativeImage(Image image) {
125         if (isDisposed())
126             return;
127 
128         boolean autosize = ((TrayIcon)target).isImageAutoSize();
129         AffineTransform tx = GraphicsEnvironment.getLocalGraphicsEnvironment().
130                 getDefaultScreenDevice().getDefaultConfiguration().
131                 getDefaultTransform();
132         int w = Region.clipScale(TRAY_ICON_WIDTH, tx.getScaleX());
133         int h = Region.clipScale(TRAY_ICON_HEIGHT, tx.getScaleY());
134         int imgWidth = Region.clipScale(image.getWidth(observer), tx.getScaleX());
135         int imgHeight = Region.clipScale(image.getHeight(observer), tx.getScaleY());
136         BufferedImage bufImage = new BufferedImage(w,
137                 h, BufferedImage.TYPE_INT_ARGB);
138         Graphics2D gr = bufImage.createGraphics();
139         if (gr != null) {
140             try {
141                 gr.setPaintMode();
142 
143                 gr.drawImage(image, 0, 0, (autosize ? w : imgWidth),
144                              (autosize ? h : imgHeight), observer);
145 
146                 createNativeImage(bufImage);
147 
148                 updateNativeIcon(!firstUpdate);
149                 if (firstUpdate) firstUpdate = false;
150 
151             } finally {
152                 gr.dispose();
153             }
154         }
155     }
156 
createNativeImage(BufferedImage bimage)157     void createNativeImage(BufferedImage bimage) {
158         Raster raster = bimage.getRaster();
159         byte[] andMask = new byte[TRAY_ICON_MASK_SIZE];
160         int[]  pixels = ((DataBufferInt)raster.getDataBuffer()).getData();
161         int npixels = pixels.length;
162         int ficW = raster.getWidth();
163 
164         for (int i = 0; i < npixels; i++) {
165             int ibyte = i / 8;
166             int omask = 1 << (7 - (i % 8));
167 
168             if ((pixels[i] & 0xff000000) == 0) {
169                 // Transparent bit
170                 if (ibyte < andMask.length) {
171                     andMask[ibyte] |= omask;
172                 }
173             }
174         }
175 
176         if (raster instanceof IntegerComponentRaster) {
177             ficW = ((IntegerComponentRaster)raster).getScanlineStride();
178         }
179         setNativeIcon(((DataBufferInt)bimage.getRaster().getDataBuffer()).getData(),
180                       andMask, ficW, raster.getWidth(), raster.getHeight());
181     }
182 
postEvent(AWTEvent event)183     void postEvent(AWTEvent event) {
184         WToolkit.postEvent(WToolkit.targetToAppContext(target), event);
185     }
186 
create()187     native void create();
_dispose()188     synchronized native void _dispose();
189 
190     /*
191      * Updates/adds the icon in/to the system tray.
192      * @param doUpdate if {@code true}, updates the icon,
193      * otherwise, adds the icon
194      */
updateNativeIcon(boolean doUpdate)195     native void updateNativeIcon(boolean doUpdate);
196 
setNativeIcon(int[] rData, byte[] andMask, int nScanStride, int width, int height)197     native void setNativeIcon(int[] rData, byte[] andMask, int nScanStride,
198                               int width, int height);
199 
_displayMessage(String caption, String text, String messageType)200     native void _displayMessage(String caption, String text, String messageType);
201 
202     class IconObserver implements ImageObserver {
203         @Override
imageUpdate(Image image, int flags, int x, int y, int width, int height)204         public boolean imageUpdate(Image image, int flags, int x, int y, int width, int height) {
205             if (image != ((TrayIcon)target).getImage() || // if the image has been changed
206                 isDisposed())
207             {
208                 return false;
209             }
210             if ((flags & (ImageObserver.FRAMEBITS | ImageObserver.ALLBITS |
211                           ImageObserver.WIDTH | ImageObserver.HEIGHT)) != 0)
212             {
213                 updateNativeImage(image);
214             }
215             return (flags & ImageObserver.ALLBITS) == 0;
216         }
217     }
218 }
219