/* * Copyright (c) 2007-2008 Timothy Wall, All Rights Reserved * Parts Copyright (c) 2007 Olivier Chafik * * This library is free software; you can * redistribute it and/or modify it under the terms of the GNU Lesser * General Public License as published by the Free Software Foundation; * either version 2.1 of the License, or (at your option) any later * version. <p/> This library is distributed in the hope that it will be * useful, but WITHOUT ANY WARRANTY; without even the implied warranty * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package com.sun.jna.examples; import java.awt.AlphaComposite; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Cursor; import java.awt.Component; import java.awt.Container; import java.awt.Dialog; import java.awt.Dimension; import java.awt.Frame; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GraphicsConfiguration; import java.awt.GraphicsDevice; import java.awt.GraphicsEnvironment; import java.awt.Point; import java.awt.Rectangle; import java.awt.Shape; import java.awt.Toolkit; import java.awt.event.AWTEventListener; import java.awt.event.ContainerEvent; import java.awt.event.MouseEvent; import java.awt.AWTEvent; import java.awt.Window; import java.awt.event.ComponentEvent; import java.awt.event.ComponentListener; import java.awt.event.HierarchyEvent; import java.awt.event.HierarchyListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.awt.geom.Area; import java.awt.geom.PathIterator; import java.awt.image.BufferedImage; import java.awt.image.Raster; import java.util.ArrayList; import java.util.List; import javax.swing.Icon; import javax.swing.JComponent; import javax.swing.JLayeredPane; import javax.swing.JPanel; import javax.swing.JRootPane; import javax.swing.PopupFactory; import javax.swing.RootPaneContainer; import javax.swing.SwingUtilities; import com.sun.jna.Memory; import com.sun.jna.Native; import com.sun.jna.NativeLong; import com.sun.jna.Platform; import com.sun.jna.Pointer; import com.sun.jna.examples.unix.X11; import com.sun.jna.examples.unix.X11.Display; import com.sun.jna.examples.unix.X11.GC; import com.sun.jna.examples.unix.X11.Pixmap; import com.sun.jna.examples.unix.X11.XVisualInfo; import com.sun.jna.examples.unix.X11.Xext; import com.sun.jna.examples.unix.X11.Xrender.XRenderPictFormat; import com.sun.jna.examples.win32.GDI32; import com.sun.jna.examples.win32.User32; import com.sun.jna.examples.win32.GDI32.BITMAPINFO; import com.sun.jna.examples.win32.User32.BLENDFUNCTION; import com.sun.jna.examples.win32.User32.POINT; import com.sun.jna.examples.win32.User32.SIZE; import com.sun.jna.examples.win32.W32API.HANDLE; import com.sun.jna.examples.win32.W32API.HBITMAP; import com.sun.jna.examples.win32.W32API.HDC; import com.sun.jna.examples.win32.W32API.HRGN; import com.sun.jna.examples.win32.W32API.HWND; import com.sun.jna.ptr.ByteByReference; import com.sun.jna.ptr.IntByReference; import com.sun.jna.ptr.PointerByReference; /** * Provides additional features on a Java {@link Window}. * <ul> * <li>Non-rectangular shape (bitmap mask, no antialiasing) * <li>Transparency (constant alpha applied to window contents or * transparent background) * <li>Fully transparent window (the transparency of all painted pixels is * applied to the window). * </ul> * NOTE: since there is no explicit way to force PopupFactory to use a * heavyweight popup, and anything but a heavyweight popup will be * clipped by a window mask, an additional subwindow is added to all * masked windows to implicitly force PopupFactory to use a heavyweight * window and avoid clipping. * <p> * NOTE: Neither shaped windows nor transparency * currently works with Java 1.4 under X11. This is at least partly due * to 1.4 using multiple X11 windows for a single given Java window. It * *might* be possible to remedy by applying the window * region/transparency to all descendants, but I haven't tried it. In * addition, windows must be both displayable <em>and</em> visible * before the corresponding native Drawable may be obtained; in later * Java versions, the window need only be displayable. * <p> * NOTE: If you use {@link #setWindowMask(Window,Shape)} and override {@link * Window#paint(Graphics)} on OS X, you'll need to explicitly set the clip * mask on the <code>Graphics</code> object with the window mask; only the * content pane of the window and below have the window mask automatically * applied.<p> * NOTE: On OSX, the property * <code>apple.awt.draggableWindowBackground</code> is set automatically when * a window's background color has an alpha component. That property must be * set to its final value <em>before</em> the heavyweight peer for the Window * is created. Once {@link Component#addNotify} has been called on the * component, causing creation of the heavyweight peer, changing this * property has no effect. * @see <a href="http://developer.apple.com/technotes/tn2007/tn2196.html#APPLE_AWT_DRAGGABLEWINDOWBACKGROUND">Apple Technote 2007</a> */ // TODO: setWindowMask() should accept a threshold; some cases want a // 50% threshold, some might want zero/non-zero public class WindowUtils { private static final String TRANSPARENT_OLD_BG = "transparent-old-bg"; private static final String TRANSPARENT_OLD_OPAQUE = "transparent-old-opaque"; private static final String TRANSPARENT_ALPHA = "transparent-alpha"; /** Use this to clear a window mask. */ public static final Shape MASK_NONE = null; /** * This class forces a heavyweight popup on the parent * {@link Window}. See the implementation of {@link PopupFactory}; * a heavyweight is forced if there is an occluding subwindow on the * target window. * <p> * Ideally we'd have more control over {@link PopupFactory} but this * is a fairly simple, lightweight workaround. Note that, at least as of * JDK 1.6, the following do not have the desired effect:<br> * <code><pre> * ToolTipManager.sharedInstance().setLightWeightPopupEnabled(false); * JPopupMenu.setDefaultLightWeightPopupEnabled(false); * System.setProperty("JPopupMenu.defaultLWPopupEnabledKey", "false"); * </pre></code> */ private static class HeavyweightForcer extends Window { private final boolean packed; public HeavyweightForcer(Window parent) { super(parent); pack(); packed = true; } public boolean isVisible() { // Only want to be 'visible' once the peer is instantiated // via pack; this tricks PopupFactory into using a heavyweight // popup to avoid being obscured by this window return packed; } public Rectangle getBounds() { return getOwner().getBounds(); } } /** * This can be installed over a {@link JLayeredPane} in order to * listen for repaint requests. The content's repaint method will be * invoked whenever any part of the ancestor window is repainted. */ protected static class RepaintTrigger extends JComponent { protected class Listener extends WindowAdapter implements ComponentListener, HierarchyListener, AWTEventListener { public void windowOpened(WindowEvent e) { repaint(); } public void componentHidden(ComponentEvent e) {} public void componentMoved(ComponentEvent e) {} public void componentResized(ComponentEvent e) { setSize(getParent().getSize()); repaint(); } public void componentShown(ComponentEvent e) { repaint(); } public void hierarchyChanged(HierarchyEvent e) { repaint(); } public void eventDispatched(AWTEvent e) { if (e instanceof MouseEvent) { Component src = ((MouseEvent)e).getComponent(); if (src != null && SwingUtilities.isDescendingFrom(src, content)) { MouseEvent me = SwingUtilities.convertMouseEvent(src, (MouseEvent)e, content); Component c = SwingUtilities.getDeepestComponentAt(content, me.getX(), me.getY()); if (c != null) { setCursor(c.getCursor()); } } } } } private final Listener listener = createListener(); private final JComponent content; public RepaintTrigger(JComponent content) { this.content = content; } public void addNotify() { super.addNotify(); Window w = SwingUtilities.getWindowAncestor(this); setSize(getParent().getSize()); w.addComponentListener(listener); w.addWindowListener(listener); Toolkit.getDefaultToolkit().addAWTEventListener(listener, AWTEvent.MOUSE_EVENT_MASK|AWTEvent.MOUSE_MOTION_EVENT_MASK); } public void removeNotify() { Toolkit.getDefaultToolkit().removeAWTEventListener(listener); Window w = SwingUtilities.getWindowAncestor(this); w.removeComponentListener(listener); w.removeWindowListener(listener); super.removeNotify(); } private Rectangle dirty; protected void paintComponent(Graphics g) { Rectangle bounds = g.getClipBounds(); if (dirty == null || !dirty.contains(bounds)) { if (dirty == null) { dirty = bounds; } else { dirty = dirty.union(bounds); } content.repaint(dirty); } else { dirty = null; } } protected Listener createListener() { return new Listener(); } }; /** Window utilities with differing native implementations. */ public static abstract class NativeWindowUtils { protected abstract class TransparentContentPane extends JPanel implements AWTEventListener { private boolean transparent; public TransparentContentPane(Container oldContent) { super(new BorderLayout()); add(oldContent, BorderLayout.CENTER); setTransparent(true); if (oldContent instanceof JPanel) { ((JComponent)oldContent).setOpaque(false); } } public void addNotify() { super.addNotify(); Toolkit.getDefaultToolkit().addAWTEventListener(this, AWTEvent.CONTAINER_EVENT_MASK); } public void removeNotify() { Toolkit.getDefaultToolkit().removeAWTEventListener(this); super.removeNotify(); } public void setTransparent(boolean transparent) { this.transparent = transparent; setOpaque(!transparent); setDoubleBuffered(!transparent); repaint(); } public void eventDispatched(AWTEvent e) { if (e.getID() == ContainerEvent.COMPONENT_ADDED && SwingUtilities.isDescendingFrom(((ContainerEvent)e).getChild(), this)) { Component child = ((ContainerEvent)e).getChild(); NativeWindowUtils.this.setDoubleBuffered(child, false); } } public void paint(Graphics gr) { if (transparent) { Rectangle r = gr.getClipBounds(); final int w = r.width; final int h = r.height; if (getWidth() > 0 && getHeight() > 0) { final BufferedImage buf = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB_PRE); Graphics2D g = buf.createGraphics(); g.setComposite(AlphaComposite.Clear); g.fillRect(0, 0, w, h); g.dispose(); g = buf.createGraphics(); g.translate(-r.x, -r.y); super.paint(g); g.dispose(); paintDirect(buf, r); } } else { super.paint(gr); } } /** Use the contents of the given BufferedImage to paint directly * on this component's ancestor window. */ protected abstract void paintDirect(BufferedImage buf, Rectangle bounds); } protected Window getWindow(Component c) { return c instanceof Window ? (Window)c : SwingUtilities.getWindowAncestor(c); } /** * Execute the given action when the given window becomes * displayable. */ protected void whenDisplayable(Component w, final Runnable action) { if (w.isDisplayable() && (!Holder.requiresVisible || w.isVisible())) { action.run(); } else if (Holder.requiresVisible) { getWindow(w).addWindowListener(new WindowAdapter() { public void windowOpened(WindowEvent e) { e.getWindow().removeWindowListener(this); action.run(); } public void windowClosed(WindowEvent e) { e.getWindow().removeWindowListener(this); } }); } else { // Hierarchy events are fired in direct response to // displayability changes w.addHierarchyListener(new HierarchyListener() { public void hierarchyChanged(HierarchyEvent e) { if ((e.getChangeFlags() & HierarchyEvent.DISPLAYABILITY_CHANGED) != 0 && e.getComponent().isDisplayable()) { e.getComponent().removeHierarchyListener(this); action.run(); } } }); } } protected Raster toRaster(Shape mask) { Raster raster = null; if (mask != MASK_NONE) { Rectangle bounds = mask.getBounds(); if (bounds.width > 0 && bounds.height > 0) { BufferedImage clip = new BufferedImage(bounds.x + bounds.width, bounds.y + bounds.height, BufferedImage.TYPE_BYTE_BINARY); Graphics2D g = clip.createGraphics(); g.setColor(Color.black); g.fillRect(0, 0, bounds.x + bounds.width, bounds.y + bounds.height); g.setColor(Color.white); g.fill(mask); raster = clip.getRaster(); } } return raster; } protected Raster toRaster(Component c, Icon mask) { Raster raster = null; if (mask != null) { Rectangle bounds = new Rectangle(0, 0, mask.getIconWidth(), mask.getIconHeight()); BufferedImage clip = new BufferedImage(bounds.width, bounds.height, BufferedImage.TYPE_INT_ARGB); Graphics2D g = clip.createGraphics(); g.setComposite(AlphaComposite.Clear); g.fillRect(0, 0, bounds.width, bounds.height); g.setComposite(AlphaComposite.SrcOver); mask.paintIcon(c, g, 0, 0); raster = clip.getAlphaRaster(); } return raster; } protected Shape toShape(Raster raster) { final Area area = new Area(new Rectangle(0, 0, 0, 0)); RasterRangesUtils.outputOccupiedRanges(raster, new RasterRangesUtils.RangesOutput() { public boolean outputRange(int x, int y, int w, int h) { area.add(new Area(new Rectangle(x, y, w, h))); return true; } }); return area; } /** * Set the overall alpha transparency of the window. An alpha of * 1.0 is fully opaque, 0.0 is fully transparent. */ public void setWindowAlpha(Window w, float alpha) { // do nothing } /** Default: no support. */ public boolean isWindowAlphaSupported() { return false; } /** Return the default graphics configuration. */ public GraphicsConfiguration getAlphaCompatibleGraphicsConfiguration() { GraphicsEnvironment env = GraphicsEnvironment .getLocalGraphicsEnvironment(); GraphicsDevice dev = env.getDefaultScreenDevice(); return dev.getDefaultConfiguration(); } /** * Set the window to be transparent. Only explicitly painted * pixels will be non-transparent. All pixels will be composited * with whatever is under the window using their alpha values. */ public void setWindowTransparent(Window w, boolean transparent) { // do nothing } protected void setDoubleBuffered(Component root, boolean buffered) { if (root instanceof JComponent) { ((JComponent)root).setDoubleBuffered(buffered); } if (root instanceof JRootPane && buffered) { ((JRootPane)root).setDoubleBuffered(true); } else if (root instanceof Container) { Component[] kids = ((Container)root).getComponents(); for (int i=0;i < kids.length;i++) { setDoubleBuffered(kids[i], buffered); } } } protected void setLayersTransparent(Window w, boolean transparent) { Color bg = transparent ? new Color(0, 0, 0, 0) : null; if (w instanceof RootPaneContainer) { RootPaneContainer rpc = (RootPaneContainer)w; JRootPane root = rpc.getRootPane(); JLayeredPane lp = root.getLayeredPane(); Container c = root.getContentPane(); JComponent content = (c instanceof JComponent) ? (JComponent)c : null; if (transparent) { lp.putClientProperty(TRANSPARENT_OLD_OPAQUE, Boolean.valueOf(lp.isOpaque())); lp.setOpaque(false); root.putClientProperty(TRANSPARENT_OLD_OPAQUE, Boolean.valueOf(root.isOpaque())); root.setOpaque(false); if (content != null) { content.putClientProperty(TRANSPARENT_OLD_OPAQUE, Boolean.valueOf(content.isOpaque())); content.setOpaque(false); } root.putClientProperty(TRANSPARENT_OLD_BG, root.getParent().getBackground()); } else { lp.setOpaque(Boolean.TRUE.equals(lp.getClientProperty(TRANSPARENT_OLD_OPAQUE))); lp.putClientProperty(TRANSPARENT_OLD_OPAQUE, null); root.setOpaque(Boolean.TRUE.equals(root.getClientProperty(TRANSPARENT_OLD_OPAQUE))); root.putClientProperty(TRANSPARENT_OLD_OPAQUE, null); if (content != null) { content.setOpaque(Boolean.TRUE.equals(content.getClientProperty(TRANSPARENT_OLD_OPAQUE))); content.putClientProperty(TRANSPARENT_OLD_OPAQUE, null); } bg = (Color)root.getClientProperty(TRANSPARENT_OLD_BG); root.putClientProperty(TRANSPARENT_OLD_BG, null); } } w.setBackground(bg); } /** Override this method to provide bitmap masking of the given * heavyweight component. */ protected void setMask(Component c, Raster raster) { throw new UnsupportedOperationException("Window masking is not available"); } /** * Set the window mask based on the given Raster, which should * be treated as a bitmap (zero/nonzero values only). A value of * <code>null</code> means to remove the mask. */ protected void setWindowMask(Component w, Raster raster) { if (w.isLightweight()) throw new IllegalArgumentException("Component must be heavyweight: " + w); setMask(w, raster); } /** Set the window mask based on a {@link Shape}. */ public void setWindowMask(Component w, Shape mask) { setWindowMask(w, toRaster(mask)); } /** * Set the window mask based on an Icon. All non-transparent * pixels will be included in the mask. */ public void setWindowMask(Component w, Icon mask) { setWindowMask(w, toRaster(w, mask)); } /** * Use this method to ensure heavyweight popups are used in * conjunction with a given window. This prevents the window's * alpha setting or mask region from being applied to the popup. */ protected void setForceHeavyweightPopups(Window w, boolean force) { if (!(w instanceof HeavyweightForcer)) { Window[] owned = w.getOwnedWindows(); for (int i = 0; i < owned.length; i++) { if (owned[i] instanceof HeavyweightForcer) { if (force) return; owned[i].dispose(); } } Boolean b = Boolean.valueOf(System.getProperty("jna.force_hw_popups", "true")); if (force && b.booleanValue()) { new HeavyweightForcer(w); } } } } /** Canonical lazy loading of a singleton. */ private static class Holder { /** * Indicates whether a window must be visible before its native * handle can be obtained. This wart is caused by the Java * 1.4/X11 implementation. */ public static boolean requiresVisible; public static final NativeWindowUtils INSTANCE; static { if (Platform.isWindows()) { INSTANCE = new W32WindowUtils(); } else if (Platform.isMac()) { INSTANCE = new MacWindowUtils(); } else if (Platform.isX11()) { INSTANCE = new X11WindowUtils(); requiresVisible = System.getProperty("java.version") .matches("^1\\.4\\..*"); } else { String os = System.getProperty("os.name"); throw new UnsupportedOperationException("No support for " + os); } } } private static NativeWindowUtils getInstance() { return Holder.INSTANCE; } private static class W32WindowUtils extends NativeWindowUtils { private HWND getHWnd(Component w) { HWND hwnd = new HWND(); hwnd.setPointer(Native.getComponentPointer(w)); return hwnd; } /** * W32 alpha will only work if <code>sun.java2d.noddraw</code> * is set */ public boolean isWindowAlphaSupported() { return Boolean.getBoolean("sun.java2d.noddraw"); } /** Indicates whether UpdateLayeredWindow is in use. */ private boolean usingUpdateLayeredWindow(Window w) { if (w instanceof RootPaneContainer) { JRootPane root = ((RootPaneContainer)w).getRootPane(); return root.getClientProperty(TRANSPARENT_OLD_BG) != null; } return false; } /** Keep track of the alpha level, since we can't read it from * the window itself. */ private void storeAlpha(Window w, byte alpha) { if (w instanceof RootPaneContainer) { JRootPane root = ((RootPaneContainer)w).getRootPane(); Byte b = alpha == (byte)0xFF ? null : new Byte(alpha); root.putClientProperty(TRANSPARENT_ALPHA, b); } } /** Return the last alpha level we set on the window. */ private byte getAlpha(Window w) { if (w instanceof RootPaneContainer) { JRootPane root = ((RootPaneContainer)w).getRootPane(); Byte b = (Byte)root.getClientProperty(TRANSPARENT_ALPHA); if (b != null) { return b.byteValue(); } } return (byte)0xFF; } public void setWindowAlpha(final Window w, final float alpha) { if (!isWindowAlphaSupported()) { throw new UnsupportedOperationException("Set sun.java2d.noddraw=true to enable transparent windows"); } whenDisplayable(w, new Runnable() { public void run() { HWND hWnd = getHWnd(w); User32 user = User32.INSTANCE; int flags = user.GetWindowLong(hWnd, User32.GWL_EXSTYLE); byte level = (byte)((int)(255 * alpha) & 0xFF); if (usingUpdateLayeredWindow(w)) { // If already using UpdateLayeredWindow, continue to // do so BLENDFUNCTION blend = new BLENDFUNCTION(); blend.SourceConstantAlpha = level; blend.AlphaFormat = User32.AC_SRC_ALPHA; user.UpdateLayeredWindow(hWnd, null, null, null, null, null, 0, blend, User32.ULW_ALPHA); } else if (alpha == 1f) { flags &= ~User32.WS_EX_LAYERED; user.SetWindowLong(hWnd, User32.GWL_EXSTYLE, flags); } else { flags |= User32.WS_EX_LAYERED; user.SetWindowLong(hWnd, User32.GWL_EXSTYLE, flags); user.SetLayeredWindowAttributes(hWnd, 0, level, User32.LWA_ALPHA); } setForceHeavyweightPopups(w, alpha != 1f); storeAlpha(w, level); } }); } /** W32 makes the client responsible for repainting the <em>entire</em> * window on any change. It also does not paint window decorations * when the window is transparent. */ private class W32TransparentContentPane extends TransparentContentPane { private HDC memDC; private HBITMAP hBitmap; private Pointer pbits; private Dimension bitmapSize; public W32TransparentContentPane(Container content) { super(content); } private void disposeBackingStore() { GDI32 gdi = GDI32.INSTANCE; if (hBitmap != null) { gdi.DeleteObject(hBitmap); hBitmap = null; } if (memDC != null) { gdi.DeleteDC(memDC); memDC = null; } } public void removeNotify() { super.removeNotify(); disposeBackingStore(); } public void setTransparent(boolean transparent) { super.setTransparent(transparent); if (!transparent) { disposeBackingStore(); } } protected void paintDirect(BufferedImage buf, Rectangle bounds) { // TODO: paint frame decoration if window is decorated Window win = SwingUtilities.getWindowAncestor(this); GDI32 gdi = GDI32.INSTANCE; User32 user = User32.INSTANCE; int x = bounds.x; int y = bounds.y; Point origin = SwingUtilities.convertPoint(this, x, y, win); int w = bounds.width; int h = bounds.height; int ww = win.getWidth(); int wh = win.getHeight(); HDC screenDC = user.GetDC(null); HANDLE oldBitmap = null; try { if (memDC == null) { memDC = gdi.CreateCompatibleDC(screenDC); } if (hBitmap == null || !win.getSize().equals(bitmapSize)) { if (hBitmap != null) { gdi.DeleteObject(hBitmap); hBitmap = null; } BITMAPINFO bmi = new BITMAPINFO(); bmi.bmiHeader.biWidth = ww; bmi.bmiHeader.biHeight = wh; bmi.bmiHeader.biPlanes = 1; bmi.bmiHeader.biBitCount = 32; bmi.bmiHeader.biCompression = GDI32.BI_RGB; bmi.bmiHeader.biSizeImage = ww * wh * 4; PointerByReference ppbits = new PointerByReference(); hBitmap = gdi.CreateDIBSection(memDC, bmi, GDI32.DIB_RGB_COLORS, ppbits, null, 0); pbits = ppbits.getValue(); bitmapSize = new Dimension(ww, wh); } oldBitmap = gdi.SelectObject(memDC, hBitmap); Raster raster = buf.getData(); int[] pixel = new int[4]; int[] bits = new int[w]; for (int row = 0; row < h; row++) { for (int col = 0; col < w; col++) { raster.getPixel(col, row, pixel); int alpha = (pixel[3] & 0xFF) << 24; int red = (pixel[2] & 0xFF); int green = (pixel[1] & 0xFF) << 8; int blue = (pixel[0] & 0xFF) << 16; bits[col] = alpha | red | green | blue; } int v = wh - (origin.y + row) - 1; pbits.write((v*ww+origin.x)*4, bits, 0, bits.length); } SIZE winSize = new SIZE(); winSize.cx = win.getWidth(); winSize.cy = win.getHeight(); POINT winLoc = new POINT(); winLoc.x = win.getX(); winLoc.y = win.getY(); POINT srcLoc = new POINT(); BLENDFUNCTION blend = new BLENDFUNCTION(); HWND hWnd = getHWnd(win); // extract current constant alpha setting, if possible ByteByReference bref = new ByteByReference(); IntByReference iref = new IntByReference(); byte level = getAlpha(win); try { // GetLayeredwindowAttributes supported WinXP and later if (user.GetLayeredWindowAttributes(hWnd, null, bref, iref) && (iref.getValue() & User32.LWA_ALPHA) != 0) { level = bref.getValue(); } } catch(UnsatisfiedLinkError e) { } blend.SourceConstantAlpha = level; blend.AlphaFormat = User32.AC_SRC_ALPHA; user.UpdateLayeredWindow(hWnd, screenDC, winLoc, winSize, memDC, srcLoc, 0, blend, User32.ULW_ALPHA); } finally { user.ReleaseDC(null, screenDC); if (memDC != null && oldBitmap != null) { gdi.SelectObject(memDC, oldBitmap); } } } } /** Note that w32 does <em>not</em> paint window decorations when * the window is transparent. */ public void setWindowTransparent(final Window w, final boolean transparent) { if (!(w instanceof RootPaneContainer)) { throw new IllegalArgumentException("Window must be a RootPaneContainer"); } if (!isWindowAlphaSupported()) { throw new UnsupportedOperationException("Set sun.java2d.noddraw=true to enable transparent windows"); } boolean isTransparent = w.getBackground() != null && w.getBackground().getAlpha() == 0; if (transparent == isTransparent) return; whenDisplayable(w, new Runnable() { public void run() { User32 user = User32.INSTANCE; HWND hWnd = getHWnd(w); int flags = user.GetWindowLong(hWnd, User32.GWL_EXSTYLE); JRootPane root = ((RootPaneContainer)w).getRootPane(); JLayeredPane lp = root.getLayeredPane(); Container content = root.getContentPane(); if (content instanceof W32TransparentContentPane) { ((W32TransparentContentPane)content).setTransparent(transparent); } else if (transparent) { W32TransparentContentPane w32content = new W32TransparentContentPane(content); root.setContentPane(w32content); lp.add(new RepaintTrigger(w32content), JLayeredPane.DRAG_LAYER); } if (transparent && !usingUpdateLayeredWindow(w)) { flags |= User32.WS_EX_LAYERED; user.SetWindowLong(hWnd, User32.GWL_EXSTYLE, flags); } else if (!transparent && usingUpdateLayeredWindow(w)) { flags &= ~User32.WS_EX_LAYERED; user.SetWindowLong(hWnd, User32.GWL_EXSTYLE, flags); } setLayersTransparent(w, transparent); setForceHeavyweightPopups(w, transparent); setDoubleBuffered(w, !transparent); } }); } public void setWindowMask(final Component w, final Shape mask) { if (mask instanceof Area && ((Area)mask).isPolygonal()) { setMask(w, (Area)mask); } else { super.setWindowMask(w, mask); } } // NOTE: Deletes hrgn after setting the window region private void setWindowRegion(final Component w, final HRGN hrgn) { whenDisplayable(w, new Runnable() { public void run() { GDI32 gdi = GDI32.INSTANCE; User32 user = User32.INSTANCE; HWND hWnd = getHWnd(w); try { user.SetWindowRgn(hWnd, hrgn, true); setForceHeavyweightPopups(getWindow(w), hrgn != null); } finally { gdi.DeleteObject(hrgn); } } }); } // Take advantage of CreatePolyPolygonalRgn on w32 private void setMask(final Component w, final Area area) { GDI32 gdi = GDI32.INSTANCE; PathIterator pi = area.getPathIterator(null); int mode = pi.getWindingRule() == PathIterator.WIND_NON_ZERO ? GDI32.WINDING: GDI32.ALTERNATE; float[] coords = new float[6]; List points = new ArrayList(); int size = 0; List sizes = new ArrayList(); while (!pi.isDone()) { int type = pi.currentSegment(coords); if (type == PathIterator.SEG_MOVETO) { size = 1; points.add(new POINT((int)coords[0], (int)coords[1])); } else if (type == PathIterator.SEG_LINETO) { ++size; points.add(new POINT((int)coords[0], (int)coords[1])); } else if (type == PathIterator.SEG_CLOSE) { sizes.add(new Integer(size)); } else { throw new RuntimeException("Area is not polygonal: " + area); } pi.next(); } POINT[] lppt = (POINT[])new POINT().toArray(points.size()); POINT[] pts = (POINT[])points.toArray(new POINT[points.size()]); for (int i=0;i < lppt.length;i++) { lppt[i].x = pts[i].x; lppt[i].y = pts[i].y; } int[] counts = new int[sizes.size()]; for (int i=0;i < counts.length;i++) { counts[i] = ((Integer)sizes.get(i)).intValue(); } HRGN hrgn = gdi.CreatePolyPolygonRgn(lppt, counts, counts.length, mode); setWindowRegion(w, hrgn); } protected void setMask(final Component w, final Raster raster) { GDI32 gdi = GDI32.INSTANCE; final HRGN region = raster != null ? gdi.CreateRectRgn(0, 0, 0, 0) : null; if (region != null) { final HRGN tempRgn = gdi.CreateRectRgn(0, 0, 0, 0); try { RasterRangesUtils.outputOccupiedRanges(raster, new RasterRangesUtils.RangesOutput() { public boolean outputRange(int x, int y, int w, int h) { GDI32 gdi = GDI32.INSTANCE; gdi.SetRectRgn(tempRgn, x, y, x + w, y + h); return gdi.CombineRgn(region, region, tempRgn, GDI32.RGN_OR) != GDI32.ERROR; } }); } finally { gdi.DeleteObject(tempRgn); } } setWindowRegion(w, region); } } private static class MacWindowUtils extends NativeWindowUtils { public boolean isWindowAlphaSupported() { return true; } private OSXMaskingContentPane installMaskingPane(Window w) { OSXMaskingContentPane content; if (w instanceof RootPaneContainer) { // TODO: replace layered pane instead? final RootPaneContainer rpc = (RootPaneContainer)w; Container oldContent = rpc.getContentPane(); if (oldContent instanceof OSXMaskingContentPane) { content = (OSXMaskingContentPane)oldContent; } else { content = new OSXMaskingContentPane(oldContent); // TODO: listen for content pane changes rpc.setContentPane(content); } } else { Component oldContent = w.getComponentCount() > 0 ? w.getComponent(0) : null; if (oldContent instanceof OSXMaskingContentPane) { content = (OSXMaskingContentPane)oldContent; } else { content = new OSXMaskingContentPane(oldContent); w.add(content); } } return content; } /** Note that the property * <code>apple.awt.draggableWindowBackground</code> must be set to its * final value <em>before</em> the heavyweight peer for the Window is * created. Once {@link Component#addNotify} has been called on the * component, causing creation of the heavyweight peer, changing this * property has no effect. * @see <a href="http://developer.apple.com/technotes/tn2007/tn2196.html#APPLE_AWT_DRAGGABLEWINDOWBACKGROUND">Apple Technote 2007</a> */ public void setWindowTransparent(Window w, boolean transparent) { boolean isTransparent = w.getBackground() != null && w.getBackground().getAlpha() == 0; if (transparent != isTransparent) { setBackgroundTransparent(w, transparent, "setWindowTransparent"); } } /** Setting this false restores the original setting. */ private static final String WDRAG = "apple.awt.draggableWindowBackground"; private void fixWindowDragging(Window w, String context) { if (w instanceof RootPaneContainer) { JRootPane p = ((RootPaneContainer)w).getRootPane(); Boolean oldDraggable = (Boolean)p.getClientProperty(WDRAG); if (oldDraggable == null) { p.putClientProperty(WDRAG, Boolean.FALSE); if (w.isDisplayable()) { System.err.println(context + "(): To avoid content dragging, " + context + "() must be called before the window is realized, or " + WDRAG + " must be set to Boolean.FALSE before the window is realized. If you really want content dragging, set " + WDRAG + " on the window's root pane to Boolean.TRUE before calling " + context + "() to hide this message."); } } } } /** Note that the property * <code>apple.awt.draggableWindowBackground</code> must be set to its * final value <em>before</em> the heavyweight peer for the Window is * created. Once {@link Component#addNotify} has been called on the * component, causing creation of the heavyweight peer, changing this * property has no effect. * @see <a href="http://developer.apple.com/technotes/tn2007/tn2196.html#APPLE_AWT_DRAGGABLEWINDOWBACKGROUND">Apple Technote 2007</a> */ public void setWindowAlpha(final Window w, final float alpha) { if (w instanceof RootPaneContainer) { JRootPane p = ((RootPaneContainer)w).getRootPane(); p.putClientProperty("Window.alpha", new Float(alpha)); fixWindowDragging(w, "setWindowAlpha"); } whenDisplayable(w, new Runnable() { public void run() { Object peer = w.getPeer(); try { peer.getClass().getMethod("setAlpha", new Class[]{ float.class }).invoke(peer, new Object[]{ new Float(alpha) }); } catch (Exception e) { } } }); } protected void setWindowMask(Component w, Raster raster) { if (raster != null) { setWindowMask(w, toShape(raster)); } else { setWindowMask(w, new Rectangle(0, 0, w.getWidth(), w.getHeight())); } } public void setWindowMask(Component c, final Shape shape) { if (c instanceof Window) { Window w = (Window)c; OSXMaskingContentPane content = installMaskingPane(w); content.setMask(shape); setBackgroundTransparent(w, shape != MASK_NONE, "setWindowMask"); } else { // not yet implemented } } /** Mask out unwanted pixels and ensure background gets cleared. * @author Olivier Chafik */ private static class OSXMaskingContentPane extends JPanel { private Shape shape; public OSXMaskingContentPane(Component oldContent) { super(new BorderLayout()); if (oldContent != null) { add(oldContent, BorderLayout.CENTER); } } public void setMask(Shape shape) { this.shape = shape; repaint(); } public void paint(Graphics graphics) { Graphics2D g = (Graphics2D)graphics.create(); g.setComposite(AlphaComposite.Clear); g.fillRect(0, 0, getWidth(), getHeight()); g.dispose(); if (shape != null) { g = (Graphics2D)graphics.create(); g.setClip(shape); super.paint(g); g.dispose(); } else { super.paint(graphics); } } } private void setBackgroundTransparent(Window w, boolean transparent, String context) { JRootPane rp = w instanceof RootPaneContainer ? ((RootPaneContainer)w).getRootPane() : null; if (transparent) { if (rp != null) { rp.putClientProperty(TRANSPARENT_OLD_BG, w.getBackground()); } w.setBackground(new Color(0,0,0,0)); } else { if (rp != null) { Color bg = (Color)rp.getClientProperty(TRANSPARENT_OLD_BG); // If the old bg is a // apple.laf.CColorPaintUIResource, the window's // transparent state will not change if (bg != null) { bg = new Color(bg.getRed(), bg.getGreen(), bg.getBlue(), bg.getAlpha()); } w.setBackground(bg); rp.putClientProperty(TRANSPARENT_OLD_BG, null); } else { w.setBackground(null); } } fixWindowDragging(w, context); } } private static class X11WindowUtils extends NativeWindowUtils { private static Pixmap createBitmap(final Display dpy, X11.Window win, Raster raster) { final X11 x11 = X11.INSTANCE; Rectangle bounds = raster.getBounds(); int width = bounds.x + bounds.width; int height = bounds.y + bounds.height; final Pixmap pm = x11.XCreatePixmap(dpy, win, width, height, 1); final GC gc = x11.XCreateGC(dpy, pm, new NativeLong(0), null); if (gc == null) { return null; } x11.XSetForeground(dpy, gc, new NativeLong(0)); x11.XFillRectangle(dpy, pm, gc, 0, 0, width, height); final List rlist = new ArrayList(); try { RasterRangesUtils.outputOccupiedRanges(raster, new RasterRangesUtils.RangesOutput() { public boolean outputRange(int x, int y, int w, int h) { rlist.add(new Rectangle(x, y, w, h)); return true; } }); X11.XRectangle[] rects = (X11.XRectangle[]) new X11.XRectangle().toArray(rlist.size()); for (int i=0;i < rects.length;i++) { Rectangle r = (Rectangle)rlist.get(i); rects[i].x = (short)r.x; rects[i].y = (short)r.y; rects[i].width = (short)r.width; rects[i].height = (short)r.height; // Optimization: write directly to native memory Pointer p = rects[i].getPointer(); p.setShort(0, (short)r.x); p.setShort(2, (short)r.y); p.setShort(4, (short)r.width); p.setShort(6, (short)r.height); rects[i].setAutoSynch(false); // End optimization } final int UNMASKED = 1; x11.XSetForeground(dpy, gc, new NativeLong(UNMASKED)); x11.XFillRectangles(dpy, pm, gc, rects, rects.length); } finally { x11.XFreeGC(dpy, gc); } return pm; } private boolean didCheck; private long[] alphaVisualIDs = {}; public boolean isWindowAlphaSupported() { return getAlphaVisualIDs().length > 0; } private static long getVisualID(GraphicsConfiguration config) { // Use reflection to call // X11GraphicsConfig.getVisual try { Object o = config.getClass() .getMethod("getVisual", (Class[])null) .invoke(config, (Object[])null); return ((Number)o).longValue(); } catch (Exception e) { e.printStackTrace(); return -1; } } /** Return the default graphics configuration. */ public GraphicsConfiguration getAlphaCompatibleGraphicsConfiguration() { if (isWindowAlphaSupported()) { GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment(); GraphicsDevice[] devices = env.getScreenDevices(); for (int i = 0; i < devices.length; i++) { GraphicsConfiguration[] configs = devices[i].getConfigurations(); for (int j = 0; j < configs.length; j++) { long visualID = getVisualID(configs[j]); long[] ids = getAlphaVisualIDs(); for (int k = 0; k < ids.length; k++) { if (visualID == ids[k]) { return configs[j]; } } } } } return super.getAlphaCompatibleGraphicsConfiguration(); } /** * Return the visual ID of the visual which supports an alpha * channel. */ private synchronized long[] getAlphaVisualIDs() { if (didCheck) { return alphaVisualIDs; } didCheck = true; X11 x11 = X11.INSTANCE; Display dpy = x11.XOpenDisplay(null); if (dpy == null) return alphaVisualIDs; XVisualInfo info = null; try { int screen = x11.XDefaultScreen(dpy); XVisualInfo template = new XVisualInfo(); template.screen = screen; template.depth = 32; template.c_class = X11.TrueColor; NativeLong mask = new NativeLong(X11.VisualScreenMask | X11.VisualDepthMask | X11.VisualClassMask); IntByReference pcount = new IntByReference(); info = x11.XGetVisualInfo(dpy, mask, template, pcount); if (info != null) { List list = new ArrayList(); XVisualInfo[] infos = (XVisualInfo[])info.toArray(pcount.getValue()); for (int i = 0; i < infos.length; i++) { XRenderPictFormat format = X11.Xrender.INSTANCE.XRenderFindVisualFormat(dpy, infos[i].visual); if (format.type == X11.Xrender.PictTypeDirect && format.direct.alphaMask != 0) { list.add(infos[i].visualid); } } alphaVisualIDs = new long[list.size()]; for (int i=0;i < alphaVisualIDs.length;i++) { alphaVisualIDs[i] = ((Number)list.get(i)).longValue(); } return alphaVisualIDs; } } finally { if (info != null) { x11.XFree(info.getPointer()); } x11.XCloseDisplay(dpy); } return alphaVisualIDs; } private static X11.Window getContentWindow(Window w, X11.Display dpy, X11.Window win, Point offset) { if ((w instanceof Frame && !((Frame)w).isUndecorated()) || (w instanceof Dialog && !((Dialog)w).isUndecorated())) { X11 x11 = X11.INSTANCE; X11.WindowByReference rootp = new X11.WindowByReference(); X11.WindowByReference parentp = new X11.WindowByReference(); PointerByReference childrenp = new PointerByReference(); IntByReference countp = new IntByReference(); x11.XQueryTree(dpy, win, rootp, parentp, childrenp, countp); Pointer p = childrenp.getValue(); int[] ids = p.getIntArray(0, countp.getValue()); for (int i=0;i < ids.length;i++) { // TODO: more verification of correct window? X11.Window child = new X11.Window(ids[i]); X11.XWindowAttributes xwa = new X11.XWindowAttributes(); x11.XGetWindowAttributes(dpy, child, xwa); offset.x = -xwa.x; offset.y = -xwa.y; win = child; break; } if (p != null) { x11.XFree(p); } } return win; } private static X11.Window getDrawable(Component w) { int id = (int)Native.getComponentID(w); if (id == X11.None) return null; return new X11.Window(id); } private static final long OPAQUE = 0xFFFFFFFFL; private static final String OPACITY = "_NET_WM_WINDOW_OPACITY"; public void setWindowAlpha(final Window w, final float alpha) { if (!isWindowAlphaSupported()) { throw new UnsupportedOperationException("This X11 display does not provide a 32-bit visual"); } Runnable action = new Runnable() { public void run() { X11 x11 = X11.INSTANCE; Display dpy = x11.XOpenDisplay(null); if (dpy == null) return; try { X11.Window win = getDrawable(w); if (alpha == 1f) { x11.XDeleteProperty(dpy, win, x11.XInternAtom(dpy, OPACITY, false)); } else { int opacity = (int)((long)(alpha * OPAQUE) & 0xFFFFFFFF); IntByReference patom = new IntByReference(opacity); x11.XChangeProperty(dpy, win, x11.XInternAtom(dpy, OPACITY, false), X11.XA_CARDINAL, 32, X11.PropModeReplace, patom.getPointer(), 1); } } finally { x11.XCloseDisplay(dpy); } } }; whenDisplayable(w, action); } private class X11TransparentContentPane extends TransparentContentPane { public X11TransparentContentPane(Container oldContent) { super(oldContent); } private Memory buffer; private int[] pixels; private final int[] pixel = new int[4]; // Painting directly to the original Graphics // fails to properly composite unless the destination // is pure black. Too bad. protected void paintDirect(BufferedImage buf, Rectangle bounds) { Window window = SwingUtilities.getWindowAncestor(this); X11 x11 = X11.INSTANCE; X11.Display dpy = x11.XOpenDisplay(null); X11.Window win = getDrawable(window); Point offset = new Point(); win = getContentWindow(window, dpy, win, offset); X11.GC gc = x11.XCreateGC(dpy, win, new NativeLong(0), null); Raster raster = buf.getData(); int w = bounds.width; int h = bounds.height; if (buffer == null || buffer.getSize() != w*h*4) { buffer = new Memory(w*h*4); pixels = new int[w*h]; } for (int y=0;y<h;y++) { for (int x=0;x < w;x++) { raster.getPixel(x, y, pixel); int alpha = pixel[3]&0xFF; int red = pixel[2]&0xFF; int green = pixel[1]&0xFF; int blue = pixel[0]&0xFF; // TODO: use visual RGB masks to position bits // This layout (ABGR) works empirically pixels[y*w + x] = (alpha<<24)|(blue<<16)|(green<<8)|red; } } X11.XWindowAttributes xwa = new X11.XWindowAttributes(); x11.XGetWindowAttributes(dpy, win, xwa); X11.XImage image = x11.XCreateImage(dpy, xwa.visual, 32, X11.ZPixmap, 0, buffer, w, h, 32, w * 4); buffer.write(0, pixels, 0, pixels.length); offset.x += bounds.x; offset.y += bounds.y; x11.XPutImage(dpy, win, gc, image, 0, 0, offset.x, offset.y, w, h); x11.XFree(image.getPointer()); x11.XFreeGC(dpy, gc); x11.XCloseDisplay(dpy); } } public void setWindowTransparent(final Window w, final boolean transparent) { if (!(w instanceof RootPaneContainer)) { throw new IllegalArgumentException("Window must be a RootPaneContainer"); } if (!isWindowAlphaSupported()) { throw new UnsupportedOperationException("This X11 display does not provide a 32-bit visual"); } if (!w.getGraphicsConfiguration() .equals(getAlphaCompatibleGraphicsConfiguration())) { throw new IllegalArgumentException("Window GraphicsConfiguration '" + w.getGraphicsConfiguration() + "' does not support transparency"); } boolean isTransparent = w.getBackground() != null && w.getBackground().getAlpha() == 0; if (transparent == isTransparent) return; whenDisplayable(w, new Runnable() { public void run() { JRootPane root = ((RootPaneContainer)w).getRootPane(); JLayeredPane lp = root.getLayeredPane(); Container content = root.getContentPane(); if (content instanceof X11TransparentContentPane) { ((X11TransparentContentPane)content).setTransparent(transparent); } else if (transparent) { X11TransparentContentPane x11content = new X11TransparentContentPane(content); root.setContentPane(x11content); lp.add(new RepaintTrigger(x11content), JLayeredPane.DRAG_LAYER); } setLayersTransparent(w, transparent); setForceHeavyweightPopups(w, transparent); setDoubleBuffered(w, !transparent); } }); } private interface PixmapSource { Pixmap getPixmap(Display dpy, X11.Window win); } private void setWindowShape(final Window w, final PixmapSource src) { Runnable action = new Runnable() { public void run() { X11 x11 = X11.INSTANCE; Display dpy = x11.XOpenDisplay(null); if (dpy == null) { return; } Pixmap pm = null; try { X11.Window win = getDrawable(w); pm = src.getPixmap(dpy, win); Xext ext = Xext.INSTANCE; ext.XShapeCombineMask(dpy, win, X11.Xext.ShapeBounding, 0, 0, pm == null ? Pixmap.None : pm, X11.Xext.ShapeSet); } finally { if (pm != null) { x11.XFreePixmap(dpy, pm); } x11.XCloseDisplay(dpy); } setForceHeavyweightPopups(getWindow(w), pm != null); } }; whenDisplayable(w, action); } protected void setMask(final Component w, final Raster raster) { setWindowShape(getWindow(w), new PixmapSource() { public Pixmap getPixmap(Display dpy, X11.Window win) { return raster != null ? createBitmap(dpy, win, raster) : null; } }); } } /** * Applies the given mask to the given window. Does nothing if the * operation is not supported. The mask is treated as a bitmap and * ignores transparency. */ public static void setWindowMask(Window w, Shape mask) { getInstance().setWindowMask(w, mask); } /** * Applies the given mask to the given heavyweight component. Does nothing * if the operation is not supported. The mask is treated as a bitmap and * ignores transparency. */ public static void setComponentMask(Component c, Shape mask) { getInstance().setWindowMask(c, mask); } /** * Applies the given mask to the given window. Does nothing if the * operation is not supported. The mask is treated as a bitmap and * ignores transparency. */ public static void setWindowMask(Window w, Icon mask) { getInstance().setWindowMask(w, mask); } /** Indicate a window can have a global alpha setting. */ public static boolean isWindowAlphaSupported() { return getInstance().isWindowAlphaSupported(); } /** * Returns a {@link GraphicsConfiguration} comptible with alpha * compositing. */ public static GraphicsConfiguration getAlphaCompatibleGraphicsConfiguration() { return getInstance().getAlphaCompatibleGraphicsConfiguration(); } /** * Set the overall window transparency. An alpha of 1.0 is fully * opaque, 0.0 fully transparent. The alpha level is applied equally * to all window pixels.<p> * NOTE: Windows requires that <code>sun.java2d.noddraw=true</code> * in order for alpha to work.<p> * NOTE: On OSX, the property * <code>apple.awt.draggableWindowBackground</code> must be set to its * final value <em>before</em> the heavyweight peer for the Window is * created. Once {@link Component#addNotify} has been called on the * component, causing creation of the heavyweight peer, changing this * property has no effect. * @see <a href="http://developer.apple.com/technotes/tn2007/tn2196.html#APPLE_AWT_DRAGGABLEWINDOWBACKGROUND">Apple Technote 2007</a> */ public static void setWindowAlpha(Window w, float alpha) { getInstance().setWindowAlpha(w, Math.max(0f, Math.min(alpha, 1f))); } /** * Set the window to be transparent. Only explicitly painted pixels * will be non-transparent. All pixels will be composited with * whatever is under the window using their alpha values. * * On OSX, the property <code>apple.awt.draggableWindowBackground</code> * must be set to its final value <em>before</em> the heavyweight peer for * the Window is created. Once {@link Component#addNotify} has been * called on the component, causing creation of the heavyweight peer, * changing this property has no effect. * @see <a href="http://developer.apple.com/technotes/tn2007/tn2196.html#APPLE_AWT_DRAGGABLEWINDOWBACKGROUND">Apple Technote 2007</a> */ public static void setWindowTransparent(Window w, boolean transparent) { getInstance().setWindowTransparent(w, transparent); } }