Bookmarks
=========

* :download:`Download example <PyObjCExample-Bookmarks.zip>`

A PyObjC Example without documentation

.. rst-class:: tabber

Sources
-------

.. rst-class:: tabbertab

Bookmarks.py
............

.. sourcecode:: python

    import BookmarksDocument  # noqa: F401
    import DNDArrayController  # noqa: F401
    import DNDTableView  # noqa: F401
    
    if __name__ == "__main__":
        from PyObjCTools import AppHelper
    
        AppHelper.runEventLoop()

.. rst-class:: tabbertab

BookmarksDocument.py
....................

.. sourcecode:: python

    #
    #  BookmarksDocument.py
    #  Bookmarks
    #
    #  Converted by u.fiedler on 10.02.05.
    #
    #  The original version was written in Objective-C by Malcolm Crawford
    #  at http://homepage.mac.com/mmalc/CocoaExamples/controllers.html
    
    import objc
    from Cocoa import NSDocument, NSKeyedArchiver, NSKeyedUnarchiver, NSMutableArray
    from objc import super  # noqa: A004
    
    # BookmarksDocument defines this as it may be used for copy and paste
    # in addition to just drag and drop
    CopiedRowsType = "COPIED_ROWS_TYPE"
    
    
    class BookmarksDocument(NSDocument):
        bookmarksArray = objc.ivar("bookmarksArray")
    
        def init(self):
            self = super().init()
            if self is None:
                return None
            self.bookmarksArray = NSMutableArray.array()
            return self
    
        def windowNibName(self):
            return "BookmarksDocument"
    
        # Straightforward, standard document class
        # Allows content array to be saved, and file opened
        # Provides accessor methods for bookmarksArray
    
        # open and save -- very simple, just (un)archive bookmarksArray
        def dataRepresentationOfType_(self, aType):
            return NSKeyedArchiver.archivedDataWithRootObject_(self.bookmarksArray)
    
        def loadDataRepresentation_ofType_(self, data, aType):
            self.bookmarksArray = NSKeyedUnarchiver.unarchiveObjectWithData_(data)
            return True

.. rst-class:: tabbertab

DNDArrayController.py
.....................

