1"""
2Abstract: Custom that handles Drag and Drop for table views by acting as a datasource.
3"""
4from Cocoa import *
5import objc
6
7class DragSupportDataSource (NSObject):
8    # all the table views for which self is the datasource
9    registeredTableViews = objc.ivar()
10
11    def init(self):
12        self = super(DragSupportDataSource, self).init()
13        if self is None:
14            return None
15
16        self.registeredTableViews = NSMutableSet.alloc().init()
17        return self;
18
19    # ******** table view data source necessities *********
20
21    # We use this method as a way of registering for drag types for all
22    # the table views that will depend on us to implement D&D. Instead of
23    # setting up innumerable outlets, simply depend on the fact that every
24    # table view will ask its datasource for number of rows.
25    def numberOfRowsInTableView_(self, aTableView):
26        # this is potentially slow if there are lots of table views
27        if not self.registeredTableViews.containsObject_(aTableView):
28            aTableView.registerForDraggedTypes_([NSStringPboardType])
29            #Cache the table views that have "registered" with us.
30            self.registeredTableViews.addObject_(aTableView)
31
32        # return 0 so the table view will fall back to getting data from
33        # its binding
34        return 0
35
36    def tableView_objectValueForTableColumn_row_(self, aView, aColumn, rowIdx):
37        # return None so the table view will fall back to getting data from
38        # its binding
39        return None
40
41    # put the managedobject's ID on the pasteboard as an URL
42    def tableView_writeRowsWithIndexes_toPasteboard_(self, tv, rowIndexes, pboard):
43        success = False
44
45        infoForBinding = tv.infoForBinding_(NSContentBinding)
46        if infoForBinding is not None:
47            arrayController = infoForBinding.objectForKey_(NSObservedObjectKey)
48            objects = arrayController.arrangedObjects().objectsAtIndexes_(
49                    rowIndexes)
50
51            objectIDs = NSMutableArray.array()
52            for i in xrange(objects.count()):
53                item = objects[i]
54                objectID = item.objectID()
55                representedURL = objectID.URIRepresentation()
56                objectIDs.append(representedURL)
57
58            pboard.declareTypes_owner_([NSStringPboardType], None)
59            pboard.addTypes_owner_([NSStringPboardType], None)
60            success = pboard.setString_forType_(
61                    objectIDs.componentsJoinedByString_(', '), NSStringPboardType)
62
63        return success
64
65    # *************** actual drag and drop work *****************
66    def tableView_validateDrop_proposedRow_proposedDropOperation_(
67            self, tableView, info, row, operation):
68
69        # Avoid drag&drop on self. This might be interersting to enable in
70        # light of ordered relationships
71        if info.draggingSource() is not tableView:
72            return NSDragOperationCopy
73        else:
74            return NSDragOperationNone
75
76    def tableView_acceptDrop_row_dropOperation_(
77            self, tableView, info, row, operation):
78
79        success = False
80        urlStrings = info.draggingPasteboard().stringForType_(NSStringPboardType)
81
82        # get to the arraycontroller feeding the destination table view
83        destinationContentBindingInfo = tableView.infoForBinding_(NSContentBinding)
84        if destinationContentBindingInfo is not None:
85
86            destinationArrayController = destinationContentBindingInfo.objectForKey_(NSObservedObjectKey)
87            sourceArrayController = None
88
89            # check for the arraycontroller feeding the source table view
90            contentSetBindingInfo = destinationArrayController.infoForBinding_(NSContentSetBinding)
91            if contentSetBindingInfo is not None:
92                sourceArrayController = contentSetBindingInfo.objectForKey_(NSObservedObjectKey)
93
94            # there should be exactly one item selected in the source controller, otherwise the destination controller won't be able to manipulate the relationship when we do addObject:
95            if (sourceArrayController is not None) and (sourceArrayController.selectedObjects().count() == 1):
96                context = destinationArrayController.managedObjectContext()
97                destinationControllerEntity = NSEntityDescription.entityForName_inManagedObjectContext_(destinationArrayController.entityName(), context)
98
99                items = urlStrings.split(', ')
100                itemsToAdd = []
101
102                for i in xrange(len(items)):
103                    urlString = items[i]
104
105                    # take the URL and get the managed object - assume
106                    # all controllers using the same context
107                    url = NSURL.URLWithString_(urlString)
108                    objectID = context.persistentStoreCoordinator().managedObjectIDForURIRepresentation_(url)
109                    if objectID is not None:
110                        object = context.objectRegisteredForID_(objectID)
111
112                        # make sure objects match the entity expected by
113                        # the destination controller, and not already there
114                        if object is not None and (object.entity() is destinationControllerEntity) and not (destinationArrayController.arrangedObjects().containsObject_(object)):
115                            itemsToAdd.append(object)
116
117                if len(itemsToAdd) > 0:
118                    destinationArrayController.addObjects_(itemsToAdd)
119                    success = True
120
121        return success
122