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 
  26 package sun.swing;
  27 
  28 import java.awt.*;
  29 import java.awt.dnd.DragGestureEvent;
  30 import java.awt.dnd.DragGestureListener;
  31 import java.awt.dnd.DragGestureRecognizer;
  32 import java.awt.dnd.DragSource;
  33 import java.awt.dnd.DropTarget;
  34 import java.awt.dnd.InvalidDnDOperationException;
  35 import java.awt.dnd.peer.DragSourceContextPeer;
  36 import java.awt.event.ContainerEvent;
  37 import java.awt.event.ContainerListener;
  38 import java.awt.geom.AffineTransform;
  39 import java.awt.image.BufferedImage;
  40 import java.awt.image.DataBufferInt;
  41 import java.beans.PropertyChangeEvent;
  42 import java.beans.PropertyChangeListener;
  43 import java.security.AccessController;
  44 import javax.swing.JComponent;
  45 
  46 import javax.swing.JLayeredPane;
  47 import javax.swing.JPanel;
  48 import javax.swing.JRootPane;
  49 import javax.swing.LayoutFocusTraversalPolicy;
  50 import javax.swing.RepaintManager;
  51 import javax.swing.RootPaneContainer;
  52 import javax.swing.SwingUtilities;
  53 
  54 import sun.awt.AWTAccessor;
  55 import sun.awt.DisplayChangedListener;
  56 import sun.awt.LightweightFrame;
  57 import sun.security.action.GetPropertyAction;
  58 import 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
  71 public 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 }