Now that Shane showed us the wonderfully simple solution for tables, I am trying to find a similar solution for a Text field. The Text field drag and drop that is built in is not very helpful since often the dropped file gets embedded in the existing text. It also will not happen unless the text field has focus. I handled this in ASS in the “on Dropped” handler by clearing the text first and then allowing the drop. You want it to happen though after the drop so user doesn’t accidentally erase the text while hovering. I got this to work partially using a custom OBJ-C class (with Craig’s help) for the text field but it gets tricky because of the filed editor which intercepts the custom behavior.
So before I go further with the oBJ-C I thought maybe there is a way to do this too in the applescript side. So far the handlers are not being called.
applicationWillFinishLaunching_(aNotification)
my destinationField's registerForDraggedTypes_({"NSFilenamesPboardType"})
end applicationWillFinishLaunching_
on draggingEntered_(info)
return current application's NSDragOperationCopy
end draggingEntered_
on draggingUpdated_(info)
return current application's NSDragOperationCopy
end draggingUpdated_
on concludeDragOperation_(info)
return true
end concludeDragOperation_
on performDragOperation_(info)
return true
end performDragOperation_
on prepareForDragOperation_(info)
return true
end prepareForDragOperation_
on draggingExited_(info)
return true
end draggingExited_
Apparently, you need all six handlers for this to work.
What you’re wanting to do – make a text field respond while it doesn’t have focus – goes against the HI guidelines, and such things are often difficult. I suspect the only way to do it is to subclass the text field; I can’t get it to call draggingEntered_ in a delegate.
You only need a couple of those handlers, draggingEntered_ and performDragOperation_.
In the OBJ-C class I am using now it simply becomes the focus when hovered over and sets the StringValue to empty. But none of the other handlers gets called so I can’t make it happen after the drop. I may be wrong but in apps where you drag a file into a text field it gets the little green cross as it becomes active and accepts the drop which replaces any existing string. It would seem limited if you had to always click first in the field and then drag.
Craig suggested I go the subclassing route which I haven’t quite figured out yet. At least I got the table drops simplified with your example. Thanks Shane.
Rob
PS glad to know those run-only script files aren’t so hackable.
Subclassing is simple: File → New File, choosing subclass of NSObject. That gives you a new class, in which you change the parent from NSObject to NSTextField. Put your handlers in here. In IB, click on your text field, click on the Identity tab of the Inspector, and change the Class to whatever your new class is called. Save and go…
FWIW, if you want the whole window or an NSView to accept a drag, there’s no need to subclass – you just make your script a delegate of the window or view, and put the handlers in there. For some reason that doesn’t work with NSTextViews, though.
I tried making the script the delegate of the window but the handlers still don’t respond. I believe this has to do with the built in field editor overriding the custom drop handlers. From the docs on Drag and drop for TextField:
To provide a custom field editor for your text field (or any other control) you need to implement a method to respond to the NSWindow delegate message windowWillReturnFieldEditor:toObject: in the delegate of the window containing the text field you want to respond to drags. The client specified in the toObject: argument is the text field that is about to be edited, for which it uses the NSTextView object you return instead of the standard field editor.
Hmm, it works fine here. From the docs: “Either a window object or its delegate may implement these methods; however, the delegate’s implementation takes precedence if there are implementations in both places.”
Yes I got it working and the draggingEntered_ gets called and clears the text field for the new file path.
property destinationTextField : missing value
on draggingEntered_(info)
destinationTextField's setStringValue_("")
log "in"
return current application's NSDragOperationCopy
end draggingEntered_
on draggingUpdated_(info)
log "update"
return current application's NSDragOperationCopy
end draggingUpdated_
on draggingExited_(info)
log "out"
--return true
end draggingExited_
on concludeDragOperation_(info)
log "done"
--return true
end concludeDragOperation_
Ideally this would happen in the concludeDragOperation_ handler so the path is not erased before one lets the drop go. At least that is the behavior I had in the old ASS version using the “on conclude drop” handler there. The concludeDragOperation_ handler here is not being called, only the first three get called, which I believe is due to the field editor thing. I am not sure how to get the concludeDragOperation_ called and the online solutions to do this in OBJ-C are a bit complicated. Any ides appreciated greatly on this last bit.
property destinationTextField : missing value
property oldValue : ""
property mainWindow : missing value
on draggingEntered_(info)
set oldValue to destinationTextField's stringValue()
destinationTextField's setStringValue_("")
return current application's NSDragOperationCopy
end draggingEntered_
on draggingUpdated_(info)
return current application's NSDragOperationCopy
end draggingUpdated_
on draggingExited_(info)
destinationTextField's setStringValue_(oldValue)
# keeps window as first responder
mainWindow's makeFirstResponder_(missing value)
--return true
end draggingExited_
on performDragOperation_(info)
destinationTextField's setStringValue_(pasteboardFilePath(info))
return true
end performDragOperation_
on pasteboardFilePath(info)
set pb to info's draggingPasteboard
set fpaths to pb's propertyListForType_("NSFilenamesPboardType")
return fpaths's objectAtIndex_(0)
end pasteboardFilePath
on applicationWillFinishLaunching_(aNotification)
my mainWindow's registerForDraggedTypes_({"NSFilenamesPboardType"})
end applicationWillFinishLaunching_
on applicationShouldTerminate_(sender)
return current application's NSTerminateNow
end applicationShouldTerminate_
Hey - that works! I added the setStringValue_(“”) to the performDragOperation_ and now the old path is retained until the drop is let go. It is a hack maybe but looking over the swapping of field editors stuff today made my head spin. I think this is good for now and saves creating yet another class file. So now we have pretty simple ASOC methods for drag and drop in table and a modified drop for text field. Cool!
on performDragOperation_(info)
destinationTextField's setStringValue_("")
# here you would get the filepath from the pasteboard
destinationTextField's setStringValue_("did it!")
return true
end performDragOperation_
yes - I just put it together. So I made this script the delegate for the window, and for the text field too, and added these three handlers. Now dropping a file onto the text field replaces any existing string with the new path, and only after the drop is let go.
on draggingEntered_(info)
return current application's NSDragOperationCopy
end draggingEntered_
on draggingExited_(info)
mainWindow's makeFirstResponder_(missing value)
end draggingExited_
on performDragOperation_(info)
set pb to info's draggingPasteboard()
set theFiles to (pb's propertyListForType_("NSFilenamesPboardType"))
set theFile to theFiles's objectAtIndex_(0)
destinationField's setStringValue_("")
destinationField's setStringValue_(theFile)
return true
end performDragOperation_
In my app however, the text field is bound to values in the selection of a table so I had to set the array’s value to retain the value in the text field. it works now.
on performDragOperation_(info)
set pb to info's draggingPasteboard()
set theFiles to (pb's propertyListForType_("NSFilenamesPboardType"))
set theFile to theFiles's objectAtIndex_(0)
destinationField's setStringValue_("")
--destinationField's setStringValue_(theFile)
set indexnum to theTable's selectionIndex()
set theArrayValues to theTable's arrangedObjects()'s objectAtIndex_(indexnum)
theArrayValues's setValue_forKey_(theFile, "destinationPath")
return true
end performDragOperation_
I’ve been using the drag and drop below for a text field and it works fine:
on applicationWillFinishLaunching_(aNotification)
my mainWindow's registerForDraggedTypes_({"NSFilenamesPboardType"})
end applicationWillFinishLaunching_
on draggingEntered_(info)
return current application's NSDragOperationCopy
end draggingEntered_
on draggingExited_(info)
mainWindow's makeFirstResponder_(missing value)
end draggingExited_
on performDragOperation_(info)
set pb to info's draggingPasteboard()
set theFiles to (pb's propertyListForType_("NSFilenamesPboardType"))
set theFile to theFiles's objectAtIndex_(0)
destinationField's setStringValue_("")
destinationField's setStringValue_(theFile)
return true
end performDragOperation_
Now I have two text fields. Is there a way to differentiate between them so the file gets dropped on the one you are hovering over? Now the window accepts the drop basically and the performDragOperation_ handler sets the text in text filed 1.
What I want is to be able to drop a file in one text field and another file in field 2. There is the problem of which has focus since normally, without the window accepting the drop, only the initial responder accepts a drop the drop and you have to manually click in the other field to get it to work. I saw some OBJ-C code on this very topic online before but can’t find it now. Any thoughts, appreciated.
Yes, I noticed the drag info was showing location - just need to parse that out and allocate. I’ll check out the current event too. Oddly it was so easy in ASS to just attach drop to each field in IB and then allocate in the on Drop handler. Makes you wonder what they were using as the bridge in that situation.
It works! You can grab the location right off the dragging info’s draggingLocation.
on draggingEntered_(info)
return current application's NSDragOperationCopy
end draggingEntered_
on draggingExited_(info)
aWindow's makeFirstResponder_(missing value)
end draggingExited_
on performDragOperation_(info)
set drLoc to info's draggingLocation as list
set dropPosition to (y of item 1 of drLoc)
set pb to info's draggingPasteboard()
set theFiles to (pb's propertyListForType_("NSFilenamesPboardType"))
set theFile to theFiles's objectAtIndex_(0)
if 109 < dropPosition and dropPosition < 135 then
thesourceField's setStringValue_("")
thesourceField's setStringValue_(theFile)
else
thedestinationField's setStringValue_("")
thedestinationField's setStringValue_(theFile)
end if
return true
end performDragOperation_