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