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