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.LightweightFrame; 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 /** 84 * {@code copyBufferEnabled}, true by default, defines the following strategy. 85 * A duplicating (copy) buffer is created for the original pixel buffer. 86 * The copy buffer is synchronized with the original buffer every time the 87 * latter changes. {@code JLightweightFrame} passes the copy buffer array 88 * to the {@link LightweightContent#imageBufferReset} method. The code spot 89 * which synchronizes two buffers becomes the only critical section guarded 90 * by the lock (managed with the {@link LightweightContent#paintLock()}, 91 * {@link LightweightContent#paintUnlock()} methods). 92 */ 93 private boolean copyBufferEnabled; 94 private int[] copyBuffer; 95 96 private PropertyChangeListener layoutSizeListener; 97 private RepaintListener repaintListener; 98 99 static { 100 SwingAccessor.setJLightweightFrameAccessor(new SwingAccessor.JLightweightFrameAccessor() { 101 @Override 102 public void updateCursor(JLightweightFrame frame) { 103 frame.updateClientCursor(); 104 } 105 }); 106 } 107 108 /** 109 * Constructs a new, initially invisible {@code JLightweightFrame} 110 * instance. 111 */ 112 public JLightweightFrame() { 113 super(); 114 copyBufferEnabled = "true".equals(AccessController. 115 doPrivileged(new GetPropertyAction("swing.jlf.copyBufferEnabled", "true"))); 116 117 add(rootPane, BorderLayout.CENTER); 118 setFocusTraversalPolicy(new LayoutFocusTraversalPolicy()); 119 if (getGraphicsConfiguration().isTranslucencyCapable()) { 120 setBackground(new Color(0, 0, 0, 0)); 121 } 122 123 layoutSizeListener = new PropertyChangeListener() { 124 @Override 125 public void propertyChange(PropertyChangeEvent e) { 126 Dimension d = (Dimension)e.getNewValue(); 127 128 if ("preferredSize".equals(e.getPropertyName())) { 129 content.preferredSizeChanged(d.width, d.height); 130 131 } else if ("maximumSize".equals(e.getPropertyName())) { 132 content.maximumSizeChanged(d.width, d.height); 133 134 } else if ("minimumSize".equals(e.getPropertyName())) { 135 content.minimumSizeChanged(d.width, d.height); 136 } 137 } 138 }; 139 140 repaintListener = (JComponent c, int x, int y, int w, int h) -> { 141 Window jlf = SwingUtilities.getWindowAncestor(c); 142 if (jlf != JLightweightFrame.this) { 143 return; 144 } 145 Point p = SwingUtilities.convertPoint(c, x, y, jlf); 146 Rectangle r = new Rectangle(p.x, p.y, w, h).intersection( 147 new Rectangle(0, 0, bbImage.getWidth(), bbImage.getHeight())); 148 149 if (!r.isEmpty()) { 150 notifyImageUpdated(r.x, r.y, r.width, r.height); 151 } 152 }; 153 154 SwingAccessor.getRepaintManagerAccessor().addRepaintListener( 155 RepaintManager.currentManager(this), repaintListener); 156 } 157 158 @Override 159 public void dispose() { 160 SwingAccessor.getRepaintManagerAccessor().removeRepaintListener( 161 RepaintManager.currentManager(this), repaintListener); 162 super.dispose(); 163 } 164 165 /** 166 * Sets the {@link LightweightContent} instance for this frame. 167 * The {@code JComponent} object returned by the 168 * {@link LightweightContent#getComponent()} method is immediately 169 * added to the frame's content pane. 170 * 171 * @param content the {@link LightweightContent} instance 172 */ 173 public void setContent(final LightweightContent content) { 174 if (content == null) { 175 System.err.println("JLightweightFrame.setContent: content may not be null!"); 176 return; 177 } 178 this.content = content; 179 this.component = content.getComponent(); 180 181 Dimension d = this.component.getPreferredSize(); 182 content.preferredSizeChanged(d.width, d.height); 183 184 d = this.component.getMaximumSize(); 185 content.maximumSizeChanged(d.width, d.height); 186 187 d = this.component.getMinimumSize(); 188 content.minimumSizeChanged(d.width, d.height); 189 190 initInterior(); 191 } 192 193 @Override 194 public Graphics getGraphics() { 195 if (bbImage == null) return null; 196 197 Graphics2D g = bbImage.createGraphics(); 198 g.setBackground(getBackground()); 199 g.setColor(getForeground()); 200 g.setFont(getFont()); 201 return g; 202 } 203 204 /** 205 * {@inheritDoc} 206 * 207 * @see LightweightContent#focusGrabbed() 208 */ 209 @Override 210 public void grabFocus() { 211 if (content != null) content.focusGrabbed(); 212 } 213 214 /** 215 * {@inheritDoc} 216 * 217 * @see LightweightContent#focusUngrabbed() 218 */ 219 @Override 220 public void ungrabFocus() { 221 if (content != null) content.focusUngrabbed(); 222 } 223 224 private void syncCopyBuffer(boolean reset, int x, int y, int w, int h) { 225 content.paintLock(); 226 try { 227 int[] srcBuffer = ((DataBufferInt)bbImage.getRaster().getDataBuffer()).getData(); 228 if (reset) { 229 copyBuffer = new int[srcBuffer.length]; 230 } 231 int linestride = bbImage.getWidth(); 232 233 for (int i=0; i<h; i++) { 234 int from = (y + i) * linestride + x; 235 System.arraycopy(srcBuffer, from, copyBuffer, from, w); 236 } 237 } finally { 238 content.paintUnlock(); 239 } 240 } 241 242 private void notifyImageUpdated(int x, int y, int width, int height) { 243 if (copyBufferEnabled) { 244 syncCopyBuffer(false, x, y, width, height); 245 } 246 content.imageUpdated(x, y, width, height); 247 } 248 249 @SuppressWarnings("serial") // anonymous class inside 250 private void initInterior() { 251 contentPane = new JPanel() { 252 @Override 253 public void paint(Graphics g) { 254 if (!copyBufferEnabled) { 255 content.paintLock(); 256 } 257 try { 258 super.paint(g); 259 260 final Rectangle clip = g.getClipBounds() != null ? 261 g.getClipBounds() : 262 new Rectangle(0, 0, contentPane.getWidth(), contentPane.getHeight()); 263 264 clip.x = Math.max(0, clip.x); 265 clip.y = Math.max(0, clip.y); 266 clip.width = Math.min(contentPane.getWidth(), clip.width); 267 clip.height = Math.min(contentPane.getHeight(), clip.height); 268 269 EventQueue.invokeLater(new Runnable() { 270 @Override 271 public void run() { 272 notifyImageUpdated(clip.x, clip.y, clip.width, clip.height); 273 } 274 }); 275 } finally { 276 if (!copyBufferEnabled) { 277 content.paintUnlock(); 278 } 279 } 280 } 281 @Override 282 protected boolean isPaintingOrigin() { 283 return true; 284 } 285 }; 286 contentPane.setLayout(new BorderLayout()); 287 contentPane.add(component); 288 if ("true".equals(AccessController. 289 doPrivileged(new GetPropertyAction("swing.jlf.contentPaneTransparent", "false")))) 290 { 291 contentPane.setOpaque(false); 292 } 293 setContentPane(contentPane); 294 295 contentPane.addContainerListener(new ContainerListener() { 296 @Override 297 public void componentAdded(ContainerEvent e) { 298 Component c = JLightweightFrame.this.component; 299 if (e.getChild() == c) { 300 c.addPropertyChangeListener("preferredSize", layoutSizeListener); 301 c.addPropertyChangeListener("maximumSize", layoutSizeListener); 302 c.addPropertyChangeListener("minimumSize", layoutSizeListener); 303 } 304 } 305 @Override 306 public void componentRemoved(ContainerEvent e) { 307 Component c = JLightweightFrame.this.component; 308 if (e.getChild() == c) { 309 c.removePropertyChangeListener(layoutSizeListener); 310 } 311 } 312 }); 313 } 314 315 @SuppressWarnings("deprecation") 316 @Override public void reshape(int x, int y, int width, int height) { 317 super.reshape(x, y, width, height); 318 319 if (width == 0 || height == 0) { 320 return; 321 } 322 if (!copyBufferEnabled) { 323 content.paintLock(); 324 } 325 try { 326 if ((bbImage == null) || (width != bbImage.getWidth()) || (height != bbImage.getHeight())) { 327 boolean createBB = true; 328 int newW = width; 329 int newH = height; 330 if (bbImage != null) { 331 int oldW = bbImage.getWidth(); 332 int oldH = bbImage.getHeight(); 333 if ((oldW >= newW) && (oldH >= newH)) { 334 createBB = false; 335 } else { 336 if (oldW >= newW) { 337 newW = oldW; 338 } else { 339 newW = Math.max((int)(oldW * 1.2), width); 340 } 341 if (oldH >= newH) { 342 newH = oldH; 343 } else { 344 newH = Math.max((int)(oldH * 1.2), height); 345 } 346 } 347 } 348 if (createBB) { 349 BufferedImage oldBB = bbImage; 350 bbImage = new BufferedImage(newW, newH, BufferedImage.TYPE_INT_ARGB_PRE); 351 if (oldBB != null) { 352 Graphics g = bbImage.getGraphics(); 353 try { 354 g.drawImage(oldBB, 0, 0, newW, newH, null); 355 } finally { 356 g.dispose(); 357 oldBB.flush(); 358 } 359 } 360 int[] pixels = ((DataBufferInt)bbImage.getRaster().getDataBuffer()).getData(); 361 if (copyBufferEnabled) { 362 syncCopyBuffer(true, 0, 0, width, height); 363 pixels = copyBuffer; 364 } 365 content.imageBufferReset(pixels, 0, 0, width, height, bbImage.getWidth()); 366 return; 367 } 368 } 369 content.imageReshaped(0, 0, width, height); 370 371 } finally { 372 if (!copyBufferEnabled) { 373 content.paintUnlock(); 374 } 375 } 376 } 377 378 @Override 379 public JRootPane getRootPane() { 380 return rootPane; 381 } 382 383 @Override 384 public void setContentPane(Container contentPane) { 385 getRootPane().setContentPane(contentPane); 386 } 387 388 @Override 389 public Container getContentPane() { 390 return getRootPane().getContentPane(); 391 } 392 393 @Override 394 public void setLayeredPane(JLayeredPane layeredPane) { 395 getRootPane().setLayeredPane(layeredPane); 396 } 397 398 @Override 399 public JLayeredPane getLayeredPane() { 400 return getRootPane().getLayeredPane(); 401 } 402 403 @Override 404 public void setGlassPane(Component glassPane) { 405 getRootPane().setGlassPane(glassPane); 406 } 407 408 @Override 409 public Component getGlassPane() { 410 return getRootPane().getGlassPane(); 411 } 412 413 414 /* 415 * Notifies client toolkit that it should change a cursor. 416 * 417 * Called from the peer via SwingAccessor, because the 418 * Component.updateCursorImmediately method is final 419 * and could not be overridden. 420 */ 421 private void updateClientCursor() { 422 Point p = MouseInfo.getPointerInfo().getLocation(); 423 SwingUtilities.convertPointFromScreen(p, this); 424 Component target = SwingUtilities.getDeepestComponentAt(this, p.x, p.y); 425 if (target != null) { 426 content.setCursor(target.getCursor()); 427 } 428 } 429 }