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