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