1/*
2 * Copyright (c) 2013, 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 sun.swing;
27
28import java.awt.*;
29import java.awt.dnd.DragGestureEvent;
30import java.awt.dnd.DragGestureListener;
31import java.awt.dnd.DragGestureRecognizer;
32import java.awt.dnd.DragSource;
33import java.awt.dnd.DropTarget;
34import java.awt.dnd.InvalidDnDOperationException;
35import java.awt.dnd.peer.DragSourceContextPeer;
36import java.awt.event.ContainerEvent;
37import java.awt.event.ContainerListener;
38import java.awt.geom.AffineTransform;
39import java.awt.image.BufferedImage;
40import java.awt.image.DataBufferInt;
41import java.beans.PropertyChangeEvent;
42import java.beans.PropertyChangeListener;
43import java.security.AccessController;
44import javax.swing.JComponent;
45
46import javax.swing.JLayeredPane;
47import javax.swing.JPanel;
48import javax.swing.JRootPane;
49import javax.swing.LayoutFocusTraversalPolicy;
50import javax.swing.RepaintManager;
51import javax.swing.RootPaneContainer;
52import javax.swing.SwingUtilities;
53
54import sun.awt.AWTAccessor;
55import sun.awt.DisplayChangedListener;
56import sun.awt.LightweightFrame;
57import sun.security.action.GetPropertyAction;
58import sun.swing.SwingUtilities2.RepaintListener;
59
60/**
61 * The frame serves as a lightweight container which paints its content
62 * to an offscreen image and provides access to the image's data via the
63 * {@link LightweightContent} interface. Note, that it may not be shown
64 * as a standalone toplevel frame. Its purpose is to provide functionality
65 * for lightweight embedding.
66 *
67 * @author Artem Ananiev
68 * @author Anton Tarasov
69 */
70@SuppressWarnings("serial") // JDK-implementation class
71public final class JLightweightFrame extends LightweightFrame implements RootPaneContainer {
72
73    private final JRootPane rootPane = new JRootPane();
74
75    private LightweightContent content;
76
77    private Component component;
78    private JPanel contentPane;
79
80    private BufferedImage bbImage;
81
82    private volatile double scaleFactorX;
83    private volatile double scaleFactorY;
84
85    /**
86     * {@code copyBufferEnabled}, true by default, defines the following strategy.
87     * A duplicating (copy) buffer is created for the original pixel buffer.
88     * The copy buffer is synchronized with the original buffer every time the
89     * latter changes. {@code JLightweightFrame} passes the copy buffer array
90     * to the {@link LightweightContent#imageBufferReset} method. The code spot
91     * which synchronizes two buffers becomes the only critical section guarded
92     * by the lock (managed with the {@link LightweightContent#paintLock()},
93     * {@link LightweightContent#paintUnlock()} methods).
94     */
95    private static boolean copyBufferEnabled;
96    private int[] copyBuffer;
97
98    private PropertyChangeListener layoutSizeListener;
99    private RepaintListener repaintListener;
100
101    static {
102        SwingAccessor.setJLightweightFrameAccessor(new SwingAccessor.JLightweightFrameAccessor() {
103            @Override
104            public void updateCursor(JLightweightFrame frame) {
105                frame.updateClientCursor();
106            }
107        });
108        copyBufferEnabled = "true".equals(AccessController.
109            doPrivileged(new GetPropertyAction("swing.jlf.copyBufferEnabled", "true")));
110    }
111
112    /**
113     * Constructs a new, initially invisible {@code JLightweightFrame}
114     * instance.
115     */
116    public JLightweightFrame() {
117        super();
118        AffineTransform defaultTransform =
119                           getGraphicsConfiguration().getDefaultTransform();
120        scaleFactorX = defaultTransform.getScaleX();
121        scaleFactorY = defaultTransform.getScaleY();
122        copyBufferEnabled = "true".equals(AccessController.
123            doPrivileged(new GetPropertyAction("swing.jlf.copyBufferEnabled", "true")));
124
125        add(rootPane, BorderLayout.CENTER);
126        setFocusTraversalPolicy(new LayoutFocusTraversalPolicy());
127        if (getGraphicsConfiguration().isTranslucencyCapable()) {
128            setBackground(new Color(0, 0, 0, 0));
129        }
130
131        layoutSizeListener = new PropertyChangeListener() {
132            @Override
133            public void propertyChange(PropertyChangeEvent e) {
134                Dimension d = (Dimension)e.getNewValue();
135
136                if ("preferredSize".equals(e.getPropertyName())) {
137                    content.preferredSizeChanged(d.width, d.height);
138
139                } else if ("maximumSize".equals(e.getPropertyName())) {
140                    content.maximumSizeChanged(d.width, d.height);
141
142                } else if ("minimumSize".equals(e.getPropertyName())) {
143                    content.minimumSizeChanged(d.width, d.height);
144                }
145            }
146        };
147
148        repaintListener = (JComponent c, int x, int y, int w, int h) -> {
149            Window jlf = SwingUtilities.getWindowAncestor(c);
150            if (jlf != JLightweightFrame.this) {
151                return;
152            }
153            Point p = SwingUtilities.convertPoint(c, x, y, jlf);
154            Rectangle r = new Rectangle(p.x, p.y, w, h).intersection(
155                    new Rectangle(0, 0,
156                          (int)Math.round(bbImage.getWidth() / scaleFactorX),
157                          (int)Math.round(bbImage.getHeight() / scaleFactorY)));
158
159            if (!r.isEmpty()) {
160                notifyImageUpdated(r.x, r.y, r.width, r.height);
161            }
162        };
163
164        SwingAccessor.getRepaintManagerAccessor().addRepaintListener(
165            RepaintManager.currentManager(this), repaintListener);
166    }
167
168    @Override
169    public void dispose() {
170        SwingAccessor.getRepaintManagerAccessor().removeRepaintListener(
171            RepaintManager.currentManager(this), repaintListener);
172        super.dispose();
173    }
174
175    /**
176     * Sets the {@link LightweightContent} instance for this frame.
177     * The {@code JComponent} object returned by the
178     * {@link LightweightContent#getComponent()} method is immediately
179     * added to the frame's content pane.
180     *
181     * @param content the {@link LightweightContent} instance
182     */
183    public void setContent(final LightweightContent content) {
184        if (content == null) {
185            System.err.println("JLightweightFrame.setContent: content may not be null!");
186            return;
187        }
188        this.content = content;
189        this.component = content.getComponent();
190
191        Dimension d = this.component.getPreferredSize();
192        content.preferredSizeChanged(d.width, d.height);
193
194        d = this.component.getMaximumSize();
195        content.maximumSizeChanged(d.width, d.height);
196
197        d = this.component.getMinimumSize();
198        content.minimumSizeChanged(d.width, d.height);
199
200        initInterior();
201    }
202
203    @Override
204    public Graphics getGraphics() {
205        if (bbImage == null) return null;
206
207        Graphics2D g = bbImage.createGraphics();
208        g.setBackground(getBackground());
209        g.setColor(getForeground());
210        g.setFont(getFont());
211        g.scale(scaleFactorX, scaleFactorY);
212        return g;
213    }
214
215    /**
216     * {@inheritDoc}
217     *
218     * @see LightweightContent#focusGrabbed()
219     */
220    @Override
221    public void grabFocus() {
222        if (content != null) content.focusGrabbed();
223    }
224
225    /**
226     * {@inheritDoc}
227     *
228     * @see LightweightContent#focusUngrabbed()
229     */
230    @Override
231    public void ungrabFocus() {
232        if (content != null) content.focusUngrabbed();
233    }
234
235    @Override
236    @SuppressWarnings("deprecation")
237    public int getScaleFactor() {
238        return (int)scaleFactorX;
239    }
240
241    @Override
242    public double getScaleFactorX() {
243        return scaleFactorX;
244    }
245
246    @Override
247    public double getScaleFactorY() {
248        return scaleFactorY;
249    }
250
251    @Override
252    @SuppressWarnings("deprecation")
253    public void notifyDisplayChanged(final int scaleFactor) {
254        notifyDisplayChanged(scaleFactor, scaleFactor);
255    }
256
257    @Override
258    public void notifyDisplayChanged(final double scaleFactorX,
259                                                    final double scaleFactorY) {
260        if (Double.compare(scaleFactorX, this.scaleFactorX) != 0 ||
261                         Double.compare(scaleFactorY, this.scaleFactorY) != 0) {
262            if (!copyBufferEnabled) content.paintLock();
263            try {
264                if (bbImage != null) {
265                    resizeBuffer(getWidth(), getHeight(), scaleFactorX,
266                                                                  scaleFactorY);
267                }
268            } finally {
269                if (!copyBufferEnabled) content.paintUnlock();
270            }
271            this.scaleFactorX = scaleFactorX;
272            this.scaleFactorY = scaleFactorY;
273
274            if(isVisible()) {
275                final Object peer =
276                        AWTAccessor.getComponentAccessor().getPeer(this);
277                if (peer instanceof DisplayChangedListener) {
278                    ((DisplayChangedListener) peer).displayChanged();
279                }
280                repaint();
281            }
282        }
283    }
284
285    @Override
286    public void addNotify() {
287        super.addNotify();
288        final Object peer = AWTAccessor.getComponentAccessor().getPeer(this);
289        if (peer instanceof DisplayChangedListener) {
290            ((DisplayChangedListener) peer).displayChanged();
291        }
292    }
293
294    private void syncCopyBuffer(boolean reset, int x, int y, int w, int h,
295                                                 double scaleX, double scaleY) {
296        content.paintLock();
297        try {
298            int[] srcBuffer = ((DataBufferInt)bbImage.getRaster().getDataBuffer()).getData();
299            if (reset) {
300                copyBuffer = new int[srcBuffer.length];
301            }
302            int linestride = bbImage.getWidth();
303
304            int startX = (int)Math.floor(x * scaleX);
305            int startY = (int)Math.floor(y * scaleY);
306            int width  = (int)Math.ceil((x + w) * scaleX) - startX;
307            int height = (int)Math.ceil((y + h) * scaleY) - startY;
308            if (startX + width > linestride) {
309                width = linestride - startX;
310            }
311            if (startY + height > bbImage.getHeight()) {
312                height = bbImage.getHeight() - startY;
313            }
314
315            for (int i = 0; i < height; i++) {
316                int from = (startY + i) * linestride + startX;
317                System.arraycopy(srcBuffer, from, copyBuffer, from, width);
318            }
319        } finally {
320            content.paintUnlock();
321        }
322    }
323
324    private void notifyImageUpdated(int x, int y, int width, int height) {
325        if (copyBufferEnabled) {
326            syncCopyBuffer(false, x, y, width, height, scaleFactorX,
327                                                                  scaleFactorY);
328        }
329        content.imageUpdated(x, y, width, height);
330    }
331
332    @SuppressWarnings("serial") // anonymous class inside
333    private void initInterior() {
334        contentPane = new JPanel() {
335            @Override
336            public void paint(Graphics g) {
337                if (!copyBufferEnabled) {
338                    content.paintLock();
339                }
340                try {
341                    super.paint(g);
342
343                    final Rectangle clip = g.getClipBounds() != null ?
344                            g.getClipBounds() :
345                            new Rectangle(0, 0, contentPane.getWidth(), contentPane.getHeight());
346
347                    clip.x = Math.max(0, clip.x);
348                    clip.y = Math.max(0, clip.y);
349                    clip.width = Math.min(contentPane.getWidth(), clip.width);
350                    clip.height = Math.min(contentPane.getHeight(), clip.height);
351
352                    EventQueue.invokeLater(new Runnable() {
353                        @Override
354                        public void run() {
355                            Rectangle c = contentPane.getBounds().intersection(clip);
356                            notifyImageUpdated(c.x, c.y, c.width, c.height);
357                        }
358                    });
359                } finally {
360                    if (!copyBufferEnabled) {
361                        content.paintUnlock();
362                    }
363                }
364            }
365            @Override
366            protected boolean isPaintingOrigin() {
367                return true;
368            }
369        };
370        contentPane.setLayout(new BorderLayout());
371        contentPane.add(component);
372        if ("true".equals(AccessController.
373            doPrivileged(new GetPropertyAction("swing.jlf.contentPaneTransparent", "false"))))
374        {
375            contentPane.setOpaque(false);
376        }
377        setContentPane(contentPane);
378
379        contentPane.addContainerListener(new ContainerListener() {
380            @Override
381            public void componentAdded(ContainerEvent e) {
382                Component c = JLightweightFrame.this.component;
383                if (e.getChild() == c) {
384                    c.addPropertyChangeListener("preferredSize", layoutSizeListener);
385                    c.addPropertyChangeListener("maximumSize", layoutSizeListener);
386                    c.addPropertyChangeListener("minimumSize", layoutSizeListener);
387                }
388            }
389            @Override
390            public void componentRemoved(ContainerEvent e) {
391                Component c = JLightweightFrame.this.component;
392                if (e.getChild() == c) {
393                    c.removePropertyChangeListener(layoutSizeListener);
394                }
395            }
396        });
397    }
398
399    @SuppressWarnings("deprecation")
400    @Override public void reshape(int x, int y, int width, int height) {
401        super.reshape(x, y, width, height);
402
403        if (width == 0 || height == 0) {
404            return;
405        }
406        if (!copyBufferEnabled) {
407            content.paintLock();
408        }
409        try {
410            boolean createBB = (bbImage == null);
411            int newW = width;
412            int newH = height;
413            if (bbImage != null) {
414                int imgWidth = (int)Math.round(bbImage.getWidth() /
415                                                                  scaleFactorX);
416                int imgHeight = (int)Math.round(bbImage.getHeight() /
417                                                                  scaleFactorY);
418                if (width != imgWidth || height != imgHeight) {
419                    createBB = true;
420                    if (bbImage != null) {
421                        int oldW = imgWidth;
422                        int oldH = imgHeight;
423                        if ((oldW >= newW) && (oldH >= newH)) {
424                            createBB = false;
425                        } else {
426                            if (oldW >= newW) {
427                                newW = oldW;
428                            } else {
429                                newW = Math.max((int)(oldW * 1.2), width);
430                            }
431                            if (oldH >= newH) {
432                                newH = oldH;
433                            } else {
434                                newH = Math.max((int)(oldH * 1.2), height);
435                            }
436                        }
437                    }
438                }
439            }
440            if (createBB) {
441                resizeBuffer(newW, newH, scaleFactorX, scaleFactorY);
442                return;
443            }
444            content.imageReshaped(0, 0, width, height);
445
446        } finally {
447            if (!copyBufferEnabled) {
448                content.paintUnlock();
449            }
450        }
451    }
452
453    private void resizeBuffer(int width, int height, double newScaleFactorX,
454                                                     double newScaleFactorY) {
455        bbImage = new BufferedImage((int)Math.round(width * newScaleFactorX),
456                                    (int)Math.round(height * newScaleFactorY),
457                                        BufferedImage.TYPE_INT_ARGB_PRE);
458        int[] pixels= ((DataBufferInt)bbImage.getRaster().getDataBuffer()).getData();
459        if (copyBufferEnabled) {
460            syncCopyBuffer(true, 0, 0, width, height, newScaleFactorX,
461                                                               newScaleFactorY);
462            pixels = copyBuffer;
463        }
464        content.imageBufferReset(pixels, 0, 0, width, height,
465                          bbImage.getWidth(), newScaleFactorX, newScaleFactorY);
466    }
467
468    @Override
469    public JRootPane getRootPane() {
470        return rootPane;
471    }
472
473    @Override
474    public void setContentPane(Container contentPane) {
475        getRootPane().setContentPane(contentPane);
476    }
477
478    @Override
479    public Container getContentPane() {
480        return getRootPane().getContentPane();
481    }
482
483    @Override
484    public void setLayeredPane(JLayeredPane layeredPane) {
485        getRootPane().setLayeredPane(layeredPane);
486    }
487
488    @Override
489    public JLayeredPane getLayeredPane() {
490        return getRootPane().getLayeredPane();
491    }
492
493    @Override
494    public void setGlassPane(Component glassPane) {
495        getRootPane().setGlassPane(glassPane);
496    }
497
498    @Override
499    public Component getGlassPane() {
500        return getRootPane().getGlassPane();
501    }
502
503
504    /*
505     * Notifies client toolkit that it should change a cursor.
506     *
507     * Called from the peer via SwingAccessor, because the
508     * Component.updateCursorImmediately method is final
509     * and could not be overridden.
510     */
511    private void updateClientCursor() {
512        PointerInfo pointerInfo = MouseInfo.getPointerInfo();
513        if (pointerInfo == null) {
514            /*
515             * This can happen when multiple graphics device cannot decide
516             * which graphics device contains the current mouse position
517             * or on systems without a mouse
518             */
519             return;
520        }
521        Point p = pointerInfo.getLocation();
522        SwingUtilities.convertPointFromScreen(p, this);
523        Component target = SwingUtilities.getDeepestComponentAt(this, p.x, p.y);
524        if (target != null) {
525            content.setCursor(target.getCursor());
526        }
527    }
528
529    public <T extends DragGestureRecognizer> T createDragGestureRecognizer(
530            Class<T> abstractRecognizerClass,
531            DragSource ds, Component c, int srcActions,
532            DragGestureListener dgl)
533    {
534        return content == null ? null : content.createDragGestureRecognizer(
535                abstractRecognizerClass, ds, c, srcActions, dgl);
536    }
537
538    public DragSourceContextPeer createDragSourceContextPeer(DragGestureEvent dge) throws InvalidDnDOperationException {
539        return content == null ? null : content.createDragSourceContextPeer(dge);
540    }
541
542    public void addDropTarget(DropTarget dt) {
543        if (content == null) return;
544        content.addDropTarget(dt);
545    }
546
547    public void removeDropTarget(DropTarget dt) {
548        if (content == null) return;
549        content.removeDropTarget(dt);
550    }
551}
552