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