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