/*
 * Copyright (c) 2013, 2018, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package sun.swing;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.MouseInfo;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Window;
import java.awt.dnd.DragGestureEvent;
import java.awt.dnd.DragGestureListener;
import java.awt.dnd.DragGestureRecognizer;
import java.awt.dnd.DragSource;
import java.awt.dnd.DropTarget;
import java.awt.dnd.InvalidDnDOperationException;
import java.awt.dnd.peer.DragSourceContextPeer;
import java.awt.event.ContainerEvent;
import java.awt.event.ContainerListener;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.security.AccessController;
import javax.swing.JComponent;

import javax.swing.JLayeredPane;
import javax.swing.JPanel;
import javax.swing.JRootPane;
import javax.swing.LayoutFocusTraversalPolicy;
import javax.swing.RepaintManager;
import javax.swing.RootPaneContainer;
import javax.swing.SwingUtilities;

import sun.awt.AWTAccessor;
import sun.awt.DisplayChangedListener;
import sun.awt.LightweightFrame;
import sun.awt.OverrideNativeWindowHandle;
import sun.security.action.GetPropertyAction;
import sun.swing.SwingUtilities2.RepaintListener;

The frame serves as a lightweight container which paints its content to an offscreen image and provides access to the image's data via the LightweightContent interface. Note, that it may not be shown as a standalone toplevel frame. Its purpose is to provide functionality for lightweight embedding.
Author:Artem Ananiev, Anton Tarasov
/** * The frame serves as a lightweight container which paints its content * to an offscreen image and provides access to the image's data via the * {@link LightweightContent} interface. Note, that it may not be shown * as a standalone toplevel frame. Its purpose is to provide functionality * for lightweight embedding. * * @author Artem Ananiev * @author Anton Tarasov */
public final class JLightweightFrame extends LightweightFrame implements RootPaneContainer { private final JRootPane rootPane = new JRootPane(); private LightweightContent content; private Component component; private JPanel contentPane; private BufferedImage bbImage; private volatile int scaleFactor = 1;
copyBufferEnabled, true by default, defines the following strategy. A duplicating (copy) buffer is created for the original pixel buffer. The copy buffer is synchronized with the original buffer every time the latter changes. JLightweightFrame passes the copy buffer array to the LightweightContent.imageBufferReset method. The code spot which synchronizes two buffers becomes the only critical section guarded by the lock (managed with the LightweightContent.paintLock(), LightweightContent.paintUnlock() methods).
/** * {@code copyBufferEnabled}, true by default, defines the following strategy. * A duplicating (copy) buffer is created for the original pixel buffer. * The copy buffer is synchronized with the original buffer every time the * latter changes. {@code JLightweightFrame} passes the copy buffer array * to the {@link LightweightContent#imageBufferReset} method. The code spot * which synchronizes two buffers becomes the only critical section guarded * by the lock (managed with the {@link LightweightContent#paintLock()}, * {@link LightweightContent#paintUnlock()} methods). */
private static boolean copyBufferEnabled; private int[] copyBuffer; private PropertyChangeListener layoutSizeListener; private RepaintListener repaintListener; static { SwingAccessor.setJLightweightFrameAccessor(new SwingAccessor.JLightweightFrameAccessor() { @Override public void updateCursor(JLightweightFrame frame) { frame.updateClientCursor(); } }); copyBufferEnabled = "true".equals(AccessController. doPrivileged(new GetPropertyAction("swing.jlf.copyBufferEnabled", "true"))); }
Constructs a new, initially invisible JLightweightFrame instance.
/** * Constructs a new, initially invisible {@code JLightweightFrame} * instance. */
public JLightweightFrame() { super(); copyBufferEnabled = "true".equals(AccessController. doPrivileged(new GetPropertyAction("swing.jlf.copyBufferEnabled", "true"))); add(rootPane, BorderLayout.CENTER); setFocusTraversalPolicy(new LayoutFocusTraversalPolicy()); if (getGraphicsConfiguration().isTranslucencyCapable()) { setBackground(new Color(0, 0, 0, 0)); } layoutSizeListener = new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent e) { Dimension d = (Dimension)e.getNewValue(); if ("preferredSize".equals(e.getPropertyName())) { content.preferredSizeChanged(d.width, d.height); } else if ("maximumSize".equals(e.getPropertyName())) { content.maximumSizeChanged(d.width, d.height); } else if ("minimumSize".equals(e.getPropertyName())) { content.minimumSizeChanged(d.width, d.height); } } }; repaintListener = (JComponent c, int x, int y, int w, int h) -> { Window jlf = SwingUtilities.getWindowAncestor(c); if (jlf != JLightweightFrame.this) { return; } Point p = SwingUtilities.convertPoint(c, x, y, jlf); Rectangle r = new Rectangle(p.x, p.y, w, h).intersection( new Rectangle(0, 0, bbImage.getWidth() / scaleFactor, bbImage.getHeight() / scaleFactor)); if (!r.isEmpty()) { notifyImageUpdated(r.x, r.y, r.width, r.height); } }; SwingAccessor.getRepaintManagerAccessor().addRepaintListener( RepaintManager.currentManager(this), repaintListener); } @Override public void dispose() { SwingAccessor.getRepaintManagerAccessor().removeRepaintListener( RepaintManager.currentManager(this), repaintListener); super.dispose(); }
Sets the LightweightContent instance for this frame. The JComponent object returned by the LightweightContent.getComponent() method is immediately added to the frame's content pane.
Params:
/** * Sets the {@link LightweightContent} instance for this frame. * The {@code JComponent} object returned by the * {@link LightweightContent#getComponent()} method is immediately * added to the frame's content pane. * * @param content the {@link LightweightContent} instance */
public void setContent(final LightweightContent content) { if (content == null) { System.err.println("JLightweightFrame.setContent: content may not be null!"); return; } this.content = content; this.component = content.getComponent(); Dimension d = this.component.getPreferredSize(); content.preferredSizeChanged(d.width, d.height); d = this.component.getMaximumSize(); content.maximumSizeChanged(d.width, d.height); d = this.component.getMinimumSize(); content.minimumSizeChanged(d.width, d.height); initInterior(); } @Override public Graphics getGraphics() { if (bbImage == null) return null; Graphics2D g = bbImage.createGraphics(); g.setBackground(getBackground()); g.setColor(getForeground()); g.setFont(getFont()); g.scale(scaleFactor, scaleFactor); return g; }
{@inheritDoc}
See Also:
/** * {@inheritDoc} * * @see LightweightContent#focusGrabbed() */
@Override public void grabFocus() { if (content != null) content.focusGrabbed(); }
{@inheritDoc}
See Also:
/** * {@inheritDoc} * * @see LightweightContent#focusUngrabbed() */
@Override public void ungrabFocus() { if (content != null) content.focusUngrabbed(); } @Override public int getScaleFactor() { return scaleFactor; } @Override public void notifyDisplayChanged(final int scaleFactor) { if (scaleFactor != this.scaleFactor) { if (!copyBufferEnabled) content.paintLock(); try { if (bbImage != null) { resizeBuffer(getWidth(), getHeight(), scaleFactor); } } finally { if (!copyBufferEnabled) content.paintUnlock(); } this.scaleFactor = scaleFactor; } if (getPeer() instanceof DisplayChangedListener) { ((DisplayChangedListener)getPeer()).displayChanged(); } repaint(); } @Override public void addNotify() { super.addNotify(); if (getPeer() instanceof DisplayChangedListener) { ((DisplayChangedListener)getPeer()).displayChanged(); } } private void syncCopyBuffer(boolean reset, int x, int y, int w, int h, int scale) { content.paintLock(); try { int[] srcBuffer = ((DataBufferInt)bbImage.getRaster().getDataBuffer()).getData(); if (reset) { copyBuffer = new int[srcBuffer.length]; } int linestride = bbImage.getWidth(); x *= scale; y *= scale; w *= scale; h *= scale; for (int i=0; i<h; i++) { int from = (y + i) * linestride + x; System.arraycopy(srcBuffer, from, copyBuffer, from, w); } } finally { content.paintUnlock(); } } private void notifyImageUpdated(int x, int y, int width, int height) { if (copyBufferEnabled) { syncCopyBuffer(false, x, y, width, height, scaleFactor); } content.imageUpdated(x, y, width, height); } private void initInterior() { contentPane = new JPanel() { @Override public void paint(Graphics g) { if (!copyBufferEnabled) { content.paintLock(); } try { super.paint(g); final Rectangle clip = g.getClipBounds() != null ? g.getClipBounds() : new Rectangle(0, 0, contentPane.getWidth(), contentPane.getHeight()); clip.x = Math.max(0, clip.x); clip.y = Math.max(0, clip.y); clip.width = Math.min(contentPane.getWidth(), clip.width); clip.height = Math.min(contentPane.getHeight(), clip.height); EventQueue.invokeLater(new Runnable() { @Override public void run() { Rectangle c = contentPane.getBounds().intersection(clip); notifyImageUpdated(c.x, c.y, c.width, c.height); } }); } finally { if (!copyBufferEnabled) { content.paintUnlock(); } } } @Override protected boolean isPaintingOrigin() { return true; } }; contentPane.setLayout(new BorderLayout()); contentPane.add(component); if ("true".equals(AccessController. doPrivileged(new GetPropertyAction("swing.jlf.contentPaneTransparent", "false")))) { contentPane.setOpaque(false); } setContentPane(contentPane); contentPane.addContainerListener(new ContainerListener() { @Override public void componentAdded(ContainerEvent e) { Component c = JLightweightFrame.this.component; if (e.getChild() == c) { c.addPropertyChangeListener("preferredSize", layoutSizeListener); c.addPropertyChangeListener("maximumSize", layoutSizeListener); c.addPropertyChangeListener("minimumSize", layoutSizeListener); } } @Override public void componentRemoved(ContainerEvent e) { Component c = JLightweightFrame.this.component; if (e.getChild() == c) { c.removePropertyChangeListener(layoutSizeListener); } } }); } @SuppressWarnings("deprecation") @Override public void reshape(int x, int y, int width, int height) { super.reshape(x, y, width, height); if (width == 0 || height == 0) { return; } if (!copyBufferEnabled) { content.paintLock(); } try { boolean createBB = (bbImage == null); int newW = width; int newH = height; if (bbImage != null) { int imgWidth = bbImage.getWidth() / scaleFactor; int imgHeight = bbImage.getHeight() / scaleFactor; if (width != imgWidth || height != imgHeight) { createBB = true; if (bbImage != null) { int oldW = imgWidth; int oldH = imgHeight; if ((oldW >= newW) && (oldH >= newH)) { createBB = false; } else { if (oldW >= newW) { newW = oldW; } else { newW = Math.max((int)(oldW * 1.2), width); } if (oldH >= newH) { newH = oldH; } else { newH = Math.max((int)(oldH * 1.2), height); } } } } } if (createBB) { resizeBuffer(newW, newH, scaleFactor); return; } content.imageReshaped(0, 0, width, height); } finally { if (!copyBufferEnabled) { content.paintUnlock(); } } } private void resizeBuffer(int width, int height, int newScaleFactor) { bbImage = new BufferedImage(width*newScaleFactor,height*newScaleFactor, BufferedImage.TYPE_INT_ARGB_PRE); int[] pixels= ((DataBufferInt)bbImage.getRaster().getDataBuffer()).getData(); if (copyBufferEnabled) { syncCopyBuffer(true, 0, 0, width, height, newScaleFactor); pixels = copyBuffer; } content.imageBufferReset(pixels, 0, 0, width, height, width * newScaleFactor, newScaleFactor); } @Override public JRootPane getRootPane() { return rootPane; } @Override public void setContentPane(Container contentPane) { getRootPane().setContentPane(contentPane); } @Override public Container getContentPane() { return getRootPane().getContentPane(); } @Override public void setLayeredPane(JLayeredPane layeredPane) { getRootPane().setLayeredPane(layeredPane); } @Override public JLayeredPane getLayeredPane() { return getRootPane().getLayeredPane(); } @Override public void setGlassPane(Component glassPane) { getRootPane().setGlassPane(glassPane); } @Override public Component getGlassPane() { return getRootPane().getGlassPane(); } /* * Notifies client toolkit that it should change a cursor. * * Called from the peer via SwingAccessor, because the * Component.updateCursorImmediately method is final * and could not be overridden. */ private void updateClientCursor() { Point p = MouseInfo.getPointerInfo().getLocation(); SwingUtilities.convertPointFromScreen(p, this); Component target = SwingUtilities.getDeepestComponentAt(this, p.x, p.y); if (target != null) { content.setCursor(target.getCursor()); } } //Called by reflection by SwingNode public void overrideNativeWindowHandle(long handle, Runnable closeWindow) { final Object peer = AWTAccessor.getComponentAccessor().getPeer(this); if (peer instanceof OverrideNativeWindowHandle) { ((OverrideNativeWindowHandle) peer).overrideWindowHandle(handle); } if (closeWindow != null) { closeWindow.run(); } } public <T extends DragGestureRecognizer> T createDragGestureRecognizer( Class<T> abstractRecognizerClass, DragSource ds, Component c, int srcActions, DragGestureListener dgl) { return content == null ? null : content.createDragGestureRecognizer( abstractRecognizerClass, ds, c, srcActions, dgl); } public DragSourceContextPeer createDragSourceContextPeer(DragGestureEvent dge) throws InvalidDnDOperationException { return content == null ? null : content.createDragSourceContextPeer(dge); } public void addDropTarget(DropTarget dt) { if (content == null) return; content.addDropTarget(dt); } public void removeDropTarget(DropTarget dt) { if (content == null) return; content.removeDropTarget(dt); } }