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