JDK-4731797 : Mouse click/drag zone is not read correctly from Win32
  • Type: Bug
  • Component: client-libs
  • Sub-Component: java.awt
  • Affected Version: 1.4.0,1.4.1
  • Priority: P4
  • Status: Resolved
  • Resolution: Fixed
  • OS: windows_2000
  • CPU: x86
  • Submitted: 2002-08-14
  • Updated: 2003-04-01
  • Resolved: 2003-03-26
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.
Other Other
1.4.1_03 03Fixed 1.4.2Fixed
Related Reports
Duplicate :  
Relates :  
Relates :  
Relates :  
Relates :  
Relates :  
Relates :  
Description
aints.gridx     = 3;
        constraints.gridy     = 3;
        txfLocationClickY = new JTextField(3);
        txfLocationClickY.setEditable(false);
        txfLocationClickY.setBackground(Color.white);
        txfLocationClickY.setHorizontalAlignment(JTextField.TRAILING);
        panel.add(txfLocationClickY, constraints);
        constraints = new GridBagConstraints();
        constraints.gridx     = 4;
        constraints.gridy     = 3;
        txfDifferenceClickX = new JTextField(3);
        txfDifferenceClickX.setEditable(false);
        txfDifferenceClickX.setBackground(Color.white);
        txfDifferenceClickX.setHorizontalAlignment(JTextField.TRAILING);
        panel.add(txfDifferenceClickX, constraints);
        constraints = new GridBagConstraints();
        constraints.gridx     = 5;
        constraints.gridy     = 3;
        txfDifferenceClickY = new JTextField(3);
        txfDifferenceClickY.setEditable(false);
        txfDifferenceClickY.setBackground(Color.white);
        txfDifferenceClickY.setHorizontalAlignment(JTextField.TRAILING);
        panel.add(txfDifferenceClickY, constraints);

        // Layout, size, and position the frame.
        pack();
        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
        Dimension frameSize  = getSize();
        setLocation((screenSize.width - frameSize.width) / 2, (screenSize.height
- frameSize.height) / 2);
    }
}

---------------------------------------------------------
// MouseClickZones
//    written by Curtis Clauson for The Snake Pit - Development
//
// Native Win32 console C++ source.

#include <windows.h>
#include <iostream.h>
#include <iomanip.h>


int
main()
{
	int dragx = GetSystemMetrics(SM_CXDRAG);
	int dragy = GetSystemMetrics(SM_CYDRAG);
	int dblx = GetSystemMetrics(SM_CXDOUBLECLK);
	int dbly = GetSystemMetrics(SM_CYDOUBLECLK);

	cout << "Mouse Click Zone:" << endl
		  << "  Drag  : " << dragx << ", " << dragy << endl
		  << "  Double: " << dblx  << ", " << dbly  << endl;
	return 0;
}

---------- END SOURCE ----------

CUSTOMER WORKAROUND :
Developers have been using the MouseListener.mouseReleased
instead of the MouseListener.mouseClicked event for Java on
Win32. However, a lot of the JRE core classes do not, and
most non-Win32 cross-platform Java component developers do
not. This creates an inconsistent mouse behavior on Win32
depending over what component the mouse was clicked.
(Review ID: 160629) 
======================================================================
Name: gm110360			Date: 08/14/2002


FULL PRODUCT VERSION :
java version "1.4.0"
and java version "1.4.1-beta"

FULL OPERATING SYSTEM VERSION :
Microsoft Windows 2000 [Version 5.00.2195]

A DESCRIPTION OF THE PROBLEM :
On Win32 platforms, Java does not correctly obtain the
"Mouse Click Zone" metrics from the Windows OS. These
metrics are obtained by the GetSystemMetrics() function
using the SM_CXDRAG and SM_CYDRAG metrics. These define the
maximum x and y pixels a mouse cursor can be dragged between
a mouse pressed and mouse released operation and still be
interpreted as a click event, and not start a drag event.

This behavior is consistent in Java on Win32 platforms
regardless of the set look and feel. It is not an OS
problem, as a native application shows a different metric
than what Java uses (For example: On my Win2KPro, I show a
metric of {4, 4}, while Java uses {1, 1}).

