PageRenderTime 51ms CodeModel.GetById 6ms RepoModel.GetById 0ms app.codeStats 1ms

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