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