Drag and drop for Text field?

Hi all,

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.

Anyone do this yet?

Thanks, Rob

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

What I have read about it all points to subclassing the NSTextField or even the view that contains it

http://www.stone.com/The_Cocoa_Files/Ins_and_Outs_of_Drag_and_D.html

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.

- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>) sender
{
	NSLog(@"draggingentered");
	[self setStringValue:@""];
	[self becomeFirstResponder];
	return NSDragOperationCopy;

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.

Rob

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.

Thanks, rob

This is kind of a hack but it works. :slight_smile:


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_

Don’t make any changes until the performDragOperation_ handler, which gets called when you drop.

I know but only the first three handlers are getting called- performDragOperation_ isn’t. I’ll try Craig’s hack next.

Rob

Hi Craig, Shane,

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_

Thanks, Rob

That’s great! If you look at my post above I have added the code to get the filepath.

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_

Thanks Craig and Shane, Rob

Hi All,

Back with more details on drag and drop.

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.

Thanks, Rob

What about getting the current event (NSApp’s currentEvent()) and getting locationInWindow() of it to work out which field it’s over?

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.

Thanks, Shane

Rob

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_

Thanks, Rob