Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8238575

DragSourceEvent.getLocation() returns wrong value on HiDPI screens (Windows)

XMLWordPrintable

    • 9
    • b22
    • x86_64
    • windows_10
    • Verified

        ADDITIONAL SYSTEM INFORMATION :
        OpenJDK 64-Bit Server VM 13+33
        Windows 10 with HiDPI scaling active (e.g. 150% or 200%)

        A DESCRIPTION OF THE PROBLEM :
        On Windows 10, on a monitor with HiDPI scaling active, DragSourceEvent.getLocation() will consistently return the wrong value.

        === Debugging notes: ===

        Both MouseInfo and DragSourceEvent get their coordinates from a call to the "GetCursorPos" Windows API function. The latter returns device coordinates rather than logical coordinates for DPI-aware applications (including java.exe and the netbeans launcher since #883 ).

        But in the implementation of MouseInfo.getPointerInfo(), extra code was added to convert the device coordinates to logical coordinates, as part of the original JDK patch which introduced HiDPI support on Windows. See Java_sun_awt_windows_WMouseInfoPeer_fillPointWithCoords in https://github.com/openjdk/jdk/blame/6bab0f539fba8fb441697846347597b4a0ade428/src/java.desktop/windows/native/libawt/windows/MouseInfo.cpp . This was forgotten in the code that initializes the coordinates for DragSourceEvent (see GetCursorPos call for call_dSCenter/call_dSCmotion in https://github.com/openjdk/jdk/blob/1440dc39400f89b7df8af2249f30eaf5092c1574/src/java.desktop/windows/native/libawt/windows/awt_DnDDS.cpp ).

        STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
        On Windows 10, configure the display with a single monitor at 150% DPI scaling. Run the attached executable test case. Drag the selected text from the JTextField to the JLabel in the middle of the JFrame. While dragging, an "X" should be painted underneath the mouse cursor--but due to the bug, the X ends up in the wrong spot.

        EXPECTED VERSUS ACTUAL BEHAVIOR :
        EXPECTED -
        While dragging, an "X" should be painted underneath the mouse cursor.
        ACTUAL -
        The X ends up in the wrong spot. (Because DragSourceEvent.getLocation() ends up returning device coordinates instead of logical coordinates.)

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

        import java.awt.BorderLayout;
        import java.awt.Color;
        import java.awt.Point;
        import javax.swing.JButton;
        import javax.swing.JFrame;
        import javax.swing.JTextField;
        import javax.swing.TransferHandler;
        import java.awt.EventQueue;
        import java.awt.Font;
        import java.awt.Graphics;
        import java.awt.dnd.DragSource;
        import java.awt.dnd.DragSourceDragEvent;
        import java.awt.dnd.DragSourceDropEvent;
        import java.awt.dnd.DragSourceEvent;
        import java.awt.dnd.DragSourceListener;
        import java.util.AbstractMap.SimpleEntry;
        import java.util.ArrayList;
        import java.util.List;
        import java.util.Map.Entry;
        import javax.swing.JLabel;
        import javax.swing.SwingUtilities;

        // See example at http://zetcode.com/tutorials/javaswingtutorial/draganddrop
        public class DragSourceEventHiDPIBugExhibit extends JFrame {
          private final List<Entry<String,Point>> pointsToDraw = new ArrayList<>();
          private JTextField field;
          private JLabel label;

          public DragSourceEventHiDPIBugExhibit() {
            initComponents();
            DragSource ds = DragSource.getDefaultDragSource();
            ds.addDragSourceListener(new DragSourceListener() {
              private void reportEvent(DragSourceEvent dse, String method) {
                pointsToDraw.add(new SimpleEntry<>(method, new Point(dse.getX(), dse.getY())));
                if (pointsToDraw.size() > 100)
                  pointsToDraw.remove(0);
                repaint();
              }

              @Override
              public void dragEnter(DragSourceDragEvent dsde) {
                reportEvent(dsde, "enter");
              }

              @Override
              public void dragOver(DragSourceDragEvent dsde) {
                reportEvent(dsde, "over");
              }

              @Override
              public void dropActionChanged(DragSourceDragEvent dsde) {
                reportEvent(dsde, "changed");
              }

              @Override
              public void dragExit(DragSourceEvent dse) {
                reportEvent(dse, "exit");
              }

              @Override
              public void dragDropEnd(DragSourceDropEvent dsde) {
                reportEvent(dsde, "end");
              }
            });
          }

          @Override
          public void paint(Graphics g) {
            super.paint(g);
            g.setColor(Color.BLACK);
            for (Entry<String,Point> entry : pointsToDraw) {
              Point p = entry.getValue();
              SwingUtilities.convertPointFromScreen(p, this);
              final int x = p.x;
              final int y = p.y;
              // Draw an "x" with the poitn in the middle
              g.drawLine(x - 3, y - 3, x + 3, y + 3);
              g.drawLine(x + 3, y - 3, x - 3, y + 3);
              g.setFont(new Font("Dialog", 0, 10));
              g.drawString(entry.getKey(), x + 5, y);
            }
          }

          private void initComponents() {
            setTitle("Exhibit");

            setLayout(new BorderLayout());
            field = new JTextField(15);
            field.setText("Drag selected text from the text field to the label below.");
            field.selectAll();

            field.setDragEnabled(true);
            label = new JLabel("During dragging, an X should be painted at the mouse location.");
            label.setOpaque(false);
            label.setTransferHandler(new TransferHandler("text"));
            add(field, BorderLayout.NORTH);
            add(label, BorderLayout.CENTER);
            pack();
            setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            setLocationRelativeTo(null);
          }

          public static void main(String[] args) {
            EventQueue.invokeLater(() -> {
              JFrame ex = new DragSourceEventHiDPIBugExhibit();
              ex.setSize(800, 600);
              ex.setVisible(true);
            });
          }
        }

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

        CUSTOMER SUBMITTED WORKAROUND :
        In the NetBeans IDE, a workaround was to use MouseInfo.getPointerInfo() instead of DragSourceEvent.getLocation(). See https://github.com/apache/netbeans/pull/1804 .

        FREQUENCY : always


              serb Sergey Bylokhov
              webbuggrp Webbug Group
              Votes:
              0 Vote for this issue
              Watchers:
              6 Start watching this issue

                Created:
                Updated:
                Resolved: