Leopard Calendar Store

Cool,

After much reading of developer docs and cocoa lists etc that’s way over my head, I managed to, I believe, figure out priority #1. Despite what all those sources said, since I don’t need this code at all on Tiger and don’t execute it if it is present, all I had to do was the following:

  1. add the CalendarStore.framework to the project & build at least once
  2. add this line to the Other Linker Flags on the Builds section of Targets:
    –weak_framework CalendarStore
  3. build again for good measure
  4. delete CalendarStore.framework (I had to both where Xcode stuck it in “Scripts” and in “Link Binary with Libraries” under Target.
  5. build again!
  6. of course you have to make sure you never create or call the custom obj-c class that in turn imports CalendarStore.framework if Leopard is not running

http://developer.apple.com/documentation/MacOSX/Conceptual/BPFrameworks/Concepts/WeakLinking.html

so now we’re down to priorities 2 & 3 + figuring out why “location” won’t work!

Made a little progress tonight. You can now return ONLY the events associated with the selected calendar(s). Here is the code to add to the -(NSArray *)getEventsinRangewithStart:(NSDate *)theStartdate withEnd:(NSDate *)theEnddate withCals:(NSMutableArray *)theuids method:

This will provide an array to hold the calendar objects for the predicate:

NSMutableArray *selectedCalendars = [NSMutableArray arrayWithCapacity:10]; //This will hold the array of Calendar objects so that only events from the checked calendar(s) are returned

Then, after the calStore initialization, add this Enumeration to fill the array:

NSEnumerator *calUIDEnumerator = [theuids objectEnumerator];
	id calUID;
	while (calUID = [calUIDEnumerator nextObject]) {
		[selectedCalendars addObject:[calStore calendarWithUID:calUID]];
	}

It works perfectly, since you already set up the method to receive the list of calendar UIDs from the AS portion. There are some other cleanup details for your other methods, but I will leave that you, since you know where to put the stuff.

I have been puzzling over the problem of Tiger and Leopard issues, and also tried reading the documentation, but I am totally confused about the whole picture. I will continue pondering…

Hi Craig,

Thanks a lot. Sorry got a bit side tracked here on implementing something related to Growl too… obj-c is a fussy beast.

First with regards to the Tiger / Leopard stuff – I think I have that sussed out as I said in last post. I really can’t believe how simple it was given how complicated people suggest it is.

Second, aside from this whole issue still wondering about that “location” property and memory problems.

Now then…

What you just posted is as far as I can tell the same thing I had in the commented out portion of the current version of the app online (with a couple different variable names).

I think you overlooked the part where I wanted to conditionally use “calendars” for all calendars if the content of the passed array/list was {“*”} rather than a list of uid strings.

Edit:

was having problems and still having some but jumped the gun here… will post later.

OK… I thought I had solved it by explicitly delcaring *prediate but I was wrong. Compiler continues to object to unused variable ‘predicate’.

See Screen shot:

http://www.woodenbrain.com/Images/WBCcalStore.m.jpg

And here is the currently “Broken” version of the project:

http://www.woodenbrain.com/sw/betas/WBCCalendarStore-broken.zip
(edit: before had the url of the last semi-functional version, not the really broken version, posted.)


-(NSArray *)getEventsinRangewithStart:(NSDate *)theStartdate withEnd:(NSDate *)theEnddate withCals:(NSMutableArray *)theuids {
   NSMutableArray *eventInformation = [NSMutableArray arrayWithCapacity:10];
   CalCalendarStore *calStore = [CalCalendarStore defaultCalendarStore];
   // here we define *predicate (the search function) in advance because it appears in two variations in the conditional if / else; not sure if this is necessary
 
   // we check whether we are passed the wildcard array {"*"}, in which case we use the default calendar list
   // not sure if this is the best conditional check
   
   // must explicitly define *predicate EVEN THOUGH we are going to do so in the following conditional statements.  WTF.
   
NSPredicate *predicate;
if ([theuids objectAtIndex:0] == [NSString stringWithString:@"*"]) {
//if (YES) {
NSLOG(@"met wildcard condition, using all calendars");
 NSPredicate *predicate = [CalCalendarStore eventPredicateWithStartDate:theStartdate endDate:theEnddate calendars:[calStore calendars]];
}
else
// loop through the passed uid's list (theuids) and get the corresponding calendar object from the C Store and add each to theCalendars array
{
 NSLOG(@"did not meet wildcard condition, using specific calendars");
  NSMutableArray *selectedCalendars = [NSMutableArray arrayWithCapacity:10];
   NSEnumerator *calUIDEnumerator = [theuids objectEnumerator];
   id calUID;
   while (calUID = [calUIDEnumerator nextObject]) {
       [selectedCalendars addObject:[calStore calendarWithUID:calUID]];
	   }
	   NSPredicate *predicate = [CalCalendarStore eventPredicateWithStartDate:theStartdate endDate:theEnddate calendars:selectedCalendars];
}

// the following line would be done in the condition above if the array passed = {"*"} in theory; but it doesn't work so here it is without the condition
//NSPredicate *predicate = [CalCalendarStore eventPredicateWithStartDate:theStartdate endDate:theEnddate calendars:[calStore calendars]];

// now do a search on the predicate (search function) and then loop through results, creating an array of a list of available properties for each event to return to AS
   
   NSEnumerator *eventEnumerator = [[calStore eventsWithPredicate:predicate] objectEnumerator];

Nota Bene: I am not really an Objective-C programmer, but I think I spotted the problem you are having with predicate (it is really at the level of C, with which I have more direct experience).

It looks like you have a variable scoping problem. Here is a skeleton version of the code you posted:

There are three distinct variables named predicate here. The one in the function scope of getEventsinRangeWithStart:withCals: and one in each block at the branches of the conditional. A variable declared inside a block (curly braces) is only visible inside that block or other nested scopes. This is called block scope. If this block scoped variable happens to have the same name as a variable in an outer scope, then the inner variable ˜shadows’ the outer variable from the point the variable is declared to the end of the immediately enclosing block (this varies by language, JavaScript does not really have block scoping–even though one can declare new variables in a block, they are still scoped at the function level, not the level of the immediately enclosing block).

To my eye, it looks like the two inner predicate variables are never used (they are initialized, but values they hold are never referenced). The outer predicate is referenced, but never initialized.

To solve this scope problem, I think you want to write the code like this:

In short: Declare it once in the outer scope, and assign a value to it in the branches of the conditional, but do not redeclare it.

chris,

that’s a fantastic explanation, thanks. and it doesn’t hurt that it worked.

so with your help and craig’s of course, I believe I have a fully functional demo now!

it is at least good enough for my needs with a few limitations, and i’m going to start folding it in to what i really need these methods for, OmniGrowl.

Here is the project URL once more:

http://www.woodenbrain.com/sw/betas/WBCCalendarStore.zip

and these are remaining to-dos. help from experts appreciated, and source is well commented related to these issues.

  1. check if there really are any memory leakage problems
  2. figure out why the event location property can’t be retreived, and/or file bug report with apple
  3. (somehow) get rid of the compile warnings like for [[event calendar] title]
  4. possilby iron out some of the other non-supported properties
  5. eventually optimize the code

WBC, Craig and others,

Fantastic work on the CalendarStore. I started looking into this earlier this year and quickly got overwhelmed and gave up. My goal is to be able to access the Calendar Store from within Applescript, even if it is through some sort of helper app. What I’d like to do is be able to trigger a text to speech reading of events for the day from Applescript. I need to trigger from Applescript because the home automation software I use supports it and this is how I plan to trigger it (i.e. press a button in the bathroom and "Good morning the weather today is… Today is Garbage day, you have a meeting at 10am. " etc. Is it possible to trigger an Applescript Studio app from Applescript? Any thoughts on the feasibility of this?

It is possible to trigger an AS Studio app from applescript if:

  1. that studio app includes an applescript dictionary

or

  1. you use the simpler but more “hacky” method that is actually being used by the demo app of scripting the interface buttons.

with that method, you can tell the studio app to ‘perform click’ on a button with a normal applescript.

if you look at OmniGrowl, it installs a bunch of “immediate actions” which use method #2.