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