JDK-6779670 : Recursive procedures in the HW/LW Mixing code must traverse parent containers
  • Type: Bug
  • Component: client-libs
  • Sub-Component: java.awt
  • Affected Version: 6u12
  • Priority: P2
  • Status: Closed
  • Resolution: Fixed
  • OS: generic,windows_xp
  • CPU: generic,x86
  • Submitted: 2008-12-03
  • Updated: 2011-01-19
  • Resolved: 2009-05-15
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 6 JDK 7
6u14 b01Fixed 7Resolved
Related Reports
Duplicate :  
Relates :  
Description
Since the 6637655 (Mixing of heavyweight/lightweight components does not wrk with GlassPane childre) has been fixed, the hw/lw mixing now takes into account non-opaque containers that contain opaque children when traversing the component hierarchy upwards to calculate the current shape (see Component.calculateCurrentShape()).

However some cases still do not work. Consider there's an opaque lightweight component that shows up in a non-opaque container. Currently the mixOnShowing() method will take the parent of the showing component (i.e. its non-opaque container) and invoke the recursiveSubtractAndApply() method for that container. Actually, after this operation is complete, the method must also get the parent of the container and do the same operation for all of the components that are placed below the container in the z-order. Otherwise the shown component may not affect the shape of heavyweight components in other containers. The following test demosntrates the bug:

import javax.swing.*;
import java.awt.*;

public class MyTest {

