United StatesChange Country, Oracle Worldwide Web Sites Communities I am a... I want to...
Bug ID: JDK-6776743 Lightweight components must be counted as opaque rectangles for the purposes of hw/lw mixing
JDK-6776743 : Lightweight components must be counted as opaque rectangles for the purposes of hw/lw mixing

Details
Type:
Bug
Submit Date:
2008-11-26
Status:
Closed
Updated Date:
2011-01-26
Project Name:
JDK
Resolved Date:
2009-05-15
Component:
client-libs
OS:
generic
Sub-Component:
java.awt
CPU:
generic
Priority:
P2
Resolution:
Fixed
Affected Versions:
6u12
Fixed Versions:
6u14 (b01)

Related Reports
Relates:
Relates:
Relates:
Relates:

Sub Tasks

Description
The HW/LW Mixing feature is designed so that it considers the 'opaque' property of lightweight components in order to decide whether these components should affect heavyweight components underneath the lightweights. The logic is simple: if the component is opaque, then its shape (a rectangle) gets cut off of the heavyweights. If it isn't opaque, the component does not affect the shapes of heavyweights. This made it possible to correctly handle, say, the GlassPane-like components which themselves are non-opaque and cover the whole area of the frame. If we were to cut off the shape of the GlassPane of the heavyweights, we wouldn't see any heavyweight components in a JFrame with the hw/lw mixing feature enabled.

However, this led to ignoring the opaque children of non-opaque containers which has been fixed with 6637655. Also there were some minor issues identified with JInternalFrame's that were fixed with 6768332.

Currently we run into even bigger problem: modern L&Fs turn almost every lightweight component to non-opaque in order to render their rounded corners. This makes the components non-mixable with heavyweight components breaking the hw/lw mixing feature completely.

                                    

Comments
EVALUATION

To resolve the problem stated in the Description we should:

1. Consider lightweight components as opaque rectangles for the purposes of hw/lw mixing. I.e. we should ignore the traditional 'opaque' property of components. This, of course, will look a little weird with, say, the rounded corners (leaving them sort of unrendered), but it seems way better than the behavior w/o the hw/lw mixing at all when the component is non-opaque.

2. Provide an API that would allow marking specific components as non-opaque-for-mixing (like the GlassPane's, PopupPane's, etc.) If this mark is set, the hw/lw mixing code will consider this component as a transparent one (but still traversing its descendants if they're opaque, of course).

3. Provide a new system property (like sun.awt.disableMixing) to disable the hw/lw mixing feature.


#1 by itself will make the GlassPane cover all the hw's, hence effectively hiding them by emptying their shapes. With #2 we'll be able to mark the GlassPane and the PopupPane that Swing creates by default as non-opaque eliminating this problem for regular applications. 

However some developers install custom GlassPane's in order to draw some fancy effects (e.g. bluring the content of the frame to indicate it's currently inactive). These custom GlassPane's won't be marked as non-opaque-for-mixing by default, and therefore, once shown, will hide all heavyweight components on the frame. This is actually a regression. We believe that mixing of hw and lw components is a rare case (since it's being said in every possible manual that users should not mix different components unless they know what they're doing), and therefore the number of such applications is considered small. However the developer of an affected application will then have a choice to fix the regression:

1. The developer may use the new API to mark their GlassPane, and make the application behave normally.

2. Provide the end-users with the system property that will disable the hw/lw mixing, and the problem will go away as well.


A possible API is as follows:

   public static void com.sun.awt.AWTUtilities.setComponentNonOpaqueForMixing(Component c, boolean nonOpaque);
   public static boolean com.sun.awt.AWTUtilities.isComponentNonOpaqueForMixing(Component c);

The part "NonOpaqueForMixing" should probably get a better name. We could postpone finding a better name till the API becomes officially public (maybe in JDK 7).


For the future implementations we could aslo consider introducing some kind of component-level shapes that would allow L&F developers to assign specific shapes they want their components to look like. This way we will eliminate a bit of ugliness with the rounded corners not being rendered with the currently proposed solution.
                                     
2008-11-26
SUGGESTED FIX

--- old/src/share/classes/com/sun/awt/AWTUtilities.java	2008-12-24 17:11:22.000000000 +0300
+++ new/src/share/classes/com/sun/awt/AWTUtilities.java	2008-12-24 17:11:22.000000000 +0300
@@ -28,6 +28,7 @@
  * <li>Setting a constant alpha value for each pixel of a top-level window
  * <li>Making a window non-opaque, after that it paints only explicitly 
  * painted pixels on the screen, with arbitrary alpha values for every pixel.
+ * <li>Tagging a component as 'non-opaque-for-mixing'.
  * </ul>
  * A "top-level window" is an instance of the {@code Window} class (or its
  * descendant, such as {@code JFrame}).
