Coding Headstart

A PyObjC Example without documentation

Sources

AppController.py

import objc
from CalendarStore import CalCalendarStore, CalEvent, CalTask
from Cocoa import NSApp, NSApplication, NSDate, NSLog, NSObject


class AppController(NSObject):
    mainWindow = objc.IBOutlet()
    taskCreationDialog = objc.IBOutlet()
    priorityPopup = objc.IBOutlet()
    eventCreationDialog = objc.IBOutlet()
    calendarData = objc.IBOutlet()

    calItemTitle = objc.ivar()
    calItemStartDate = objc.ivar()
    calItemEndDate = objc.ivar()

    objc.synthesize("calItemTitle", copy=True)
    objc.synthesize("calItemStartDate", copy=True)
    objc.synthesize("calItemEndDate", copy=True)

    @objc.IBAction
    def showTaskCreationDialog_(self, sender):
        # Set default values for the title, start date and priority
        # Cocoa bindings will clear out the related fields in the sheet
        self._.calItemTitle = None
        self._.calItemStartDate = NSDate.date()
        NSApp.beginSheet_modalForWindow_modalDelegate_didEndSelector_contextInfo_(
            self.taskCreationDialog,
            self.mainWindow,
            self,
            "didEndSheet:returnCode:contextInfo:",
            None,
        )

    @objc.IBAction
    def showEventCreationDialog_(self, sender):
        # Set default values for the title and start/end date
        # Cocoa bindings will clear out the related fields in the sheet
        self._.calItemTitle = None
        self._.calItemStartDate = NSDate.date()
        self._.calItemEndDate = NSDate.dateWithTimeIntervalSinceNow_(3600)
        NSApp.beginSheet_modalForWindow_modalDelegate_didEndSelector_contextInfo_(
            self.eventCreationDialog,
            self.mainWindow,
            self,
            "didEndSheet:returnCode:contextInfo:",
            None,
        )

    # Called when the "Add" button is pressed on the event/task entry sheet
    # This starts the sheet dismissal process
    @objc.IBAction
    def dismissDialog_(self, sender):
        NSApp.endSheet_(sender.window())

    @objc.selectorFor(
        NSApplication.beginSheet_modalForWindow_modalDelegate_didEndSelector_contextInfo_
    )
    def didEndSheet_returnCode_contextInfo_(self, sheet, returnCode, contextInfo):
        # Find out which calendar was selected for the new event/task
        # We do this using the calendarData array controller which is bound to
        # the calendar popups in the sheet
        selectedCalendar = None
        count = len(self.calendarData.selectedObjects())
        if count > 0:
            selectedCalendarUID = self.calendarData.selectedObjects()[0].uid()
            selectedCalendar = CalCalendarStore.defaultCalendarStore().calendarWithUID_(
                selectedCalendarUID
            )

        # Create an event/task based on which sheet was used
        if sheet is self.taskCreationDialog:
            if self._.calItemTitle is None:
                self._.calItemTitle = "My Task"

            self.createNewTaskWithCalendar_title_priority_dueDate_(
                selectedCalendar,
                self._.calItemTitle,
                self.priorityPopup.selectedTag(),
                self._.calItemStartDate,
            )

        else:
            if self._.calItemTitle is None:
                self._.calItemTitle = "My Event"

            self.createNewEventWithCalendar_title_startDate_endDate_(
                selectedCalendar,
                self._.calItemTitle,
                self._.calItemStartDate,
                self._.calItemEndDate,
            )

        # Dismiss the sheet
        sheet.orderOut_(self)

    def createNewEventWithCalendar_title_startDate_endDate_(
        self, calendar, title, startDate, endDate
    ):
        # Create a new CalEvent object
        newEvent = CalEvent.event()

        # Set the calendar, title, start date and end date on the new event
        # using the parameters passed to this method
        newEvent._.calendar = calendar
        newEvent._.title = title
        newEvent._.startDate = startDate
        newEvent._.endDate = endDate

        # Save the new event to the calendar store (CalCalendarStore) and
        # return it
        res, err = CalCalendarStore.defaultCalendarStore().saveEvent_span_error_(
            newEvent, 0, None
        )
        if res:
            return newEvent

        NSLog("error:%@", err.localizedDescription())
        return None

    def createNewTaskWithCalendar_title_priority_dueDate_(
        self, calendar, title, priority, dueDate
    ):
        # Create a new CalTask object
        newTask = CalTask.task()

        # Set the calendar, title, priority and due date on the new task
        # using the parameters passed to this method
        newTask._.calendar = calendar
        newTask._.title = title
        newTask._.priority = priority
        newTask._.dueDate = dueDate

        # Save the new task to the calendar store (CalCalendarStore) and
        # return it
        res, err = CalCalendarStore.defaultCalendarStore().saveTask_error_(
            newTask, None
        )
        if res:
            return newTask

        NSLog("error:%@", err.localizedDescription())
        return None

CalController.py

