Hello Martin

Your observation is correct,
Swing doesn't count a button as rollovered
if it was disabled and enabled again
(actually I haven't seen a GUI library
which is ready for such situations)

This problem very rare affects anybody
so I'd see it as a Swing limitation
(however limitation is a bit too strong word for this case)

While it is definitely possible to fix this bug
I doubt it worth doing, because in this case
we'll have to fix tons of other cases when a component
was moved/disabled/hidden and shown again
which is not reasonable

Here is a workaround for your test case:

    private void disableButtonTemporarily() {
        button.setEnabled(false);
        Timer timer = new Timer(1000, new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                button.setEnabled(true);
                Point l = MouseInfo.getPointerInfo().getLocation();
                Point buttonLocationToScreen = new Point();

SwingUtilities.convertPointToScreen(buttonLocationToScreen, button);
if (new Rectangle(buttonLocationToScreen, button.getSize()).contains(l)) {
                    button.getModel().setRollover(true);
                }
            }
        });
        timer.setRepeats(false);
        timer.start();
    }


Thanks
alexp


Hi,

Sorry to intrude without a proper introduction. I just want some insight before reporting a possible bug.

A friend of mine asked me to help him with a problem he has with a swing app. In this app some buttons are disabled for a short period of time, after which, if the user has not moved the mouse, the button doesn't show the rollover state after becoming enabled again. I tested this on Java6 update 3, using this quick&dirty test:

------------

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

public class RolloverTest extends JFrame {
    private JButton button;

    public static void main(final String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                new RolloverTest();
            }
        });
    }

    public RolloverTest() {
        super("Rollover Test");
        createButton();
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        pack();
        setVisible(true);
    }

    private void createButton() {
        button = new JButton("Press me");
        button.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                disableButtonTemporarily();
            }
        });
        add(button);
    }

    private void disableButtonTemporarily() {
        button.setEnabled(false);
        Timer timer = new Timer(1000, new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                button.setEnabled(true);
            }
        });
        timer.setRepeats(false);
        timer.start();
    }
}

-----------

Sorry I couldn't test it on the latest codebase, I don't have an environment set up for that purpose. Please check if you can that this also happens with later versions of Swing or the JDK.

Also, I did some digging in the source at openjdk to see what could be the problem. Since it happens not only with JButton, but also to JMenuItem, JCheckBox, etc, at first I thought that the problem might be in the DefaultButtonModel, that the flag was simply lost when the button is disabled. But apparently DefaultButtonModel.setEnabled() unsets other flags but not the rollover state, although DefaultButtonModel.setRollover() prevents changes to the rollover flag if the button is disabled. In consequence, I couldn't manage a workaround by providing a different ButtonModel.

It seems that AbstractButton.setEnabled() forces the rollover state to change before disabling the button. There's no comment on the source as to why this state change is handled by AbstractButton and not by the ButtonModel, as happens with the other states. Is there a reason why AbstractButton.setEnabled() has to handle the rollover and not ButtonModel.setEnabled()? It seems that this is due to the order of how event propagation is to occur, but I'm uncertain.

----------

setEnabled in AbstractButton:

    public void setEnabled(boolean b) {
        if (!b && model.isRollover()) {
            model.setRollover(false);
        }
        super.setEnabled(b);
        model.setEnabled(b);
    }

setEnabled in DefaultButtonModel:

    public void setEnabled(boolean b) {
        if(isEnabled() == b) {
            return;
        }

        if (b) {
            stateMask |= ENABLED;
        } else {
            stateMask &= ~ENABLED;
            // unarm and unpress, just in case
            stateMask &= ~ARMED;
            stateMask &= ~PRESSED;
        }


        fireStateChanged();
    }

----------

If AbstractButton didn't force the rollover to false, and let the ModelButton handle this, an implementation of ModelButton could store the right state of the rollover even though it would be superseded by the enabled state ( i.e. although rollover is true, it would be shown as false if enabled is false). Therefore, when the button is reenabled, the rollover could be restored as expected. But that's only if there isn't a special reason why AbstractButton has to handle the rollover state before disabling the button.

That's all. Sorry for the long mail.

Best Regards,

Martin Alterisio

Reply via email to