@@ -441,5 +442,55 @@
         }
         return ((SunToolkit)curToolkit).isTranslucencyCapable(gc);
     }
+
+    /**
+     * Tags a component as 'non-opaque-for-mixing'.
+     *
+     * A lightweight component tagged as 'non-opaque-for-mixing' does not
+     * affect the shapes of heavyweight components positioned underneath the
+     * lightweight component in the z-order.  Note that descendants of the
+     * lightweight component still affect the shapes of heavyweight components,
+     * in other words, the tag value is not inherited by descendant components.
+     * <p>
+     * The most common example when this tag is needed is a glass pane
+     * component. The default glass pane created by the {@code JRootPane} class
+     * is tagged as 'non-opaque-for-mixing' by default. If a developer creates
+     * a custom component and installs it as a glass pane for a frame, the
+     * component must be tagged as 'non-opaque-for-mixing' explicitly by the
+     * developer. Otherwise, the heavyweight components in the frame may behave
+     * incorrectly.
+     *
+     * @param component the component that needs to be tagged
+     * @param nonOpaque whether the component should be 'non-opaque-for-mixing'
+     * @throws NullPointerException if the component argument is {@code null}
+     */
+    public static void setComponentNonOpaqueForMixing(Component component,
+            boolean nonOpaque)
+    {
+        if (component == null) {
+            throw new NullPointerException(
+                    "The component argument should not be null.");
+        }
+
+        AWTAccessor.getComponentAccessor().setNonOpaqueForMixing(component,
+                nonOpaque);
+    }
+
+    /**
+     * Indicates whether a component is tagged as 'non-opaque-for-mixing'.
+     *
+     * @param component the component that needs to be tagged
+     * @throws NullPointerException if the component argument is {@code null}
+     * @see #setComponentNonOpaqueForMixing
+     */
+    public static boolean isComponentNonOpaqueForMixing(Component component) {
+        if (component == null) {
+            throw new NullPointerException(
+                    "The component argument should not be null.");
+        }
+
+        return AWTAccessor.getComponentAccessor().
+            isNonOpaqueForMixing(component);
+    }
 }
 
--- old/src/share/classes/java/awt/Component.java	2008-12-24 17:11:22.000000000 +0300
+++ new/src/share/classes/java/awt/Component.java	2008-12-24 17:11:22.000000000 +0300
@@ -771,19 +771,11 @@
      */
     private transient boolean isAddNotifyComplete = false;
 
-    /* Indicates whether this component should be considered opaque by
-     * the HW/LW Mixing code. If it's true, the component is considered opaque.
-     * Otherwise the mixing code uses the isOpaque() method to determine
-     * this property.
-     */
-    private transient boolean isOpaqueForMixing = false;
-
-    private static final PropertyChangeListener opaquePropertyChangeListener = 
-        new PropertyChangeListener() {
-            public void propertyChange(java.beans.PropertyChangeEvent evt) {
-                ((Component)evt.getSource()).mixOnOpaqueChanging();
-            }
-        };
+    /* Indicates whether this component should be considered non-opaque by
+     * the HW/LW Mixing code. If it's true, the component is considered
+     * non-opaque, and therefore does not affect the shape of HW components.
+     */
+    private transient boolean isNonOpaqueForMixing = false;
 
     /**
      * Should only be used in subclass getBounds to check that part of bounds
@@ -821,9 +813,21 @@
                     return comp.backgroundEraseDisabled;
                 }
 
-                public void setOpaqueForMixing(Component comp, boolean opaque) {
-                    comp.isOpaqueForMixing = opaque;
+                public void setNonOpaqueForMixing(Component comp, boolean nonOpaque) {
+                    if (nonOpaque != comp.isNonOpaqueForMixing) {
+                        comp.isNonOpaqueForMixing = nonOpaque;
+                        if (comp.isNonOpaqueForMixing) {
+                            comp.mixOnHiding(comp.isLightweight());
+                        } else {
+                            comp.mixOnShowing();
+                        }
+                    }
                 }
+
+                public boolean isNonOpaqueForMixing(Component comp) {
+                    return comp.isNonOpaqueForMixing;
+                }
+
                 public Rectangle getBounds(Component comp) {
                     return new Rectangle(comp.x, comp.y, comp.width, comp.height);
                 }
@@ -6641,7 +6645,6 @@
             }
                 
             if (!isAddNotifyComplete) { 
-                addPropertyChangeListener("opaque", opaquePropertyChangeListener);
                 mixOnShowing();
             }
             
@@ -6738,7 +6741,6 @@
                 p.dispose();
                 
                 mixOnHiding(isLightweight);
-                removePropertyChangeListener("opaque", opaquePropertyChangeListener);
                 
                 isAddNotifyComplete = false;
                 // Nullifying compoundShape means that the component has normal shape
@@ -9588,9 +9590,9 @@
                     this.compoundShape = null;
                     peer.applyShape(null);
                 } else {
-		    if (shape.equals(getAppliedShape())) {
-			return;
-		    }
+                    if (shape.equals(getAppliedShape())) {
+                        return;
+                    }
                     this.compoundShape = shape;
                     Point compAbsolute = getLocationOnWindow();
                     if (mixingLog.isLoggable(Level.FINER)) {
@@ -9659,10 +9661,10 @@
      */
     Region getOpaqueShape() {
         checkTreeLock();
-        if (isOpaqueForMixing()) {
-            return getNormalShape();
-        } else {
+        if (isNonOpaqueForMixing()) {
             return Region.getInstanceXYWH(0, 0, 0, 0);
+        } else {
+            return getNormalShape();
         }
     }
 
@@ -9690,8 +9692,8 @@
         return nextBelow >= parent.getComponentCount() ? -1 : nextBelow;
     }
 
