JDK-6421347 : Regression: Drop cursor flashes to NO_DROP temporarily when dropping
  • Type: Bug
  • Component: client-libs
  • Sub-Component: java.awt
  • Affected Version: 6
  • Priority: P3
  • Status: Closed
  • Resolution: Fixed
  • OS: windows_xp
  • CPU: x86
  • Submitted: 2006-05-03
  • Updated: 2011-03-07
  • Resolved: 2011-03-07
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.
JDK 7
7 b04Fixed
Related Reports
Relates :  
Relates :  
Relates :  
Relates :  
Description
Run the test case below, or any other drag and drop enabled Swing demo. Enter some text, drag it, and drop it in the window. The drop cursor flashes to NO_DROP temporarily. This is a very recent regression. However, it does NOT seem related to 4869264 which was recently putback. And it is NOT the same issue as 6388028 which talks about the drop cursor flashing when the action is changed.

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class DTest extends JFrame {
    
    public DTest() {
        super("DTest");
        
        JTextArea ta = new JTextArea();
        ta.setDragEnabled(true);
        getContentPane().add(new JScrollPane(ta));
    }

    private static void createAndShowGUI(String[] args) {
        DTest test = new DTest();
        test.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        test.setSize(400, 400);
        test.setLocationRelativeTo(null);
        test.setVisible(true);
    }
    
    public static void main(final String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createAndShowGUI(args);
            }
        });
    }
}

Comments
SUGGESTED FIX JCK tests were passed. Webrevs: http://javaweb.sfbay/jcg/1.7.0-dolphin/awt/6421347/
08-11-2006

EVALUATION An architect solution for changing cursor to custom form made synchronous as it is required by MS Spec. The main idea is in making synchronous calls to Java with conserving ability to invoke back-calls on AWT thread. The last is the reason why we have to start secondary loop. Pragmatic reason to make the calls synchronous is that drag-drop notification events have to be dispatched at the same point where the native events was thrown. That is the only guaranty of correct OLE drag-drop session and adequate custom cursor form setup. With synchronous approach we except "overlapped" OS and Java cursor change reactions. Java thread AWT thread | | | [OS D&D event]==>[start IDragSource callback] [start java hndl]<----(post java event)------[ ] [AWT obj req ]<==========================>[ start secondary loop ] [exit java hndl ]===========================>[ exit secondary loop ] | [exit IDragSource callback ] | | There is a problem with custom D&D cursor form setup (MS bug 6480706). To implement workaround we are using m_bRestoreNodropCustomCursor flag to restore custom form of NO_DROP cursor that could be unreasonably overwritten by OS in some particular cases (please read CR 6480706). Implemented workaround (in opposite to previous solution) does not produce NO_DROP - COPY cursor flicking in case when drag source and drop target are the same window (text control as topical example). Calls sequence for NO_DROP area: -------------------------------- start DoDragDrop IDragSource::QueryContinueDrag cursor form changing is forbidden at the first call (m_bRestoreNodropCustomCursor is false) In other case we have NO_DROP - COPY cursor flicking IDragSource::GiveFeedback cursor form could be changed by Java code here m_bRestoreNodropCustomCursor becomes true [just after IDragSource::GiveFeedback OS can unreasonably overwrite cursor to default in some particular cases] {IDragSource::QueryContinueDrag}[,..n] if m_bRestoreNodropCustomCursor is true - check cursor form, restore form from system default (if need), drop m_bRestoreNodropCustomCursor to false Calls sequence for over drop target: ------------------------------------ start DoDragDrop {IDragSource::QueryContinueDrag IDragSource::GiveFeedback}[,..n] each QueryContinueDrag call balanced by GiveFeedback. Cursor form are conserving. m_bRestoreNodropCustomCursor is permanently false that gives a change for drop target to change cursor form accordingly desired drop operation.
21-10-2006

