PageRenderTime 275ms CodeModel.GetById 20ms app.highlight 242ms RepoModel.GetById 1ms app.codeStats 0ms

/core/externals/update-engine/externals/gdata-objectivec-client/Examples/CalendarSample/CalendarSampleWindowController.m

http://macfuse.googlecode.com/
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