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