EVALUATION Drag & drop procedure made compatible with MS OLE specification. Start, change, and drop flashing of OLE cursor was fixed, but due to reported MS bug 6480706 we have to implement workaround for custom cursor in method AwtDragSource::QueryContinueDrag. After the MS fix the only place where we call native WIN32 SetCursor for drag&drop operation will AwtDragSource::GiveFeedback. Fix summary: 1. In accordance with MS specification the only acceptable place to change GUI(visible) cursor is AwtDragSource::GiveFeedback callback method. 2. Cursor form selection and GUI(visible) cursor installation was separated into different functions (AwtDragSource::SetCursor and AwtDragSource::ChangeCursor respectively), because it is independent actions in case of drag&drop. 3. Drag source callback notifications to Java made synchronous to correspond specification.
18-10-2006

SUGGESTED FIX I've filed bug 6459849 in order to determine whether the JCK test mentioned above is correct. If the test may be excluded or revised, then the fix that changes DragSourceContext may be used; otherwise we can revert to the earlier variant of the fix.
14-08-2006

SUGGESTED FIX The previous fix works perfectly, however one might regard the fix as a hack since it uses Reflection API to find out whether a custom DnD cursor is used or default DnD cursor are used in the current DnD operation. (The value of private field DragSourceContext.useCustomCursor is read.) Another variant of the fix is: 1) not to set the initial cursor in DragSourceContext's constructor if default cursors are used (thus getCursor() on an instance of DragSourceContext returns null just after construction if null cursor is specified as a paramater of the constructor); 2) do not set the cursor before invoking DoDragDrop() in awt_DnDDS.cpp if the cursor is null (this condition means that default cursors are used); 3) do not sets the cursor back to the initial one after DoDragDrop() returns. I verified that null cursor is already handled properly in XAWT and MAWT: the cursor remains unchanged. A visible side effect of the fix is that if default cursors are used, then NoDrop cursor does not appear for a while in the beginning of a DnD operation in contrast to as that was before. This effect seems good. However with this fix JCK test javasoft.sqe.tests.api.java.awt.dnd.DragSourceContext.CtorTests.DragSourceContext0004 fails because the test constructs an instance of DragSourceContex with null cursor parameter and expects its getCursor() method to return some non-null cursor (with the fix getCursor() returns null in this case). The test verifies an unspecified behavior. Should discuss with JCK whether the test is valid. Below is the fix in question. --- DragSourceContext.java Thu Aug 10 13:37:22 2006 *************** *** 190,197 **** trigger.getSourceAsDragGestureRecognizer().getSourceActions(); useCustomCursor = (dragCursor != null); - - updateCurrentCursor(trigger.getDragAction(), getSourceActions(), DEFAULT); } /** --- 190,195 ---- --- awt_DnDDS.cpp Thu Aug 10 20:01:35 2006 *************** *** 66,72 **** DWORD effects = DROPEFFECT_NONE; JNIEnv* env = (JNIEnv *)JNU_GetEnv(jvm, JNI_VERSION_1_2); jobject peer = env->NewLocalRef(dragSource->GetPeer()); ! HCURSOR initialCursor = dragSource->SetCursor(sdrp->cursor); env->DeleteGlobalRef(sdrp->cursor); delete sdrp; --- 66,75 ---- DWORD effects = DROPEFFECT_NONE; JNIEnv* env = (JNIEnv *)JNU_GetEnv(jvm, JNI_VERSION_1_2); jobject peer = env->NewLocalRef(dragSource->GetPeer()); ! ! if (!JNU_IsNull(env, sdrp->cursor)) { ! dragSource->SetCursor(sdrp->cursor); ! } env->DeleteGlobalRef(sdrp->cursor); delete sdrp; *************** *** 92,106 **** } dragSource->m_dwPerformedDropEffect = DROPEFFECT_NONE; - if (initialCursor != NULL) { - if (m_isOldCursorCustom == TRUE) { - AwtToolkit::GetInstance().NotifyCustomCursor(); - } else { - AwtToolkit::GetInstance().NotifySystemCursor(); - } - ::SetCursor(initialCursor); - } - call_dSCddfinished(env, peer, res == DRAGDROP_S_DROP && effects != DROPEFFECT_NONE, convertDROPEFFECTToActions(effects), dragSource->m_dragPoint.x, dragSource->m_dragPoint.y); --- 95,100 ----
11-08-2006

