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 }