.. sourcecode:: python

    #
    #  DNDArrayController.py
    #  Bookmarks
    #
    #  Converted by u.fiedler on 10.02.05.
    #
    #  The original version was written in Objective-C by Malcolm Crawford
    #  at http://homepage.mac.com/mmalc/CocoaExamples/controllers.html
    #
    #  See "Dragging Files" for a conceptual introduction:
    #  file:///Developer/ADC%20Reference%20Library/documentation/Cocoa/Conceptual/DragandDrop/index.html#//apple_ref/doc/uid/10000069i  # noqa: B950
    #  or http://developer.apple.com/documentation/Cocoa/Conceptual/DragandDrop/Tasks/DraggingFiles.html  # noqa: B950
    
    import objc
    from BookmarksDocument import CopiedRowsType
    from Cocoa import (
        NSURL,
        NSArrayController,
        NSCalendarDate,
        NSDragOperationCopy,
        NSDragOperationMove,
        NSIndexSet,
        NSMakeRange,
        NSMutableIndexSet,
        NSNotFound,
        NSTableViewDropAbove,
        NSURLPboardType,
    )
    
    MovedRowsType = "MOVED_ROWS_TYPE"
    
    
    class DNDArrayController(NSArrayController):
        # DNDArrayController is delegate and dataSource of tableView
        tableView = objc.IBOutlet()
    
        def awakeFromNib(self):
            """register for drag and drop"""
            self.tableView.registerForDraggedTypes_(
                [CopiedRowsType, MovedRowsType, NSURLPboardType]
            )
            self.tableView.setAllowsMultipleSelection_(True)
    
        def tableView_writeRows_toPasteboard_(self, tv, rows, pboard):
            # declare our own pasteboard types
            typesArray = [CopiedRowsType, MovedRowsType]
    
            # If the number of rows is not 1, then we only support our own types.
            # If there is just one row, then try to create an NSURL from the url
            # value in that row.  If that's possible, add NSURLPboardType to the
            # list of supported types, and add the NSURL to the pasteboard.
            if len(rows) != 1:
                pboard.declareTypes_owner_(typesArray, self)
            else:
                # Try to create an URL
                # If we can, add NSURLPboardType to the declared types and write
                # the URL to the pasteboard; otherwise declare existing types
                row = rows[0]
                urlString = self.arrangedObjects()[row].valueForKey_("url")
                url = None
                if urlString:
                    url = NSURL.URLWithString_(urlString)
                if urlString and url:
                    typesArray.append(NSURLPboardType)
                    pboard.declareTypes_owner_(typesArray, self)
                    url.writeToPasteboard_(pboard)
                else:
                    pboard.declareTypes_owner_(typesArray, self)
    
            # add rows array for local move
            pboard.setPropertyList_forType_(rows, MovedRowsType)
    
            # create new array of selected rows for remote drop
            # could do deferred provision, but keep it direct for clarity
            rowCopies = self.arrangedObjects()[:]
    
            # setPropertyList works here because we're using dictionaries, strings,
            # and dates; otherwise, archive collection to NSData...
            pboard.setPropertyList_forType_(rowCopies, CopiedRowsType)
            return True
    
        def tableView_validateDrop_proposedRow_proposedDropOperation_(
            self, tv, info, row, op
        ):
            dragOp = NSDragOperationCopy
            # if drag source is self, it's a move
            if info.draggingSource() == self.tableView:
                dragOp = NSDragOperationMove
            # we want to put the object at, not over,
            # the current row (contrast NSTableViewDropOn)
            tv.setDropRow_dropOperation_(row, NSTableViewDropAbove)
            return dragOp
    
        def tableView_acceptDrop_row_dropOperation_(self, tv, info, row, op):
            if row < 0:
                row = 0
            if info.draggingSource() == self.tableView:
                rows = info.draggingPasteboard().propertyListForType_(MovedRowsType)
                indexSet = self.indexSetFromRows_(rows)
                self.moveObjectsInArrangedObjectsFromIndexes_toIndex_(indexSet, row)
                # set selected rows to those that were just moved
                # Need to work out what moved where to determine proper selection...
                rowsAbove = self.rowsAboveRow_inIndexSet_(row, indexSet)
                aRange = NSMakeRange(row - rowsAbove, indexSet.count())
                indexSet = NSIndexSet.indexSetWithIndexesInRange_(aRange)
                # set selected rows to those that were just copied
                self.setSelectionIndexes_(indexSet)
                return True
    
            # Can we get rows from another document?  If so, add them, then return.
            newRows = info.draggingPasteboard().propertyListForType_(CopiedRowsType)
            if newRows:
                aRange = NSMakeRange(row, newRows.count())
                indexSet = NSIndexSet.indexSetWithIndexesInRange_(aRange)
                self.insertObjects_atArrangedObjectIndexes_(newRows, indexSet)
                self.setSelectionIndexes_(indexSet)
                return True
    
            # Can we get an URL?  If so, add a new row, configure it, then return.
            url = NSURL.URLFromPasteboard_(info.draggingPasteboard())
            if url:
                newObject = self.newObject()
                self.insertObject_atArrangedObjectIndex_(newObject, row)
                newObject.setValue_forKey_(url.absoluteString(), "url")
                newObject.setValue_forKey_(NSCalendarDate.date(), "date")
                # set selected rows to those that were just copied
                self.setSelectionIndex_(row)
                return True
            return False
    
        def moveObjectsInArrangedObjectsFromIndexes_toIndex_(self, indexSet, insertIndex):
            objects = self.arrangedObjects()
            index = indexSet.lastIndex()
            aboveInsertIndexCount = 0
            removeIndex = 0
    
            while index != NSNotFound:
                if index >= insertIndex:
                    removeIndex = index + aboveInsertIndexCount
                    aboveInsertIndexCount += 1
                else:
                    removeIndex = index
                    insertIndex -= 1
                obj = objects.objectAtIndex_(removeIndex)
                self.removeObjectAtArrangedObjectIndex_(removeIndex)
                self.insertObject_atArrangedObjectIndex_(obj, insertIndex)
                index = indexSet.indexLessThanIndex_(index)
    
        def indexSetFromRows_(self, rows):
            indexSet = NSMutableIndexSet.indexSet()
            for row in rows:
                indexSet.addIndex_(row)
            return indexSet
    
        def rowsAboveRow_inIndexSet_(self, row, indexSet):
            currentIndex = indexSet.firstIndex()
            i = 0
            while currentIndex != NSNotFound:
                if currentIndex < row:
                    i += 1
                currentIndex = indexSet.indexGreaterThanIndex_(currentIndex)
            return i

.. rst-class:: tabbertab

DNDTableView.py
...............

.. sourcecode:: python

    #
    #  DNDTableView.py
    #  Bookmarks
    #
    #  Converted by u.fiedler on 10.02.05.
    #
    #  The original version was written in Objective-C by Malcolm Crawford
    #  at http://homepage.mac.com/mmalc/CocoaExamples/controllers.html
    
    from AppKit import NSDragOperationLink, NSTableView
    from objc import super  # noqa: A004
    
    
    class DNDTableView(NSTableView):
        def draggingSourceOperationMaskForLocal_(self, flag):
            # This is a bug fix. See
            # file:///Developer/ADC%20Reference%20Library/documentation/Cocoa/Conceptual/DragandDrop/Tasks/faq.html#//apple_ref/doc/uid/20002248/BBCGGBHE  # noqa: B950
            # or http://developer.apple.com/documentation/Cocoa/Conceptual/DragandDrop/Tasks/faq.html#//apple_ref/doc/uid/20002248/BBCFIJGF  # noqa: B950
            if not flag:
                return NSDragOperationLink  # link for external dragged URLs
            return super().draggingSourceOperationMaskForLocal_(flag)

.. rst-class:: tabbertab

setup.py
........

.. sourcecode:: python

    """
    Script for building the example:
    
    Usage:
        python3 setup.py py2app
    """
    from setuptools import setup
    
    plist = {
        "CFBundleDocumentTypes": [
            {
                "CFBundleTypeExtensions": ["Bookmarks", "*"],
                "CFBundleTypeName": "Bookmarks File",
                "CFBundleTypeRole": "Editor",
                "NSDocumentClass": "BookmarksDocument",
            }
        ]
    }
    
    setup(
        name="Bookmarks",
        app=["Bookmarks.py"],
        data_files=["English.lproj"],
        options={"py2app": {"plist": plist}},
        setup_requires=["py2app", "pyobjc-framework-Cocoa"],
    )

