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