JDK-4407521 : Cursor not modified during Drag and Drop
  • Type: Bug
  • Component: client-libs
  • Sub-Component: java.awt
  • Affected Version: 1.3.0
  • Priority: P4
  • Status: Closed
  • Resolution: Fixed
  • OS: windows_nt
  • CPU: x86
  • Submitted: 2001-01-24
  • Updated: 2003-04-12
  • Resolved: 2002-08-22
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
1.4.2 mantisFixed
Related Reports
Relates :  
Relates :  
Relates :  
Relates :  
Description

Name: boT120536			Date: 01/23/2001


java version "1.3.0"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.3.0-C)
Java HotSpot(TM) Client VM (build 1.3.0-C, mixed mode)

When initiating a DnD operation within a JTree, the JTree acts as both a
source and a target.  When the JTree rejects the drag, the cursor should change
to the NoDrop cursor, but does not.  If you hit the Shift key, the
DropTargetListener receives the "dropActionChanged" message and the cursor is
properly changed to the NoDrop cursor.

Also, when the drag is rejected by the DropTargetListener, the
DropSourceListener does NOT receive the "dragExit" message as stated in section
2.3.3 of the "Drag and Drop Subsystem for JFC" document
(http://java.sun.com/j2se/1.3/docs/guide/dragndrop/spec/dnd1.doc5.html).

By the way, I tried this using JDK 1.2.2 on NT4 and encountered the same
problems.
//
// ~~~~~~~~~~~~~~~~~  code sample follows  ~~~~~~~~~~~~~~~~~~~~
//

/**
The following test program demonstrates several cursor problems in the DnD
system.

Try dragging a "true" node within the tree.  The drag is rejected by the
DropTargetListener, but the cursor never changes to the NoDrop cursor!

Also, the DragSourceListener only receives the "dragEnd" and "dragDropEnd"
messages, never the "dragEnter" or "dragOver" messages.

Lastly, the cursor never changes when you drag over a component (JLabel
or JButton) with no associated DropTarget.  Shouldn't the cursor change
to the NoDrop cursor?
*/

import java.awt.*;
import java.awt.datatransfer.*;
import java.awt.dnd.*;

import javax.swing.*;
import javax.swing.tree.*;


public class DndTree extends JPanel
{
   /** scrollpane to display tree */
   protected JScrollPane scrollPane = new JScrollPane();

   /** JTree displaying the server */
   protected JTree serverTree = new JTree();

   /** root node of the tree */
   protected DefaultMutableTreeNode root;

   /** tree model */
   protected DefaultTreeModel model;

   // DND related vars
   protected DragSource          myDragSource;
   protected DndSourceHandler    dndSource;
   protected DndTargetHandler    dndTarget;
   protected DropTarget          myDropTarget;

   /**
    * Constructor.
    */
   public DndTree()
   {
      try
      {
         jbInit();
      }
      catch(Exception ex)
      {
         System.out.println(ex);
      }

      root = new DefaultMutableTreeNode("root");

      // give the tree a model using root
      model = new DefaultTreeModel(root);
      serverTree.setModel(model);

      // add some child nodes
      model.insertNodeInto(new DefaultMutableTreeNode(new Boolean(true)), root,
0);
      model.insertNodeInto(new DefaultMutableTreeNode(new Boolean(false)), root,
0);
      model.insertNodeInto(new DefaultMutableTreeNode(new Boolean(false)), root,
0);
      model.insertNodeInto(new DefaultMutableTreeNode(new Boolean(true)), root,
0);

      // set other tree display properties
      serverTree.putClientProperty("JTree.lineStyle", "Angled");
      serverTree.setShowsRootHandles(true);
            serverTree.getSelectionModel().setSelectionMode(
                                     TreeSelectionModel.SINGLE_TREE_SELECTION);

      // setup DND for dragging IElements into the Task Editor
      myDragSource = new DragSource();
      dndSource = new DndSourceHandler();
      myDragSource.createDefaultDragGestureRecognizer(serverTree,
                                         DnDConstants.ACTION_MOVE, dndSource);

      // setup DND target to receive drags within tree
      dndTarget = new DndTargetHandler();
      myDropTarget = new DropTarget(serverTree, dndTarget);

   }

   private void jbInit() throws Exception
   {
      this.setLayout(new BorderLayout());
      this.add(new JLabel("just some text", JLabel.CENTER), BorderLayout.NORTH);
      this.add(scrollPane, BorderLayout.CENTER);
      this.add(new JButton("RIGHT"), BorderLayout.EAST);
      this.add(new JButton("LEFT"), BorderLayout.WEST);
      scrollPane.getViewport().add(serverTree, null);

   }

   /**
    * Allows the JTree to act as a drag source
    */
   private class DndSourceHandler implements DragSourceListener,
DragGestureListener
   {
      /**
       */
      public void dragGestureRecognized(DragGestureEvent dge)
      {
         TreePath path = serverTree.getSelectionPath();
         if (null == path)
            return;

         DefaultMutableTreeNode node = (DefaultMutableTreeNode)
                                       path.getLastPathComponent();
         Boolean b = (Boolean) node.getUserObject();
         if ( b.booleanValue() == true ) {

            // create an object to contain our data
            Transferable t = (Transferable) new StringSelection(b.toString());

            // start the DnD drag operation
            myDragSource.startDrag(dge, DragSource.DefaultLinkDrop, t,
                                   DndSourceHandler.this);
            System.out.println("source starting drag!");
         }
      }
      public void dragEnter(DragSourceDragEvent dsde) {
         System.out.println("source dragEnter: " + dsde.getTargetActions());
      }
      public void dragExit(DragSourceEvent dse) {
         System.out.println("source dragExit: " +
                            dse.getDragSourceContext().getCursor().toString());
      }
      public void dragDropEnd(DragSourceDropEvent dsde) {
         System.out.println("source dragDropEnd: " + dsde.getDropAction());
      }
      public void dragOver(DragSourceDragEvent dsde) {
         System.out.println("source dragOver: " + dsde.getTargetActions());
      }
      public void dropActionChanged(DragSourceDragEvent dsde) {
         System.out.println("source dropActionChanged: " +
                            dsde.getTargetActions());
      }

   }

   /**
    * Allows JTree to act as a drop target
    */
   protected class DndTargetHandler implements DropTargetListener {
      public void dragEnter(DropTargetDragEvent dtde) {
         System.out.println("target rejecting drag!");
         dtde.rejectDrag();
         System.out.println("target dragEnter: " + dtde.getDropAction());
      }
      public void dragOver(DropTargetDragEvent dtde) {
      }
      public void drop(DropTargetDropEvent dtde) {
         // reject the drop
         System.out.println("target rejecting drop!");
         dtde.rejectDrop();
         System.out.println("target dragEnter: " + dtde.getDropAction());
      }
      public void dragExit(DropTargetEvent dte) {
         System.out.println("target dragExit: " +
dte.getDropTargetContext().getComponent().
                            getCursor().toString());
      }
      public void dropActionChanged(DropTargetDragEvent dtde) {
         System.out.println("target dropActionChanged: " +
                             dtde.getDropAction());
      }
   }


   //
   //
   //          TESTING ONLY !!
   //
   //
   static public void main(String[] argv) {
      // create a frame
      final JFrame frame = new JFrame("DndTree Frame");
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.setSize(300, 200);
      frame.setLocation(350, 250);
      frame.setVisible(true);
      // create a dialog based on the frame
      JDialog dialog = new JDialog(frame, "DnD Tree Tester", true);
      dialog.getContentPane().setLayout(new BorderLayout());
      dialog.setSize(new Dimension(400, 350));
      dialog.setLocation(300, 200);
      dialog.setResizable(true);
      // add the tree panel to the dialog
      DndTree panel = new DndTree();
      dialog.getContentPane().add(panel, BorderLayout.CENTER);
      dialog.setVisible(true);
   }

}
(Review ID: 112887) 
======================================================================

Comments
CONVERTED DATA BugTraq+ Release Management Values COMMIT TO FIX: mantis mantis-b02 FIXED IN: mantis mantis-b02 INTEGRATED IN: mantis mantis-b02 VERIFIED IN: mantis
24-08-2004

PUBLIC COMMENTS .
24-08-2004

EVALUATION Looks serious enough for Mantis. ###@###.### 2002-07-15 Name: agR10216 Date: 07/25/2002 I reproduced the bug with the build 1.4.1-rc-b16. But I actually got the following: 1. The cursor doesn't change after rejectDrag(). 2. DragSourceListener.dragExit() is not called in response to rejectDrag(). 3. On Windows DragSourceListener.dragEnter() and dragOver() are called regardless of rejectDrag(). On Solaris/Linux DragSourceListener.dragOver() is called regardless of rejectDrag(), but DragSourceListener.dragEnter() is not. 4. The cursor doesn't change when dragged over a component with no associated drop target. ------------------------------------------------------------------------------------ Some adjustments: 1. The custom cursor is used in the test (non-null cursor is specified in the startDrag()). So it is never updated. This behaviour is specified in 1.4.1: see bug 4455820. But if a default cursor is used (null cursor is specified in the startDrag()), it does not change either. The expected behavior is that a default drag cursor is set to NoDrop when dragging over the drop target that has rejected the drag. 2. The javadoc for DragSourceListener.dragExit() says that this metod is invoked if "The current drop site has rejected the drag." The DnD spec, section 2.3.3 says that the method is invoked if "The current DropTarget's DropTargetListener has invoked rejectDrag() since the last dragEnter() or dragOver() invocation." So DragSourceListener.dragExit() must be called immediately after the drag source is notified about drag rejecting. With the fix of this bug DragSourceListener.dragExit() will be called repeatedly as the cursor drags over the drop target that has rejected the drag. 3. The javadoc for DragSourceListener.dragEnter() says that "This method is invoked when all the following conditions are true: - The cursor's hotspot enters the operable part of a platform-dependent drop site. - The drop site is active. - The drop site accepts the drag." The DnD spec, section 2.3.3 says that "The DragSourceListener's dragEnter() method is invoked when the following conditions are true: - The logical cursor's hotspot initially intersects a GUI Component's visible geometry. - That Component has an active DropTarget associated." With the fix of this bug the behaviour will conform to the javadoc. The DnD spec should be corrected to correspond to the javadoc in the next release. The javadoc for DragSourceListener.dragOver() says that "This method is invoked when all the following conditions are true: - The cursor's hotspot has moved, but still intersects the operable part of the drop site associated with the previous dragEnter() invocation. - The drop site is still active. - The drop site accepts the drag." The DnD spec, section 2.3.3 says that "The DragSourceListener's dragOver() method is invoked when the following conditions are true: - The cursor's logical hotspot has moved but still intersects the visible geometry of the Component associated with the previous dragEnter() invocation. - That Component still has a DropTarget associated. - That DropTarget is still active. - The DropTarget's registered DropTargetListener dragOver() method is invoked and returns successfully. - The DropTarget does not reject the drag via rejectDrag()." With the fix of this bug the behaviour will conform to both of these specs. DragSourceListener.dragEnter() and DragSourceListener.dragOver() must not be called if rejectDrag() has been called. 4. A custom cursor is never updated (see point 1). But a default cursor is updated properly in this case. ------------------------------------------------------------------------------------ Current implementation: To inform the drag source about progress of the DnD operation the native DnD subsystem calls the methods of IDropSource interface on Windows or some callbacks in Motif implementation. These functions are supplied with the current drop action. The call of rejectDrag() results in setting the current drop action to none. (DropTargetContextPeer.currentDA = DnDConstants.ACTION_NONE). This drop action is passed to the drag source, and accordingly to the current state of the drag source and this drop action some method of DragSourceListener is called and the cursor is updated. Just after the call of rejectDrag() the drag source recieves none as the current drop action but later the drop target supplies the drag source with the non-none action because rejectDrag() is not called any more. Therefore the cursor does not change visibly. On Windows DragSourceListener.dragEnter() is called on the second call of IDropSource::GiveFeedback(dropEffect), but none dropEffect is passed only once as a result of rejectDrag(). That is why DragSourceListener.dragEnter() is called even though the drag has been rejected. ------------------------------------------------------------------------------------ Thus, with the fix of this bug: 1. A default drag cursor will be set to NoDrop when dragging over the drop target that has rejected the drag. 2. DragSourceListener.dragExit() will be called repeatedly as the cursor drags over the drop target that has rejected the drag. 3. DragSourceListener.dragEnter() and DragSourceListener.dragOver() will not be called if rejectDrag() has been called. ###@###.### 2002-07-24 ======================================================================
24-07-2002

SUGGESTED FIX Name: agR10216 Date: 07/25/2002 On the drop target side remember that drag was rejected and set the current drop action to none: ------------------------------------------------------------------------------------ --- SunDropTargetContextPeer.java Wed Jul 24 15:34:53 2002 *************** *** 63,68 **** --- 63,70 ---- private Transferable local; + private boolean dragRejected = false; + protected int dropStatus = STATUS_NONE; protected boolean dropComplete = false; *************** *** 407,412 **** --- 409,416 ---- currentDTC = null; local = null; + + dragRejected = false; } } *************** *** 472,477 **** --- 476,485 ---- currentA = currentDT.getDefaultActions(); + if (dragRejected) { + currentDA = DnDConstants.ACTION_NONE; + } + try { DropTargetDragEvent dtde = new DropTargetDragEvent(dtc, hots, *************** *** 581,586 **** --- 589,595 ---- throw new InvalidDnDOperationException("No Drag pending"); } currentDA = DnDConstants.ACTION_NONE; + dragRejected = true; } /** ------------------------------------------------------------------------------------ Call DragSourceListener.dragEnter() on the first call of IDropSource::GiveFeedback() if it is appropriate. Call DragSourceListener.dragExit() if current drop action is none: ------------------------------------------------------------------------------------ --- awt_DnDDS.cpp Wed Jul 24 15:45:49 2002 *************** *** 115,121 **** m_lastmods = 0; m_droptarget = NULL; ! m_enterpending = FALSE; m_cursor = NULL; --- 115,121 ---- m_lastmods = 0; m_droptarget = NULL; ! m_enterpending = TRUE; m_cursor = NULL; *************** *** 498,526 **** ::GetCursorPos(&curs); ! HWND win = ::WindowFromPoint(curs); int invalid = (dwEffect == DROPEFFECT_NONE); ! if (m_droptarget != NULL) { ! if (!invalid) { ! (*(m_enterpending ? call_dSCenter : call_dSCmotion)) ! (env, m_peer, m_actions, modifiers, curs.x, curs.y); - m_droptarget = win; - m_enterpending = FALSE; - } else { - if (!m_enterpending) { - call_dSCexit(env, m_peer, curs.x, curs.y); - } - - m_droptarget = (HWND)NULL; - } - m_enterpending = FALSE; - } else if (!invalid) { - m_droptarget = win; - m_enterpending = TRUE; } if (m_droptarget != NULL) { --- 498,516 ---- ::GetCursorPos(&curs); ! m_droptarget = ::WindowFromPoint(curs); int invalid = (dwEffect == DROPEFFECT_NONE); ! if (invalid) { ! call_dSCexit(env, m_peer, curs.x, curs.y); ! m_droptarget = (HWND)NULL; ! m_enterpending = TRUE; ! } else if (m_droptarget != NULL) { ! (*(m_enterpending ? call_dSCenter : call_dSCmotion)) ! (env, m_peer, m_actions, modifiers, curs.x, curs.y); m_enterpending = FALSE; } if (m_droptarget != NULL) { ------------------------------------------------------------------------------------ ###@###.### 2002-07-24 ======================================================================
24-07-2002