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