-    final boolean isOpaqueForMixing() {
-        return isOpaqueForMixing || isOpaque();
+    final boolean isNonOpaqueForMixing() {
+        return isNonOpaqueForMixing;
     }
 
     private Region calculateCurrentShape() {
@@ -9862,42 +9864,32 @@
         }
     }
 
-    void mixOnOpaqueChanging() {
-        synchronized (getTreeLock()) {
-            if (mixingLog.isLoggable(Level.FINE)) {
-                mixingLog.fine("this = " + this);
-            }
-            if (!isMixingNeeded()) {
-                return;
-            }
-            if (isOpaqueForMixing()) {
-                mixOnShowing();
-            } else {
-                mixOnHiding(isLightweight());
-            }
-        }
-    }
-
     void mixOnValidating() {
         // This method gets overriden in the Container. Obviously, a plain
         // non-container components don't need to handle validation.
     }
 
     final boolean isMixingNeeded() {
+        if (SunToolkit.getSunAwtDisableMixing()) {
+            if (mixingLog.isLoggable(Level.FINE)) {
+                mixingLog.fine("this = " + this + "; Mixing disabled via sun.awt.disableMixing");
+            }
+            return false;
+        }
         if (!areBoundsValid()) {
             if (mixingLog.isLoggable(Level.FINE)) {
                 mixingLog.fine("this = " + this + "; areBoundsValid = " + areBoundsValid());
             }
             return false;
         }
-	Window window = getContainingWindow();
+        Window window = getContainingWindow();
         if (window != null) {
             if (!window.hasHeavyweightDescendants() || !window.hasLightweightDescendants()) {
-		if (mixingLog.isLoggable(Level.FINE)) {
-		    mixingLog.fine("containing window = " + window +
-                        "; has h/w descendants = " + window.hasHeavyweightDescendants() +
-                        "; has l/w descendants = " + window.hasLightweightDescendants());
-		}
+                if (mixingLog.isLoggable(Level.FINE)) {
+                    mixingLog.fine("containing window = " + window +
+                            "; has h/w descendants = " + window.hasHeavyweightDescendants() +
+                            "; has l/w descendants = " + window.hasLightweightDescendants());
+                }
                 return false;
             }
         }
--- old/src/share/classes/java/awt/Container.java	2008-12-24 17:11:23.000000000 +0300
+++ new/src/share/classes/java/awt/Container.java	2008-12-24 17:11:23.000000000 +0300
@@ -3913,7 +3913,7 @@
     @Override
     final Region getOpaqueShape() {
         checkTreeLock();
-        if (isLightweight() && !isOpaqueForMixing()
+        if (isLightweight() && isNonOpaqueForMixing()
                 && hasLightweightDescendants())
         {
             Region s = Region.getInstanceXYWH(0, 0, 0, 0);
@@ -4057,7 +4057,7 @@
                 recursiveApplyCurrentShape();
             }
 
-            if (isLightweight() && !isOpaqueForMixing()) {
+            if (isLightweight() && isNonOpaqueForMixing()) {
                 Container parent = getContainer();
                 if (parent != null && isShowing()) {
                     parent.recursiveSubtractAndApplyShape(getOpaqueShape(), getSiblingIndexBelow());
--- old/src/share/classes/javax/swing/JInternalFrame.java	2008-12-24 17:11:24.000000000 +0300
+++ new/src/share/classes/javax/swing/JInternalFrame.java	2008-12-24 17:11:24.000000000 +0300
@@ -324,7 +324,6 @@
     public JInternalFrame(String title, boolean resizable, boolean closable, 
                                 boolean maximizable, boolean iconifiable) {
         
-        AWTAccessor.getComponentAccessor().setOpaqueForMixing(this, true);
         setRootPane(createRootPane());
         setLayout(new BorderLayout());
         this.title = title;
--- old/src/share/classes/javax/swing/JPopupMenu.java	2008-12-24 17:11:24.000000000 +0300
+++ new/src/share/classes/javax/swing/JPopupMenu.java	2008-12-24 17:11:24.000000000 +0300
@@ -166,7 +166,6 @@
      * for the popup menu.
      */
     public JPopupMenu(String label) {
-        AWTAccessor.getComponentAccessor().setOpaqueForMixing(this, true);
         this.label = label;
         lightWeightPopup = getDefaultLightWeightPopupEnabled();
         setSelectionModel(new DefaultSingleSelectionModel());
--- old/src/share/classes/javax/swing/JRootPane.java	2008-12-24 17:11:24.000000000 +0300
+++ new/src/share/classes/javax/swing/JRootPane.java	2008-12-24 17:11:24.000000000 +0300
@@ -16,6 +16,7 @@
 import java.util.Vector;
 import java.io.Serializable;
 import javax.swing.border.*;
+import sun.awt.AWTAccessor;
 import sun.security.action.GetBooleanAction;
 
 
@@ -525,6 +526,7 @@
       */
     protected Component createGlassPane() {
         JComponent c = new JPanel();
+        AWTAccessor.getComponentAccessor().setNonOpaqueForMixing(c, true);
         c.setName(this.getName()+".glassPane");
         c.setVisible(false);
         ((JPanel)c).setOpaque(false);
--- old/src/share/classes/sun/awt/AWTAccessor.java	2008-12-24 17:11:24.000000000 +0300
+++ new/src/share/classes/sun/awt/AWTAccessor.java	2008-12-24 17:11:24.000000000 +0300
@@ -91,11 +91,17 @@
         boolean getBackgroundEraseDisabled(Component comp);
 
         // See 6768307 and 6768332 for details.
-        /** Sets whether this component should be considered as opaque
+        // Also see 6776743.
+        /** Sets whether this component should be considered as non-opaque
          * by the HW/LW Mixing code. This is needed to workaround special
-         * cases like JInternalFrame and JPopupMenu.
+         * cases like GlassPane or PopupPane.
          */
-        void setOpaqueForMixing(Component comp, boolean opaque);
+        void setNonOpaqueForMixing(Component comp, boolean nonOpaque);
+
+        /** Indicates whether a component is tagged as
+         * 'non-opaque-for-mixing'.
+         */
+        boolean isNonOpaqueForMixing(Component comp);
 
         /**
          * Gets the bounds of this component in the form of a
--- old/src/share/classes/sun/awt/SunToolkit.java	2008-12-24 17:11:25.000000000 +0300
+++ new/src/share/classes/sun/awt/SunToolkit.java	2008-12-24 17:11:25.000000000 +0300
@@ -1960,6 +1960,14 @@
     }
 
     /**
+     * Returns the value of "sun.awt.disableMixing" property. Default
+     * value is {@code false}.
+     */
+    public static boolean getSunAwtDisableMixing() {
+        return AccessController.doPrivileged(new GetBooleanAction("sun.awt.disableMixing"));
+    }
+
+    /**
      * Returns whether or not a containing top level window for the passed
      * component is
      * {@link com.sun.awt.AWTUtilities.Translucency#PERPIXEL_TRANSLUCENT PERPIXEL_TRANSLUCENT}.
--- old/test/java/awt/Mixing/OpaqueTest.java	2008-12-24 17:11:25.000000000 +0300
+++ new/test/java/awt/Mixing/OpaqueTest.java	2008-12-24 17:11:25.000000000 +0300
@@ -19,6 +19,7 @@
 import java.awt.event.*;
 import javax.swing.*;
 import test.java.awt.regtesthelpers.Util;
+import com.sun.awt.AWTUtilities;
 
 
 
@@ -99,10 +100,10 @@
         // flag value.
         for (int i = 0; i < 9; ++i) {
             if (i == 3) {
-                light.setOpaque(false);
+                AWTUtilities.setComponentNonOpaqueForMixing(light, true);
             }
             if (i == 6) {
-                light.setOpaque(true);
+                AWTUtilities.setComponentNonOpaqueForMixing(light, false);
             }
 
             robot.mousePress(InputEvent.BUTTON1_MASK);
                                     
2008-12-24
EVALUATION

Actually we can automatically tag the user-supplied glass panes with the 'non-opaque-for-mixing' property in the setGlassPane() method of the root pane. This way we avoid introducing a regression.
                                     
2009-01-13



Hardware and Software, Engineered to Work Together