0001: /*
0002: * Copyright (c) 2007 Timothy Wall, All Rights Reserved
0003: * Parts Copyright (c) 2007 Olivier Chafik
0004: *
0005: * This library is free software; you can
0006: * redistribute it and/or modify it under the terms of the GNU Lesser
0007: * General Public License as published by the Free Software Foundation;
0008: * either version 2.1 of the License, or (at your option) any later
0009: * version. <p/> This library is distributed in the hope that it will be
0010: * useful, but WITHOUT ANY WARRANTY; without even the implied warranty
0011: * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0012: * Lesser General Public License for more details.
0013: */
0014: package org.netbeans.core.nativeaccess.transparency;
0015:
0016: import java.awt.AlphaComposite;
0017: import java.awt.BorderLayout;
0018: import java.awt.Color;
0019: import java.awt.Component;
0020: import java.awt.Container;
0021: import java.awt.Graphics;
0022: import java.awt.Graphics2D;
0023: import java.awt.GraphicsConfiguration;
0024: import java.awt.GraphicsDevice;
0025: import java.awt.GraphicsEnvironment;
0026: import java.awt.Point;
0027: import java.awt.Rectangle;
0028: import java.awt.Shape;
0029: import java.awt.Window;
0030: import java.awt.event.ComponentEvent;
0031: import java.awt.event.ComponentListener;
0032: import java.awt.event.HierarchyEvent;
0033: import java.awt.event.HierarchyListener;
0034: import java.awt.event.WindowAdapter;
0035: import java.awt.event.WindowEvent;
0036: import java.awt.geom.Area;
0037: import java.awt.image.BufferedImage;
0038: import java.awt.image.Raster;
0039: import java.util.ArrayList;
0040: import java.util.List;
0041:
0042: import javax.swing.Icon;
0043: import javax.swing.JComponent;
0044: import javax.swing.JLayeredPane;
0045: import javax.swing.JPanel;
0046: import javax.swing.JRootPane;
0047: import javax.swing.PopupFactory;
0048: import javax.swing.RootPaneContainer;
0049: import javax.swing.SwingUtilities;
0050:
0051: import com.sun.jna.Native;
0052: import com.sun.jna.NativeLong;
0053: import com.sun.jna.Platform;
0054: import com.sun.jna.Pointer;
0055: import org.netbeans.core.nativeaccess.transparency.unix.X11;
0056: import org.netbeans.core.nativeaccess.transparency.unix.X11.Display;
0057: import org.netbeans.core.nativeaccess.transparency.unix.X11.Drawable;
0058: import org.netbeans.core.nativeaccess.transparency.unix.X11.GC;
0059: import org.netbeans.core.nativeaccess.transparency.unix.X11.Pixmap;
0060: import org.netbeans.core.nativeaccess.transparency.unix.X11.XVisualInfo;
0061: import org.netbeans.core.nativeaccess.transparency.unix.X11.Xext;
0062: import org.netbeans.core.nativeaccess.transparency.unix.X11.Xrender.XRenderPictFormat;
0063: import org.netbeans.core.nativeaccess.transparency.win32.GDI32;
0064: import org.netbeans.core.nativeaccess.transparency.win32.User32;
0065: import org.netbeans.core.nativeaccess.transparency.win32.GDI32.BITMAPINFO;
0066: import org.netbeans.core.nativeaccess.transparency.win32.User32.BLENDFUNCTION;
0067: import org.netbeans.core.nativeaccess.transparency.win32.User32.POINT;
0068: import org.netbeans.core.nativeaccess.transparency.win32.User32.SIZE;
0069: import org.netbeans.core.nativeaccess.transparency.win32.W32API.HANDLE;
0070: import org.netbeans.core.nativeaccess.transparency.win32.W32API.HBITMAP;
0071: import org.netbeans.core.nativeaccess.transparency.win32.W32API.HDC;
0072: import org.netbeans.core.nativeaccess.transparency.win32.W32API.HRGN;
0073: import org.netbeans.core.nativeaccess.transparency.win32.W32API.HWND;
0074: import com.sun.jna.ptr.ByteByReference;
0075: import com.sun.jna.ptr.IntByReference;
0076: import com.sun.jna.ptr.PointerByReference;
0077:
0078: /**
0079: * Provides additional features on a Java {@link Window}.
0080: * <ul>
0081: * <li>Non-rectangular shape (bitmap mask, no antialiasing)
0082: * <li>Transparency (constant alpha applied to window contents or
0083: * transparent background)
0084: * <li>Fully transparent window (the transparency of all painted pixels is
0085: * applied to the window).
0086: * </ul>
0087: * NOTE: since there is no explicit way to force PopupFactory to use a
0088: * heavyweight popup, and anything but a heavyweight popup will be
0089: * clipped by a window mask, an additional subwindow is added to all
0090: * masked windows to implicitly force PopupFactory to use a heavyweight
0091: * window and avoid clipping.
0092: * <p>
0093: * NOTE: {@link #setWindowTransparent} on X11 doesn't composite
0094: * entirely correctly; depending on what's drawn in the window it mey be
0095: * more or less noticable.
0096: * <p>
0097: * NOTE: Neither shaped windows nor transparency
0098: * currently works with Java 1.4 under X11. This is at least partly due
0099: * to 1.4 using multiple X11 windows for a single given Java window. It
0100: * *might* be possible to remedy by applying the window
0101: * region/transparency to all descendants, but I haven't tried it. In
0102: * addition, windows must be both displayable <em>and</em> visible
0103: * before the corresponding native Drawable may be obtained; in later
0104: * Java versions, the window need only be displayable.
0105: * <p>
0106: * NOTE: If you use {@link #setWindowMask(Window,Shape)} and override {@link
0107: * Window#paint(Graphics)} on OS X, you'll need to explicitly set the clip
0108: * mask on the <code>Graphics</code> object with the window mask; only the
0109: * content pane of the window and below have the window mask automatically
0110: * applied.
0111: */
0112: // TODO: setWindowMask() should accept a threshold; some cases want a
0113: // 50% threshold, some might want zero/non-zero
0114: public class WindowUtils {
0115: public static boolean doPaint;
0116: private static final String TRANSPARENT_OLD_BG = "transparent-old-bg";
0117: private static final String TRANSPARENT_OLD_OPAQUE = "transparent-old-opaque";
0118: private static final String TRANSPARENT_ALPHA = "transparent-alpha";
0119: /** Use this to clear a window mask. */
0120: public static final Shape MASK_NONE = null;
0121:
0122: /**
0123: * This class forces a heavyweight popup on the parent
0124: * {@link Window}. See the implementation of {@link PopupFactory};
0125: * a heavyweight is forced if there is an occluding subwindow on the
0126: * target window.
0127: * <p>
0128: * Ideally we'd have more control over {@link PopupFactory} but this
0129: * is a fairly simple, lightweight workaround.
0130: */
0131: private static class HeavyweightForcer extends Window {
0132: private boolean packed;
0133:
0134: public HeavyweightForcer(Window parent) {
0135: super (parent);
0136: pack();
0137: packed = true;
0138: }
0139:
0140: public boolean isVisible() {
0141: // Only want to be 'visible' once the peer is instantiated
0142: // via pack
0143: return packed;
0144: }
0145:
0146: public Rectangle getBounds() {
0147: return getOwner().getBounds();
0148: }
0149: }
0150:
0151: /**
0152: * This can be installed over a {@link JLayeredPane} in order to
0153: * listen for repaint requests. The {@link #update} method will be
0154: * invoked whenever any part of the ancestor window is repainted.
0155: */
0156: private static abstract class RepaintTrigger extends JComponent {
0157: protected class Listener extends WindowAdapter implements
0158: ComponentListener, HierarchyListener {
0159: public void windowOpened(WindowEvent e) {
0160: repaint();
0161: }
0162:
0163: public void componentHidden(ComponentEvent e) {
0164: }
0165:
0166: public void componentMoved(ComponentEvent e) {
0167: }
0168:
0169: public void componentResized(ComponentEvent e) {
0170: setSize(getParent().getSize());
0171: repaint();
0172: }
0173:
0174: public void componentShown(ComponentEvent e) {
0175: repaint();
0176: }
0177:
0178: public void hierarchyChanged(HierarchyEvent e) {
0179: repaint();
0180: }
0181: }
0182:
0183: private Listener listener = createListener();
0184:
0185: public void addNotify() {
0186: super .addNotify();
0187: Window w = SwingUtilities.getWindowAncestor(this );
0188: setSize(getParent().getSize());
0189: w.addComponentListener(listener);
0190: w.addWindowListener(listener);
0191: }
0192:
0193: public void removeNotify() {
0194: Window w = SwingUtilities.getWindowAncestor(this );
0195: w.removeComponentListener(listener);
0196: w.removeWindowListener(listener);
0197: super .removeNotify();
0198: }
0199:
0200: private boolean painting;
0201:
0202: protected void paintComponent(Graphics g) {
0203: if (!painting) {
0204: painting = true;
0205: update();
0206: painting = false;
0207: }
0208: }
0209:
0210: protected Listener createListener() {
0211: return new Listener();
0212: }
0213:
0214: protected abstract void update();
0215:
0216: public static void remove(Container c) {
0217: for (int i = 0; i < c.getComponentCount(); i++) {
0218: if (c.getComponent(i) instanceof RepaintTrigger) {
0219: c.remove(i);
0220: return;
0221: }
0222: }
0223: }
0224: };
0225:
0226: /**
0227: * Execute the given action when the given window becomes
0228: * displayable.
0229: */
0230: public static void whenDisplayable(Window w, final Runnable action) {
0231: if (w.isDisplayable()
0232: && (!Holder.requiresVisible || w.isVisible())) {
0233: action.run();
0234: } else if (Holder.requiresVisible) {
0235: w.addWindowListener(new WindowAdapter() {
0236: public void windowOpened(WindowEvent e) {
0237: e.getWindow().removeWindowListener(this );
0238: action.run();
0239: }
0240:
0241: public void windowClosed(WindowEvent e) {
0242: e.getWindow().removeWindowListener(this );
0243: }
0244: });
0245: } else {
0246: // Hierarchy events are fired in direct response to
0247: // displayability
0248: // changes
0249: w.addHierarchyListener(new HierarchyListener() {
0250: public void hierarchyChanged(HierarchyEvent e) {
0251: if ((e.getChangeFlags() & HierarchyEvent.DISPLAYABILITY_CHANGED) != 0
0252: && e.getComponent().isDisplayable()) {
0253: e.getComponent().removeHierarchyListener(this );
0254: action.run();
0255: }
0256: }
0257: });
0258: }
0259: }
0260:
0261: /** Window utilities with differing native implementations. */
0262: public static abstract class NativeWindowUtils {
0263: /**
0264: * Set the overall alpha transparency of the window. An alpha of
0265: * 1.0 is fully opaque, 0.0 is fully transparent.
0266: */
0267: public void setWindowAlpha(Window w, float alpha) {
0268: // do nothing
0269: }
0270:
0271: /** Default: no support. */
0272: public boolean isWindowAlphaSupported() {
0273: return false;
0274: }
0275:
0276: /** Return the default graphics configuration. */
0277: public GraphicsConfiguration getAlphaCompatibleGraphicsConfiguration() {
0278: GraphicsEnvironment env = GraphicsEnvironment
0279: .getLocalGraphicsEnvironment();
0280: GraphicsDevice dev = env.getDefaultScreenDevice();
0281: return dev.getDefaultConfiguration();
0282: }
0283:
0284: /**
0285: * Set the window to be transparent. Only explicitly painted
0286: * pixels will be non-transparent. All pixels will be composited
0287: * with whatever is under the window using their alpha values.
0288: */
0289: public void setWindowTransparent(Window w, boolean transparent) {
0290: // do nothing
0291: }
0292:
0293: protected void setLayersTransparent(Window w,
0294: boolean transparent) {
0295: Color bg = transparent ? new Color(0, 0, 0, 0) : null;
0296: if (w instanceof RootPaneContainer) {
0297: RootPaneContainer rpc = (RootPaneContainer) w;
0298: JRootPane root = rpc.getRootPane();
0299: JLayeredPane lp = root.getLayeredPane();
0300: Container c = root.getContentPane();
0301: JComponent content = (c instanceof JComponent) ? (JComponent) c
0302: : null;
0303: if (transparent) {
0304: lp.putClientProperty(TRANSPARENT_OLD_OPAQUE,
0305: Boolean.valueOf(lp.isOpaque()));
0306: lp.setOpaque(false);
0307: root.putClientProperty(TRANSPARENT_OLD_OPAQUE,
0308: Boolean.valueOf(root.isOpaque()));
0309: root.setOpaque(false);
0310: if (content != null) {
0311: content.putClientProperty(
0312: TRANSPARENT_OLD_OPAQUE, Boolean
0313: .valueOf(content.isOpaque()));
0314: content.setOpaque(false);
0315: }
0316: root.putClientProperty(TRANSPARENT_OLD_BG, root
0317: .getParent().getBackground());
0318: } else {
0319: lp
0320: .setOpaque(Boolean.TRUE
0321: .equals(lp
0322: .getClientProperty(TRANSPARENT_OLD_OPAQUE)));
0323: root
0324: .setOpaque(Boolean.TRUE
0325: .equals(root
0326: .getClientProperty(TRANSPARENT_OLD_OPAQUE)));
0327: if (content != null) {
0328: content
0329: .setOpaque(Boolean.TRUE
0330: .equals(content
0331: .getClientProperty(TRANSPARENT_OLD_OPAQUE)));
0332: }
0333: bg = (Color) root
0334: .getClientProperty(TRANSPARENT_OLD_BG);
0335: }
0336: }
0337: w.setBackground(bg);
0338: }
0339:
0340: /**
0341: * Set the window mask based on the given Raster, which should
0342: * be treated as a bitmap (zero/nonzero values only). A value of
0343: * <code>null</code> means to remove the mask.
0344: */
0345: public abstract void setWindowMask(Window w, Raster raster);
0346:
0347: /** Set the window mask based on a {@link Shape}. */
0348: public void setWindowMask(Window w, Shape mask) {
0349: Raster raster = null;
0350: if (mask != MASK_NONE) {
0351: Rectangle bounds = mask.getBounds();
0352: if (bounds.width > 0 && bounds.height > 0) {
0353: BufferedImage bitmap = new BufferedImage(bounds.x
0354: + bounds.width, bounds.y + bounds.height,
0355: BufferedImage.TYPE_BYTE_BINARY);
0356: Graphics2D g = bitmap.createGraphics();
0357: g.setColor(Color.black);
0358: g.fillRect(bounds.x, bounds.y, bounds.width,
0359: bounds.height);
0360: g.setColor(Color.white);
0361: g.fill(mask);
0362: raster = bitmap.getData();
0363: }
0364: }
0365: setWindowMask(w, raster);
0366: }
0367:
0368: /**
0369: * Set the window mask based on an Icon. All non-transparent
0370: * pixels will be included in the mask.
0371: */
0372: public void setWindowMask(final Window w, Icon mask) {
0373: Raster raster = null;
0374: if (mask != null) {
0375: Rectangle bounds = new Rectangle(0, 0, mask
0376: .getIconWidth(), mask.getIconHeight());
0377: BufferedImage clip = new BufferedImage(bounds.width,
0378: bounds.height, BufferedImage.TYPE_INT_ARGB);
0379: Graphics2D g = clip.createGraphics();
0380: g.setComposite(AlphaComposite.Clear);
0381: g.fillRect(0, 0, bounds.width, bounds.height);
0382: g.setComposite(AlphaComposite.SrcOver);
0383: mask.paintIcon(w, g, 0, 0);
0384: raster = clip.getAlphaRaster();
0385: }
0386: setWindowMask(w, raster);
0387: }
0388:
0389: /**
0390: * Use this method to ensure heavyweight popups are used in
0391: * conjunction with a given window. This prevents the window's
0392: * alpha setting or mask region from being applied to the popup.
0393: */
0394: protected void setForceHeavyweightPopups(Window w, boolean force) {
0395: if (!(w instanceof HeavyweightForcer)) {
0396: Window[] owned = w.getOwnedWindows();
0397: for (int i = 0; i < owned.length; i++) {
0398: if (owned[i] instanceof HeavyweightForcer) {
0399: owned[i].dispose();
0400: }
0401: }
0402: if (force) {
0403: new HeavyweightForcer(w);
0404: }
0405: }
0406: }
0407: }
0408:
0409: /** Canonical lazy loading of a singleton. */
0410: private static class Holder {
0411: /**
0412: * Indicates whether a window must be visible before its native
0413: * handle can be obtained. This wart is caused by the Java
0414: * 1.4/X11 implementation.
0415: */
0416: public static boolean requiresVisible;
0417: public static final NativeWindowUtils INSTANCE;
0418: static {
0419: String os = System.getProperty("os.name");
0420: if (os.startsWith("Windows")) {
0421: INSTANCE = new W32WindowUtils();
0422: } else if (os.startsWith("Mac")) {
0423: INSTANCE = new MacWindowUtils();
0424: } else if (os.startsWith("Linux") || os.startsWith("SunOS")) {
0425: INSTANCE = new X11WindowUtils();
0426: requiresVisible = System.getProperty("java.version")
0427: .matches("^1\\.4\\..*");
0428: } else {
0429: throw new UnsupportedOperationException(
0430: "No support for " + os);
0431: }
0432: }
0433: }
0434:
0435: private static NativeWindowUtils getInstance() {
0436: return Holder.INSTANCE;
0437: }
0438:
0439: private static class W32WindowUtils extends NativeWindowUtils {
0440: public HWND getHWnd(Window w) {
0441: HWND hwnd = new HWND();
0442: hwnd.setPointer(Native.getWindowPointer(w));
0443: return hwnd;
0444: }
0445:
0446: /**
0447: * W32 alpha will only work if <code>sun.java2d.noddraw</code>
0448: * is set
0449: */
0450: public boolean isWindowAlphaSupported() {
0451: return Boolean.getBoolean("sun.java2d.noddraw");
0452: }
0453:
0454: /** Indicates whether UpdateLayeredWindow is in use. */
0455: private boolean isTransparent(Window w) {
0456: if (w instanceof RootPaneContainer) {
0457: JRootPane root = ((RootPaneContainer) w).getRootPane();
0458: return root.getClientProperty(TRANSPARENT_OLD_BG) != null;
0459: }
0460: return false;
0461: }
0462:
0463: /** Keep track of the alpha level, since we can't read it from
0464: * the window itself.
0465: */
0466: private void storeAlpha(Window w, byte alpha) {
0467: if (w instanceof RootPaneContainer) {
0468: JRootPane root = ((RootPaneContainer) w).getRootPane();
0469: Byte b = alpha == (byte) 0xFF ? null : new Byte(alpha);
0470: root.putClientProperty(TRANSPARENT_ALPHA, b);
0471: }
0472: }
0473:
0474: /** Return the last alpha level we set on the window. */
0475: private byte getAlpha(Window w) {
0476: if (w instanceof RootPaneContainer) {
0477: JRootPane root = ((RootPaneContainer) w).getRootPane();
0478: Byte b = (Byte) root
0479: .getClientProperty(TRANSPARENT_ALPHA);
0480: if (b != null) {
0481: return b.byteValue();
0482: }
0483: }
0484: return (byte) 0xFF;
0485: }
0486:
0487: public void setWindowAlpha(final Window w, final float alpha) {
0488: if (!isWindowAlphaSupported()) {
0489: throw new UnsupportedOperationException(
0490: "Set sun.java2d.noddraw=true to enable transparent windows");
0491: }
0492: whenDisplayable(w, new Runnable() {
0493: public void run() {
0494: HWND hWnd = getHWnd(w);
0495: User32 user = User32.INSTANCE;
0496: int flags = user.GetWindowLong(hWnd,
0497: User32.GWL_EXSTYLE);
0498: byte level = (byte) ((int) (255 * alpha) & 0xFF);
0499: if (isTransparent(w)) {
0500: // If already using UpdateLayeredWindow, continue to
0501: // do so
0502: BLENDFUNCTION blend = new BLENDFUNCTION();
0503: blend.SourceConstantAlpha = level;
0504: blend.AlphaFormat = User32.AC_SRC_ALPHA;
0505: user.UpdateLayeredWindow(hWnd, null, null,
0506: null, null, null, 0, blend,
0507: User32.ULW_ALPHA);
0508: } else if (alpha == 1f) {
0509: flags &= ~User32.WS_EX_LAYERED;
0510: user.SetWindowLong(hWnd, User32.GWL_EXSTYLE,
0511: flags);
0512: } else {
0513: flags |= User32.WS_EX_LAYERED;
0514: user.SetWindowLong(hWnd, User32.GWL_EXSTYLE,
0515: flags);
0516: user.SetLayeredWindowAttributes(hWnd, 0, level,
0517: User32.LWA_ALPHA);
0518: }
0519: setForceHeavyweightPopups(w, alpha != 1f);
0520: storeAlpha(w, level);
0521: }
0522: });
0523: }
0524:
0525: private class W32RepaintTrigger extends RepaintTrigger {
0526: public void setBounds(int x, int y, int w, int h) {
0527: super .setBounds(x, y, w, h);
0528: // FIXME this is a hack to get the window to properly
0529: // refresh. figure out what w32 api needs tweaking to
0530: // do it explicitly
0531: if (w > 0 && h > 0)
0532: SwingUtilities.getWindowAncestor(this ).toFront();
0533: }
0534:
0535: public void update() {
0536: GDI32 gdi = GDI32.INSTANCE;
0537: User32 user = User32.INSTANCE;
0538: Window win = SwingUtilities.getWindowAncestor(this );
0539: int w = win.getWidth();
0540: int h = win.getHeight();
0541: HDC screenDC = user.GetDC(null);
0542: HDC memDC = gdi.CreateCompatibleDC(screenDC);
0543: HBITMAP hBitmap = null;
0544: HANDLE oldBitmap = null;
0545: try {
0546: BufferedImage buf = new BufferedImage(w, h,
0547: BufferedImage.TYPE_INT_ARGB_PRE);
0548: Graphics2D g = buf.createGraphics();
0549: g.setComposite(AlphaComposite.Clear);
0550: g.fillRect(0, 0, w, h);
0551: g.setComposite(AlphaComposite.SrcOver);
0552: Point origin = SwingUtilities.convertPoint(
0553: getParent(), 0, 0, win);
0554: getParent().paint(
0555: g.create(origin.x, origin.y, getWidth(),
0556: getHeight()));
0557: BITMAPINFO bmi = new BITMAPINFO();
0558: bmi.bmiHeader.biWidth = w;
0559: bmi.bmiHeader.biHeight = h;
0560: bmi.bmiHeader.biPlanes = 1;
0561: bmi.bmiHeader.biBitCount = 32;
0562: bmi.bmiHeader.biCompression = GDI32.BI_RGB;
0563: bmi.bmiHeader.biSizeImage = w * h * 4;
0564: PointerByReference ppbits = new PointerByReference();
0565: hBitmap = gdi.CreateDIBSection(memDC, bmi,
0566: GDI32.DIB_RGB_COLORS, ppbits, null, 0);
0567: oldBitmap = gdi.SelectObject(memDC, hBitmap);
0568: Pointer pbits = ppbits.getValue();
0569: Raster raster = buf.getData();
0570: int[] pixel = new int[4];
0571: int[] bits = new int[w * h];
0572: for (int row = 0; row < h; row++) {
0573: for (int col = 0; col < w; col++) {
0574: raster.getPixel(col, h - row - 1, pixel);
0575: int alpha = (pixel[3] & 0xFF) << 24;
0576: int red = (pixel[2] & 0xFF);
0577: int green = (pixel[1] & 0xFF) << 8;
0578: int blue = (pixel[0] & 0xFF) << 16;
0579: bits[col + row * w] = alpha | red | green
0580: | blue;
0581: }
0582: }
0583: pbits.write(0, bits, 0, bits.length);
0584: SIZE size = new SIZE();
0585: size.cx = w;
0586: size.cy = h;
0587: POINT srcLoc = new POINT();
0588: BLENDFUNCTION blend = new BLENDFUNCTION();
0589: POINT loc = new POINT();
0590: loc.x = win.getX();
0591: loc.y = win.getY();
0592: HWND hWnd = getHWnd(win);
0593: // extract current constant alpha setting, if possible
0594: ByteByReference bref = new ByteByReference();
0595: IntByReference iref = new IntByReference();
0596: byte level = getAlpha(win);
0597: if (user.GetLayeredWindowAttributes(hWnd, null,
0598: bref, iref)
0599: && (iref.getValue() & User32.LWA_ALPHA) != 0) {
0600: level = bref.getValue();
0601: }
0602: blend.SourceConstantAlpha = level;
0603: blend.AlphaFormat = User32.AC_SRC_ALPHA;
0604: user.UpdateLayeredWindow(hWnd, screenDC, loc, size,
0605: memDC, srcLoc, 0, blend, User32.ULW_ALPHA);
0606: } finally {
0607: user.ReleaseDC(null, screenDC);
0608: if (hBitmap != null) {
0609: gdi.SelectObject(memDC, oldBitmap);
0610: gdi.DeleteObject(hBitmap);
0611: }
0612: gdi.DeleteDC(memDC);
0613: }
0614: }
0615: }
0616:
0617: public void setWindowTransparent(final Window w,
0618: final boolean transparent) {
0619: if (!(w instanceof RootPaneContainer)) {
0620: throw new IllegalArgumentException(
0621: "Window must be a RootPaneContainer");
0622: }
0623: if (!isWindowAlphaSupported()) {
0624: throw new UnsupportedOperationException(
0625: "Set sun.java2d.noddraw=true to enable transparent windows");
0626: }
0627: boolean isTransparent = w.getBackground() != null
0628: && w.getBackground().getAlpha() == 0;
0629: if (!(transparent ^ isTransparent))
0630: return;
0631: whenDisplayable(w, new Runnable() {
0632: public void run() {
0633: User32 user = User32.INSTANCE;
0634: HWND hWnd = getHWnd(w);
0635: int flags = user.GetWindowLong(hWnd,
0636: User32.GWL_EXSTYLE);
0637: JRootPane root = ((RootPaneContainer) w)
0638: .getRootPane();
0639: JLayeredPane lp = root.getLayeredPane();
0640: if (transparent && !isTransparent(w)) {
0641: flags |= User32.WS_EX_LAYERED;
0642: user.SetWindowLong(hWnd, User32.GWL_EXSTYLE,
0643: flags);
0644: lp.add(new W32RepaintTrigger(),
0645: JLayeredPane.DRAG_LAYER);
0646: } else if (!transparent && isTransparent(w)) {
0647: flags &= ~User32.WS_EX_LAYERED;
0648: user.SetWindowLong(hWnd, User32.GWL_EXSTYLE,
0649: flags);
0650: RepaintTrigger.remove(lp);
0651: }
0652: setLayersTransparent(w, transparent);
0653: setForceHeavyweightPopups(w, transparent);
0654: }
0655: });
0656: }
0657:
0658: public void setWindowMask(final Window w, final Raster raster) {
0659: whenDisplayable(w, new Runnable() {
0660: public void run() {
0661: GDI32 gdi = GDI32.INSTANCE;
0662: User32 user = User32.INSTANCE;
0663: HWND hWnd = getHWnd(w);
0664: final HRGN result = gdi.CreateRectRgn(0, 0, 0, 0);
0665: try {
0666: if (raster == null) {
0667: gdi.SetRectRgn(result, 0, 0, w.getWidth(),
0668: w.getHeight());
0669: } else {
0670: final HRGN tempRgn = gdi.CreateRectRgn(0,
0671: 0, 0, 0);
0672: try {
0673: RasterRangesUtils
0674: .outputOccupiedRanges(
0675: raster,
0676: new RasterRangesUtils.RangesOutput() {
0677: public boolean outputRange(
0678: int x,
0679: int y,
0680: int w, int h) {
0681: GDI32 gdi = GDI32.INSTANCE;
0682: gdi
0683: .SetRectRgn(
0684: tempRgn,
0685: x,
0686: y,
0687: x
0688: + w,
0689: y
0690: + h);
0691: return gdi
0692: .CombineRgn(
0693: result,
0694: result,
0695: tempRgn,
0696: GDI32.RGN_OR) != GDI32.ERROR;
0697: }
0698: });
0699: } finally {
0700: gdi.DeleteObject(tempRgn);
0701: }
0702: }
0703: user.SetWindowRgn(hWnd, result, true);
0704: } finally {
0705: gdi.DeleteObject(result);
0706: }
0707: setForceHeavyweightPopups(w, raster != null);
0708: }
0709: });
0710: }
0711: }
0712:
0713: private static class MacWindowUtils extends NativeWindowUtils {
0714: private Shape shapeFromRaster(Raster raster) {
0715: final Area area = new Area(new Rectangle(0, 0, 0, 0));
0716: RasterRangesUtils.outputOccupiedRanges(raster,
0717: new RasterRangesUtils.RangesOutput() {
0718: public boolean outputRange(int x, int y, int w,
0719: int h) {
0720: area
0721: .add(new Area(new Rectangle(x, y,
0722: w, h)));
0723: return true;
0724: }
0725: });
0726: return area;
0727: }
0728:
0729: public boolean isWindowAlphaSupported() {
0730: return true;
0731: }
0732:
0733: private OSXTransparentContent installTransparentContent(Window w) {
0734: OSXTransparentContent content;
0735: if (w instanceof RootPaneContainer) {
0736: // TODO: replace layered pane instead?
0737: final RootPaneContainer rpc = (RootPaneContainer) w;
0738: Container oldContent = rpc.getContentPane();
0739: if (oldContent instanceof OSXTransparentContent) {
0740: content = (OSXTransparentContent) oldContent;
0741: } else {
0742: content = new OSXTransparentContent(oldContent);
0743: // TODO: listen for content pane changes
0744: rpc.setContentPane(content);
0745: }
0746: } else {
0747: Component oldContent = w.getComponentCount() > 0 ? w
0748: .getComponent(0) : null;
0749: if (oldContent instanceof OSXTransparentContent) {
0750: content = (OSXTransparentContent) oldContent;
0751: } else {
0752: content = new OSXTransparentContent(oldContent);
0753: w.add(content);
0754: }
0755: }
0756: return content;
0757: }
0758:
0759: public void setWindowTransparent(Window w, boolean transparent) {
0760: boolean isTransparent = w.getBackground() != null
0761: && w.getBackground().getAlpha() == 0;
0762: if (!(transparent ^ isTransparent))
0763: return;
0764: installTransparentContent(w);
0765: setBackgroundTransparent(w, transparent);
0766: setLayersTransparent(w, transparent);
0767: }
0768:
0769: public void setWindowAlpha(final Window w, final float alpha) {
0770: whenDisplayable(w, new Runnable() {
0771: public void run() {
0772: Object peer = w.getPeer();
0773: try {
0774: peer
0775: .getClass()
0776: .getMethod("setAlpha",
0777: new Class[] { float.class })
0778: .invoke(
0779: peer,
0780: new Object[] { new Float(alpha) });
0781: } catch (Exception e) {
0782: }
0783: }
0784: });
0785: }
0786:
0787: public void setWindowMask(Window w, Raster raster) {
0788: if (raster != null) {
0789: setWindowMask(w, shapeFromRaster(raster));
0790: } else {
0791: setWindowMask(w, new Rectangle(0, 0, w.getWidth(), w
0792: .getHeight()));
0793: }
0794: }
0795:
0796: /** Mask out unwanted pixels and ensure background gets cleared.
0797: * @author Olivier Chafik
0798: */
0799: static class OSXTransparentContent extends JPanel {
0800: private Shape shape;
0801:
0802: public OSXTransparentContent() {
0803: this (null);
0804: }
0805:
0806: public OSXTransparentContent(Component oldContent) {
0807: super (new BorderLayout());
0808: if (oldContent != null) {
0809: add(oldContent, BorderLayout.CENTER);
0810: }
0811: }
0812:
0813: public void setMask(Shape shape) {
0814: this .shape = shape;
0815: repaint();
0816: }
0817:
0818: public void paint(Graphics graphics) {
0819: Graphics2D g = (Graphics2D) graphics.create();
0820: g.setComposite(AlphaComposite.Clear);
0821: g.fillRect(0, 0, getWidth(), getHeight());
0822: g.dispose();
0823: if (shape != null) {
0824: g = (Graphics2D) graphics.create();
0825: g.setClip(shape);
0826: super .paint(g);
0827: g.dispose();
0828: } else {
0829: super .paint(graphics);
0830: }
0831: }
0832: }
0833:
0834: private void setBackgroundTransparent(Window w,
0835: boolean transparent) {
0836: if (transparent) {
0837: w.setBackground(new Color(0, 0, 0, 0));
0838: } else {
0839: // FIXME restore background to original color
0840: w.setBackground(null);
0841: }
0842: }
0843:
0844: public void setWindowMask(Window w, final Shape shape) {
0845: OSXTransparentContent content = installTransparentContent(w);
0846: content.setMask(shape);
0847: setBackgroundTransparent(w, shape != MASK_NONE);
0848: }
0849: }
0850:
0851: private static class X11WindowUtils extends NativeWindowUtils {
0852: private Pixmap createBitmap(final Display dpy, X11.Window win,
0853: Window w, Raster raster) {
0854: final X11 x11 = X11.INSTANCE;
0855: Rectangle bounds = raster.getBounds();
0856: int width = bounds.x + bounds.width;
0857: int height = bounds.y + bounds.height;
0858: final Pixmap pm = x11.XCreatePixmap(dpy, win, width,
0859: height, 1);
0860: final GC gc = x11.XCreateGC(dpy, pm, new NativeLong(0),
0861: null);
0862: if (gc == null) {
0863: return null;
0864: }
0865: x11.XSetForeground(dpy, gc, new NativeLong(0));
0866: x11.XFillRectangle(dpy, pm, gc, 0, 0, width, height);
0867: final int UNMASKED = 1;
0868: x11.XSetForeground(dpy, gc, new NativeLong(UNMASKED));
0869: X11.XWindowAttributes atts = new X11.XWindowAttributes();
0870: int status = x11.XGetWindowAttributes(dpy, win, atts);
0871: if (status == 0) {
0872: return null;
0873: }
0874: try {
0875: RasterRangesUtils.outputOccupiedRanges(raster,
0876: new RasterRangesUtils.RangesOutput() {
0877: public boolean outputRange(int x, int y,
0878: int w, int h) {
0879: return x11.XFillRectangle(dpy, pm, gc,
0880: x, y, w, h) != 0;
0881: }
0882: });
0883: } finally {
0884: x11.XFreeGC(dpy, gc);
0885: }
0886: return pm;
0887: }
0888:
0889: private boolean didCheck;
0890: private int[] alphaVisuals = {};
0891:
0892: public boolean isWindowAlphaSupported() {
0893: if (!didCheck) {
0894: didCheck = true;
0895: alphaVisuals = getAlphaVisuals();
0896: }
0897: return alphaVisuals.length > 0;
0898: }
0899:
0900: /** Return the default graphics configuration. */
0901: public GraphicsConfiguration getAlphaCompatibleGraphicsConfiguration() {
0902: if (isWindowAlphaSupported()) {
0903: GraphicsEnvironment env = GraphicsEnvironment
0904: .getLocalGraphicsEnvironment();
0905: GraphicsDevice[] devices = env.getScreenDevices();
0906: for (int i = 0; i < devices.length; i++) {
0907: GraphicsConfiguration[] configs = devices[i]
0908: .getConfigurations();
0909: for (int j = 0; j < configs.length; j++) {
0910: // Use reflection to call
0911: // X11GraphicsConfig.getVisual
0912: try {
0913: Object o = configs[j]
0914: .getClass()
0915: .getMethod("getVisual",
0916: (Class[]) null)
0917: .invoke(configs[j], (Object[]) null);
0918: int visual = ((Integer) o).intValue();
0919: for (int k = 0; k < alphaVisuals.length; k++) {
0920: if (visual == alphaVisuals[k])
0921: return configs[j];
0922: }
0923: } catch (Exception e) {
0924: e.printStackTrace();
0925: }
0926: }
0927: }
0928: }
0929: return super .getAlphaCompatibleGraphicsConfiguration();
0930: }
0931:
0932: /**
0933: * Return the visual ID of the visual which supports an alpha
0934: * channel.
0935: */
0936: private int[] getAlphaVisuals() {
0937: X11 x11 = X11.INSTANCE;
0938: Display dpy = x11.XOpenDisplay(null);
0939: if (dpy == null)
0940: return new int[0];
0941: XVisualInfo info = null;
0942: try {
0943: int screen = x11.XDefaultScreen(dpy);
0944: XVisualInfo template = new XVisualInfo();
0945: template.screen = screen;
0946: template.depth = 32;
0947: template.c_class = X11.TrueColor;
0948: NativeLong mask = new NativeLong(X11.VisualScreenMask
0949: | X11.VisualDepthMask | X11.VisualClassMask);
0950: IntByReference pcount = new IntByReference();
0951: info = x11.XGetVisualInfo(dpy, mask, template, pcount);
0952: if (info != null) {
0953: List list = new ArrayList();
0954: XVisualInfo[] infos = (XVisualInfo[]) info
0955: .toArray(pcount.getValue());
0956: for (int i = 0; i < infos.length; i++) {
0957: XRenderPictFormat format = X11.Xrender.INSTANCE
0958: .XRenderFindVisualFormat(dpy,
0959: infos[i].visual);
0960: if (format.type == X11.Xrender.PictTypeDirect
0961: && format.direct.alphaMask != 0) {
0962: list.add(new Integer(infos[i].visualID));
0963: }
0964: }
0965: int[] ids = new int[list.size()];
0966: for (int i = 0; i < list.size(); i++) {
0967: ids[i] = ((Integer) list.get(i)).intValue();
0968: }
0969: return ids;
0970: }
0971: } finally {
0972: if (info != null) {
0973: x11.XFree(info.getPointer());
0974: }
0975: x11.XCloseDisplay(dpy);
0976: }
0977: return new int[0];
0978: }
0979:
0980: public X11.Window getDrawable(Window w) {
0981: int id = (int) Native.getWindowID(w);
0982: if (id == X11.None)
0983: return null;
0984: return new X11.Window(id);
0985: }
0986:
0987: private static final long OPAQUE = 0xFFFFFFFFL;
0988: private static final String OPACITY = "_NET_WM_WINDOW_OPACITY";
0989:
0990: public void setWindowAlpha(final Window w, final float alpha) {
0991: if (!isWindowAlphaSupported()) {
0992: throw new UnsupportedOperationException(
0993: "This X11 display does not provide a 32-bit visual");
0994: }
0995: Runnable action = new Runnable() {
0996: public void run() {
0997: X11 x11 = X11.INSTANCE;
0998: Display dpy = x11.XOpenDisplay(null);
0999: if (dpy == null)
1000: return;
1001: try {
1002: X11.Window win = getDrawable(w);
1003: if (alpha == 1f) {
1004: x11.XDeleteProperty(dpy, win, x11
1005: .XInternAtom(dpy, OPACITY, false));
1006: } else {
1007: int opacity = (int) ((long) (alpha * OPAQUE) & 0xFFFFFFFF);
1008: IntByReference patom = new IntByReference(
1009: opacity);
1010: x11.XChangeProperty(dpy, win, x11
1011: .XInternAtom(dpy, OPACITY, false),
1012: X11.XA_CARDINAL, 32,
1013: X11.PropModeReplace, patom
1014: .getPointer(), 1);
1015: }
1016: } finally {
1017: x11.XCloseDisplay(dpy);
1018: }
1019: }
1020: };
1021: whenDisplayable(w, action);
1022: }
1023:
1024: private class X11TransparentContent extends JPanel {
1025: private boolean transparent;
1026:
1027: public X11TransparentContent(Container oldContent) {
1028: super (new BorderLayout());
1029: add(oldContent, BorderLayout.CENTER);
1030: setTransparent(true);
1031: if (oldContent instanceof JPanel) {
1032: ((JComponent) oldContent).setOpaque(false);
1033: }
1034: }
1035:
1036: public void setTransparent(boolean transparent) {
1037: this .transparent = transparent;
1038: setOpaque(!transparent);
1039: repaint();
1040: }
1041:
1042: public void paint(Graphics gr) {
1043: if (transparent) {
1044: int w = getWidth();
1045: int h = getHeight();
1046: if (w > 0 && h > 0) {
1047: BufferedImage buf = new BufferedImage(w, h,
1048: BufferedImage.TYPE_INT_ARGB);
1049: Graphics2D g = buf.createGraphics();
1050: g.setComposite(AlphaComposite.Clear);
1051: g.fillRect(0, 0, w, h);
1052: g.setComposite(AlphaComposite.SrcOver);
1053: super .paint(g);
1054: g = (Graphics2D) gr.create();
1055: g.setComposite(AlphaComposite.Src);
1056: g.drawImage(buf, 0, 0, w, h, null);
1057: g.dispose();
1058: }
1059: } else {
1060: super .paint(gr);
1061: }
1062: }
1063: }
1064:
1065: public void setWindowTransparent(final Window w,
1066: final boolean transparent) {
1067: if (!(w instanceof RootPaneContainer)) {
1068: throw new IllegalArgumentException(
1069: "Window must be a RootPaneContainer");
1070: }
1071: if (!isWindowAlphaSupported()) {
1072: throw new UnsupportedOperationException(
1073: "This X11 display does not provide a 32-bit visual");
1074: }
1075: if (!w.getGraphicsConfiguration().equals(
1076: getAlphaCompatibleGraphicsConfiguration())) {
1077: throw new IllegalArgumentException(
1078: "Window GraphicsConfiguration does not support transparency");
1079: }
1080: boolean isTransparent = w.getBackground() != null
1081: && w.getBackground().getAlpha() == 0;
1082: if (!(transparent ^ isTransparent))
1083: return;
1084: whenDisplayable(w, new Runnable() {
1085: public void run() {
1086: JRootPane root = ((RootPaneContainer) w)
1087: .getRootPane();
1088: Container content = root.getContentPane();
1089: if (content instanceof X11TransparentContent) {
1090: ((X11TransparentContent) content)
1091: .setTransparent(transparent);
1092: } else if (transparent) {
1093: root.setContentPane(new X11TransparentContent(
1094: content));
1095: }
1096: setLayersTransparent(w, transparent);
1097: setForceHeavyweightPopups(w, transparent);
1098: }
1099: });
1100: }
1101:
1102: public void setWindowMask(final Window w, final Raster raster) {
1103: Runnable action = new Runnable() {
1104: public void run() {
1105: X11 x11 = X11.INSTANCE;
1106: Xext ext = Xext.INSTANCE;
1107: Display dpy = x11.XOpenDisplay(null);
1108: if (dpy == null)
1109: return;
1110: Pixmap pm = null;
1111: try {
1112: X11.Window win = getDrawable(w);
1113: if (raster == null
1114: || ((pm = createBitmap(dpy, win, w,
1115: raster)) == null)) {
1116: ext.XShapeCombineMask(dpy, win,
1117: X11.Xext.ShapeBounding, 0, 0,
1118: Pixmap.None, X11.Xext.ShapeSet);
1119: } else {
1120: ext.XShapeCombineMask(dpy, win,
1121: X11.Xext.ShapeBounding, 0, 0, pm,
1122: X11.Xext.ShapeSet);
1123: }
1124: } finally {
1125: if (pm != null) {
1126: x11.XFreePixmap(dpy, pm);
1127: }
1128: x11.XCloseDisplay(dpy);
1129: }
1130: setForceHeavyweightPopups(w, raster != null);
1131: }
1132: };
1133: whenDisplayable(w, action);
1134: }
1135: }
1136:
1137: /**
1138: * Applies the given mask to the given window. Does nothing if the
1139: * operation is not supported. The mask is treated as a bitmap and
1140: * ignores transparency.
1141: */
1142: public static void setWindowMask(Window w, Shape mask) {
1143: getInstance().setWindowMask(w, mask);
1144: }
1145:
1146: /**
1147: * Applies the given mask to the given window. Does nothing if the
1148: * operation is not supported. The mask is treated as a bitmap and
1149: * ignores transparency.
1150: */
1151: public static void setWindowMask(Window w, Icon mask) {
1152: getInstance().setWindowMask(w, mask);
1153: }
1154:
1155: /** Indicate a window can have a global alpha setting. */
1156: public static boolean isWindowAlphaSupported() {
1157: return getInstance().isWindowAlphaSupported();
1158: }
1159:
1160: /**
1161: * Returns a {@link GraphicsConfiguration} comptible with alpha
1162: * compositing.
1163: */
1164: public static GraphicsConfiguration getAlphaCompatibleGraphicsConfiguration() {
1165: return getInstance().getAlphaCompatibleGraphicsConfiguration();
1166: }
1167:
1168: /**
1169: * Set the overall window transparency. An alpha of 1.0 is fully
1170: * opaque, 0.0 fully transparent. The alpha level is applied equally
1171: * to all window pixels.<p>
1172: * NOTE: Windows requires that <code>sun.java2d.noddraw=true</code>
1173: * in order for alpha to work.
1174: */
1175: public static void setWindowAlpha(Window w, float alpha) {
1176: try {
1177: getInstance().setWindowAlpha(w,
1178: Math.max(0f, Math.min(alpha, 1f)));
1179: } catch (ThreadDeath td) {
1180: throw td;
1181: } catch (Throwable e) {
1182:
1183: }
1184: }
1185:
1186: /**
1187: * Set the window to be transparent. Only explicitly painted pixels
1188: * will be non-transparent. All pixels will be composited with
1189: * whatever is under the window using their alpha values.
1190: */
1191: public static void setWindowTransparent(Window w,
1192: boolean transparent) {
1193: getInstance().setWindowTransparent(w, transparent);
1194: }
1195: }
|