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