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 public void notifyDisplayChanged(final int scaleFactor) { 245 if (scaleFactor != this.scaleFactor) { 246 if (!copyBufferEnabled) content.paintLock(); 247 try { 248 if (bbImage != null) { 249 resizeBuffer(getWidth(), getHeight(), scaleFactor); 250 } 251 } finally { 252 if (!copyBufferEnabled) content.paintUnlock(); 253 } 254 this.scaleFactor = scaleFactor; 255 } 256 if (getPeer() instanceof DisplayChangedListener) { 257 ((DisplayChangedListener)getPeer()).displayChanged(); 258 } 259 repaint(); 260 } 261 262 @Override 263 public void addNotify() { 264 super.addNotify(); 265 if (getPeer() instanceof DisplayChangedListener) { 266 ((DisplayChangedListener)getPeer()).displayChanged(); 267 } 268 } 269 270 private void syncCopyBuffer(boolean reset, int x, int y, int w, int h, int scale) { 271 content.paintLock(); 272 try { 273 int[] srcBuffer = ((DataBufferInt)bbImage.getRaster().getDataBuffer()).getData(); 274 if (reset) { 275 copyBuffer = new int[srcBuffer.length]; 276 } 277 int linestride = bbImage.getWidth(); 278 279 x *= scale; 280 y *= scale; 281 w *= scale; 282 h *= scale; 283 284 for (int i=0; i<h; i++) { 285 int from = (y + i) * linestride + x; 286 System.arraycopy(srcBuffer, from, copyBuffer, from, w); 287 } 288 } finally { 289 content.paintUnlock(); 290 } 291 } 292 293 private void notifyImageUpdated(int x, int y, int width, int height) { 294 if (copyBufferEnabled) { 295 syncCopyBuffer(false, x, y, width, height, scaleFactor); 296 } 297 content.imageUpdated(x, y, width, height); 298 } 299 300 @SuppressWarnings("serial") // anonymous class inside 301 private void initInterior() { 302 contentPane = new JPanel() { 303 @Override 304 public void paint(Graphics g) { 305 if (!copyBufferEnabled) { 306 content.paintLock(); 307 } 308 try { 309 super.paint(g); 310 311 final Rectangle clip = g.getClipBounds() != null ? 312 g.getClipBounds() : 313 new Rectangle(0, 0, contentPane.getWidth(), contentPane.getHeight()); 314 315 clip.x = Math.max(0, clip.x); 316 clip.y = Math.max(0, clip.y); 317 clip.width = Math.min(contentPane.getWidth(), clip.width); 318 clip.height = Math.min(contentPane.getHeight(), clip.height); 319 320 EventQueue.invokeLater(new Runnable() { 321 @Override 322 public void run() { 323 Rectangle c = contentPane.getBounds().intersection(clip); 324 notifyImageUpdated(c.x, c.y, c.width, c.height); 325 } 326 }); 327 } finally { 328 if (!copyBufferEnabled) { 329 content.paintUnlock(); 330 } 331 } 332 } 333 @Override 334 protected boolean isPaintingOrigin() { 335 return true; 336 } 337 }; 338 contentPane.setLayout(new BorderLayout()); 339 contentPane.add(component); 340 if ("true".equals(AccessController. 341 doPrivileged(new GetPropertyAction("swing.jlf.contentPaneTransparent", "false")))) 342 { 343 contentPane.setOpaque(false); 344 } 345 setContentPane(contentPane); 346 347 contentPane.addContainerListener(new ContainerListener() { 348 @Override 349 public void componentAdded(ContainerEvent e) { 350 Component c = JLightweightFrame.this.component; 351 if (e.getChild() == c) { 352 c.addPropertyChangeListener("preferredSize", layoutSizeListener); 353 c.addPropertyChangeListener("maximumSize", layoutSizeListener); 354 c.addPropertyChangeListener("minimumSize", layoutSizeListener); 355 } 356 } 357 @Override 358 public void componentRemoved(ContainerEvent e) { 359 Component c = JLightweightFrame.this.component; 360 if (e.getChild() == c) { 361 c.removePropertyChangeListener(layoutSizeListener); 362 } 363 } 364 }); 365 } 366 367 @SuppressWarnings("deprecation") 368 @Override public void reshape(int x, int y, int width, int height) { 369 super.reshape(x, y, width, height); 370 371 if (width == 0 || height == 0) { 372 return; 373 } 374 if (!copyBufferEnabled) { 375 content.paintLock(); 376 } 377 try { 378 boolean createBB = (bbImage == null); 379 int newW = width; 380 int newH = height; 381 if (bbImage != null) { 382 int imgWidth = bbImage.getWidth() / scaleFactor; 383 int imgHeight = bbImage.getHeight() / scaleFactor; 384 if (width != imgWidth || height != imgHeight) { 385 createBB = true; 386 if (bbImage != null) { 387 int oldW = imgWidth; 388 int oldH = imgHeight; 389 if ((oldW >= newW) && (oldH >= newH)) { 390 createBB = false; 391 } else { 392 if (oldW >= newW) { 393 newW = oldW; 394 } else { 395 newW = Math.max((int)(oldW * 1.2), width); 396 } 397 if (oldH >= newH) { 398 newH = oldH; 399 } else { 400 newH = Math.max((int)(oldH * 1.2), height); 401 } 402 } 403 } 404 } 405 } 406 if (createBB) { 407 resizeBuffer(newW, newH, scaleFactor); 408 return; 409 } 410 content.imageReshaped(0, 0, width, height); 411 412 } finally { 413 if (!copyBufferEnabled) { 414 content.paintUnlock(); 415 } 416 } 417 } 418 419 private void resizeBuffer(int width, int height, int newScaleFactor) { 420 bbImage = new BufferedImage(width*newScaleFactor,height*newScaleFactor, 421 BufferedImage.TYPE_INT_ARGB_PRE); 422 int[] pixels= ((DataBufferInt)bbImage.getRaster().getDataBuffer()).getData(); 423 if (copyBufferEnabled) { 424 syncCopyBuffer(true, 0, 0, width, height, newScaleFactor); 425 pixels = copyBuffer; 426 } 427 content.imageBufferReset(pixels, 0, 0, width, height, 428 width * newScaleFactor, newScaleFactor); 429 } 430 431 @Override 432 public JRootPane getRootPane() { 433 return rootPane; 434 } 435 436 @Override 437 public void setContentPane(Container contentPane) { 438 getRootPane().setContentPane(contentPane); 439 } 440 441 @Override 442 public Container getContentPane() { 443 return getRootPane().getContentPane(); 444 } 445 446 @Override 447 public void setLayeredPane(JLayeredPane layeredPane) { 448 getRootPane().setLayeredPane(layeredPane); 449 } 450 451 @Override 452 public JLayeredPane getLayeredPane() { 453 return getRootPane().getLayeredPane(); 454 } 455 456 @Override 457 public void setGlassPane(Component glassPane) { 458 getRootPane().setGlassPane(glassPane); 459 } 460 461 @Override 462 public Component getGlassPane() { 463 return getRootPane().getGlassPane(); 464 } 465 466 467 /* 468 * Notifies client toolkit that it should change a cursor. 469 * 470 * Called from the peer via SwingAccessor, because the 471 * Component.updateCursorImmediately method is final 472 * and could not be overridden. 473 */ 474 private void updateClientCursor() { 475 Point p = MouseInfo.getPointerInfo().getLocation(); 476 SwingUtilities.convertPointFromScreen(p, this); 477 Component target = SwingUtilities.getDeepestComponentAt(this, p.x, p.y); 478 if (target != null) { 479 content.setCursor(target.getCursor()); 480 } 481 } 482 483 public <T extends DragGestureRecognizer> T createDragGestureRecognizer( 484 Class<T> abstractRecognizerClass, 485 DragSource ds, Component c, int srcActions, 486 DragGestureListener dgl) 487 { 488 return content == null ? null : content.createDragGestureRecognizer( 489 abstractRecognizerClass, ds, c, srcActions, dgl); 490 } 491 492 public DragSourceContextPeer createDragSourceContextPeer(DragGestureEvent dge) throws InvalidDnDOperationException { 493 return content == null ? null : content.createDragSourceContextPeer(dge); 494 } 495 496 public void addDropTarget(DropTarget dt) { 497 if (content == null) return; 498 content.addDropTarget(dt); 499 } 500 501 public void removeDropTarget(DropTarget dt) { 502 if (content == null) return; 503 content.removeDropTarget(dt); 504 } 505 }