1/*
2 * Copyright (c) 2011, 2016, 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
26package com.apple.laf;
27
28import java.awt.*;
29import java.awt.event.*;
30import java.awt.geom.Rectangle2D;
31import java.awt.image.BufferedImage;
32import java.beans.PropertyVetoException;
33
34import javax.swing.*;
35import javax.swing.plaf.*;
36
37import sun.swing.SwingUtilities2;
38
39/**
40 * From MacDockIconUI
41 *
42 * A JRSUI L&F implementation of JInternalFrame.JDesktopIcon
43 */
44public final class AquaInternalFrameDockIconUI extends DesktopIconUI
45        implements MouseListener, MouseMotionListener {
46
47    private JInternalFrame.JDesktopIcon fDesktopIcon;
48    private JInternalFrame fFrame;
49    private ScaledImageLabel fIconPane;
50    private DockLabel fDockLabel;
51    private boolean fTrackingIcon;
52
53    public static ComponentUI createUI(final JComponent c) {
54        return new AquaInternalFrameDockIconUI();
55    }
56
57    @Override
58    public void installUI(final JComponent c) {
59        fDesktopIcon = (JInternalFrame.JDesktopIcon)c;
60        installComponents();
61        installListeners();
62    }
63
64    @Override
65    public void uninstallUI(final JComponent c) {
66        uninstallComponents();
67        uninstallListeners();
68        fDesktopIcon = null;
69        fFrame = null;
70    }
71
72    private void installComponents() {
73        fFrame = fDesktopIcon.getInternalFrame();
74        fIconPane = new ScaledImageLabel();
75        fDesktopIcon.setLayout(new BorderLayout());
76        fDesktopIcon.add(fIconPane, BorderLayout.CENTER);
77    }
78
79    private void uninstallComponents() {
80        fDesktopIcon.setLayout(null);
81        fDesktopIcon.remove(fIconPane);
82    }
83
84    private void installListeners() {
85        fDesktopIcon.addMouseListener(this);
86        fDesktopIcon.addMouseMotionListener(this);
87    }
88
89    private void uninstallListeners() {
90        fDesktopIcon.removeMouseMotionListener(this);
91        fDesktopIcon.removeMouseListener(this);
92    }
93
94    @Override
95    public Dimension getMinimumSize(final JComponent c) {
96        return new Dimension(32, 32);
97    }
98
99    @Override
100    public Dimension getMaximumSize(final JComponent c) {
101        return new Dimension(128, 128);
102    }
103
104    @Override
105    public Dimension getPreferredSize(final JComponent c) {
106        return new Dimension(64, 64); //$ Dock preferred size
107    }
108
109    void updateIcon() {
110        fIconPane.updateIcon();
111    }
112
113    @Override
114    public void mousePressed(final MouseEvent e) {
115        fTrackingIcon = fIconPane.mouseInIcon(e);
116        if (fTrackingIcon) fIconPane.repaint();
117    }
118
119    @Override
120    public void mouseReleased(final MouseEvent e) {// only when it's actually in the image
121        if (fFrame.isIconifiable() && fFrame.isIcon()) {
122            if (fTrackingIcon) {
123                fTrackingIcon = false;
124                if (fIconPane.mouseInIcon(e)) {
125                    if (fDockLabel != null) fDockLabel.hide();
126                    try {
127                        fFrame.setIcon(false);
128                    } catch(final PropertyVetoException e2) {}
129                } else {
130                    fIconPane.repaint();
131                }
132            }
133        }
134
135        // if the mouse was completely outside fIconPane, hide the label
136        if (fDockLabel != null && !fIconPane.getBounds().contains(e.getX(), e.getY())) fDockLabel.hide();
137    }
138
139    @Override
140    @SuppressWarnings("deprecation")
141    public void mouseEntered(final MouseEvent e) {
142        if ((e.getModifiers() & InputEvent.BUTTON1_MASK) != 0) return;
143        String title = fFrame.getTitle();
144        if (title == null || title.equals("")) title = "Untitled";
145        fDockLabel = new DockLabel(title);
146        fDockLabel.show(fDesktopIcon);
147    }
148
149    @Override
150    @SuppressWarnings("deprecation")
151    public void mouseExited(final MouseEvent e) {
152        if (fDockLabel != null && (e.getModifiers() & InputEvent.BUTTON1_MASK) == 0) fDockLabel.hide();
153    }
154
155    @Override
156    public void mouseClicked(final MouseEvent e) { }
157
158    @Override
159    public void mouseDragged(final MouseEvent e) { }
160
161    @Override
162    public void mouseMoved(final MouseEvent e) { }
163
164    @SuppressWarnings("serial") // Superclass is not serializable across versions
165    private final class ScaledImageLabel extends JLabel {
166        ScaledImageLabel() {
167            super(null, null, CENTER);
168        }
169
170        void updateIcon() {
171            int width = fFrame.getWidth();
172            int height = fFrame.getHeight();
173
174            // Protect us from unsized frames, like in JCK test DefaultDesktopManager2008
175            if (width <= 0 || height <= 0) {
176                width = 128;
177                height = 128;
178            }
179
180            final Image fImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB_PRE);
181            final Graphics g = fImage.getGraphics();
182            fFrame.paint(g);
183            g.dispose();
184
185            final float scale = (float)fDesktopIcon.getWidth() / (float)Math.max(width, height) * 0.89f;
186            // Sending in -1 for width xor height causes it to maintain aspect ratio
187            setIcon(new ImageIcon(fImage.getScaledInstance((int)(width * scale), -1, Image.SCALE_SMOOTH)));
188        }
189
190        @Override
191        public void paint(final Graphics g) {
192            if (getIcon() == null) updateIcon();
193
194            g.translate(0, 2);
195
196            if (!fTrackingIcon) {
197                super.paint(g);
198                return;
199            }
200
201            final ImageIcon prev = (ImageIcon)getIcon();
202            final ImageIcon pressedIcon = new ImageIcon(AquaUtils.generateSelectedDarkImage(prev.getImage()));
203            setIcon(pressedIcon);
204            super.paint(g);
205            setIcon(prev);
206        }
207
208        boolean mouseInIcon(final MouseEvent e) {
209            return getBounds().contains(e.getX(), e.getY());
210        }
211
212        @Override
213        public Dimension getPreferredSize() {
214            return new Dimension(64, 64); //$ Dock preferred size
215        }
216    }
217
218    @SuppressWarnings("serial") // Superclass is not serializable across versions
219    private static final class DockLabel extends JLabel {
220        static final int NUB_HEIGHT = 7;
221        static final int ROUND_ADDITIONAL_HEIGHT = 8;
222        static final int ROUND_ADDITIONAL_WIDTH = 12;
223
224        DockLabel(final String text) {
225            super(text);
226            setBorder(null);
227            setOpaque(false);
228            setFont(AquaFonts.getDockIconFont());
229
230            final FontMetrics metrics = getFontMetrics(getFont());
231            setSize(SwingUtilities.computeStringWidth(metrics, getText()) + ROUND_ADDITIONAL_WIDTH * 2, metrics.getAscent() + NUB_HEIGHT + ROUND_ADDITIONAL_HEIGHT);
232        }
233
234        @Override
235        public void paint(final Graphics g) {
236            final int width = getWidth();
237            final int height = getHeight();
238
239            final Font font = getFont();
240            final FontMetrics metrics = getFontMetrics(font);
241            g.setFont(font);
242
243            final String text = getText().trim();
244            final int ascent = metrics.getAscent();
245
246            final Rectangle2D stringBounds = metrics.getStringBounds(text, g);
247            final int halfway = width / 2;
248
249            final int x = (halfway - (int)stringBounds.getWidth() / 2);
250
251            final Graphics2D g2d = g instanceof Graphics2D ? (Graphics2D)g : null;
252            if (g2d != null) {
253                g.setColor(UIManager.getColor("DesktopIcon.labelBackground"));
254                final Object origAA = g2d.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
255                g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
256
257                final int roundHeight = height - ROUND_ADDITIONAL_HEIGHT + 1;
258                g.fillRoundRect(0, 0, width, roundHeight, roundHeight, roundHeight);
259
260                final int[] xpts = { halfway, halfway + NUB_HEIGHT, halfway - NUB_HEIGHT };
261                final int[] ypts = { height, height - NUB_HEIGHT, height - NUB_HEIGHT };
262                g.fillPolygon(xpts, ypts, 3);
263
264                g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, origAA);
265            }
266
267            g.setColor(Color.black);
268            SwingUtilities2.drawString(this, g, text, x, 2 + ascent);
269            g.setColor(Color.white);
270            SwingUtilities2.drawString(this, g, text, x, 1 + ascent);
271        }
272
273        public void show(final Component invoker) {
274            final int desiredLocationX = (invoker.getWidth() - getWidth()) / 2;
275            final int desiredLocationY = -(getHeight() + 6);
276
277            Container parent = invoker.getParent();
278
279            for (Container p = parent; p != null; p = p.getParent()) {
280                if (p instanceof JRootPane) {
281                    if (p.getParent() instanceof JInternalFrame) continue;
282                    parent = ((JRootPane)p).getLayeredPane();
283                    for (p = parent.getParent(); p != null && (!(p instanceof java.awt.Window)); p = p.getParent());
284                    break;
285                }
286            }
287
288            final Point p = SwingUtilities.convertPoint(invoker, desiredLocationX, desiredLocationY, parent);
289            setLocation(p.x, p.y);
290            if (parent instanceof JLayeredPane) {
291                ((JLayeredPane)parent).add(this, JLayeredPane.POPUP_LAYER, 0);
292            }
293        }
294
295        @Override
296        @Deprecated
297        public void hide() {
298            final Container parent = getParent();
299            final Rectangle r = this.getBounds();
300            if (parent == null) return;
301            parent.remove(this);
302            parent.repaint(r.x, r.y, r.width, r.height);
303        }
304    }
305}
306