SUGGESTED FIX Do not set the cursor before invoking DoDragDrop() if default cursors are used; do not sets the cursor back to the initial one after DoDragDrop() returns. --- WDragSourceContextPeer.java Fri Aug 4 19:28:58 2006 -------------------------------------------------------------------------------- *** 12,26 **** --- 12,29 ---- import java.awt.datatransfer.Transferable; import java.awt.dnd.DragGestureEvent; import java.awt.dnd.InvalidDnDOperationException; + import java.awt.dnd.DragSourceContext; import java.awt.event.InputEvent; import java.util.Map; + import java.lang.reflect.Field; + import sun.awt.Mutex; import sun.awt.dnd.SunDragSourceContextPeer; /** -------------------------------------------------------------------------------- *** 69,81 **** setNativeContext(nativeCtxtLocal); WDropTargetContextPeer.setCurrentJVMLocalSourceTransferable(trans); ! doDragDrop(getNativeContext(), getCursor()); } /** * downcall into native code */ native long createDragSource(Component component, --- 72,119 ---- setNativeContext(nativeCtxtLocal); WDropTargetContextPeer.setCurrentJVMLocalSourceTransferable(trans); ! boolean isCustomCursorUsed = true; ! DragSourceContext dsc = getDragSourceContext(); ! // get the value of the boolean field DragSourceContext.useCustomCursor ! Field field = getDragSourceContextUseCustomCursorField(); ! if (field != null) { ! try { ! isCustomCursorUsed = field.getBoolean(dsc); ! } catch(IllegalAccessException e) { } + } + doDragDrop(getNativeContext(), (isCustomCursorUsed ? getCursor() : null)); + } + + private static Field dragSourceContextUseCustomCursorField; + + private static synchronized Field getDragSourceContextUseCustomCursorField() { + if (dragSourceContextUseCustomCursorField == null) { + dragSourceContextUseCustomCursorField = + (Field)java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction() { + public Object run() { + Field field = null; + try { + field = DragSourceContext.class. + getDeclaredField("useCustomCursor"); + field.setAccessible(true); + return field; + } catch (SecurityException e) { + } catch (NoSuchFieldException e) { + } + return null; + } + }); + } + return dragSourceContextUseCustomCursorField; + } + /** * downcall into native code */ native long createDragSource(Component component, --- awt_DnDDS.cpp Fri Aug 4 19:28:07 2006 -------------------------------------------------------------------------------- *** 64,75 **** StartDragRec* sdrp = (StartDragRec*)param; AwtDragSource* dragSource = sdrp->dragSource; DWORD effects = DROPEFFECT_NONE; JNIEnv* env = (JNIEnv *)JNU_GetEnv(jvm, JNI_VERSION_1_2); jobject peer = env->NewLocalRef(dragSource->GetPeer()); - HCURSOR initialCursor = dragSource->SetCursor(sdrp->cursor); env->DeleteGlobalRef(sdrp->cursor); delete sdrp; HRESULT res; --- 64,78 ---- StartDragRec* sdrp = (StartDragRec*)param; AwtDragSource* dragSource = sdrp->dragSource; DWORD effects = DROPEFFECT_NONE; JNIEnv* env = (JNIEnv *)JNU_GetEnv(jvm, JNI_VERSION_1_2); jobject peer = env->NewLocalRef(dragSource->GetPeer()); + if (!JNU_IsNull(env, sdrp->cursor)) { + dragSource->SetCursor(sdrp->cursor); + } + env->DeleteGlobalRef(sdrp->cursor); delete sdrp; HRESULT res; -------------------------------------------------------------------------------- *** 90,108 **** if (effects == DROPEFFECT_NONE && dragSource->m_dwPerformedDropEffect != DROPEFFECT_NONE) { effects = dragSource->m_dwPerformedDropEffect; } dragSource->m_dwPerformedDropEffect = DROPEFFECT_NONE; - if (initialCursor != NULL) { - if (m_isOldCursorCustom == TRUE) { - AwtToolkit::GetInstance().NotifyCustomCursor(); - } else { - AwtToolkit::GetInstance().NotifySystemCursor(); - } - ::SetCursor(initialCursor); - } - call_dSCddfinished(env, peer, res == DRAGDROP_S_DROP && effects != DROPEFFECT_NONE, convertDROPEFFECTToActions(effects), dragSource->m_dragPoint.x, dragSource->m_dragPoint.y); env->DeleteLocalRef(peer); --- 93,102 ----
04-08-2006

EVALUATION The previous fix (it removes the code that restores the cursor just after DoDragDrop() returns) does not completely resolve the issue. I analysed the whole cursor updating machinery. We set null cursor in a window class used in the AWT native code, therefore, according to MSND, the only way to change the cursor shape is to invoke SetCursor(). I examined all invocations of this function in AWT code. NoDrop cursor is not set after a drop in the scenario in question. However sometimes NoDrop cursor is visible for a moment just after a drop. This makes me think that the issue is not a Java bug. We can adapt to this bogus behavior. In addition to the previous fix don't set the cursor just before invoking DoDragDrop() if default DnD cursors are used for the current DnD operation. This change fixes the issue and seems to be safe because default cursors are continually updated in the course of a DnD operation.
04-08-2006

EVALUATION AwtDragSource::_DoDragDrop() is invoked to start a DnD operation. This function sets an initial cursor (it's NoDrop), invokes Windows function DoDragDrop() that carries out an OLE DnD operation, and then sets the cursor back to the initial one. Then the cursor is updated ordinarily on WM_SETCURSOR. So for a moment one might see NoDrop cursor. The code that restores the cursor after a drop exists since the creation of the file awt_DnDDS.cpp. I see no reason to restore the cursor. The cursor is updated on WM_SETCURSOR after a drop.
28-07-2006

SUGGESTED FIX Remove the piece of code that sets the cursor back to the initial one. --- awt_DnDDS.cpp Fri Jul 28 19:05:46 2006 *************** *** 66,73 **** DWORD effects = DROPEFFECT_NONE; JNIEnv* env = (JNIEnv *)JNU_GetEnv(jvm, JNI_VERSION_1_2); jobject peer = env->NewLocalRef(dragSource->GetPeer()); - HCURSOR initialCursor = dragSource->SetCursor(sdrp->cursor); env->DeleteGlobalRef(sdrp->cursor); delete sdrp; --- 66,74 ---- DWORD effects = DROPEFFECT_NONE; JNIEnv* env = (JNIEnv *)JNU_GetEnv(jvm, JNI_VERSION_1_2); jobject peer = env->NewLocalRef(dragSource->GetPeer()); + dragSource->SetCursor(sdrp->cursor); + env->DeleteGlobalRef(sdrp->cursor); delete sdrp; *************** *** 92,106 **** } dragSource->m_dwPerformedDropEffect = DROPEFFECT_NONE; - if (initialCursor != NULL) { - if (m_isOldCursorCustom == TRUE) { - AwtToolkit::GetInstance().NotifyCustomCursor(); - } else { - AwtToolkit::GetInstance().NotifySystemCursor(); - } - ::SetCursor(initialCursor); - } - call_dSCddfinished(env, peer, res == DRAGDROP_S_DROP && effects != DROPEFFECT_NONE, convertDROPEFFECTToActions(effects), dragSource->m_dragPoint.x, dragSource->m_dragPoint.y); --- 93,98 ----
28-07-2006

EVALUATION Looks like the problem is a native behavior. In our case a timing has place. I could reproduce the same problem with Tiger. For this I just put Sleep after DoDragDrop() Win32 function invocation. From my point of view. The issue is a native behavior peculiar to OLE DnD which we use in our windows implementation. At the moment I am trying to reproduce NO_DROP pointer at the moment of drop with pure native target and source applications. I noticed that at the moment of drop a pointer flashed which was set before 'drag & drop'. In other words if I set a pointer using SetCursor() Win32 API method before DoDragDrop() invocation I see the same pointer before drag complete. As far as we set pointer to NO_DROP in the period from creating SourceContext to receiving first mouse event we see the same pointer (NO_DROP) at the moment of drop. I have not found that we set pointer in NO_DROP at the moment of drop in our native code. So there could be some mistake in using of OLE DnD API (which I have not found) or just native behavior.
10-07-2006

EVALUATION At the moment the next regression using SwingSet2 was found: 1. After fix in Progressbar Demo if I try drag a text I see COPY cursor. Without the fix cursor is NO_DROP during drag. Looks like that is a common problem the same is with tree demo. In other words after the fix if a drop into the current target is not possible instead of NO_DROP cursor COPY cursor is shown. I
12-05-2006

EVALUATION After some investigations it was figured out that the fix suggested for updateCurrentCursor(DEFAULT/ENTER) fixes as problem in the begining of drag as a problem with NO_DROP when drag is completed. It occurs because of we save a cursor when we are starting drag in a native structure and restore it when finish the drag. But there were found bad regressions. I continue to work on this.
12-05-2006

EVALUATION I have found a host there the problem is reproducible always. I am working on the problem.
11-05-2006

EVALUATION I'd like to clear something up. Previous evaluation talks about the NO_DROP cursor being shown at the "beginning" of a drag operation. I see that, and agree it is not a regression in any recent build (although it'll be nice to fix). The problem I'm seeing is the NO_DROP cursor when I release the mouse to drop.
05-05-2006

