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