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