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