Published using Google Docs
How to implement drag-and-drop in MacRuby.
Updated automatically every 5 minutes

Greetings,

Are you looking to capture inbound drags from outside your app, or drag from (for example) an NSTableView to an NSOutlineView?

Doing a drag internally just involves your source view's datasource (for a tableView in this case, obviously) implementing:

  # Drag source

  def tableView(aView, writeRows:rows, toPasteboard:pboard)

    dragging = rows.collect {|row| @all_rows[row].identifier}

    data = NSArchiver.archivedDataWithRootObject(dragging)

    pboard.declareTypes(['NSGeneralPboardType'], owner:nil)

    pboard.setData(data, forType:'NSGeneralPboardType')

    true

  end

Your destination data source needs to call something like this (I do it in awakeFromNib):

    outline.registerForDraggedTypes(["NSGeneralPboardType"])

Then implement something like this (there's some semi-complex logic here to prevent dropping on top levels, represented by hashes, in my outline; you could ignore that, but it nicely demonstrates some useful tricks like re-targeting drops):

  # Drag and drop stuff

  def outlineView(outlineView, validateDrop:info, proposedItem:item, proposedChildIndex:index)

    return NSDragOperationNone if (item.nil? || item.is_a?(Hash))

    if(index != -1)

      nearest_index = (index == 0) ? 0 : index-1

      prior = self.outlineView(self, child:nearest_index, ofItem:item)

      outlineView.setDropItem(prior, dropChildIndex:NSOutlineViewDropOnItemIndex)

    end

    return NSDragOperationEvery

  end

  def outlineView(outlineView, acceptDrop:info, item:item, childIndex:index)

    data = info.draggingPasteboard.dataForType('NSGeneralPboardType')

    rows = NSUnarchiver.unarchiveObjectWithData(data)

    NSLog("%@", rows)

    rows.each do |row|

      # Stuff

    end

    return true

  end

That's how to drag from table to outline.  The table-side validateDrop and acceptDrop methods are subtly different; I can paste up code from them if you'd like.  My table is only accepting drops from outside the application, though, so it behaves a little differently than intra-application drag/drop.

Actually, that's pretty easy, although the 'devil' is in the details of how to process 'info' in the acceptDrop method...

In awakeFromNib, since I'm interested in browser-drag-sources, I do this (yes, this can just be [...] as in the previous code, instead of NSA.aWO'ing it):

    view.registerForDraggedTypes(

      NSArray.arrayWithObjects("BookmarkDictionaryListPboardType", "MozURLType",

                               NSFilenamesPboardType, NSURLPboardType, NSStringPboardType, nil))

then...

  def tableView(aView, validateDrop:info, proposedRow:row, proposedDropOperation:op)

    NSDragOperationEvery

  end

  def tableView(aView, acceptDrop:info, row:row, dropOperation:op)

    # Accept the drop

    return true

  end

I hope the formatting is preserved at least somewhat, if not this is likely to get ugly...  :)

--  Morgan