"""
Bindings and notification support for Calendar data used
by this application.  Exposes read-only collections
(calendars, events, tasks) as observable entities.
"""
from CalendarStore import (
    CalCalendarsChangedExternallyNotification,
    CalCalendarsChangedNotification,
    CalCalendarStore,
    CalEventsChangedExternallyNotification,
    CalEventsChangedNotification,
    CalPriorityHigh,
    CalPriorityMedium,
    CalTasksChangedExternallyNotification,
    CalTasksChangedNotification,
)
from Cocoa import NSDate, NSNotificationCenter, NSObject, NSString, NSValueTransformer

highPriority = "High"
normPriority = "Normal"
lowPriority = "Low"
nonePriority = "None"


class CalPriorityToStringTransformer(NSValueTransformer):
    """
    The CalPriorityToStringTransformer class allows easy conversion between
    CalPriority values (0-9) and human-readable priority strings (High,
    Normal, Low, None). This allows us to populate the priority dropdown
    using bindings
    """

    @classmethod
    def transformedValueClass(cls):
        return type(NSString)

    @classmethod
    def allowsReverseTransformation(cls):
        return False

    def transformedValue_(self, value):
        priority = value.unsignedIntValue()
        if priority < CalPriorityHigh:
            return nonePriority

        elif priority < CalPriorityMedium:
            return highPriority

        elif priority == CalPriorityMedium:
            return normPriority

        return lowPriority


class CalController(NSObject):
    def awakeFromNib(self):
        # Register a transformer object for easy generation of
        # human-readable priority strings
        #
        # See CalPriorityToStringTransformer implementation below

        prioTransformer = CalPriorityToStringTransformer.alloc().init()
        NSValueTransformer.setValueTransformer_forName_(
            prioTransformer, "CalPriorityToStringTransformer"
        )

        # Register for notifications on calendars, events and tasks so we can
        # update the GUI to reflect any changes beneath us
        NSNotificationCenter.defaultCenter().addObserver_selector_name_object_(
            self, "calendarsChanged:", CalCalendarsChangedExternallyNotification, None
        )

        NSNotificationCenter.defaultCenter().addObserver_selector_name_object_(
            self, "calendarsChanged:", CalCalendarsChangedNotification, None
        )

        NSNotificationCenter.defaultCenter().addObserver_selector_name_object_(
            self, "eventsChanged:", CalEventsChangedExternallyNotification, None
        )

        NSNotificationCenter.defaultCenter().addObserver_selector_name_object_(
            self, "eventsChanged:", CalEventsChangedNotification, None
        )

        NSNotificationCenter.defaultCenter().addObserver_selector_name_object_(
            self, "tasksChanged:", CalTasksChangedExternallyNotification, None
        )

        NSNotificationCenter.defaultCenter().addObserver_selector_name_object_(
            self, "tasksChanged:", CalTasksChangedNotification, None
        )

    # Set up the read-only calendars/events/tasks arrays from Calendar Store
    # as observable keys for Cocoa Bindings
    # This in conjunction with the notifications will allow for immediate UI
    # updates whenever calendar data changes outside of this app
    def calendars(self):
        return CalCalendarStore.defaultCalendarStore().calendars()

    def events(self):
        store = CalCalendarStore.defaultCalendarStore()
        # Pull all events starting now from all calendars in the CalendarStore
        allEventsPredicate = (
            CalCalendarStore.eventPredicateWithStartDate_endDate_calendars_(
                NSDate.date(), NSDate.distantFuture(), store.calendars()
            )
        )
        return store.eventsWithPredicate_(allEventsPredicate)

    def tasks(self):
        store = CalCalendarStore.defaultCalendarStore()
        # Pull all uncompleted tasks from all calendars in the CalendarStore
        return store.tasksWithPredicate_(
            CalCalendarStore.taskPredicateWithUncompletedTasks_(store.calendars())
        )

    # With the observable keys set up above and the appropriate bindings in IB,
    # we can trigger UI updates just by signaling changes to the keys
    def calendarsChanged_(self, notification):
        self.willChangeValueForKey_("calendars")
        self.didChangeValueForKey_("calendars")

    def eventsChanged_(self, notification):
        self.willChangeValueForKey_("events")
        self.didChangeValueForKey_("events")

    def tasksChanged_(self, notification):
        self.willChangeValueForKey_("tasks")
        self.didChangeValueForKey_("tasks")

main.py

import AppController  # noqa: F401
import CalController  # noqa: F401
import objc
from PyObjCTools import AppHelper

objc.setVerbose(True)


AppHelper.runEventLoop()

setup.py

"""
Script for building the example.

Usage:
    python3 setup.py py2app
"""
from setuptools import setup

setup(
    name="PyCalendarStore",
    app=["main.py"],
    data_files=["English.lproj"],
    setup_requires=[
        "py2app",
        "pyobjc-framework-CalendarStore",
        "pyobjc-framework-Cocoa",
    ],
)