| /* | |
| * Copyright (c) 2013, 2024, 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.*; | |
| 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.geom.AffineTransform; | |
| import java.awt.image.BufferedImage; | |
| import java.awt.image.DataBufferInt; | |
| import java.beans.PropertyChangeEvent; | |
| import java.beans.PropertyChangeListener; | |
| 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.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 | |
| * {@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 | |
| */ | |
| @SuppressWarnings("serial") // JDK-implementation class | |
| 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 double scaleFactorX; | |
| private volatile double scaleFactorY; | |
| private PropertyChangeListener layoutSizeListener; | |
| private RepaintListener repaintListener; | |
| static { | |
| SwingAccessor.setJLightweightFrameAccessor(new SwingAccessor.JLightweightFrameAccessor() { | |
| @Override | |
| public void updateCursor(JLightweightFrame frame) { | |
| frame.updateClientCursor(); | |
| } | |
| }); | |
| } | |
| /** | |
| * {@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 = "true".equals( | |
| System.getProperty("swing.jlf.copyBufferEnabled", "true")); | |
| private int[] copyBuffer; | |
| /** | |
| * Constructs a new, initially invisible {@code JLightweightFrame} | |
| * instance. | |
| */ | |
| public JLightweightFrame() { | |
| super(); | |
| AffineTransform defaultTransform = | |
| getGraphicsConfiguration().getDefaultTransform(); | |
| scaleFactorX = defaultTransform.getScaleX(); | |
| scaleFactorY = defaultTransform.getScaleY(); | |
| copyBufferEnabled = "true".equals(System.getProperty("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, | |
| (int)Math.round(bbImage.getWidth() / scaleFactorX), | |
| (int)Math.round(bbImage.getHeight() / scaleFactorY))); | |
| 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 {@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(scaleFactorX, scaleFactorY); | |
| return g; | |
| } | |
| /** | |
| * {@inheritDoc} | |
| * | |
| * @see LightweightContent#focusGrabbed() | |
| */ | |
| @Override | |
| public void grabFocus() { | |
| if (content != null) content.focusGrabbed(); | |
| } | |
| /** | |
| * {@inheritDoc} | |
| * | |
| * @see LightweightContent#focusUngrabbed() | |
| */ | |
| @Override | |
| public void ungrabFocus() { | |
| if (content != null) content.focusUngrabbed(); | |
| } | |
| @Override | |
| @SuppressWarnings("deprecation") | |
| public int getScaleFactor() { | |
| return (int)scaleFactorX; | |
| } | |
| @Override | |
| public double getScaleFactorX() { | |
| return scaleFactorX; | |
| } | |
| @Override | |
| public double getScaleFactorY() { | |
| return scaleFactorY; | |
| } | |
| @Override | |
| @SuppressWarnings("deprecation") | |
| public void notifyDisplayChanged(final int scaleFactor) { | |
| notifyDisplayChanged(scaleFactor, scaleFactor); | |
| } | |
| @Override | |
| public void notifyDisplayChanged(final double scaleFactorX, | |
| final double scaleFactorY) { | |
| if (Double.compare(scaleFactorX, this.scaleFactorX) != 0 || | |
| Double.compare(scaleFactorY, this.scaleFactorY) != 0) { | |
| if (!copyBufferEnabled) content.paintLock(); | |
| try { | |
| if (bbImage != null) { | |
| resizeBuffer(getWidth(), getHeight(), scaleFactorX, | |
| scaleFactorY); | |
| } | |
| } finally { | |
| if (!copyBufferEnabled) content.paintUnlock(); | |
| } | |
| this.scaleFactorX = scaleFactorX; | |
| this.scaleFactorY = scaleFactorY; | |
| if(isVisible()) { | |
| final Object peer = | |
| AWTAccessor.getComponentAccessor().getPeer(this); | |
| if (peer instanceof DisplayChangedListener) { | |
| ((DisplayChangedListener) peer).displayChanged(); | |
| } | |
| repaint(); | |
| } | |
| } | |
| } | |
| @Override | |
| public void addNotify() { | |
| super.addNotify(); | |
| final Object peer = AWTAccessor.getComponentAccessor().getPeer(this); | |
| if (peer instanceof DisplayChangedListener) { | |
| ((DisplayChangedListener) peer).displayChanged(); | |
| } | |
| } | |
| private void syncCopyBuffer(boolean reset, int x, int y, int w, int h, | |
| double scaleX, double scaleY) { | |
| content.paintLock(); | |
| try { | |
| int[] srcBuffer = ((DataBufferInt)bbImage.getRaster().getDataBuffer()).getData(); | |
| if (reset) { | |
| copyBuffer = new int[srcBuffer.length]; | |
| } | |
| int linestride = bbImage.getWidth(); | |
| int startX = (int)Math.floor(x * scaleX); | |
| int startY = (int)Math.floor(y * scaleY); | |
| int width = (int)Math.ceil((x + w) * scaleX) - startX; | |
| int height = (int)Math.ceil((y + h) * scaleY) - startY; | |
| if (startX + width > linestride) { | |
| width = linestride - startX; | |
| } | |
| if (startY + height > bbImage.getHeight()) { | |
| height = bbImage.getHeight() - startY; | |
| } | |
| for (int i = 0; i < height; i++) { | |
| int from = (startY + i) * linestride + startX; | |
| System.arraycopy(srcBuffer, from, copyBuffer, from, width); | |
| } | |
| } finally { | |
| content.paintUnlock(); | |
| } | |
| } | |
| private void notifyImageUpdated(int x, int y, int width, int height) { | |
| if (copyBufferEnabled) { | |
| syncCopyBuffer(false, x, y, width, height, scaleFactorX, | |
| scaleFactorY); | |
| } | |
| 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.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); | |
| } | |
| } | |
| }); | |
| contentPane.add(component); | |
| contentPane.revalidate(); | |
| contentPane.repaint(); | |
| if ("true".equals(System.getProperty("swing.jlf.contentPaneTransparent", "false"))) | |
| { | |
| contentPane.setOpaque(false); | |
| } | |
| setContentPane(contentPane); | |
| } | |
| @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 = (int)Math.round(bbImage.getWidth() / | |
| scaleFactorX); | |
| int imgHeight = (int)Math.round(bbImage.getHeight() / | |
| scaleFactorY); | |
| 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, scaleFactorX, scaleFactorY); | |
| return; | |
| } | |
| content.imageReshaped(0, 0, width, height); | |
| } finally { | |
| if (!copyBufferEnabled) { | |
| content.paintUnlock(); | |
| } | |
| } | |
| } | |
| private void resizeBuffer(int width, int height, double newScaleFactorX, | |
| double newScaleFactorY) { | |
| bbImage = new BufferedImage((int)Math.round(width * newScaleFactorX), | |
| (int)Math.round(height * newScaleFactorY), | |
| BufferedImage.TYPE_INT_ARGB_PRE); | |
| int[] pixels= ((DataBufferInt)bbImage.getRaster().getDataBuffer()).getData(); | |
| if (copyBufferEnabled) { | |
| syncCopyBuffer(true, 0, 0, width, height, newScaleFactorX, | |
| newScaleFactorY); | |
| pixels = copyBuffer; | |
| } | |
| content.imageBufferReset(pixels, 0, 0, width, height, | |
| bbImage.getWidth(), newScaleFactorX, newScaleFactorY); | |
| } | |
| @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() { | |
| PointerInfo pointerInfo = MouseInfo.getPointerInfo(); | |
| if (pointerInfo == null) { | |
| /* | |
| * This can happen when multiple graphics device cannot decide | |
| * which graphics device contains the current mouse position | |
| * or on systems without a mouse | |
| */ | |
| return; | |
| } | |
| Point p = pointerInfo.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); | |
| } | |
| } |