    private static void createGui() {
        final JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        frame.setLayout(null);
        final Button button = new Button("AWT Button");
	button.setBounds(100,100,100,100);
        frame.add(button);

        frame.getGlassPane().setVisible(true);
        Container glassPane = (Container) frame.getGlassPane();
        glassPane.setLayout(null);
        final JButton jbutton = new JButton("JButton");
        jbutton.setBounds(50,50,100,100);
        glassPane.add(jbutton);
	(new Thread(){
		public void run(){
				try{
  				 Thread.sleep(3000);
		  		 jbutton.setVisible(true);
				 Thread.sleep(3000);
				 jbutton.setBounds(50,50,110,110);
			}catch(Exception e){}
		}
	}).start();

	jbutton.setVisible(false);
        frame.setSize(400, 400);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    public static void main(String[] args) throws Exception {
        SwingUtilities.invokeAndWait(new Runnable() {
            public void run() {
                MyTest.createGui();
            }
        });
    }
} 

Actually the shape of the lightweight button in the GlassPane must be cut off of the heavyweight AWT button, but in the current implementation it does not. This is a bug.

Comments
SUGGESTED FIX --- old/src/share/classes/java/awt/Component.java 2008-12-10 17:23:04.000000000 +0300 +++ new/src/share/classes/java/awt/Component.java 2008-12-10 17:23:04.000000000 +0300 @@ -9585,9 +9585,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)) { @@ -9728,7 +9728,7 @@ cont = cont.getContainer(); } } - + if (mixingLog.isLoggable(Level.FINE)) { mixingLog.fine("currentShape=" + s); } @@ -9760,6 +9760,44 @@ applyCompoundShape(getAppliedShape().getDifference(s)); } + private final void applyCurrentShapeBelowMe() { + checkTreeLock(); + Container parent = getContainer(); + if (parent != null) { + // First, reapply shapes of my siblings + parent.recursiveApplyCurrentShape(getSiblingIndexBelow()); + + // Second, if my container is non-opaque, reapply shapes of siblings of my container + Container parent2 = parent.getContainer(); + while (!parent.isOpaque() && parent2 != null && parent.isShowing()) { + parent2.recursiveApplyCurrentShape(parent.getSiblingIndexBelow()); + + parent = parent2; + parent2 = parent.getContainer(); + } + } + } + + final void subtractAndApplyShapeBelowMe() { + checkTreeLock(); + Container parent = getContainer(); + if (parent != null && isShowing()) { + Region opaqueShape = getOpaqueShape(); + + // First, cut my siblings + parent.recursiveSubtractAndApplyShape(opaqueShape, getSiblingIndexBelow()); + + // Second, if my container is non-opaque, cut siblings of my container + Container parent2 = parent.getContainer(); + while (!parent.isOpaque() && parent2 != null && parent.isShowing()) { + parent2.recursiveSubtractAndApplyShape(opaqueShape, parent.getSiblingIndexBelow()); + + parent = parent2; + parent2 = parent.getContainer(); + } + } + } + void mixOnShowing() { synchronized (getTreeLock()) { if (mixingLog.isLoggable(Level.FINE)) { @@ -9769,10 +9807,7 @@ return; } if (isLightweight()) { - Container parent = getContainer(); - if (parent != null && isShowing()) { - parent.recursiveSubtractAndApplyShape(getOpaqueShape(), getSiblingIndexBelow()); - } + subtractAndApplyShapeBelowMe(); } else { applyCurrentShape(); } @@ -9790,11 +9825,8 @@ return; } if (isLightweight) { - Container parent = getContainer(); - if (parent != null) { - parent.recursiveApplyCurrentShape(getSiblingIndexBelow()); - } - } //XXX: else applyNormalShape() ??? + applyCurrentShapeBelowMe(); + } } } @@ -9807,10 +9839,7 @@ return; } if (isLightweight()) { - Container parent = getContainer(); - if (parent != null) { - parent.recursiveApplyCurrentShape(parent.getComponentZOrder(this)); - } + applyCurrentShapeBelowMe(); } else { applyCurrentShape(); } @@ -9826,9 +9855,9 @@ mixingLog.fine("this = " + this + "; oldZorder=" + oldZorder + "; newZorder=" + newZorder + "; parent=" + parent); } - if (!isMixingNeeded()) { - return; - } + if (!isMixingNeeded()) { + return; + } if (isLightweight()) { if (becameHigher) { if (parent != null && isShowing()) { @@ -9839,7 +9868,7 @@ parent.recursiveApplyCurrentShape(oldZorder, newZorder); } } - } else { + } else { if (becameHigher) { applyCurrentShape(); } else { @@ -9887,14 +9916,14 @@ } 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-10 17:23:04.000000000 +0300 +++ new/src/share/classes/java/awt/Container.java 2008-12-10 17:23:04.000000000 +0300 @@ -4002,6 +4002,7 @@ } } + @Override void mixOnShowing() { synchronized (getTreeLock()) { if (mixingLog.isLoggable(Level.FINE)) { @@ -4022,6 +4023,26 @@ } } + @Override + void mixOnReshaping() { + synchronized (getTreeLock()) { + if (mixingLog.isLoggable(Level.FINE)) { + mixingLog.fine("this = " + this); + } + + if (!isMixingNeeded()) { + return; + } + + if (isLightweight() && hasHeavyweightDescendants()) { + recursiveApplyCurrentShape(); + } + + super.mixOnReshaping(); + } + } + + @Override void mixOnZOrderChanging(int oldZorder, int newZorder) { synchronized (getTreeLock()) { if (mixingLog.isLoggable(Level.FINE)) { @@ -4058,10 +4079,7 @@ } if (isLightweight() && !isOpaqueForMixing()) { - Container parent = getContainer(); - if (parent != null && isShowing()) { - parent.recursiveSubtractAndApplyShape(getOpaqueShape(), getSiblingIndexBelow()); - } + subtractAndApplyShapeBelowMe(); } super.mixOnValidating(); --- /dev/null 2008-10-20 20:26:54.048429000 +0400 +++ new/test/java/awt/Mixing/JButtonInGlassPane.java 2008-12-10 17:23:05.000000000 +0300 @@ -0,0 +1,407 @@ +/* + @test %W% %E% + @bug 6779670 + @summary Tests if a LW components in the glass pane affects HW in the content pane + @author anthony.petrov@...: area=awt.mixing + @library ../regtesthelpers + @build Util + @run main JButtonInGlassPane +*/ + + +/** + * JButtonInGlassPane.java + * + * summary: Tests whether a LW menu correctly overlaps a HW button + */ + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import test.java.awt.regtesthelpers.Util; + + + +public class JButtonInGlassPane +{ + static volatile boolean failed = false; + + private static void init() + { + //*** Create instructions for the user here *** + + String[] instructions = + { + "This is an AUTOMATIC test, simply wait until it is done.", + "The result (passed or failed) will be shown in the", + "message window below." + }; + Sysout.createDialog( ); + Sysout.printInstructions( instructions ); + + JFrame frame = new JFrame("Glass Pane children test"); + frame.setLayout(null); + + final Button button = new Button("AWT Button"); + button.setBounds(100,100,100,100); + frame.add(button); + + button.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + failed = true; + } + }); + + frame.getGlassPane().setVisible(true); + Container glassPane = (Container) frame.getGlassPane(); + glassPane.setLayout(null); + + final JButton jbutton = new JButton("JButton"); + jbutton.setBounds(50,50,100,100); + glassPane.add(jbutton); + + jbutton.setVisible(false); + + frame.setSize(400, 400); + frame.setLocationRelativeTo(null); + frame.setVisible(true); + + Robot robot = Util.createRobot(); + robot.setAutoDelay(20); + + Util.waitForIdle(robot); + + jbutton.setVisible(true); + Util.waitForIdle(robot); + + // Click the LW button - in the area that intersects with + // the HW button. + Point lLoc = jbutton.getLocationOnScreen(); + robot.mouseMove(lLoc.x + jbutton.getWidth() - 5, lLoc.y + jbutton.getHeight() - 5); + + robot.mousePress(InputEvent.BUTTON1_MASK); + robot.mouseRelease(InputEvent.BUTTON1_MASK); + Util.waitForIdle(robot); + + jbutton.setBounds(50,50,120,120); + Util.waitForIdle(robot); + + // Now click on the 'added' area of the LW button that again + // intersects with the HW. + robot.mouseMove(lLoc.x + jbutton.getWidth() - 5, lLoc.y + jbutton.getHeight() - 5); + + robot.mousePress(InputEvent.BUTTON1_MASK); + robot.mouseRelease(InputEvent.BUTTON1_MASK); + Util.waitForIdle(robot); + + if (failed) { + JButtonInGlassPane.fail("The LW button did not receive the click."); + } else { + JButtonInGlassPane.pass(); + } + }//End init() + + + + /***************************************************** + * Standard Test Machinery Section + * DO NOT modify anything in this section -- it's a + * standard chunk of code which has all of the + * synchronisation necessary for the test harness. + * By keeping it the same in all tests, it is easier + * to read and understand someone else's test, as + * well as insuring that all tests behave correctly + * with the test harness. + * There is a section following this for test- + * classes + ******************************************************/ + private static boolean theTestPassed = false; + private static boolean testGeneratedInterrupt = false; + private static String failureMessage = ""; + + private static Thread mainThread = null; + + private static int sleepTime = 300000; + + // Not sure about what happens if multiple of this test are + // instantiated in the same VM. Being static (and using + // static vars), it aint gonna work. Not worrying about + // it for now. + public static void main( String args[] ) throws InterruptedException + { + mainThread = Thread.currentThread(); + try + { + init(); + } + catch( TestPassedException e ) + { + //The test passed, so just return from main and harness will + // interepret this return as a pass + return; + } + //At this point, neither test pass nor test fail has been + // called -- either would have thrown an exception and ended the + // test, so we know we have multiple threads. + + //Test involves other threads, so sleep and wait for them to + // called pass() or fail() + try + { + Thread.sleep( sleepTime ); + //Timed out, so fail the test + throw new RuntimeException( "Timed out after " + sleepTime/1000 + " seconds" ); + } + catch (InterruptedException e) + { + //The test harness may have interrupted the test. If so, rethrow the exception + // so that the harness gets it and deals with it. + if( ! testGeneratedInterrupt ) throw e; + + //reset flag in case hit this code more than once for some reason (just safety) + testGeneratedInterrupt = false; + + if ( theTestPassed == false ) + { + throw new RuntimeException( failureMessage ); + } + } + + }//main + + public static synchronized void setTimeoutTo( int seconds ) + { + sleepTime = seconds * 1000; + } + + public static synchronized void pass() + { + Sysout.println( "The test passed." ); + Sysout.println( "The test is over, hit Ctl-C to stop Java VM" ); + //first check if this is executing in main thread + if ( mainThread == Thread.currentThread() ) + { + //Still in the main thread, so set the flag just for kicks, + // and throw a test passed exception which will be caught + // and end the test. + theTestPassed = true; + throw new TestPassedException(); + } + theTestPassed = true; + testGeneratedInterrupt = true; + mainThread.interrupt(); + }//pass() + + public static synchronized void fail() + { + //test writer didn't specify why test failed, so give generic + fail( "it just plain failed! :-)" ); + } + + public static synchronized void fail( String whyFailed ) + { + Sysout.println( "The test failed: " + whyFailed ); + Sysout.println( "The test is over, hit Ctl-C to stop Java VM" ); + //check if this called from main thread + if ( mainThread == Thread.currentThread() ) + { + //If main thread, fail now 'cause not sleeping + throw new RuntimeException( whyFailed ); + } + theTestPassed = false; + testGeneratedInterrupt = true; + failureMessage = whyFailed; + mainThread.interrupt(); + }//fail() + +}// class JButtonInGlassPane + +//This exception is used to exit from any level of call nesting +// when it's determined that the test has passed, and immediately +// end the test. +class TestPassedException extends RuntimeException +{ +} + +//*********** End Standard Test Machinery Section ********** + + +//************ Begin classes defined for the test **************** + +// if want to make listeners, here is the recommended place for them, then instantiate +// them in init() + +/* Example of a class which may be written as part of a test +class NewClass implements anInterface + { + static int newVar = 0; + + public void eventDispatched(AWTEvent e) + { + //Counting events to see if we get enough + eventCount++; + + if( eventCount == 20 ) + { + //got enough events, so pass + + JButtonInGlassPane.pass(); + } + else if( tries == 20 ) + { + //tried too many times without getting enough events so fail + + JButtonInGlassPane.fail(); + } + + }// eventDispatched() + + }// NewClass class + +*/ + + +//************** End classes defined for the test ******************* + + + + +/**************************************************** + Standard Test Machinery + DO NOT modify anything below -- it's a standard + chunk of code whose purpose is to make user + interaction uniform, and thereby make it simpler + to read and understand someone else's test. + ****************************************************/ + +/** + This is part of the standard test machinery. + It creates a dialog (with the instructions), and is the interface + for sending text messages to the user. + To print the instructions, send an array of strings to Sysout.createDialog + WithInstructions method. Put one line of instructions per array entry. + To display a message for the tester
10-12-2008

EVALUATION Either the mixOnShowing() (and some companions) or the Container.recursive*() methods must check if the container of a lightweight component is non-opaque, and if this is true, they must go one level down in the hierarchy and start the same recursive task.
03-12-2008