This bug has been has been complained about by users,
developers, and customers since version 1.0. Bug ID 4218549
is related but assumes that there is no Click Zone (or
smudge factor). Whoever closed that bug simply assumed it
was a Win32 problem without bothering to check.

Note: The system metrics SM_CXDOUBLECLK and SM_CYDOUBLECLK
are not used for click/drag differentiation, as was
erroneously stated in that bug report, but are used along
with a timer to differentiate between single and double clicks.

This bug report specifies that the zone used by Java is not
properly read from the underlying OS. Two sets of sample
code are provided. The Java code proves the zone used is {1,
1}. The native Win32 C++ code shows what the OS zone metric
really is.

This is a !!!VERY!!! serious problem, as it makes Java look
like it is slow and unresponsive on Win32 platforms, giving
false credence to the claims and myths that Java cannot be
realistically used on anything but a Solaris platform. Also,
it precludes any accessability modifications of that
click/drag zone for people with infirmities that affect how
steady they can hold or click a mouse.

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1. Find out what the native mouse click/drag zone is on
Win32 using GetSystemMetrics() with SM_CXDRAG and SM_CYDRAG
(run the MouseClickZones C++ sample).
2. Run the MouseClickZone Java sample. Press the left mouse
button in the white pad, drag the mouse for less than the OS
reported metric, and release the mouse button.


EXPECTED VERSUS ACTUAL BEHAVIOR :
Expected: The click event will be fired when the pressed
mouse is dragged for less than the OS reported mouse
click/drag zone metric.

Actual: The click event is not fired if the pressed mouse
cursor is dragged by even 1 pixel in any direction.

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------

import java.awt.*;
import java.awt.event.*;
import java.io.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.border.*;
import javax.swing.text.html.*;


/**
 * A simple Swing application to discover what the click/drag zone is for a
mouse click.
 *
 * @author Curtis Clauson for <b>The Snake Pit - Development</b>
(http://www.TheSnakePitDev.com)
 */
