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