Coding Headstart

This is a Python version of the sample code in the CalendarStore Coding Headstart for WWDC‘07.

Note that this implementation is incomplete, the bits that should be implemented by the reader have not been implemented yet.

Sources

AppController.py

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

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.
"""
import objc
from Cocoa import NSValueTransformer, NSString, NSObject, NSDate, NSNotificationCenter
from CalendarStore import CalCalendarStore, CalCalendarsChangedExternallyNotification
from CalendarStore import CalCalendarsChangedNotification, CalEventsChangedExternallyNotification
from CalendarStore import CalEventsChangedNotification, CalPriorityHigh, CalPriorityMedium
from CalendarStore import CalTasksChangedExternallyNotification, CalTasksChangedNotification

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

# Transformer class for CalPriority->String conversion
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

from PyObjCTools import AppHelper
import objc; objc.setVerbose(True)

import AppController
import CalController

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

Resources