public
class MouseClickZone
extends JFrame {
    /*
     * Class Methods
     */

    /**
     * Create and start a MouseClickZone frame.
     */
    public static
    void
    main(String[] args)
    {
        new MouseClickZone().show();
    }


    /*
     * Instance Data
     */

    /** The pad for mouse events. */
    protected JPanel     pnlMousePad;

    /** X location of a mouse down event. */
    protected int        pressX;
    /** Y location of a mouse down event. */
    protected int        pressY;
    /** X location of a mouse up event. */
    protected int        releaseX;
    /** Y location of a mouse up event. */
    protected int        releaseY;
    /** X location of a mouse click event. */
    protected int        clickX;
    /** Y location of a mouse click event. */
    protected int        clickY;

    /** Text field to display the X location of a mouse move event. */
    protected JTextField txfLocationCursorX;
    /** Text field to display the Y location of a mouse move event. */
    protected JTextField txfLocationCursorY;
    /** Text field to display the X location of a mouse down event. */
    protected JTextField txfLocationPressX;
    /** Text field to display the Y location of a mouse down event. */
    protected JTextField txfLocationPressY;
    /** Text field to display the X location of a mouse up event. */
    protected JTextField txfLocationReleaseX;
    /** Text field to display the Y location of a mouse up event. */
    protected JTextField txfLocationReleaseY;
    /** Text field to display the X difference of a mouse up event from the
mouse down event. */
    protected JTextField txfDifferenceReleaseX;
    /** Text field to display the Y difference of a mouse up event from the
mouse down event. */
    protected JTextField txfDifferenceReleaseY;
    /** Text field to display the X location of a mouse click event. */
    protected JTextField txfLocationClickX;
    /** Text field to display the Y location of a mouse click event. */
    protected JTextField txfLocationClickY;
    /** Text field to display the X difference of a mouse click event from the
mouse down event. */
    protected JTextField txfDifferenceClickX;
    /** Text field to display the Y difference of a mouse click event from the
mouse down event. */
    protected JTextField txfDifferenceClickY;


    /*
     * Constructors
     */

    /**
     * Create a new MouseClickZone frame.
     */
    public
    MouseClickZone()
    {
        // Configure the frame.
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setTitle("Mouse Click Zone");
        setResizable(false);

        // Create a content panel.
        JPanel content = new JPanel();
        content.setLayout(new BoxLayout(content, BoxLayout.Y_AXIS));
        content.setBorder(new EmptyBorder(4, 4, 4, 4));
        setContentPane(content);

        // Create the mouse pad.
        pnlMousePad = new JPanel();
        content.add(pnlMousePad);
        Insets insets = pnlMousePad.getInsets();
        pnlMousePad.setLocation(new Point(0, 0));
        pnlMousePad.setPreferredSize(new Dimension(200 + insets.left +
insets.right, 200 + insets.top + insets.bottom));
        pnlMousePad.setBackground(Color.white);
        pnlMousePad.addMouseMotionListener(new MouseMotionAdapter() {
            public void mouseMoved(MouseEvent event) {
                Dimension size   = pnlMousePad.getSize();
                Insets    insets = pnlMousePad.getInsets();
                int       x      = event.getX() - insets.left;
                int       y      = event.getY() - insets.top;
                if (   x < 0 || x >= size.width  - (insets.left + insets.right )
                    || y < 0 || y >= size.height - (insets.top  + insets.bottom)
                ) return;
                txfLocationCursorX.setText(Integer.toString(x));
                txfLocationCursorY.setText(Integer.toString(y));
            }
        });
        pnlMousePad.addMouseListener(new MouseAdapter() {
            public void mousePressed(MouseEvent event) {
                Dimension size   = pnlMousePad.getSize();
                Insets    insets = pnlMousePad.getInsets();
                pressX = event.getX() - insets.left;
                pressY = event.getY() - insets.top;
                if (   pressX < 0 || pressX >= size.width  - (insets.left +
insets.right )
                    || pressY < 0 || pressY >= size.height - (insets.top  +
insets.bottom)
                ) return;
                txfLocationPressX.setText(Integer.toString(pressX));
                txfLocationPressY.setText(Integer.toString(pressY));
                txfLocationReleaseX.setText(null);
                txfLocationReleaseY.setText(null);
                txfDifferenceReleaseX.setText(null);
                txfDifferenceReleaseY.setText(null);
                txfLocationClickX.setText(null);
                txfLocationClickY.setText(null);
                txfDifferenceClickX.setText(null);
                txfDifferenceClickY.setText(null);
            }
            public void mouseReleased(MouseEvent event) {
                Dimension size   = pnlMousePad.getSize();
                Insets    insets = pnlMousePad.getInsets();
                releaseX = event.getX() - insets.left;
                releaseY = event.getY() - insets.top;
                if (   releaseX < 0 || releaseX >= size.width  - (insets.left +
insets.right )
                    || releaseY < 0 || releaseY >= size.height - (insets.top  +
insets.bottom)
                ) return;
                txfLocationReleaseX.setText(Integer.toString(releaseX));
                txfLocationReleaseY.setText(Integer.toString(releaseY));
                txfDifferenceReleaseX.setText(Integer.toString(releaseX - pressX));
                txfDifferenceReleaseY.setText(Integer.toString(releaseY - pressY));
            }
            public void mouseClicked(MouseEvent event) {
                Dimension size   = pnlMousePad.getSize();
                Insets    insets = pnlMousePad.getInsets();
                clickX = event.getX() - insets.left;
                clickY = event.getY() - insets.top;
                if (   clickX < 0 || clickX >= size.width  - (insets.left +
insets.right )
                    || clickY < 0 || clickY >= size.height - (insets.top  +
insets.bottom)
                ) return;
                txfLocationClickX.setText(Integer.toString(clickX));
                txfLocationClickY.setText(Integer.toString(clickY));
                txfDifferenceClickX.setText(Integer.toString(clickX - pressX));
                txfDifferenceClickY.setText(Integer.toString(clickY - pressY));
            }
        });

        // Create the info panel.
        JPanel panel = new JPanel(new GridBagLayout());
        content.add(panel);
        panel.setBorder(new EmptyBorder(4, 0, 0, 0));

        JTextField         textField;
        GridBagConstraints constraints;

        constraints = new GridBagConstraints();
        constraints.gridx     = 0;
        constraints.gridy     = 0;
        txfLocationCursorX = new JTextField(3);
        txfLocationCursorX.setEditable(false);
        txfLocationCursorX.setBackground(Color.white);
        txfLocationCursorX.setHorizontalAlignment(JTextField.TRAILING);
        panel.add(txfLocationCursorX, constraints);
        constraints = new GridBagConstraints();
        constraints.gridx     = 1;
        constraints.gridy     = 0;
        txfLocationCursorY = new JTextField(3);
        txfLocationCursorY.setEditable(false);
        txfLocationCursorY.setBackground(Color.white);
        txfLocationCursorY.setHorizontalAlignment(JTextField.TRAILING);
        panel.add(txfLocationCursorY, constraints);
        constraints = new GridBagConstraints();
        constraints.gridx     = 2;
        constraints.gridy     = 0;
        constraints.gridwidth = 2;
        panel.add(new JLabel("Position", JLabel.LEADING), constraints);
        constraints = new GridBagConstraints();
        constraints.gridx     = 4;
        constraints.gridy     = 0;
        constraints.gridwidth = 2;
        panel.add(new JLabel("Difference", JLabel.LEADING), constraints);

        constraints = new GridBagConstraints();
        constraints.gridx     = 0;
        constraints.gridy     = 1;
        constraints.gridwidth = 2;
        constraints.anchor    = GridBagConstraints.WEST;
        panel.add(new JLabel("Press", JLabel.LEADING), constraints);
        constraints = new GridBagConstraints();
        constraints.gridx     = 2;
        constraints.gridy     = 1;
        txfLocationPressX = new JTextField(3);
        txfLocationPressX.setEditable(false);
        txfLocationPressX.setBackground(Color.white);
        txfLocationPressX.setHorizontalAlignment(JTextField.TRAILING);
        panel.add(txfLocationPressX, constraints);
        constraints = new GridBagConstraints();
        constraints.gridx     = 3;
        constraints.gridy     = 1;
        txfLocationPressY = new JTextField(3);
        txfLocationPressY.setEditable(false);
        txfLocationPressY.setBackground(Color.white);
        txfLocationPressY.setHorizontalAlignment(JTextField.TRAILING);
        panel.add(txfLocationPressY, constraints);

        constraints = new GridBagConstraints();
        constraints.gridx     = 0;
        constraints.gridy     = 2;
        constraints.gridwidth = 2;
        constraints.anchor    = GridBagConstraints.WEST;
        panel.add(new JLabel("Release", JLabel.LEADING), constraints);
        constraints = new GridBagConstraints();
        constraints.gridx     = 2;
        constraints.gridy     = 2;
        txfLocationReleaseX = new JTextField(3);
        txfLocationReleaseX.setEditable(false);
        txfLocationReleaseX.setBackground(Color.white);
        txfLocationReleaseX.setHorizontalAlignment(JTextField.TRAILING);
        panel.add(txfLocationReleaseX, constraints);
        constraints = new GridBagConstraints();
        constraints.gridx     = 3;
        constraints.gridy     = 2;
        txfLocationReleaseY = new JTextField(3);
        txfLocationReleaseY.setEditable(false);
        txfLocationReleaseY.setBackground(Color.white);
        txfLocationReleaseY.setHorizontalAlignment(JTextField.TRAILING);
        panel.add(txfLocationReleaseY, constraints);
        constraints = new GridBagConstraints();
        constraints.gridx     = 4;
        constraints.gridy     = 2;
        txfDifferenceReleaseX = new JTextField(3);
        txfDifferenceReleaseX.setEditable(false);
        txfDifferenceReleaseX.setBackground(Color.white);
        txfDifferenceReleaseX.setHorizontalAlignment(JTextField.TRAILING);
        panel.add(txfDifferenceReleaseX, constraints);
        constraints = new GridBagConstraints();
        constraints.gridx     = 5;
        constraints.gridy     = 2;
        txfDifferenceReleaseY = new JTextField(3);
        txfDifferenceReleaseY.setEditable(false);
        txfDifferenceReleaseY.setBackground(Color.white);
        txfDifferenceReleaseY.setHorizontalAlignment(JTextField.TRAILING);
        panel.add(txfDifferenceReleaseY, constraints);

        constraints = new GridBagConstraints();
        constraints.gridx     = 0;
        constraints.gridy     = 3;
        constraints.gridwidth = 2;
        constraints.anchor    = GridBagConstraints.WEST;
        panel.add(new JLabel("Click", JLabel.LEADING), constraints);
        constraints = new GridBagConstraints();
        constraints.gridx     = 2;
        constraints.gridy     = 3;
        txfLocationClickX = new JTextField(3);
        txfLocationClickX.setEditable(false);
        txfLocationClickX.setBackground(Color.white);
        txfLocationClickX.setHorizontalAlignment(JTextField.TRAILING);
        panel.add(txfLocationClickX, constraints);
        constraints = new GridBagConstraints();
        constr

Comments
EVALUATION This fix should be revised because of newly appeared 6404008. Perhaps Java shouldn't filter any event respecting system properties (desktop properties) because there is definetely a class of applications relaying on these events. If we filter them out and consume we break them. From the other side if we still fire every event (dragged event in the threshold area as in this case) any Java application may decide whether to react on it or not. This is how native Win32 applications behaves: some of them respect SM_CXDRAG/SM_CYDRAG, some not. As the fix for 6404008 supposes the backout of this fix I'm going to close this one as Will Not Fix.
11-04-2006

CONVERTED DATA BugTraq+ Release Management Values COMMIT TO FIX: 1.4.1_03 mantis-rc tiger FIXED IN: 1.4.1_03 mantis-rc tiger INTEGRATED IN: 1.4.1_03 mantis-b20 mantis-rc tiger tiger-b08
14-06-2004

SUGGESTED FIX Given that WmMouseMove message can be numerous, and assuming that SM_C[X|Y]DRAG is very rarely changed, I thought it better to cache the values. However, I've also deduced that once m_dragged is set to true, none of the calculations need to be performed for the remainder of the drag operation. So my new fix is this: ------- awt_Component.cpp ------- *** /tmp/sccs.daai_o Tue Mar 18 14:05:37 2003 --- awt_Component.cpp Tue Mar 18 13:55:56 2003 *************** *** 2376,2394 **** lastComp = this; lastX = x; lastY = y; - jint id; if ( (flags & ALL_MK_BUTTONS) != 0 ) { ! m_dragged = TRUE; id = java_awt_event_MouseEvent_MOUSE_DRAGGED; } else { id = java_awt_event_MouseEvent_MOUSE_MOVED; ! } MSG* msg = CreateMessage(lastMessage, flags, MAKELPARAM(x, y), x, y); ! ! SendMouseEvent(id, nowMillisUTC(), x, y, ! GetJavaModifiers(), 0, JNI_FALSE, java_awt_event_MouseEvent_NOBUTTON, msg); } --- 2376,2404 ---- lastComp = this; lastX = x; lastY = y; jint id; + if ( (flags & ALL_MK_BUTTONS) != 0 ) { ! // 4731797 - if mouse is only dragged a short distance ! // (less than SM_CXDRAG/SM_CYDRAG), MOUSE_CLICKED event should be ! // sent on the release. ! if (!m_dragged) { ! int diffX = abs(x-lastClickX); ! int diffY = abs(y-lastClickY); ! if (diffX > ::GetSystemMetrics(SM_CXDRAG) || ! diffY > ::GetSystemMetrics(SM_CYDRAG)) { ! m_dragged = TRUE; ! } ! } id = java_awt_event_MouseEvent_MOUSE_DRAGGED; } else { id = java_awt_event_MouseEvent_MOUSE_MOVED; ! } MSG* msg = CreateMessage(lastMessage, flags, MAKELPARAM(x, y), x, y); ! ! SendMouseEvent(id, nowMillisUTC(), x, y, ! GetJavaModifiers(), 0, JNI_FALSE, java_awt_event_MouseEvent_NOBUTTON, msg); }
11-06-2004

EVALUATION This is an old yet serious problem, with accessibility ramifications. Should be looked into soon. I'm committing it to Tiger. ###@###.### 2002-08-16 In awt_Component.cpp mouse click is not considering the (win32) mouse drag limits. In win32, there are two system parameters SM_CXDRAG and SM_CYDRAG. These two represent, ( width ,height) in pixels, of a rectangle centered on a drag point to allow for limited movement of the mouse pointer before a drag operation begins. This allows the user to click and release the mouse button easily without unintentionally starting a drag operation. The fix is to check for these values before considering a drag operation. ###@###.### 2003-03-11 I committed this to Mantis since it is an escalation committed to 1.4.1_03. ###@###.### 2003-03-11
11-03-2003