-
Bug
-
Resolution: Unresolved
-
P3
-
8u20, 9, 11, 12, 13
-
x86
-
os_x
ADDITIONAL SYSTEM INFORMATION :
I'm using macOS Mojave 10.14.2 (uname -a says 18.2.0).
I've reproduced this with JDK 18.0_191.
A DESCRIPTION OF THE PROBLEM :
On a regular basis, I can use a simple app to cause a MouseListener to not call mouseClicked when it should. In other words, if my mouse does not move (according to MouseEvent.getXOnScreen and MouseEvent.getYOnScreen) after mousePressed and mouseReleased, then mouseClicked should be called. But I often see that is not the case.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
javac TestJLabel.java && java TestJLabel
You will see a pane with 3 labels. Click the one called "click me".
As you move around, pressing and releasing mouse button 1, you will see lines written to stderr.
Eventually, if you reproduce the problem, you will see mousePressed followed only by mouseReleased. And that with no change in the reported mouse location nor any intervening mouseMoved call.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
*EVERY* time the MouseListener reports no mouse position change in its MouseEvent instance between mousePressed and mouseClicked, it must invoke mouseClicked. I suppose that if the mouse moved away from the mousePressed location and then back to the mousePressed location when mouseReleased is called, then you might have an argument that mouseClicked should not be called. But that is not the case. Not once is mouseMoved called.
ACTUAL -
$ javac TestJLabel.java && java TestJLabel
mouseEntered: (346,137)
mouseExited: (293,141)
mouseEntered: (295,144)
mousePressed: (301,145)
mouseReleased: (299,145)
mouseExited: (293,146)
mouseEntered: (295,152)
mousePressed: (299,154)
mouseReleased: (301,154)
mouseExited: (293,155)
mouseEntered: (295,165)
mousePressed: (308,169)
mouseReleased: (308,169)
mouseClicked: (308,169)
mouseExited: (294,171)
mouseEntered: (295,175)
mousePressed: (320,179)
mouseReleased: (320,179)
mouseClicked: (320,179)
mouseExited: (293,154)
mouseEntered: (295,166)
mousePressed: (301,170)
mouseReleased: (301,170)
mouseClicked: (301,170)
mousePressed: (301,170)
mouseReleased: (301,170)
mouseClicked: (301,170)
mousePressed: (303,163)
mouseReleased: (303,163)
mouseClicked: (303,163)
mouseExited: (293,163)
mouseEntered: (295,166)
mousePressed: (298,166)
mouseReleased: (300,166)
mousePressed: (295,166)
mouseReleased: (295,166)
mouseClicked: (295,166)
mouseExited: (294,166)
mouseEntered: (296,169)
mousePressed: (330,172)
mouseReleased: (330,172)
mouseClicked: (330,172)
mousePressed: (329,172)
mouseReleased: (329,172)
mouseExited: (292,168)
mouseEntered: (295,150)
mousePressed: (303,151) # this is it
mouseReleased: (303,151) # this is it
mouseExited: (383,143)
---------- BEGIN SOURCE ----------
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.*;
public class TestJLabel {
private static void createAndShowGUI () {
JFrame frame = new JFrame("TestJLabel");
frame.setSize(350, 210);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JLabel lab = new JLabel("click me");
lab.setBorder(new LineBorder(Color.BLACK));
lab.addMouseListener(new MouseAdapter() {
private void say (String methodName, MouseEvent ev) {
System.err.println(String.format("%s: (%d,%d)", methodName, ev.getXOnScreen(), ev.getYOnScreen()));
}
public void mousePressed (MouseEvent ev) { say("mousePressed", ev); }
public void mouseReleased (MouseEvent ev) { say("mouseReleased", ev); }
public void mouseDragged (MouseEvent ev) { say("mouseDragged", ev); }
public void mouseClicked (MouseEvent ev) { say("mouseClicked", ev); }
public void mouseMoved (MouseEvent ev) { say("mouseMoved", ev); }
public void mouseEntered (MouseEvent ev) { say("mouseEntered", ev); }
public void mouseExited (MouseEvent ev) { say("mouseExited", ev); }
});
frame.getContentPane().setLayout(new BorderLayout());
frame.getContentPane().add(new JLabel("don't click me"), BorderLayout.NORTH);
frame.getContentPane().add(lab, BorderLayout.EAST);
frame.getContentPane().add(new JLabel("don't click me either"), BorderLayout.SOUTH);
frame.setVisible(true);
}
public static void main (String[] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
If I extend the MouseAdapter in the following way, then I *think* I can work around the bug. In my specific case, I am extending BasicTableHeaderUI to more consistently handle single click events that the user makes when he indicates he wants to sort a column.
In essence, I have to make mouseClicked a noop because it's not reliable. That means I have to write mouseReleased call its super's mouseReleased and then call the super's mouseClicked if no change in position is reported. Of course there's a bug in this particular code because if the mouse moves away from the mousePressed location but then moves back to it, then the definition of "
import java.awt.event.*;
import javax.swing.event.*;
import javax.swing.table.*;
import javax.swing.plaf.basic.BasicTableHeaderUI;
public class FilesTableHeaderUI extends BasicTableHeaderUI {
protected JTableHeader header;
/*
mouseClicked is not reliably called. It's probably an awt bug.
Implement mouseClicked capability only with mousePressed and mouseReleased.
*/
public class MouseInputHandler extends BasicTableHeaderUI.MouseInputHandler {
private int xOnScreenPressed;
private int yOnScreenPressed;
public void mouseClicked (MouseEvent ev) {
// If this does get called, then we have to ignore it because it's handled by mouseReleased.
}
public void mousePressed (MouseEvent ev) {
xOnScreenPressed = ev.getXOnScreen();
yOnScreenPressed = ev.getYOnScreen();
super.mousePressed(ev);
}
public void mouseReleased (MouseEvent ev) {
int xOnScreenReleased = ev.getXOnScreen();
int yOnScreenReleased = ev.getYOnScreen();
super.mouseReleased(ev);
boolean isClick = xOnScreenPressed == xOnScreenReleased && yOnScreenPressed == yOnScreenReleased;
if (isClick && ev.getModifiersEx() == 0)
super.mouseClicked(ev);
}
}
@Override
protected MouseInputListener createMouseInputListener () {
return new MouseInputHandler();
}
}
FREQUENCY : often
I'm using macOS Mojave 10.14.2 (uname -a says 18.2.0).
I've reproduced this with JDK 18.0_191.
A DESCRIPTION OF THE PROBLEM :
On a regular basis, I can use a simple app to cause a MouseListener to not call mouseClicked when it should. In other words, if my mouse does not move (according to MouseEvent.getXOnScreen and MouseEvent.getYOnScreen) after mousePressed and mouseReleased, then mouseClicked should be called. But I often see that is not the case.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
javac TestJLabel.java && java TestJLabel
You will see a pane with 3 labels. Click the one called "click me".
As you move around, pressing and releasing mouse button 1, you will see lines written to stderr.
Eventually, if you reproduce the problem, you will see mousePressed followed only by mouseReleased. And that with no change in the reported mouse location nor any intervening mouseMoved call.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
*EVERY* time the MouseListener reports no mouse position change in its MouseEvent instance between mousePressed and mouseClicked, it must invoke mouseClicked. I suppose that if the mouse moved away from the mousePressed location and then back to the mousePressed location when mouseReleased is called, then you might have an argument that mouseClicked should not be called. But that is not the case. Not once is mouseMoved called.
ACTUAL -
$ javac TestJLabel.java && java TestJLabel
mouseEntered: (346,137)
mouseExited: (293,141)
mouseEntered: (295,144)
mousePressed: (301,145)
mouseReleased: (299,145)
mouseExited: (293,146)
mouseEntered: (295,152)
mousePressed: (299,154)
mouseReleased: (301,154)
mouseExited: (293,155)
mouseEntered: (295,165)
mousePressed: (308,169)
mouseReleased: (308,169)
mouseClicked: (308,169)
mouseExited: (294,171)
mouseEntered: (295,175)
mousePressed: (320,179)
mouseReleased: (320,179)
mouseClicked: (320,179)
mouseExited: (293,154)
mouseEntered: (295,166)
mousePressed: (301,170)
mouseReleased: (301,170)
mouseClicked: (301,170)
mousePressed: (301,170)
mouseReleased: (301,170)
mouseClicked: (301,170)
mousePressed: (303,163)
mouseReleased: (303,163)
mouseClicked: (303,163)
mouseExited: (293,163)
mouseEntered: (295,166)
mousePressed: (298,166)
mouseReleased: (300,166)
mousePressed: (295,166)
mouseReleased: (295,166)
mouseClicked: (295,166)
mouseExited: (294,166)
mouseEntered: (296,169)
mousePressed: (330,172)
mouseReleased: (330,172)
mouseClicked: (330,172)
mousePressed: (329,172)
mouseReleased: (329,172)
mouseExited: (292,168)
mouseEntered: (295,150)
mousePressed: (303,151) # this is it
mouseReleased: (303,151) # this is it
mouseExited: (383,143)
---------- BEGIN SOURCE ----------
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.*;
public class TestJLabel {
private static void createAndShowGUI () {
JFrame frame = new JFrame("TestJLabel");
frame.setSize(350, 210);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JLabel lab = new JLabel("click me");
lab.setBorder(new LineBorder(Color.BLACK));
lab.addMouseListener(new MouseAdapter() {
private void say (String methodName, MouseEvent ev) {
System.err.println(String.format("%s: (%d,%d)", methodName, ev.getXOnScreen(), ev.getYOnScreen()));
}
public void mousePressed (MouseEvent ev) { say("mousePressed", ev); }
public void mouseReleased (MouseEvent ev) { say("mouseReleased", ev); }
public void mouseDragged (MouseEvent ev) { say("mouseDragged", ev); }
public void mouseClicked (MouseEvent ev) { say("mouseClicked", ev); }
public void mouseMoved (MouseEvent ev) { say("mouseMoved", ev); }
public void mouseEntered (MouseEvent ev) { say("mouseEntered", ev); }
public void mouseExited (MouseEvent ev) { say("mouseExited", ev); }
});
frame.getContentPane().setLayout(new BorderLayout());
frame.getContentPane().add(new JLabel("don't click me"), BorderLayout.NORTH);
frame.getContentPane().add(lab, BorderLayout.EAST);
frame.getContentPane().add(new JLabel("don't click me either"), BorderLayout.SOUTH);
frame.setVisible(true);
}
public static void main (String[] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
If I extend the MouseAdapter in the following way, then I *think* I can work around the bug. In my specific case, I am extending BasicTableHeaderUI to more consistently handle single click events that the user makes when he indicates he wants to sort a column.
In essence, I have to make mouseClicked a noop because it's not reliable. That means I have to write mouseReleased call its super's mouseReleased and then call the super's mouseClicked if no change in position is reported. Of course there's a bug in this particular code because if the mouse moves away from the mousePressed location but then moves back to it, then the definition of "
import java.awt.event.*;
import javax.swing.event.*;
import javax.swing.table.*;
import javax.swing.plaf.basic.BasicTableHeaderUI;
public class FilesTableHeaderUI extends BasicTableHeaderUI {
protected JTableHeader header;
/*
mouseClicked is not reliably called. It's probably an awt bug.
Implement mouseClicked capability only with mousePressed and mouseReleased.
*/
public class MouseInputHandler extends BasicTableHeaderUI.MouseInputHandler {
private int xOnScreenPressed;
private int yOnScreenPressed;
public void mouseClicked (MouseEvent ev) {
// If this does get called, then we have to ignore it because it's handled by mouseReleased.
}
public void mousePressed (MouseEvent ev) {
xOnScreenPressed = ev.getXOnScreen();
yOnScreenPressed = ev.getYOnScreen();
super.mousePressed(ev);
}
public void mouseReleased (MouseEvent ev) {
int xOnScreenReleased = ev.getXOnScreen();
int yOnScreenReleased = ev.getYOnScreen();
super.mouseReleased(ev);
boolean isClick = xOnScreenPressed == xOnScreenReleased && yOnScreenPressed == yOnScreenReleased;
if (isClick && ev.getModifiersEx() == 0)
super.mouseClicked(ev);
}
}
@Override
protected MouseInputListener createMouseInputListener () {
return new MouseInputHandler();
}
}
FREQUENCY : often