SUGGESTED FIX The previous suggested fix is just a code clean up. The real fix should be next. ------- DragSourceContext.java ------- 194c194 < updateCurrentCursor(trigger.getDragAction(), getSourceActions(), DEFAULT); --- > updateCurrentCursor(trigger.getDragAction(), getSourceActions(), ENTER);
05-05-2006

EVALUATION I took another look at the issue. I have found that the root of the problem is different. During DragSourceContext constructing we invoce updateCurrentCursor() method with DEFAULT cursor as third argument. If the third argument is DEFAULT we set targetAct in DnDConstants.ACTION_NONE. Afterwards an intersection with targetAct (which is DnDConstants.ACTION_NONE) returns us DnDConstants.ACTION_NONE. And in DragSourceContext constructor we update cursor in ACTION_NONE. I looked at source control history and found out that the code was changed many time ago. So I tried to reproduce the problem with 1.4.1_05-b01 the issue is reproducible. I looked at JavaDoc for DragSourceContext.DEFAULT /** * An <code>int</code> used by updateCurrentCursor() * indicating that the <code>Cursor</code> should change * to the default (no drop) <code>Cursor</code>. */ So default Cursor is no drop. Looks like idea was to set cursor in NO_DROP state while first drag event would not be received. But this is incorrect. If we assume that drag is started only after first drag event we should not change the cursor. Otherwise we have to set set cursor according to target and source actions. As far as it does not matter which status we will pass as third argument ( ENTER, OVER or CHANGED) I suppose we should pass ENTER.
05-05-2006

EVALUATION Looks like we use arguments of DragSourceContext.updateCurrentCursor() method's were mixed up in wrong order. according to JavaDoc we should pass source actions as a first argument and target action as a second one. * * @param sourceAct the actions supported by the drag source * @param targetAct the drop target action * @param status one of the fields <code>DEFAULT</code>, * <code>ENTER</code>, <code>OVER</code>, * <code>CHANGED</code> */ protected synchronized void updateCurrentCursor(int sourceAct, int targetAct, int status) { But when we are constructing DragSourceContext we invoke updateCurrentCursor() in the next way 194 updateCurrentCursor(trigger.getDragAction(), getSourceActions(), DEFAULT);
04-05-2006

SUGGESTED FIX At first we have to revert arguments order. I have not tested it enough may be the fix is not completed yet. ------- DragSourceContext.java ------- 194c194 < updateCurrentCursor(trigger.getDragAction(), getSourceActions(), DEFAULT); --- > updateCurrentCursor(getSourceActions(), trigger.getDragAction(), DEFAULT);
04-05-2006

EVALUATION I'm observing NO_DROP cursor appearing for a short time when drop the text over the same window. This happens at least with JDK6.0b71 and b82. I'm testing on Windows2000.
04-05-2006