/core/externals/update-engine/externals/gdata-objectivec-client/Examples/CalendarSample/CalendarSampleWindowController.m
Objective C | 1908 lines | 1264 code | 405 blank | 239 comment | 211 complexity | 9a71863eae2530b0aee1018837ff70c6 MD5 | raw file
1/* Copyright (c) 2007 Google Inc. 2 * 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16// 17// CalendarSampleWindowController.m 18// 19 20// 21// IMPORTANT: 22// 23// The XML-based API for Google Calendar has been replaced with a more efficient 24// and easier-to-use JSON API. The JSON API is documented at 25// 26// https://developers.google.com/google-apps/calendar/ 27// 28// See the new Objective-C client library and sample code at 29// http://code.google.com/p/google-api-objectivec-client/ 30// 31// This sample application and library support for the XML-based Calendar 32// API will eventually be removed. 33// 34 35#import "CalendarSampleWindowController.h" 36 37#import "EditEventWindowController.h" 38#import "EditACLWindowController.h" 39 40#import "GData/GTMOAuth2WindowController.h" 41 42@interface CalendarSampleWindowController (PrivateMethods) 43- (void)updateUI; 44 45- (void)fetchAllCalendars; 46- (void)fetchSelectedCalendar; 47 48- (void)addACalendar; 49- (void)renameSelectedCalendar; 50- (void)deleteSelectedCalendar; 51 52- (void)fetchSelectedCalendarEvents; 53- (void)addAnEvent; 54- (void)editSelectedEvent; 55- (void)deleteSelectedEvents; 56- (void)batchDeleteSelectedEvents; 57- (void)queryTodaysEvents; 58- (void)queryFreeBusy; 59 60- (void)fetchSelectedCalendarACLEntries; 61- (void)addAnACLEntry; 62- (void)editSelectedACLEntry; 63- (void)deleteSelectedACLEntry; 64 65- (void)fetchSelectedCalendarSettingsEntries; 66 67- (GDataServiceGoogleCalendar *)calendarService; 68- (GDataEntryCalendar *)selectedCalendar; 69- (GDataEntryCalendarEvent *)singleSelectedEvent; 70- (NSArray *)selectedEvents; 71- (GDataEntryACL *)selectedACLEntry; 72- (GDataEntryCalendarSettings *)selectedSettingsEntry; 73 74- (BOOL)isACLSegmentSelected; 75- (BOOL)isEventsSegmentSelected; 76- (BOOL)isSettingsSegmentSelected; 77- (GDataFeedBase *)feedForSelectedSegment; 78 79- (GDataFeedCalendar *)calendarFeed; 80- (void)setCalendarFeed:(GDataFeedCalendar *)feed; 81- (NSError *)calendarFetchError; 82- (void)setCalendarFetchError:(NSError *)error; 83- (GDataServiceTicket *)calendarFetchTicket; 84- (void)setCalendarFetchTicket:(GDataServiceTicket *)ticket; 85 86- (GDataFeedCalendarEvent *)eventFeed; 87- (void)setEventFeed:(GDataFeedCalendarEvent *)feed; 88- (NSError *)eventFetchError; 89- (void)setEventFetchError:(NSError *)error; 90- (GDataServiceTicket *)eventFetchTicket; 91- (void)setEventFetchTicket:(GDataServiceTicket *)ticket; 92 93- (GDataFeedACL *)ACLFeed; 94- (void)setACLFeed:(GDataFeedACL *)feed; 95- (NSError *)ACLFetchError; 96- (void)setACLFetchError:(NSError *)error; 97- (GDataServiceTicket *)ACLFetchTicket; 98- (void)setACLFetchTicket:(GDataServiceTicket *)ticket; 99 100- (GDataFeedCalendarSettings *)settingsFeed; 101- (void)setSettingsFeed:(GDataFeedCalendarSettings *)feed; 102- (NSError *)settingsFetchError; 103- (void)setSettingsFetchError:(NSError *)error; 104- (GDataServiceTicket *)settingsFetchTicket; 105- (void)setSettingsFetchTicket:(GDataServiceTicket *)ticket; 106 107@end 108 109enum { 110 // calendar segmented control segment index values 111 kAllCalendarsSegment = 0, 112 kOwnedCalendarsSegment = 1 113}; 114 115enum { 116 // event/ACL/settings segmented control segment index values 117 kEventsSegment = 0, 118 kACLSegment = 1, 119 kSettingsSegment = 2 120}; 121 122@implementation CalendarSampleWindowController 123 124static CalendarSampleWindowController* gCalendarSampleWindowController = nil; 125 126static NSString *const kKeychainItemName = @"CalendarSample: Google Calendar"; 127 128+ (CalendarSampleWindowController *)sharedCalendarSampleWindowController { 129 130 if (!gCalendarSampleWindowController) { 131 gCalendarSampleWindowController = [[CalendarSampleWindowController alloc] init]; 132 } 133 return gCalendarSampleWindowController; 134} 135 136 137- (id)init { 138 return [self initWithWindowNibName:@"CalendarSampleWindow"]; 139} 140 141- (void)windowDidLoad { 142} 143 144- (void)awakeFromNib { 145 // Load the OAuth token from the keychain, if it was previously saved 146 NSString *clientID = [mClientIDField stringValue]; 147 NSString *clientSecret = [mClientSecretField stringValue]; 148 149 GTMOAuth2Authentication *auth; 150 auth = [GTMOAuth2WindowController authForGoogleFromKeychainForName:kKeychainItemName 151 clientID:clientID 152 clientSecret:clientSecret]; 153 [[self calendarService] setAuthorizer:auth]; 154 155 // Set the result text fields to have a distinctive color and mono-spaced font 156 // to aid in understanding of each calendar and event query operation. 157 [mCalendarResultTextField setTextColor:[NSColor darkGrayColor]]; 158 [mEventResultTextField setTextColor:[NSColor darkGrayColor]]; 159 160 NSFont *resultTextFont = [NSFont fontWithName:@"Monaco" size:9]; 161 [mCalendarResultTextField setFont:resultTextFont]; 162 [mEventResultTextField setFont:resultTextFont]; 163 164 [mCalendarTable setDoubleAction:@selector(logEntryXML:)]; 165 [mEventTable setDoubleAction:@selector(logEntryXML:)]; 166 167 [self updateUI]; 168} 169 170- (void)dealloc { 171 [mCalendarFeed release]; 172 [mCalendarFetchError release]; 173 [mCalendarFetchTicket release]; 174 175 [mEventFeed release]; 176 [mEventFetchError release]; 177 [mEventFetchTicket release]; 178 179 [mACLFeed release]; 180 [mACLFetchError release]; 181 [mACLFetchTicket release]; 182 183 [mSettingsFeed release]; 184 [mSettingsFetchError release]; 185 [mSettingsFetchTicket release]; 186 187 [super dealloc]; 188} 189 190#pragma mark - 191 192- (NSString *)signedInUsername { 193 // Get the email address of the signed-in user 194 GTMOAuth2Authentication *auth = [[self calendarService] authorizer]; 195 BOOL isSignedIn = auth.canAuthorize; 196 if (isSignedIn) { 197 return auth.userEmail; 198 } else { 199 return nil; 200 } 201} 202 203- (BOOL)isSignedIn { 204 NSString *name = [self signedInUsername]; 205 return (name != nil); 206} 207 208- (void)runSigninThenInvokeSelector:(SEL)signInDoneSel { 209 // Applications should have client ID and client secret strings 210 // hardcoded into the source, but the sample application asks the 211 // developer for the strings 212 NSString *clientID = [mClientIDField stringValue]; 213 NSString *clientSecret = [mClientSecretField stringValue]; 214 215 if ([clientID length] == 0 || [clientSecret length] == 0) { 216 // Remind the developer that client ID and client secret are needed 217 [mClientIDButton performSelector:@selector(performClick:) 218 withObject:self 219 afterDelay:0.5]; 220 return; 221 } 222 223 // Show the OAuth 2 sign-in controller 224 NSString *scope = [GDataServiceGoogleCalendar authorizationScope]; 225 226 NSBundle *frameworkBundle = [NSBundle bundleForClass:[GTMOAuth2WindowController class]]; 227 GTMOAuth2WindowController *windowController; 228 windowController = [GTMOAuth2WindowController controllerWithScope:scope 229 clientID:clientID 230 clientSecret:clientSecret 231 keychainItemName:kKeychainItemName 232 resourceBundle:frameworkBundle]; 233 234 [windowController setUserData:NSStringFromSelector(signInDoneSel)]; 235 [windowController signInSheetModalForWindow:[self window] 236 delegate:self 237 finishedSelector:@selector(windowController:finishedWithAuth:error:)]; 238} 239 240- (void)windowController:(GTMOAuth2WindowController *)windowController 241 finishedWithAuth:(GTMOAuth2Authentication *)auth 242 error:(NSError *)error { 243 // Callback from OAuth 2 sign-in 244 if (error == nil) { 245 [[self calendarService] setAuthorizer:auth]; 246 247 NSString *selStr = [windowController userData]; 248 if (selStr) { 249 [self performSelector:NSSelectorFromString(selStr)]; 250 } 251 } else { 252 [self setCalendarFetchError:error]; 253 [self updateUI]; 254 } 255} 256 257#pragma mark - 258 259- (void)updateUI { 260 BOOL isSignedIn = [self isSignedIn]; 261 NSString *username = [self signedInUsername]; 262 [mSignedInButton setTitle:(isSignedIn ? @"Sign Out" : @"Sign In")]; 263 [mSignedInField setStringValue:(isSignedIn ? username : @"No")]; 264 265 // calendar list display 266 [mCalendarTable reloadData]; 267 268 if (mCalendarFetchTicket != nil) { 269 [mCalendarProgressIndicator startAnimation:self]; 270 } else { 271 [mCalendarProgressIndicator stopAnimation:self]; 272 } 273 274 // calendar fetch result or selected item 275 NSString *calendarResultStr = @""; 276 if (mCalendarFetchError) { 277 calendarResultStr = [mCalendarFetchError description]; 278 } else { 279 GDataEntryCalendar *calendar = [self selectedCalendar]; 280 if (calendar) { 281 calendarResultStr = [calendar description]; 282 } 283 } 284 [mCalendarResultTextField setString:calendarResultStr]; 285 286 // add/delete calendar controls 287 BOOL canAddCalendar = ([mCalendarFeed postLink] != nil); 288 BOOL hasNewCalendarName = ([[mCalendarNameField stringValue] length] > 0); 289 [mAddCalendarButton setEnabled:(canAddCalendar && hasNewCalendarName)]; 290 291 BOOL canEditSelectedCalendar = ([[self selectedCalendar] editLink] != nil); 292 [mDeleteCalendarButton setEnabled:canEditSelectedCalendar]; 293 [mRenameCalendarButton setEnabled:(hasNewCalendarName && canEditSelectedCalendar)]; 294 295 int calendarsSegment = [mCalendarSegmentedControl selectedSegment]; 296 BOOL canEditNewCalendarName = (calendarsSegment == kOwnedCalendarsSegment); 297 [mCalendarNameField setEnabled:canEditNewCalendarName]; 298 299 // event/ACL/settings list display 300 [mEventTable reloadData]; 301 302 // the bottom table displays event, ACL, or settings entries 303 BOOL isEventDisplay = [self isEventsSegmentSelected]; 304 BOOL isACLDisplay = [self isACLSegmentSelected]; 305 306 GDataServiceTicket *entryTicket; 307 NSError *error; 308 309 if (isEventDisplay) { 310 entryTicket = mEventFetchTicket; 311 error = mEventFetchError; 312 } else if (isACLDisplay) { 313 entryTicket = mACLFetchTicket; 314 error = mACLFetchError; 315 } else { 316 entryTicket = mSettingsFetchTicket; 317 error = mSettingsFetchError; 318 } 319 320 if (entryTicket != nil) { 321 [mEventProgressIndicator startAnimation:self]; 322 } else { 323 [mEventProgressIndicator stopAnimation:self]; 324 } 325 326 // display event, ACL, or settings fetch result or selected item 327 NSString *eventResultStr = @""; 328 if (error) { 329 eventResultStr = [error description]; 330 } else { 331 GDataEntryBase *entry = nil; 332 if (isEventDisplay) { 333 entry = [self singleSelectedEvent]; 334 } else if (isACLDisplay) { 335 entry = [self selectedACLEntry]; 336 } else { 337 entry = [self selectedSettingsEntry]; 338 } 339 340 if (entry != nil) { 341 eventResultStr = [entry description]; 342 } 343 } 344 [mEventResultTextField setString:eventResultStr]; 345 346 // enable/disable cancel buttons 347 [mCalendarCancelButton setEnabled:(mCalendarFetchTicket != nil)]; 348 [mEventCancelButton setEnabled:(entryTicket != nil)]; 349 350 // enable/disable other buttons 351 BOOL isCalendarSelected = ([self selectedCalendar] != nil); 352 353 BOOL doesSelectedCalendarHaveACLFeed = 354 ([[self selectedCalendar] ACLLink] != nil); 355 356 if (isEventDisplay) { 357 358 [mAddEventButton setEnabled:isCalendarSelected]; 359 [mQueryTodayEventButton setEnabled:isCalendarSelected]; 360 361 // Events segment is selected 362 NSArray *selectedEvents = [self selectedEvents]; 363 unsigned int numberOfSelectedEvents = [selectedEvents count]; 364 365 NSString *deleteTitle = (numberOfSelectedEvents <= 1) ? 366 @"Delete Entry" : @"Delete Entries"; 367 [mDeleteEventButton setTitle:deleteTitle]; 368 369 if (numberOfSelectedEvents == 1) { 370 371 // 1 selected event 372 GDataEntryCalendarEvent *event = [selectedEvents objectAtIndex:0]; 373 BOOL isSelectedEntryEditable = ([event editLink] != nil); 374 375 [mDeleteEventButton setEnabled:isSelectedEntryEditable]; 376 [mEditEventButton setEnabled:isSelectedEntryEditable]; 377 } else { 378 // zero or many selected events 379 BOOL canBatchEdit = ([mEventFeed batchLink] != nil); 380 BOOL canDeleteAll = (canBatchEdit && numberOfSelectedEvents > 1); 381 382 [mDeleteEventButton setEnabled:canDeleteAll]; 383 [mEditEventButton setEnabled:NO]; 384 } 385 } else if (isACLDisplay) { 386 // ACL segment is selected 387 BOOL isEditableACLEntrySelected = 388 ([[self selectedACLEntry] editLink] != nil); 389 390 [mDeleteEventButton setEnabled:isEditableACLEntrySelected]; 391 [mEditEventButton setEnabled:isEditableACLEntrySelected]; 392 393 [mAddEventButton setEnabled:doesSelectedCalendarHaveACLFeed]; 394 [mQueryTodayEventButton setEnabled:NO]; 395 } else { 396 // settings segment is selected 397 [mDeleteEventButton setEnabled:NO]; 398 [mEditEventButton setEnabled:NO]; 399 [mAddEventButton setEnabled:NO]; 400 [mQueryTodayEventButton setEnabled:NO]; 401 } 402 403 [mQueryFreeBusyButton setEnabled:isSignedIn]; 404 405 // enable or disable the Events/ACL segment buttons 406 [mEntrySegmentedControl setEnabled:isCalendarSelected 407 forSegment:kEventsSegment]; 408 [mEntrySegmentedControl setEnabled:isCalendarSelected 409 forSegment:kSettingsSegment]; 410 [mEntrySegmentedControl setEnabled:doesSelectedCalendarHaveACLFeed 411 forSegment:kACLSegment]; 412 413 // Show or hide the text indicating that the client ID or client secret are 414 // needed 415 BOOL hasClientIDStrings = [[mClientIDField stringValue] length] > 0 416 && [[mClientSecretField stringValue] length] > 0; 417 [mClientIDRequiredTextField setHidden:hasClientIDStrings]; 418} 419 420- (void)displayAlert:(NSString *)title format:(NSString *)format, ... { 421 NSString *result = format; 422 if (format) { 423 va_list argList; 424 va_start(argList, format); 425 result = [[[NSString alloc] initWithFormat:format 426 arguments:argList] autorelease]; 427 va_end(argList); 428 } 429 NSBeginAlertSheet(title, nil, nil, nil, [self window], nil, nil, 430 nil, nil, result); 431} 432 433- (NSString *)displayStringForACLEntry:(GDataEntryACL *)aclEntry { 434 435 // make a concise, readable string showing the scope type, scope value, 436 // and role value for an ACL entry, like: 437 // 438 // scope: user "fred@flintstone.com" role:owner 439 440 NSMutableString *resultStr = [NSMutableString string]; 441 442 GDataACLScope *scope = [aclEntry scope]; 443 if (scope) { 444 NSString *type = ([scope type] ? [scope type] : @""); 445 NSString *value = @""; 446 if ([scope value]) { 447 value = [NSString stringWithFormat:@"\"%@\"", [scope value]]; 448 } 449 [resultStr appendFormat:@"scope: %@ %@ ", type, value]; 450 } 451 452 GDataACLRole *role = [aclEntry role]; 453 if (role) { 454 // for the role value, display only anything after the # character 455 // since roles may be rather long, like 456 // http://schemas.google.com/calendar/2005/role#collaborator 457 458 NSString *value = [role value]; 459 460 NSRange poundRange = [value rangeOfString:@"#" options:NSBackwardsSearch]; 461 if (poundRange.location != NSNotFound 462 && [value length] > (1 + poundRange.location)) { 463 value = [value substringFromIndex:(1 + poundRange.location)]; 464 } 465 [resultStr appendFormat:@"role: %@", value]; 466 } 467 return resultStr; 468} 469 470- (NSString *)displayStringForSettingsEntry:(GDataEntryCalendarSettings *)settingsEntry { 471 472 GDataCalendarSettingsProperty *prop = [settingsEntry settingsProperty]; 473 474 NSString *str = [NSString stringWithFormat:@"%@: %@", 475 [prop name], [prop value]]; 476 return str; 477} 478 479#pragma mark IBActions 480 481- (IBAction)signInClicked:(id)sender { 482 if (![self isSignedIn]) { 483 // Sign in 484 [self runSigninThenInvokeSelector:@selector(updateUI)]; 485 } else { 486 // Sign out 487 GDataServiceGoogleCalendar *service = [self calendarService]; 488 489 [GTMOAuth2WindowController removeAuthFromKeychainForName:kKeychainItemName]; 490 [service setAuthorizer:nil]; 491 [self updateUI]; 492 } 493} 494 495- (IBAction)getCalendarClicked:(id)sender { 496 if (![self isSignedIn]) { 497 [self runSigninThenInvokeSelector:@selector(fetchAllCalendars)]; 498 } else { 499 [self fetchAllCalendars]; 500 } 501} 502 503- (IBAction)calendarSegmentClicked:(id)sender { 504 // get the new calendar list for the selected segment 505 [self getCalendarClicked:sender]; 506} 507 508- (IBAction)addCalendarClicked:(id)sender { 509 [self addACalendar]; 510} 511 512- (IBAction)renameCalendarClicked:(id)sender { 513 [self renameSelectedCalendar]; 514} 515 516- (IBAction)deleteCalendarClicked:(id)sender { 517 [self deleteSelectedCalendar]; 518} 519 520- (IBAction)cancelCalendarFetchClicked:(id)sender { 521 [mCalendarFetchTicket cancelTicket]; 522 [self setCalendarFetchTicket:nil]; 523 [self updateUI]; 524} 525 526- (IBAction)cancelEventFetchClicked:(id)sender { 527 [mEventFetchTicket cancelTicket]; 528 [self setEventFetchTicket:nil]; 529 [self updateUI]; 530} 531 532- (IBAction)addEventClicked:(id)sender { 533 if ([self isEventsSegmentSelected]) { 534 [self addAnEvent]; 535 } else { 536 [self addAnACLEntry]; 537 } 538} 539 540- (IBAction)editEventClicked:(id)sender { 541 if ([self isEventsSegmentSelected]) { 542 [self editSelectedEvent]; 543 } else { 544 [self editSelectedACLEntry]; 545 } 546} 547 548- (IBAction)deleteEventClicked:(id)sender { 549 if ([self isEventsSegmentSelected]) { 550 [self deleteSelectedEvents]; 551 } else { 552 [self deleteSelectedACLEntry]; 553 } 554} 555 556- (IBAction)queryTodayClicked:(id)sender { 557 [self queryTodaysEvents]; 558} 559 560- (IBAction)queryFreeBusyClicked:(id)sender { 561 [self queryFreeBusy]; 562} 563 564- (IBAction)entrySegmentClicked:(id)sender { 565 [self fetchSelectedCalendar]; 566} 567 568- (IBAction)APIConsoleClicked:(id)sender { 569 NSURL *url = [NSURL URLWithString:@"https://code.google.com/apis/console"]; 570 [[NSWorkspace sharedWorkspace] openURL:url]; 571} 572 573- (IBAction)loggingCheckboxClicked:(id)sender { 574 [GTMHTTPFetcher setLoggingEnabled:[sender state]]; 575} 576 577// logEntryXML is called when the user double-clicks on a calendar, 578// event entry, or ACL entry 579- (IBAction)logEntryXML:(id)sender { 580 581 int row = [sender selectedRow]; 582 583 if (sender == mCalendarTable) { 584 // get the calendar entry's title 585 GDataEntryCalendar *calendar = [[mCalendarFeed entries] objectAtIndex:row]; 586 NSLog(@"%@", [calendar XMLElement]); 587 588 } else if (sender == mEventTable) { 589 // get the selected entry 590 GDataFeedBase *feed = [self feedForSelectedSegment]; 591 GDataEntryBase *entry = [[feed entries] objectAtIndex:row]; 592 NSLog(@"%@", [entry XMLElement]); 593 } 594} 595 596#pragma mark - 597 598// get a calendar service object with the current username/password 599// 600// A "service" object handles networking tasks. Service objects 601// contain user authentication information as well as networking 602// state information (such as cookies and the "last modified" date for 603// fetched data.) 604 605- (GDataServiceGoogleCalendar *)calendarService { 606 607 static GDataServiceGoogleCalendar* service = nil; 608 609 if (!service) { 610 service = [[GDataServiceGoogleCalendar alloc] init]; 611 612 [service setShouldCacheResponseData:YES]; 613 [service setServiceShouldFollowNextLinks:YES]; 614 [service setIsServiceRetryEnabled:YES]; 615 } 616 617 return service; 618} 619 620// get the calendar selected in the top list, or nil if none 621- (GDataEntryCalendar *)selectedCalendar { 622 623 NSArray *calendars = [mCalendarFeed entries]; 624 int rowIndex = [mCalendarTable selectedRow]; 625 if ([calendars count] > 0 && rowIndex > -1) { 626 627 GDataEntryCalendar *calendar = [calendars objectAtIndex:rowIndex]; 628 return calendar; 629 } 630 return nil; 631} 632 633// get the events selected in the bottom list, or nil if none 634- (NSArray *)selectedEvents { 635 636 if ([self isEventsSegmentSelected]) { 637 638 NSIndexSet *indexes = [mEventTable selectedRowIndexes]; 639 NSArray *events = [mEventFeed entries]; 640 NSArray *selectedEvents = [events objectsAtIndexes:indexes]; 641 642 if ([selectedEvents count] > 0) { 643 return selectedEvents; 644 } 645 } 646 return nil; 647} 648 649- (GDataEntryCalendarEvent *)singleSelectedEvent { 650 651 NSArray *selectedEvents = [self selectedEvents]; 652 if ([selectedEvents count] == 1) { 653 return [selectedEvents objectAtIndex:0]; 654 } 655 return nil; 656} 657 658 659// get the ACL selected in the bottom list, or nil if none 660- (GDataEntryACL *)selectedACLEntry { 661 662 if ([self isACLSegmentSelected]) { 663 664 NSArray *entries = [mACLFeed entries]; 665 int rowIndex = [mEventTable selectedRow]; 666 if ([entries count] > 0 && rowIndex > -1) { 667 668 GDataEntryACL *entry = [entries objectAtIndex:rowIndex]; 669 return entry; 670 } 671 } 672 return nil; 673} 674 675- (GDataEntryCalendarSettings *)selectedSettingsEntry { 676 677 if ([self isSettingsSegmentSelected]) { 678 679 NSArray *entries = [mSettingsFeed entries]; 680 int rowIndex = [mEventTable selectedRow]; 681 if ([entries count] > 0 && rowIndex > -1) { 682 683 GDataEntryCalendarSettings *entry = [entries objectAtIndex:rowIndex]; 684 return entry; 685 } 686 } 687 return nil; 688} 689 690- (BOOL)isACLSegmentSelected { 691 return ([mEntrySegmentedControl selectedSegment] == kACLSegment); 692} 693 694- (BOOL)isEventsSegmentSelected { 695 return ([mEntrySegmentedControl selectedSegment] == kEventsSegment); 696} 697 698- (BOOL)isSettingsSegmentSelected { 699 return ([mEntrySegmentedControl selectedSegment] == kSettingsSegment); 700} 701 702- (GDataFeedBase *)feedForSelectedSegment { 703 int segmentNum = [mEntrySegmentedControl selectedSegment]; 704 705 if (segmentNum == kEventsSegment) return mEventFeed; 706 if (segmentNum == kACLSegment) return mACLFeed; 707 return mSettingsFeed; 708} 709 710#pragma mark Add/delete calendars 711 712- (void)addACalendar { 713 714 NSString *newCalendarName = [mCalendarNameField stringValue]; 715 716 NSURL *postURL = [[mCalendarFeed postLink] URL]; 717 718 if ([newCalendarName length] > 0 && postURL != nil) { 719 720 GDataServiceGoogleCalendar *service = [self calendarService]; 721 722 GDataEntryCalendar *newEntry = [GDataEntryCalendar calendarEntry]; 723 [newEntry setTitleWithString:newCalendarName]; 724 [newEntry setIsSelected:YES]; // check the calendar in the web display 725 726 // as of Dec. '07 the server requires a color, 727 // or returns a 404 (Not Found) error 728 [newEntry setColor:[GDataColorProperty valueWithString:@"#2952A3"]]; 729 730 [service fetchEntryByInsertingEntry:newEntry 731 forFeedURL:postURL 732 delegate:self 733 didFinishSelector:@selector(addCalendarTicket:addedEntry:error:)]; 734 } 735} 736 737// add calendar callback 738- (void)addCalendarTicket:(GDataServiceTicket *)ticket 739 addedEntry:(GDataEntryCalendar *)object 740 error:(NSError *)error { 741 if (error == nil) { 742 // tell the user that the add worked 743 [self displayAlert:@"Added Calendar" 744 format:@"Calendar added"]; 745 746 [mCalendarNameField setStringValue:@""]; 747 748 // refetch the current calendars 749 [self fetchAllCalendars]; 750 [self updateUI]; 751 } else { 752 // add failed 753 [self displayAlert:@"Add failed" 754 format:@"Calendar add failed: %@", error]; 755 } 756} 757 758- (void)renameSelectedCalendar { 759 760 GDataEntryCalendar *selectedCalendar = [self selectedCalendar]; 761 NSString *newCalendarName = [mCalendarNameField stringValue]; 762 NSURL *editURL = [[[self selectedCalendar] editLink] URL]; 763 764 if (selectedCalendar && editURL && [newCalendarName length] > 0) { 765 766 // make the user confirm that the selected calendar should be renamed 767 NSBeginAlertSheet(@"Rename calendar", @"Rename", @"Cancel", nil, 768 [self window], self, 769 @selector(renameCalendarSheetDidEnd:returnCode:contextInfo:), 770 nil, nil, @"Rename the calendar \"%@\" as \"%@\"?", 771 [[selectedCalendar title] stringValue], 772 newCalendarName); 773 } 774} 775 776- (void)renameCalendarSheetDidEnd:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo { 777 778 if (returnCode == NSAlertDefaultReturn) { 779 780 NSString *newCalendarName = [mCalendarNameField stringValue]; 781 GDataEntryCalendar *selectedCalendar = [self selectedCalendar]; 782 783 GDataServiceGoogleCalendar *service = [self calendarService]; 784 785 // rename it 786 [selectedCalendar setTitleWithString:newCalendarName]; 787 788 [service fetchEntryByUpdatingEntry:selectedCalendar 789 delegate:self 790 didFinishSelector:@selector(renameCalendarTicket:renamedEntry:error:)]; 791 } 792} 793 794// rename calendar callback 795- (void)renameCalendarTicket:(GDataServiceTicket *)ticket 796 renamedEntry:(GDataEntryCalendar *)object 797 error:(NSError *)error { 798 if (error == nil) { 799 // tell the user that the rename worked 800 [self displayAlert:@"Renamed Calendar" 801 format:@"Calendar renamed"]; 802 803 // refetch the current calendars 804 [self fetchAllCalendars]; 805 [self updateUI]; 806 } else { 807 // rename failed 808 [self displayAlert:@"Rename failed" 809 format:@"Calendar rename failed: %@", error]; 810 } 811} 812 813- (void)deleteSelectedCalendar { 814 815 GDataEntryCalendar *selectedCalendar = [self selectedCalendar]; 816 if (selectedCalendar) { 817 // make the user confirm that the selected calendar should be deleted 818 NSBeginAlertSheet(@"Delete calendar", @"Delete", @"Cancel", nil, 819 [self window], self, 820 @selector(deleteCalendarSheetDidEnd:returnCode:contextInfo:), 821 nil, nil, @"Delete the calendar \"%@\"?", 822 [[selectedCalendar title] stringValue]); 823 } 824 825} 826 827- (void)deleteCalendarSheetDidEnd:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo { 828 829 if (returnCode == NSAlertDefaultReturn) { 830 831 NSURL *editURL = [[[self selectedCalendar] editLink] URL]; 832 833 if (editURL != nil) { 834 835 GDataServiceGoogleCalendar *service = [self calendarService]; 836 837 [service deleteEntry:[self selectedCalendar] 838 delegate:self 839 didFinishSelector:@selector(deleteCalendarTicket:deletedEntry:error:)]; 840 } 841 } 842} 843 844// delete calendar callback 845- (void)deleteCalendarTicket:(GDataServiceTicket *)ticket 846 deletedEntry:(GDataEntryCalendar *)object 847 error:(NSError *)error { 848 if (error == nil) { 849 // tell the user that the delete worked 850 [self displayAlert:@"Deleted Calendar" 851 format:@"Calendar deleted"]; 852 853 // refetch the current calendars 854 [self fetchAllCalendars]; 855 [self updateUI]; 856 } else { 857 [self displayAlert:@"Delete failed" 858 format:@"Calendar delete failed: %@", error]; 859 } 860} 861 862#pragma mark Fetch all calendars 863 864// begin retrieving the list of the user's calendars 865- (void)fetchAllCalendars { 866 867 [self setCalendarFeed:nil]; 868 [self setCalendarFetchError:nil]; 869 [self setCalendarFetchTicket:nil]; 870 871 [self setEventFeed:nil]; 872 [self setEventFetchError:nil]; 873 [self setEventFetchTicket:nil]; 874 875 [self setACLFeed:nil]; 876 [self setACLFetchError:nil]; 877 [self setACLFetchTicket:nil]; 878 879 [self setSettingsFeed:nil]; 880 [self setSettingsFetchError:nil]; 881 [self setSettingsFetchTicket:nil]; 882 883 GDataServiceGoogleCalendar *service = [self calendarService]; 884 GDataServiceTicket *ticket; 885 886 int segment = [mCalendarSegmentedControl selectedSegment]; 887 NSString *feedURLString; 888 889 // The sample app shows the default, non-editable feed of calendars, 890 // and the "OwnCalendars" feed, which allows calendars to be inserted 891 // and deleted. We're not demonstrating the "AllCalendars" feed, which 892 // allows subscriptions to non-owned calendars to be inserted and deleted, 893 // just because it's a bit too complex to easily keep distinct from add/ 894 // delete in the user interface. 895 896 if (segment == kAllCalendarsSegment) { 897 feedURLString = kGDataGoogleCalendarDefaultFeed; 898 } else { 899 feedURLString = kGDataGoogleCalendarDefaultOwnCalendarsFeed; 900 } 901 902 ticket = [service fetchFeedWithURL:[NSURL URLWithString:feedURLString] 903 delegate:self 904 didFinishSelector:@selector(calendarListTicket:finishedWithFeed:error:)]; 905 906 [self setCalendarFetchTicket:ticket]; 907 908 [self updateUI]; 909} 910 911// 912// calendar list fetch callbacks 913// 914 915// fetch calendar metafeed callback 916- (void)calendarListTicket:(GDataServiceTicket *)ticket 917 finishedWithFeed:(GDataFeedCalendar *)feed 918 error:(NSError *)error { 919 [self setCalendarFeed:feed]; 920 [self setCalendarFetchError:error]; 921 [self setCalendarFetchTicket:nil]; 922 923 [self updateUI]; 924} 925 926#pragma mark - 927 928- (void)fetchSelectedCalendar { 929 930 GDataEntryCalendar *calendar = [self selectedCalendar]; 931 if (calendar) { 932 933 BOOL hasACL = ([[self selectedCalendar] ACLLink] != nil); 934 BOOL isDisplayingEvents = [self isEventsSegmentSelected]; 935 BOOL isDisplayingACL = [self isACLSegmentSelected]; 936 937 if (isDisplayingEvents || (isDisplayingACL && !hasACL)) { 938 [self fetchSelectedCalendarEvents]; 939 } else if (isDisplayingACL) { 940 [self fetchSelectedCalendarACLEntries]; 941 } else { 942 [self fetchSelectedCalendarSettingsEntries]; 943 } 944 } 945} 946 947#pragma mark Fetch a calendar's events 948 949// for the calendar selected in the top list, begin retrieving the list of 950// events 951- (void)fetchSelectedCalendarEvents { 952 953 GDataEntryCalendar *calendar = [self selectedCalendar]; 954 if (calendar) { 955 956 // fetch the events feed 957 NSURL *feedURL = [[calendar alternateLink] URL]; 958 if (feedURL) { 959 960 [self setEventFeed:nil]; 961 [self setEventFetchError:nil]; 962 [self setEventFetchTicket:nil]; 963 964 // The default feed of calendar events only has up to 25 events; since the 965 // service object is set to automatically follow next links, that can lead 966 // to many fetches to retrieve all of the event entries, and each fetch 967 // may take a few seconds. 968 // 969 // To reduce the need for the library to fetch repeatedly to acquire all 970 // of the entries, we'll specify a higher number of entries the server may 971 // return in a feed. For Mac applications, a fetch with 1000 entries is 972 // reasonable; iPhone apps may want to avoid the memory hit of parsing 973 // such a large number of entries, and limit it to 100. 974 GDataQueryCalendar *query = [GDataQueryCalendar calendarQueryWithFeedURL:feedURL]; 975 [query setMaxResults:1000]; 976 977 GDataServiceGoogleCalendar *service = [self calendarService]; 978 GDataServiceTicket *ticket; 979 ticket = [service fetchFeedWithQuery:query 980 delegate:self 981 didFinishSelector:@selector(calendarEventsTicket:finishedWithFeed:error:)]; 982 [self setEventFetchTicket:ticket]; 983 984 [self updateUI]; 985 } 986 } 987} 988 989// event list fetch callback 990- (void)calendarEventsTicket:(GDataServiceTicket *)ticket 991 finishedWithFeed:(GDataFeedCalendarEvent *)feed 992 error:(NSError *)error { 993 994 [self setEventFeed:feed]; 995 [self setEventFetchError:error]; 996 [self setEventFetchTicket:nil]; 997 998 [self updateUI]; 999} 1000 1001#pragma mark Add an event 1002 1003- (void)addAnEvent { 1004 1005 // make a new event 1006 GDataEntryCalendarEvent *newEvent = [GDataEntryCalendarEvent calendarEvent]; 1007 1008 // set a title and description (the author is the authenticated user adding 1009 // the entry) 1010 [newEvent setTitleWithString:@"Sample Added Event"]; 1011 [newEvent setContentWithString:@"Description of sample added event"]; 1012 1013 // start time now, end time in an hour, reminder 10 minutes before 1014 NSDate *anHourFromNow = [NSDate dateWithTimeIntervalSinceNow:60*60]; 1015 GDataDateTime *startDateTime = [GDataDateTime dateTimeWithDate:[NSDate date] 1016 timeZone:[NSTimeZone systemTimeZone]]; 1017 GDataDateTime *endDateTime = [GDataDateTime dateTimeWithDate:anHourFromNow 1018 timeZone:[NSTimeZone systemTimeZone]]; 1019 GDataReminder *reminder = [GDataReminder reminder]; 1020 [reminder setMinutes:@"10"]; 1021 [reminder setMethod:kGDataReminderMethodEmail]; 1022 1023 GDataWhen *when = [GDataWhen whenWithStartTime:startDateTime 1024 endTime:endDateTime]; 1025 [when addReminder:reminder]; 1026 [newEvent addTime:when]; 1027 1028 // display the event edit dialog 1029 EditEventWindowController *controller = [[EditEventWindowController alloc] init]; 1030 [controller runModalForTarget:self 1031 selector:@selector(addEditControllerFinished:) 1032 event:newEvent]; 1033} 1034 1035// callback from the edit event dialog 1036- (void)addEditControllerFinished:(EditEventWindowController *)addEventController { 1037 1038 if ([addEventController wasSaveClicked]) { 1039 1040 // insert the event into the selected calendar 1041 GDataEntryCalendarEvent *event = [addEventController event]; 1042 if (event) { 1043 1044 GDataServiceGoogleCalendar *service = [self calendarService]; 1045 1046 GDataEntryCalendar *calendar = [self selectedCalendar]; 1047 NSURL *feedURL = [[calendar alternateLink] URL]; 1048 1049 [service fetchEntryByInsertingEntry:event 1050 forFeedURL:feedURL 1051 delegate:self 1052 didFinishSelector:@selector(addEventTicket:addedEntry:error:)]; 1053 } 1054 } 1055 [addEventController autorelease]; 1056} 1057 1058// event added successfully 1059- (void)addEventTicket:(GDataServiceTicket *)ticket 1060 addedEntry:(GDataFeedCalendarEvent *)entry 1061 error:(NSError *)error { 1062 if (error == nil) { 1063 // tell the user that the add worked 1064 [self displayAlert:@"Added Event" 1065 format:@"Event added"]; 1066 1067 // refetch the current calendar's events 1068 [self fetchSelectedCalendar]; 1069 [self updateUI]; 1070 } else { 1071 // the add failed 1072 [self displayAlert:@"Add failed" 1073 format:@"Event add failed: %@", error]; 1074 } 1075} 1076 1077#pragma mark Edit an event 1078 1079- (void)editSelectedEvent { 1080 1081 // display the event edit dialog 1082 GDataEntryCalendarEvent *event = [self singleSelectedEvent]; 1083 if (event) { 1084 EditEventWindowController *controller = [[EditEventWindowController alloc] init]; 1085 [controller runModalForTarget:self 1086 selector:@selector(editControllerFinished:) 1087 event:event]; 1088 } 1089} 1090 1091// callback from the edit event dialog 1092- (void)editControllerFinished:(EditEventWindowController *)editEventController { 1093 if ([editEventController wasSaveClicked]) { 1094 1095 // update the event with the changed settings 1096 GDataEntryCalendarEvent *event = [editEventController event]; 1097 if (event) { 1098 1099 GDataServiceGoogleCalendar *service = [self calendarService]; 1100 [service fetchEntryByUpdatingEntry:event 1101 delegate:self 1102 didFinishSelector:@selector(editEventTicket:editedEntry:error:)]; 1103 } 1104 } 1105 [editEventController autorelease]; 1106} 1107 1108// update event callback 1109- (void)editEventTicket:(GDataServiceTicket *)ticket 1110 editedEntry:(GDataFeedCalendarEvent *)object 1111 error:(NSError *)error { 1112 if (error == nil) { 1113 // tell the user that the update worked 1114 [self displayAlert:@"Updated Event" 1115 format:@"Event updated"]; 1116 1117 // re-fetch the selected calendar's events 1118 [self fetchSelectedCalendar]; 1119 [self updateUI]; 1120 } else { 1121 // failed 1122 [self displayAlert:@"Update failed" 1123 format:@"Event update failed: %@", error]; 1124 } 1125} 1126 1127#pragma mark Delete selected events 1128 1129- (void)deleteSelectedEvents { 1130 1131 NSArray *events = [self selectedEvents]; 1132 unsigned int numberOfSelectedEvents = [events count]; 1133 1134 if (numberOfSelectedEvents == 1) { 1135 1136 // 1 event selected 1137 GDataEntryCalendarEvent *event = [events objectAtIndex:0]; 1138 1139 // make the user confirm that the selected event should be deleted 1140 NSBeginAlertSheet(@"Delete Event", @"Delete", @"Cancel", nil, 1141 [self window], self, 1142 @selector(deleteSheetDidEnd:returnCode:contextInfo:), 1143 nil, nil, @"Delete the event \"%@\"?", 1144 [event title]); 1145 1146 } else if (numberOfSelectedEvents >= 1) { 1147 1148 NSBeginAlertSheet(@"Delete Events", @"Delete", @"Cancel", nil, 1149 [self window], self, 1150 @selector(batchDeleteSheetDidEnd:returnCode:contextInfo:), 1151 nil, nil, @"Delete %d events?", 1152 numberOfSelectedEvents); 1153 } 1154} 1155 1156// delete dialog callback 1157- (void)deleteSheetDidEnd:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo { 1158 1159 if (returnCode == NSAlertDefaultReturn) { 1160 1161 // delete the event 1162 GDataEntryCalendarEvent *event = [self singleSelectedEvent]; 1163 GDataLink *link = [event editLink]; 1164 1165 if (link) { 1166 GDataServiceGoogleCalendar *service = [self calendarService]; 1167 [service deleteEntry:event 1168 delegate:self 1169 didFinishSelector:@selector(deleteTicket:deletedEntry:error:)]; 1170 } 1171 } 1172} 1173 1174// event deleted callback 1175- (void)deleteTicket:(GDataServiceTicket *)ticket 1176 deletedEntry:(GDataFeedCalendarEvent *)nilObject 1177 error:(NSError *)error { 1178 if (error == nil) { 1179 [self displayAlert:@"Deleted Event" 1180 format:@"Event deleted"]; 1181 1182 // re-fetch the selected calendar's events 1183 [self fetchSelectedCalendar]; 1184 [self updateUI]; 1185 } else { 1186 // failed 1187 [self displayAlert:@"Delete failed" 1188 format:@"Event delete failed: %@", error]; 1189 } 1190} 1191 1192// delete dialog callback 1193- (void)batchDeleteSheetDidEnd:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo { 1194 1195 if (returnCode == NSAlertDefaultReturn) { 1196 // delete the events 1197 [self batchDeleteSelectedEvents]; 1198 } 1199} 1200 1201- (void)batchDeleteSelectedEvents { 1202 1203 NSArray *selectedEvents = [self selectedEvents]; 1204 1205 for (int idx = 0; idx < [selectedEvents count]; idx++) { 1206 1207 GDataEntryCalendarEvent *event = [selectedEvents objectAtIndex:idx]; 1208 1209 // add a batch ID to this entry 1210 static int staticID = 0; 1211 NSString *batchID = [NSString stringWithFormat:@"batchID_%u", ++staticID]; 1212 [event setBatchIDWithString:batchID]; 1213 1214 // we don't need to add the batch operation to the entries since 1215 // we're putting it in the feed to apply to all entries 1216 1217 // we could force an error on an item by nuking the entry's identifier 1218 // if (idx == 1) { [event setIdentifier:nil]; } 1219 } 1220 1221 NSURL *batchURL = [[mEventFeed batchLink] URL]; 1222 if (batchURL != nil && [selectedEvents count] > 0) { 1223 1224 // make a batch feed object: add entries, and since 1225 // we are doing the same operation for all entries in the feed, 1226 // add the operation 1227 1228 GDataFeedCalendarEvent *batchFeed = [GDataFeedCalendarEvent calendarEventFeed]; 1229 [batchFeed setEntriesWithEntries:selectedEvents]; 1230 1231 GDataBatchOperation *op = [GDataBatchOperation batchOperationWithType:kGDataBatchOperationDelete]; 1232 [batchFeed setBatchOperation:op]; 1233 1234 // now do the usual steps for authenticating for this service, and issue 1235 // the fetch 1236 1237 GDataServiceGoogleCalendar *service = [self calendarService]; 1238 1239 [service fetchFeedWithBatchFeed:batchFeed 1240 forBatchFeedURL:batchURL 1241 delegate:self 1242 didFinishSelector:@selector(batchDeleteTicket:finishedWithFeed:error:)]; 1243 } else { 1244 // the button shouldn't be enabled when we can't batch delete, so we 1245 // shouldn't get here 1246 NSBeep(); 1247 } 1248} 1249 1250// batch delete callback 1251- (void)batchDeleteTicket:(GDataServiceTicket *)ticket 1252 finishedWithFeed:(GDataFeedCalendarEvent *)feed 1253 error:(NSError *)error { 1254 if (error == nil) { 1255 // the fetch succeeded, though individual entries may have failed 1256 1257 // step through all the entries in the response feed, 1258 // and build a string reporting each 1259 1260 // show the http status to start (should be 200) 1261 NSString *format = @"http status:%d\n\n"; 1262 NSMutableString *reportStr = [NSMutableString stringWithFormat:format, 1263 [ticket statusCode]]; 1264 1265 for (GDataEntryCalendarEvent *entry in feed) { 1266 GDataBatchID *batchID = [entry batchID]; 1267 1268 // report the batch ID, entry title, and status for each item 1269 NSString *title= [[entry title] stringValue]; 1270 [reportStr appendFormat:@"%@: %@\n", [batchID stringValue], title]; 1271 1272 GDataBatchInterrupted *interrupted = [entry batchInterrupted]; 1273 if (interrupted) { 1274 [reportStr appendFormat:@"%@\n", [interrupted description]]; 1275 } 1276 1277 GDataBatchStatus *status = [entry batchStatus]; 1278 if (status) { 1279 [reportStr appendFormat:@"%d %@\n", [[status code] intValue], 1280 [status reason]]; 1281 } 1282 [reportStr appendString:@"\n"]; 1283 } 1284 1285 [self displayAlert:@"Batch delete completed" 1286 format:@"Delete completed.\n%@", reportStr]; 1287 1288 // re-fetch the selected calendar's events 1289 [self fetchSelectedCalendar]; 1290 1291 } else { 1292 // fetch failed 1293 [self displayAlert:@"Batch delete failed" 1294 format:@"Delete failed: %@", error]; 1295 } 1296 [self updateUI]; 1297} 1298 1299 1300#pragma mark Query today's events 1301 1302// utility routine to make a GDataDateTime object for sometime today 1303- (GDataDateTime *)dateTimeForTodayAtHour:(int)hour 1304 minute:(int)minute 1305 second:(int)second { 1306 1307 int const kComponentBits = (NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit 1308 | NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit); 1309 1310 NSCalendar *cal = [[[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar] autorelease]; 1311 1312 NSDateComponents *dateComponents = [cal components:kComponentBits fromDate:[NSDate date]]; 1313 [dateComponents setHour:hour]; 1314 [dateComponents setMinute:minute]; 1315 [dateComponents setSecond:second]; 1316 1317 GDataDateTime *dateTime = [GDataDateTime dateTimeWithDate:[NSDate date] 1318 timeZone:[NSTimeZone systemTimeZone]]; 1319 [dateTime setDateComponents:dateComponents]; 1320 return dateTime; 1321} 1322 1323// submit a query about today's events in the selected calendar 1324// NOTE: See GDataQueryCalendar.h for a warning about exceptions to recurring 1325// events being returned twice for a query. 1326- (void)queryTodaysEvents { 1327 1328 GDataServiceGoogleCalendar *service = [self calendarService]; 1329 1330 GDataEntryCalendar *calendar = [self selectedCalendar]; 1331 NSURL *feedURL = [[calendar alternateLink] URL]; 1332 1333 // make start and end times for today, at the beginning and end of the day 1334 1335 GDataDateTime *startOfDay = [self dateTimeForTodayAtHour:0 minute:0 second:0]; 1336 GDataDateTime *endOfDay = [self dateTimeForTodayAtHour:23 minute:59 second:59]; 1337 1338 // make the query 1339 GDataQueryCalendar* queryCal; 1340 1341 queryCal = [GDataQueryCalendar calendarQueryWithFeedURL:feedURL]; 1342 [queryCal setStartIndex:1]; 1343 [queryCal setMaxResults:10]; 1344 [queryCal setMinimumStartTime:startOfDay]; 1345 [queryCal setMaximumStartTime:endOfDay]; 1346 1347 [service fetchFeedWithQuery:queryCal 1348 delegate:self 1349 didFinishSelector:@selector(queryTicket:finishedWithFeed:error:)]; 1350} 1351 1352// callback for query of today's events 1353- (void)queryTicket:(GDataServiceTicket *)ticket 1354 finishedWithFeed:(GDataFeedCalendarEvent *)feed 1355 error:(NSError *)error { 1356 if (error == nil) { 1357 // query succeeded 1358 // 1359 // make a comma-separate list of the event titles to display 1360 NSMutableArray *titles = [NSMutableArray array]; 1361 for (GDataEntryCalendarEvent *event in feed) { 1362 NSString *title = [[event title] stringValue]; 1363 if ([title length] > 0) { 1364 [titles addObject:title]; 1365 } 1366 } 1367 1368 NSString *resultStr = [titles componentsJoinedByString:@", "]; 1369 1370 [self displayAlert:@"Query " 1371 format:@"Query result: %@", resultStr]; 1372 } else { 1373 // query failed 1374 [self displayAlert:@"Query failed" 1375 format:@"Query failed: %@", error]; 1376 } 1377} 1378 1379#pragma mark Query free/busy times 1380 1381- (void)queryFreeBusy { 1382 // make a start time for now, and an end time for 24 hours from now 1383 NSDate *now = [NSDate date]; 1384 NSTimeInterval oneDayInSecs = 60 * 60 * 24; 1385 NSDate *dayFromNow = [NSDate dateWithTimeIntervalSinceNow:oneDayInSecs]; 1386 1387 GDataDateTime *nowDateTime = [GDataDateTime dateTimeWithDate:now 1388 timeZone:[NSTimeZone systemTimeZone]]; 1389 GDataDateTime *tomorrowDateTime = [GDataDateTime dateTimeWithDate:dayFromNow 1390 timeZone:[NSTimeZone systemTimeZone]]; 1391 1392 // make the free/busy query 1393 GDataServiceGoogleCalendar *service = [self calendarService]; 1394 NSURL *feedURL = [GDataServiceGoogleCalendar freeBusyURLForUsername:[self signedInUsername]]; 1395 1396 GDataQueryCalendar* queryCal = [GDataQueryCalendar calendarQueryWithFeedURL:feedURL]; 1397 [queryCal setMinimumStartTime:nowDateTime]; 1398 [queryCal setMaximumStartTime:tomorrowDateTime]; 1399 1400 [service fetchFeedWithQuery:queryCal 1401 delegate:self 1402 didFinishSelector:@selector(queryFreeBusyTicket:finishedWithEntry:error:)]; 1403} 1404 1405- (void)queryFreeBusyTicket:(GDataServiceTicket *)ticket 1406 finishedWithEntry:(GDataEntryFreeBusy *)entry 1407 error:(NSError *)error { 1408 if (error == nil) { 1409 // query succeeded 1410 // 1411 // display the results as a list of start/end times 1412 NSArray *busies = [entry busies]; 1413 NSMutableArray *periodStrings = [NSMutableArray array]; 1414 1415 NSDateFormatter *formatter = [[[NSDateFormatter alloc] init] autorelease]; 1416 [formatter setDateStyle:NSDateFormatterNoStyle]; 1417 [formatter setTimeStyle:NSDateFormatterShortStyle]; 1418 [formatter setTimeZone:[NSTimeZone systemTimeZone]]; 1419 1420 for (int idx = 0; idx < [busies count]; idx++) { 1421 GDataCalendarBusy *busy = [busies objectAtIndex:idx]; 1422 GDataWhen *when = [busy when]; 1423 NSDate *startDate = [[when startTime] date]; 1424 NSDate *endDate = [[when endTime] date]; 1425 1426 NSString *str = [NSString stringWithFormat:@"%@-%@", 1427 [formatter stringFromDate:startDate], 1428 [formatter stringFromDate:endDate]]; 1429 [periodStrings addObject:str]; 1430 } 1431 NSString *resultStr = [periodStrings componentsJoinedByString:@", "]; 1432 1433 [self displayAlert:@"Query " 1434 format:@"Busy times: %@", resultStr]; 1435 1436 } else { 1437 // query failed 1438 [self displayAlert:@"Query failed" 1439 format:@"Query failed: %@", error]; 1440 } 1441} 1442 1443//////////////////////////////////////////////////////// 1444#pragma mark ACL 1445 1446- (void)fetchSelectedCalendarACLEntries { 1447 1448 GDataEntryCalendar *calendar = [self selectedCalendar]; 1449 if (calendar) { 1450 1451 NSURL *aclFeedURL = [[calendar ACLLink] URL]; 1452 if (aclFeedURL) { 1453 1454 // fetch the ACL feed 1455 [self setACLFeed:nil]; 1456 [self setACLFetchError:nil]; 1457 [self setACLFetchTicket:nil]; 1458 1459 GDataServiceGoogleCalendar *service = [self calendarService]; 1460 GDataServiceTicket *ticket; 1461 ticket = [service fetchACLFeedWithURL:aclFeedURL 1462 delegate:self 1463 didFinishSelector:@selector(calendarACLTicket:finishedWithFeed:error:)]; 1464 1465 [self setACLFetchTicket:ticket]; 1466 1467 [self updateUI]; 1468 } 1469 } 1470} 1471 1472// fetched ACL list callback 1473- (void)calendarACLTicket:(GDataServiceTicket *)ticket 1474 finishedWithFeed:(GDataFeedACL *)feed 1475 error:(NSError *)error { 1476 1477 [self setACLFeed:feed]; 1478 [self setACLFetchError:error]; 1479 [self setACLFetchTicket:nil]; 1480 1481 [self updateUI]; 1482} 1483 1484#pragma mark Add an ACL entry 1485 1486- (void)addAnACLEntry { 1487 1488 // make a new entry 1489 NSString *email = @"fred.flintstone@bounce.spuriousmail.com"; 1490 1491 GDataACLScope *scope = [GDataACLScope scopeWithType:@"user" 1492 value:email]; 1493 GDataACLRole *role = [GDataACLRole roleWithValue:kGDataRoleCalendarRead]; 1494 1495 GDataEntryACL *newEntry = [GDataEntryACL ACLEntryWithScope:scope role:role]; 1496 1497 // display the ACL edit dialog 1498 EditACLWindowController *controller = [[EditACLWindowController alloc] init]; 1499 [controller runModalForTarget:self 1500 selector:@selector(addACLEditControllerFinished:) 1501 ACLEntry:newEntry]; 1502} 1503 1504// callback from the edit ACL dialog 1505- (void)addACLEditControllerFinished:(EditACLWindowController *)addACLController { 1506 1507 if ([addACLController wasSaveClicked]) { 1508 1509 // insert the ACL into the selected calendar 1510 GDataEntryACL *entry = [addACLController ACLEntry]; 1511 if (entry) { 1512 1513 GDataServiceGoogleCalendar *service = [self calendarService]; 1514 1515 NSURL *postURL = [[mACLFeed postLink] URL]; 1516 if (postURL) { 1517 [service fetchACLEntryByInsertingEntry:entry 1518 forFeedURL:postURL 1519 delegate:self 1520 didFinishSelector:@selector(addACLEntryTicket:addedEntry:error:)]; 1521 } 1522 } 1523 } 1524 [addACLController autorelease]; 1525} 1526 1527// add ACL callback 1528- (void)addACLEntryTicket:(GDataServiceTicket *)ticket 1529 addedEntry:(GDataEntryACL *)entry 1530 error:(NSError *)error { 1531 if (error == nil) { 1532 // tell the user that the add worked 1533 [self displayAlert:@"Added ACL Entry" 1534 format:@"ACL Entry added"]; 1535 1536 // refetch the current calendar's ACL entries 1537 [self fetchSelectedCalendar]; 1538 [self updateUI]; 1539 } else { 1540 [self displayAlert:@"Add failed" 1541 format:@"ACL Entry add failed: %@", error]; 1542 } 1543} 1544 1545#pragma mark Edit an ACLEntry 1546 1547- (void)editSelectedACLEntry { 1548 1549 // display the ACLEntry edit dialog 1550 GDataEntryACL *entry = [self selectedACLEntry]; 1551 if (entry) { 1552 EditACLWindowController *controller = [[EditACLWindowController alloc] init]; 1553 [controller runModalForTarget:self 1554 selector:@selector(ACLEditControllerFinished:) 1555 ACLEntry:entry]; 1556 } 1557} 1558 1559// callback from the edit ACLEntry dialog 1560- (void)ACLEditControllerFinished:(EditACLWindowController *)editACLEntryController { 1561 if ([editACLEntryController wasSaveClicked]) { 1562 1563 // update the ACLEntry with the changed settings 1564 GDataEntryACL *entry = [editACLEntryController ACLEntry]; 1565 if (entry) { 1566 1567 GDataLink *link = [entry editLink]; 1568 if (link) { 1569 GDataServiceGoogleCalendar *service = [self calendarService]; 1570 [service fetchACLEntryByUpdatingEntry:entry 1571 delegate:self 1572 didFinishSelector:@selector(editACLEntryTicket:editedEntry:error:)]; 1573 } 1574 } 1575 } 1576 [editACLEntryController autorelease]; 1577} 1578 1579// ACLEntry edit callback 1580- (void)editACLEntryTicket:(GDataServiceTicket *)ticket 1581 editedEntry:(GDataFeedACL *)object 1582 error:(NSError *)error { 1583 if (error == nil) { 1584 // tell the user that the update worked 1585 [self displayAlert:@"Updated ACLEntry" 1586 format:@"ACL Entry updated"]; 1587 1588 // re-fetch the selected calendar's ACLEntries 1589 [self fetchSelectedCalendar]; 1590 [self updateUI]; 1591 } else { 1592 [self displayAlert:@"Update failed" 1593 format:@"ACLEntry update failed: %@", error]; 1594 } 1595} 1596 1597#pragma mark Delete an ACL Entry 1598 1599- (void)deleteSelectedACLEntry { 1600 1601 GDataEntryACL *entry = [self selectedACLEntry]; 1602 if (entry) { 1603 // make the user confirm that the selected ACLEntry should be deleted 1604 NSString *entryDesc = [NSString stringWithFormat:@"%@ %@", 1605 [[entry scope] type], [[entry scope] value]]; 1606 1607 NSBeginAlertSheet(@"Delete ACLEntry", @"Delete", @"Cancel", nil, 1608 [self window], self, 1609 @selector(deleteACLSheetDidEnd:returnCode:contextInfo:), 1610 nil, nil, @"Delete the ACL entry \"%@\"?", 1611 entryDesc); 1612 } 1613} 1614 1615// delete dialog callback 1616- (void)deleteACLSheetDidEnd:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo { 1617 1618 if (returnCode == NSAlertDefaultReturn) { 1619 1620 // delete the ACLEntry 1621 GDataEntryACL *entry = [self selectedACLEntry]; 1622 1623 if (entry) { 1624 GDataServiceGoogleCalendar *service = [self calendarService]; 1625 [service deleteACLEntry:entry 1626 delegate:self 1627 didFinishSelector:@selector(deleteACLEntryTicket:deletedEntry:error:)]; 1628 } 1629 } 1630} 1631 1632// ACLEntry deleted successfully 1633- (void)deleteACLEntryTicket:(GDataServiceTicket *)ticket 1634 deletedEntry:(GDataFeedACL *)object 1635 error:(NSError *)error { 1636 if (error == nil) { 1637 [self displayAlert:@"Deleted ACLEntry" 1638 format:@"ACL Entry deleted"]; 1639 1640 // re-fetch the selected calendar's events 1641 [self fetchSelectedCalendar]; 1642 [self updateUI]; 1643 } else { 1644 [self displayAlert:@"Delete failed" 1645 format:@"ACL Entry delete failed: %@", error]; 1646 } 1647} 1648 1649//////////////////////////////////////////////////////// 1650#pragma mark Settings feed 1651 1652- (void)fetchSelectedCalendarSettingsEntries { 1653 1654 GDataEntryCalendar *calendar = [self selectedCalendar]; 1655 if (calendar) { 1656 1657 NSString *username = [self signedInUsername]; 1658 1659 NSURL *settingsFeedURL = [GDataServiceGoogleCalendar settingsFeedURLForUsername:username]; 1660 if (settingsFeedURL) { 1661 1662 // fetch the settings feed 1663 [self setSettingsFeed:nil]; 1664 [self setSettingsFetchError:nil]; 1665 [self setSettingsFetchTicket:nil]; 1666 1667 GDataServiceGoogleCalendar *service = [self calendarService]; 1668 GDataServiceTicket *ticket; 1669 ticket = [service fetchFeedWithURL:settingsFeedURL 1670 delegate:self 1671 didFinishSelector:@selector(calendarSettingsTicket:finishedWithFeed:error:)]; 1672 1673 [self setSettingsFetchTicket:ticket]; 1674 1675 [self updateUI]; 1676 } 1677 } 1678} 1679 1680// settings list fetch callback 1681- (void)calendarSettingsTicket:(GDataServiceTicket *)ticket 1682 finishedWithFeed:(GDataFeedCalendarSettings *)feed 1683 error:(NSError *)error { 1684 1685 [self setSettingsFeed:feed]; 1686 [self setSettingsFetchError:error]; 1687 [self setSettingsFetchTicket:nil]; 1688 1689 [self updateUI]; 1690} 1691 1692#pragma mark TableView delegate methods 1693// 1694// table view delegate methods 1695// 1696 1697- (void)tableViewSelectionDidChange:(NSNotification *)notification { 1698 1699 if ([notification object] == mCalendarTable) { 1700 // the user clicked on a calendar, so fetch its events 1701 1702 // if the calendar lacks an ACL feed, select the events segment; 1703 // the updateUI routine will disable the ACL segment for us 1704 BOOL doesSelectedCalendarHaveACLFeed = 1705 ([[self selectedCalendar] ACLLink] != nil); 1706 1707 if (!doesSelectedCalendarHaveACLFeed && [self isACLSegmentSelected]) { 1708 [mEntrySegmentedControl setSelectedSegment:kEventsSegment]; 1709 } 1710 1711 [self fetchSelectedCalendar]; 1712 } else { 1713 // the user clicked on an event or an ACL entry; 1714 // just display it below the entry table 1715 1716 [self updateUI]; 1717 } 1718} 1719 1720// table view data source methods 1721- (int)numberOfRowsInTableView:(NSTableView *)tableView { 1722 if (tableView == mCalendarTable) { 1723 return [[mCalendarFeed entries] count]; 1724 } else { 1725 // entry table 1726 GDataFeedBase *feed = [self feedForSelectedSegment]; 1727 return [[feed entries] count]; 1728 } 1729} 1730 1731- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(int)row { 1732 1733 // calendar list table 1734 if (tableView == mCalendarTable) { 1735 // get the calendar entry's title 1736 GDataEntryCalendar *calendar = [[mCalendarFeed entries] objectAtIndex:row]; 1737 return [[calendar title] stringValue]; 1738 } 1739 1740 // entries table 1741 1742 // event entry 1743 if ([self isEventsSegmentSelected]) { 1744 // get the event entry's title 1745 GDataEntryCalendarEvent *eventEntry = [[mEventFeed entries] objectAtIndex:row]; 1746 return [[eventEntry title] stringValue]; 1747 } 1748 1749 // ACL entry 1750 if ([self isACLSegmentSelected]) { 1751 GDataEntryACL *aclEntry = [[mACLFeed entries] objectAtIndex:row]; 1752 return [self displayStringForACLEntry:aclEntry]; 1753 } 1754 1755 // settings entry 1756 GDataEntryCalendarSettings *settingsEntry = [[mSettingsFeed entries] objectAtIndex:row]; 1757 return [self displayStringForSettingsEntry:settingsEntry]; 1758} 1759 1760#pragma mark Client ID Sheet 1761 1762// Client ID and Client Secret Sheet 1763// 1764// Sample apps need this sheet to ask for the client ID and client secret 1765// strings 1766// 1767// Your application will just hardcode the client ID and client secret strings 1768// into the source rather than ask the user for them. 1769// 1770// The string values are obtained from the API Console, 1771// https://code.google.com/apis/console 1772 1773- (IBAction)clientIDClicked:(id)sender { 1774 // Show the sheet for developers to enter their client ID and client secret 1775 [NSApp beginSheet:mClientIDSheet 1776 modalForWindow:[self window] 1777 modalDelegate:self 1778 didEndSelector:@selector(clientIDSheetDidEnd:returnCode:contextInfo:) 1779 contextInfo:NULL]; 1780} 1781 1782- (IBAction)clientIDDoneClicked:(id)sender { 1783 [NSApp endSheet:mClientIDSheet returnCode:NSOKButton]; 1784} 1785 1786- (void)clientIDSheetDidEnd:(NSWindow *)sheet returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo { 1787 [sheet orderOut:self]; 1788 [self updateUI]; 1789} 1790 1791#pragma mark Control delegate methods 1792 1793- (void)controlTextDidChange:(NSNotification *)note { 1794 1795 [self updateUI]; // enabled/disable the Add Calendar button 1796} 1797 1798#pragma mark Setters and Getters 1799 1800- (GDataFeedCalendar *)calendarFeed { 1801 return mCalendarFeed; 1802} 1803 1804- (void)setCalendarFeed:(GDataFeedCalendar *)feed { 1805 [mCalendarFeed autorelease]; 1806 mCalendarFeed = [feed retain]; 1807} 1808 1809- (NSError *)calendarFetchError { 1810 return mCalendarFetchError; 1811} 1812 1813- (void)setCalendarFetchError:(NSError *)error { 1814 [mCalendarFetchError release]; 1815 mCalendarFetchError = [error retain]; 1816} 1817 1818- (GDataServiceTicket *)calendarFetchTicket { 1819 return mCalendarFetchTicket; 1820} 1821 1822- (void)setCalendarFetchTicket:(GDataServiceTicket *)ticket { 1823 [mCalendarFetchTicket release]; 1824 mCalendarFetchTicket = [ticket retain]; 1825} 1826 1827- (GDataFeedCalendarEvent *)eventFeed { 1828 return mEventFeed; 1829} 1830 1831- (void)setEventFeed:(GDataFeedCalendarEvent *)feed { 1832 [mEventFeed autorelease]; 1833 mEventFeed = [feed retain]; 1834} 1835 1836- (NSError *)eventFetchError { 1837 return mEventFetchError; 1838} 1839 1840- (void)setEventFetchError:(NSError *)error { 1841 [mEventFetchError release]; 1842 mEventFetchError = [error retain]; 1843} 1844 1845- (GDataServiceTicket *)eventFetchTicket { 1846 return mEventFetchTicket; 1847} 1848 1849- (void)setEventFetchTicket:(GDataServiceTicket *)ticket { 1850 [mEventFetchTicket release]; 1851 mEventFetchTicket = [ticket retain]; 1852} 1853 1854- (GDataFeedACL *)ACLFeed { 1855 return mACLFeed; 1856} 1857 1858- (void)setACLFeed:(GDataFeedACL *)feed { 1859 [mACLFeed autorelease]; 1860 mACLFeed = [feed retain]; 1861} 1862 1863- (NSError *)ACLFetchError { 1864 return mACLFetchError; 1865} 1866 1867- (void)setACLFetchError:(NSError *)error { 1868 [mACLFetchError release]; 1869 mACLFetchError = [error retain]; 1870} 1871 1872- (GDataServiceTicket *)ACLFetchTicket { 1873 return mACLFetchTicket; 1874} 1875 1876- (void)setACLFetchTicket:(GDataServiceTicket *)ticket { 1877 [mACLFetchTicket release]; 1878 mACLFetchTicket = [ticket retain]; 1879} 1880 1881- (GDataFeedCalendarSettings *)settingsFeed { 1882 return mSettingsFeed; 1883} 1884 1885- (void)setSettingsFeed:(GDataFeedCalendarSettings *)feed { 1886 [mSettingsFeed autorelease]; 1887 mSettingsFeed = [feed retain]; 1888} 1889 1890- (NSError *)settingsFetchError { 1891 return mSettingsFetchError; 1892} 1893 1894- (void)setSettingsFetchError:(NSError *)error { 1895 [mSettingsFetchError release]; 1896 mSettingsFetchError = [error retain]; 1897} 1898 1899- (GDataServiceTicket *)settingsFetchTicket { 1900 return mSettingsFetchTicket; 1901} 1902 1903- (void)setSettingsFetchTicket:(GDataServiceTicket *)ticket { 1904 [mACLFetchTicket release]; 1905 mACLFetchTicket = [ticket retain]; 1906} 1907 1908@end