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