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