United StatesChange Country, Oracle Worldwide Web Sites Communities I am a... I want to...
JDK-4407521 : Cursor not modified during Drag and Drop

Details
Type:
Bug
Submit Date:
2001-01-24
Status:
Closed
Updated Date:
2003-04-12
Project Name:
JDK
Resolved Date:
2002-08-22
Component:
client-libs
OS:
windows_nt
Sub-Component:
java.awt
CPU:
x86
Priority:
P4
Resolution:
Fixed
Affected Versions:
1.3.0
Fixed Versions:
1.4.2 (mantis)

Related Reports
Relates:
Relates:
Relates:
Relates:

Sub Tasks

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
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


======================================================================
                                     
2002-07-24
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
======================================================================
                                     
2002-07-24
PUBLIC COMMENTS

.
                                     
2004-08-24
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


                                     
2004-08-24



Hardware and Software, Engineered to Work Together