Skip to content

Commit

Permalink
Update to EventKit framework
Browse files Browse the repository at this point in the history
Plus: test to see if we have permission to access/edit calendars and events, if not - promtp
Various code cleanups
  • Loading branch information
pjrobertson committed Feb 14, 2022
1 parent 08c00cd commit 33e8c2d
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 83 deletions.
8 changes: 4 additions & 4 deletions CalendarPlugin.xcodeproj/project.pbxproj
Expand Up @@ -9,7 +9,7 @@
/* Begin PBXBuildFile section */
80507E7115DD0FD500BBB0B0 /* bookmark.icns in Resources */ = {isa = PBXBuildFile; fileRef = 80507E7015DD0FD500BBB0B0 /* bookmark.icns */; };
8D1AC9700486D14A00FE50C9 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DD92D38A0106425D02CA0E72 /* Cocoa.framework */; };
CDBAD6CB10B9625E000D31A3 /* CalendarStore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CDBAD6CA10B9625E000D31A3 /* CalendarStore.framework */; };
CD3E39F727B9DBD900A226FF /* EventKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CD3E39F627B9DBD900A226FF /* EventKit.framework */; };
CDDF71A61585FD2400204B71 /* QSCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CDDF71A41585FD2400204B71 /* QSCore.framework */; };
CDDF71A71585FD2400204B71 /* QSFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CDDF71A51585FD2400204B71 /* QSFoundation.framework */; };
E1EAB046068128A200774DFF /* QSiCalModule.m in Sources */ = {isa = PBXBuildFile; fileRef = E1EAB045068128A200774DFF /* QSiCalModule.m */; };
Expand All @@ -24,7 +24,7 @@
80507E7015DD0FD500BBB0B0 /* bookmark.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = bookmark.icns; sourceTree = "<group>"; };
8D1AC9730486D14A00FE50C9 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
8D1AC9740486D14A00FE50C9 /* Calendar Plugin.qsplugin */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Calendar Plugin.qsplugin"; sourceTree = BUILT_PRODUCTS_DIR; };
CDBAD6CA10B9625E000D31A3 /* CalendarStore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CalendarStore.framework; path = /System/Library/Frameworks/CalendarStore.framework; sourceTree = "<absolute>"; };
CD3E39F627B9DBD900A226FF /* EventKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = EventKit.framework; path = System/Library/Frameworks/EventKit.framework; sourceTree = SDKROOT; };
CDDF71A41585FD2400204B71 /* QSCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = QSCore.framework; sourceTree = BUILT_PRODUCTS_DIR; };
CDDF71A51585FD2400204B71 /* QSFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = QSFoundation.framework; sourceTree = BUILT_PRODUCTS_DIR; };
DD92D38A0106425D02CA0E72 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = /System/Library/Frameworks/Cocoa.framework; sourceTree = "<absolute>"; };
Expand All @@ -37,8 +37,8 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
CD3E39F727B9DBD900A226FF /* EventKit.framework in Frameworks */,
8D1AC9700486D14A00FE50C9 /* Cocoa.framework in Frameworks */,
CDBAD6CB10B9625E000D31A3 /* CalendarStore.framework in Frameworks */,
CDDF71A61585FD2400204B71 /* QSCore.framework in Frameworks */,
CDDF71A71585FD2400204B71 /* QSFoundation.framework in Frameworks */,
);
Expand Down Expand Up @@ -92,10 +92,10 @@
2E58F364FFB232C311CA0CBA /* Frameworks */ = {
isa = PBXGroup;
children = (
CD3E39F627B9DBD900A226FF /* EventKit.framework */,
CDDF71A41585FD2400204B71 /* QSCore.framework */,
CDDF71A51585FD2400204B71 /* QSFoundation.framework */,
DD92D38A0106425D02CA0E72 /* Cocoa.framework */,
CDBAD6CA10B9625E000D31A3 /* CalendarStore.framework */,
);
name = Frameworks;
sourceTree = "<group>";
Expand Down
14 changes: 9 additions & 5 deletions Info.plist
Expand Up @@ -17,9 +17,13 @@
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>1.2.0</string>
<string>1.2.1</string>
<key>CFBundleVersion</key>
<string>11F</string>
<string>120</string>
<key>NSCalendarsUsageDescription</key>
<string>Create and view calendar items from within Quicksilver</string>
<key>NSRemindersUsageDescription</key>
<string>Create new todo reminders</string>
<key>NSPrincipalClass</key>
<string>QSiCalModule</string>
<key>QSActions</key>
Expand Down Expand Up @@ -74,13 +78,13 @@
&lt;p&gt;This plugin adds actions for creating events and To-Dos from Quicksilver&lt;/p&gt;
&lt;h3&gt;Actions&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Create Calendar Event&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This action takes text from Quicksilver's 1st pane, and adds it as an event to your selected Calendar in Quicksilver's 3rd pane.&lt;br&gt;
&lt;p&gt;This action takes text from Quicksilver&apos;s 1st pane, and adds it as an event to your selected Calendar in Quicksilver&apos;s 3rd pane.&lt;br&gt;
Examples of text could be &lt;code&gt;Dinner with Tom next Tuesday&lt;/code&gt; or &lt;code&gt;Visit mum at 3pm&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;You can improve how an event displays in your Calendar by separating the name of the event and the time it takes place with two dashes &lt;code&gt;--&lt;/code&gt;&lt;br&gt;
Using &lt;code&gt;Next Tuesday -- Dinner with Tom&lt;/code&gt; will create an event called just &lt;code&gt;Dinner with Tom&lt;/code&gt; in your calendar.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Create Calendar To-Do&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This action takes text from Quicksilver's 1st pane, and adds it as an To-Do to your selected Calendar in Quicksilver's 3rd pane. &lt;br&gt;
If you do not see all your calendars appear in Quicksilver's 3rd pane, this is because not all calendars support To-Dos.&lt;br&gt;
&lt;p&gt;This action takes text from Quicksilver&apos;s 1st pane, and adds it as an To-Do to your selected Calendar in Quicksilver&apos;s 3rd pane. &lt;br&gt;
If you do not see all your calendars appear in Quicksilver&apos;s 3rd pane, this is because not all calendars support To-Dos.&lt;br&gt;
Examples of text could be &lt;code&gt;Do the shopping&lt;/code&gt; or &lt;code&gt;Pick up the kids from school&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;You can set the priority of an event by prefixing your event with any number of exclamation marks &lt;code&gt;!&lt;/code&gt;&lt;br&gt;
&lt;em&gt;One&lt;/em&gt; exclamantion mark gives the To-Do a &lt;em&gt;low&lt;/em&gt; priority, &lt;em&gt;two&lt;/em&gt; give the To-Do a &lt;em&gt;medium&lt;/em&gt; priority and &lt;em&gt;three&lt;/em&gt; gives it a &lt;em&gt;high&lt;/em&gt; priority.&lt;/p&gt;
Expand Down
5 changes: 4 additions & 1 deletion QSiCalModule.h
Expand Up @@ -7,9 +7,12 @@
//

#import "QSiCalModule.h"

#import <EventKit/EventKit.h>
@interface QSiCalModule : NSObject
{
EKEventStore *eventStore;
NSArray * _remindersCalendars;
NSArray * _eventsCalendars;
}
@end

184 changes: 111 additions & 73 deletions QSiCalModule.m
Expand Up @@ -7,7 +7,6 @@
//

#import "QSiCalModule.h"
#import <CalendarStore/CalendarStore.h>


#define dayAttributes [NSDictionary dictionaryWithObjectsAndKeys:style,NSParagraphStyleAttributeName,[NSFont fontWithName:@"Helvetica Bold" size:54], NSFontAttributeName,[NSColor colorWithCalibratedWhite:0.2 alpha:1.0],NSForegroundColorAttributeName,nil]
Expand All @@ -17,48 +16,90 @@

@implementation QSiCalModule

- (NSArray *)validIndirectObjectsForAction:(NSString *)action directObject:(QSObject *)dObject{
NSArray *listOfCalendars = [[CalCalendarStore defaultCalendarStore] calendars];

NSMutableArray *array = [[NSMutableArray alloc] initWithCapacity:[listOfCalendars count]];
if (!listOfCalendars) {
[[NSAlert alertWithMessageText:@"iCal Error" defaultButton:@"OK" alternateButton:nil otherButton:nil informativeTextWithFormat:@"You need to upgrade your calendars to a format compatible with Mac OS X Leopard by opening iCal first"] runModal];
- (id)init {
if (self = [super init]) {
eventStore = [[EKEventStore alloc] initWithAccessToEntityTypes:(EKEntityMaskEvent | EKEntityMaskReminder)];
[self requestAccessForType:EKEntityTypeReminder];
[self requestAccessForType:EKEntityTypeEvent];
_eventsCalendars = nil;
_remindersCalendars = nil;
}
for (CalCalendar *eachItem in listOfCalendars) {
if(!([[eachItem type] isEqualToString:@"Birthday"]))
{
if (![eachItem isEditable]) {
continue;
}

if ([action isEqualToString:kQSiCalCreateToDoAction]) {
// some calendars don't support adding todos, they just throw an error when you try to create a task. We have to do a bit of trickery here to determine if they can support tasks or not by attempting to add one and then listening for the err.
NSError *err = nil;
CalTask *testTask = [CalTask task];
[testTask setCalendar:eachItem];
[[CalCalendarStore defaultCalendarStore] saveTask:testTask error:&err];
// [err code] gives 1025 when "The xxx calendar does not support tasks"
if (err) {
continue;
} else {
// remove the test task from the calendar
[[CalCalendarStore defaultCalendarStore] removeTask:testTask error:nil];
}
}

QSObject *object = [QSObject objectWithName:[eachItem title]];
[object setDetails:@"Calendar"];
[object setIcon:[QSResourceManager imageNamed:@"calendarIcon"]];
[object setObject:[eachItem title] forType:@"QSICalCalendar"];
[object setObject:[eachItem uid] forMeta:@"QSiCalCalendarUID"];
[array addObject:object];


}
return self;
}

- (void)dealloc {
[eventStore release];
[super dealloc];
}

- (void)requestAccessForType:(EKEntityType) type {
[eventStore requestAccessToEntityType:type completion:^(BOOL granted, NSError *error){
if (!granted) {
// Display a message if the user has denied or restricted access to Calendar
NSBeep();
NSString *message = [NSString stringWithFormat:@"Quicksilver requires access to your %@ to edit and add %@. System preferences will now be opened for you to grant access.", type == EKEntityTypeEvent ? @"Calendars" : @"Reminders", type == EKEntityTypeEvent ? @"calendar events" : @"reminders"];
QSGCDMainSync(^{
[[NSAlert alertWithMessageText:@"Access Required" defaultButton:@"OK" alternateButton:nil otherButton:nil informativeTextWithFormat:message] runModal];
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"x-apple.systempreferences:com.apple.preference.security?Privacy"]];
});

}
}];
}
- (NSArray *)remindersCalendars {
if (!_remindersCalendars) {
NSArray *reminders = [[eventStore calendarsForEntityType:EKEntityTypeReminder] arrayByEnumeratingArrayUsingBlock:^id(EKCalendar *cal) {
if (!cal.allowsContentModifications || (cal.allowedEntityTypes & EKEntityMaskReminder) != EKEntityMaskReminder) {
return nil;
}
return cal;
}];
[reminders retain];
_remindersCalendars = reminders;
}
return [array autorelease];

return _remindersCalendars;
}

- (NSArray *)eventsCalendars {
if (!_eventsCalendars) {
NSArray *calendars = [[eventStore calendarsForEntityType:EKEntityTypeEvent] arrayByEnumeratingArrayUsingBlock:^id(EKCalendar *cal) {
if (cal.type == EKCalendarTypeBirthday || !cal.allowsContentModifications || (cal.allowedEntityTypes & EKEntityMaskEvent) != EKEntityMaskEvent) {
return nil;
}
return cal;
}];
[calendars retain];
_eventsCalendars = calendars;
}
return _eventsCalendars;
}

- (NSArray *)validIndirectObjectsForAction:(NSString *)action directObject:(QSObject *)dObject{

if ([action isEqualToString:kQSiCalCreateToDoAction]) {
NSArray *reminderCalendars = [self remindersCalendars];
NSArray *objs = [reminderCalendars arrayByEnumeratingArrayUsingBlock:^id(EKCalendar *cal) {
QSObject *object = [QSObject objectWithName:cal.title];
[object setDetails:@"Calendar"];
[object setIcon:[QSResourceManager imageNamed:@"calendarIcon"]];
[object setObject:cal.title forType:@"QSICalCalendar"];
[object setObject:cal.calendarIdentifier forMeta:@"QSiCalCalendarUID"];
return object;
}];
return objs;
} else if ([action isEqualToString:@"QSiCalCreateEventAction"]) {
NSArray *eventsCalendars = [self eventsCalendars];
NSArray *objs = [eventsCalendars arrayByEnumeratingArrayUsingBlock:^id(EKCalendar *cal) {
QSObject *object = [QSObject objectWithName:cal.title];
[object setDetails:@"Calendar"];
[object setIcon:[QSResourceManager imageNamed:@"calendarIcon"]];
[object setObject:cal.title forType:@"QSICalCalendar"];
[object setObject:cal.calendarIdentifier forMeta:@"QSiCalCalendarUID"];
return object;
}];
return objs;
}
return nil;
}

//NSLog(@"objects %@ %@",dObject,iObject);
Expand All @@ -74,27 +115,26 @@ -(QSObject *)createEvent:(QSObject *)dObject inCalendar:(QSObject *)iObject{
NSDate *date=[NSCalendarDate dateWithNaturalLanguageString:dateString];
if (!date) date=[NSDate date];

CalEvent *newEvent = [CalEvent event];
NSArray *listOfCalendars = [[CalCalendarStore defaultCalendarStore] calendars];
CalCalendar *theCalendar = nil;
EKEvent *newEvent = [EKEvent eventWithEventStore:eventStore];
EKCalendar *theCalendar = nil;
if (iObject) {
theCalendar = [[CalCalendarStore defaultCalendarStore] calendarWithUID:[iObject objectForMeta:@"QSiCalCalendarUID"]];
} else if ([listOfCalendars count]) {
theCalendar = [eventStore calendarWithIdentifier:[iObject objectForMeta:@"QSiCalCalendarUID"]];
} else if ([[self eventsCalendars] count]) {
// use a default calendar
theCalendar = [listOfCalendars objectAtIndex:0];
theCalendar = [[self eventsCalendars] objectAtIndex:0];
} else {
NSBeep();
QSiCalNotif(@"Failed to create event", @"No calendars to set the event for");
return nil;
}

[newEvent setCalendar:theCalendar];
[newEvent setTitle:subjectString];
[newEvent setStartDate:date];
[newEvent setEndDate:[date dateByAddingTimeInterval:60*60]];
newEvent.calendar = theCalendar;
newEvent.title = subjectString;
newEvent.startDate = date;
newEvent.endDate = [date dateByAddingTimeInterval:60*60];

NSError *err = nil;
[[CalCalendarStore defaultCalendarStore] saveEvent:newEvent span:CalSpanAllEvents error:&err];
[eventStore saveEvent:newEvent span:EKSpanThisEvent commit:YES error:&err];

if (err) {
NSBeep();
Expand All @@ -113,14 +153,13 @@ -(QSObject *)createToDo:(QSObject *)dObject inCalendar:(QSObject *)iObject{

NSString *subjectString = [dObject stringValue];

CalTask *newTask = [CalTask task];
NSArray *listOfCalendars = [[CalCalendarStore defaultCalendarStore] calendars];
CalCalendar *theCalendar = nil;
EKReminder *newTask = [EKReminder reminderWithEventStore:eventStore];
EKCalendar *theCalendar = nil;
if (iObject) {
theCalendar = [[CalCalendarStore defaultCalendarStore] calendarWithUID:[iObject objectForMeta:@"QSiCalCalendarUID"]];
} else if ([listOfCalendars count]) {
theCalendar = [eventStore calendarWithIdentifier:[iObject objectForMeta:@"QSiCalCalendarUID"]];
} else if ([[self remindersCalendars] count]) {
// use a default calendar
theCalendar = [listOfCalendars objectAtIndex:0];
theCalendar = [[self remindersCalendars] objectAtIndex:0];
} else {
NSBeep();
QSiCalNotif(@"Action Failed", @"No calendars to set the To-Do");
Expand All @@ -130,26 +169,25 @@ -(QSObject *)createToDo:(QSObject *)dObject inCalendar:(QSObject *)iObject{
NSArray *bits = [subjectString componentsSeparatedByString:@"!"];

// convert the number of !! at the start of the todo into a CalPriority obj
NSUInteger priority = [bits count];
CalPriority calPriority;
NSUInteger exclamations = [bits count] - 1;
NSUInteger calPriority;
NSString *todoTitle = [[bits lastObject] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
if (priority == 1) {
calPriority = CalPriorityNone;
} else if (priority == 2) {
calPriority = CalPriorityLow;
} else if (priority == 3) {
calPriority = CalPriorityMedium;
if (exclamations == 0) {
calPriority = 0;
} else if (exclamations == 1) {
calPriority = 9;
} else if (exclamations == 2) {
calPriority = 5;
} else {
calPriority = CalPriorityHigh;
calPriority = 1;
}

[newTask setCalendar:theCalendar];
[newTask setTitle:todoTitle];
[newTask setPriority:calPriority];
newTask.calendar = theCalendar;
newTask.title = todoTitle;
newTask.priority = calPriority;

NSError *err = nil;

[[CalCalendarStore defaultCalendarStore] saveTask:newTask error:&err];
[eventStore saveReminder:newTask commit:YES error:&err];

if (err) {
NSBeep();
Expand All @@ -167,4 +205,4 @@ void QSiCalNotif(NSString *title, NSString *message) {

}

@end
@end

0 comments on commit 33e8c2d

Please sign in to comment.