Bookmarks

A PyObjC Example without documentation

Sources

Bookmarks.py

import BookmarksDocument  # noqa: F401
import DNDArrayController  # noqa: F401
import DNDTableView  # noqa: F401

if __name__ == "__main__":
    from PyObjCTools import AppHelper

    AppHelper.runEventLoop()

BookmarksDocument.py

#
#  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

DNDArrayController.py

#
#  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

DNDTableView.py

#
#  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)

setup.py

"""
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"],
)