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);
|
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.
|