# Coding Headstart

- `Download example`

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