PageRenderTime 136ms CodeModel.GetById 2ms app.highlight 128ms RepoModel.GetById 1ms app.codeStats 0ms

/core/externals/update-engine/externals/gdata-objectivec-client/Examples/SpreadsheetTableSample/SpreadsheetTableSampleWindowController.m

http://macfuse.googlecode.com/
Objective C | 898 lines | 610 code | 208 blank | 80 comment | 96 complexity | f44c5a650f41f1b1941ebf9e2b3b11d8 MD5 | raw file
  1/* Copyright (c) 2009 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//  SpreadsheetTableSampleWindowController.m
 18//
 19
 20#import "SpreadsheetTableSampleWindowController.h"
 21
 22@interface SpreadsheetTableSampleWindowController (PrivateMethods)
 23- (void)updateUI;
 24
 25- (void)fetchFeedOfSpreadsheets;
 26- (void)fetchSelectedSpreadsheet;
 27- (void)fetchSelectedTable;
 28
 29- (void)addTableToSelectedWorksheet;
 30- (void)deleteSelectedTable;
 31- (void)randomizeSelectedTable;
 32
 33
 34- (GDataServiceGoogleSpreadsheet *)spreadsheetService;
 35- (GDataEntrySpreadsheet *)selectedSpreadsheet;
 36- (GDataEntryWorksheet *)selectedWorksheet;
 37- (GDataEntrySpreadsheetTable *)selectedTable;
 38- (GDataEntrySpreadsheetRecord *)selectedRecord;
 39
 40
 41- (GDataFeedSpreadsheet *)spreadsheetFeed;
 42- (void)setSpreadsheetFeed:(GDataFeedSpreadsheet *)feed;
 43- (NSError *)spreadsheetFetchError;
 44- (void)setSpreadsheetFetchError:(NSError *)error;
 45- (GDataServiceTicket *)spreadsheetFeedTicket;
 46- (void)setSpreadsheetFeedTicket:(GDataServiceTicket *)obj;
 47
 48- (GDataFeedWorksheet *)worksheetFeed;
 49- (void)setWorksheetFeed:(GDataFeedWorksheet *)feed;
 50- (NSError *)worksheetFetchError;
 51- (void)setWorksheetFetchError:(NSError *)error;
 52- (GDataServiceTicket *)worksheetFeedTicket;
 53- (void)setWorksheetFeedTicket:(GDataServiceTicket *)obj;
 54
 55- (GDataFeedSpreadsheetTable *)tableFeed;
 56- (void)setTableFeed:(GDataFeedSpreadsheetTable *)feed;
 57- (NSError *)tableFetchError;
 58- (void)setTableFetchError:(NSError *)error;
 59- (GDataServiceTicket *)tableFeedTicket;
 60- (void)setTableFeedTicket:(GDataServiceTicket *)obj;
 61
 62- (GDataFeedSpreadsheetRecord *)recordFeed;
 63- (void)setRecordFeed:(GDataFeedSpreadsheetRecord *)feed;
 64- (NSError *)recordFetchError;
 65- (void)setRecordFetchError:(NSError *)error;
 66- (GDataServiceTicket *)recordFeedTicket;
 67- (void)setRecordFeedTicket:(GDataServiceTicket *)obj;
 68
 69
 70@end
 71
 72@implementation SpreadsheetTableSampleWindowController
 73
 74+ (SpreadsheetTableSampleWindowController *)sharedWindowController {
 75
 76  static SpreadsheetTableSampleWindowController* gWindowController = nil;
 77
 78  if (!gWindowController) {
 79    gWindowController = [[SpreadsheetTableSampleWindowController alloc] init];
 80  }
 81  return gWindowController;
 82}
 83
 84
 85- (id)init {
 86  return [self initWithWindowNibName:@"SpreadsheetTableSampleWindow"];
 87}
 88
 89- (void)windowDidLoad {
 90}
 91
 92- (void)awakeFromNib {
 93  // Set the result text fields to have a distinctive color and mono-spaced font
 94  [mSpreadsheetResultTextField setTextColor:[NSColor darkGrayColor]];
 95  [mWorksheetResultTextField setTextColor:[NSColor darkGrayColor]];
 96  [mTableResultTextField setTextColor:[NSColor darkGrayColor]];
 97  [mRecordResultTextField setTextColor:[NSColor darkGrayColor]];
 98
 99  NSFont *resultTextFont = [NSFont fontWithName:@"Monaco" size:9];
100  [mSpreadsheetResultTextField setFont:resultTextFont];
101  [mWorksheetResultTextField setFont:resultTextFont];
102  [mTableResultTextField setFont:resultTextFont];
103  [mRecordResultTextField setFont:resultTextFont];
104
105  // add notifications so we can track global starts and stops of fetching
106  NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
107  [nc addObserver:self
108         selector:@selector(fetchStateChanged:)
109             name:kGTMHTTPFetcherStartedNotification
110           object:nil];
111  [nc addObserver:self
112         selector:@selector(fetchStateChanged:)
113             name:kGTMHTTPFetcherStoppedNotification
114           object:nil];
115
116  [self updateUI];
117}
118
119- (void)dealloc {
120
121  [mSpreadsheetFeed release];
122  [mSpreadsheetFeedTicket release];
123  [mSpreadsheetFetchError release];
124
125  [mWorksheetFeed release];
126  [mWorksheetFeedTicket release];
127  [mWorksheetFetchError release];
128
129  [mTableFeed release];
130  [mTableFeedTicket release];
131  [mTableFetchError release];
132
133  [mRecordFeed release];
134  [mRecordFeedTicket release];
135  [mRecordFetchError release];
136
137  [mRecordUpdateTickets release];
138
139  [super dealloc];
140}
141
142#pragma mark -
143
144- (void)updateUI {
145
146  // spreadsheet list display
147  [mSpreadsheetTable reloadData];
148
149  if (mSpreadsheetFeedTicket != nil) {
150    [mSpreadsheetProgressIndicator startAnimation:self];
151  } else {
152    [mSpreadsheetProgressIndicator stopAnimation:self];
153  }
154
155  // spreadsheet fetch result or selected item
156  NSString *spreadsheetResultStr = @"";
157  if (mSpreadsheetFetchError) {
158    spreadsheetResultStr = [mSpreadsheetFetchError description];
159  } else {
160    GDataEntrySpreadsheet *spreadsheet = [self selectedSpreadsheet];
161    if (spreadsheet) {
162      spreadsheetResultStr = [spreadsheet description];
163    }
164  }
165  [mSpreadsheetResultTextField setString:spreadsheetResultStr];
166
167
168  // worksheets list display
169  [mWorksheetTable reloadData];
170
171  if (mWorksheetFeedTicket != nil) {
172    [mWorksheetProgressIndicator startAnimation:self];
173  } else {
174    [mWorksheetProgressIndicator stopAnimation:self];
175  }
176
177  // worksheet fetch result or selected item
178  NSString *worksheetResultStr = @"";
179  if (mWorksheetFetchError) {
180    worksheetResultStr = [mWorksheetFetchError description];
181  } else {
182    GDataEntryWorksheet *worksheet = [self selectedWorksheet];
183    if (worksheet) {
184      worksheetResultStr = [worksheet description];
185    }
186  }
187  [mWorksheetResultTextField setString:worksheetResultStr];
188
189
190  // tables list display
191  [mTableTable reloadData];
192
193  if (mTableFeedTicket != nil) {
194    [mTableProgressIndicator startAnimation:self];
195  } else {
196    [mTableProgressIndicator stopAnimation:self];
197  }
198
199  // table fetch result or selected item
200  NSString *tableResultStr = @"";
201  if (mTableFetchError) {
202    tableResultStr = [mTableFetchError description];
203  } else {
204    GDataEntrySpreadsheetTable *table = [self selectedTable];
205    if (table) {
206      tableResultStr = [table description];
207    }
208  }
209  [mTableResultTextField setString:tableResultStr];
210
211
212  // record display
213  [mRecordTable reloadData];
214
215  if (mRecordFeedTicket != nil || [mRecordUpdateTickets count] > 0) {
216    [mRecordProgressIndicator startAnimation:self];
217  } else {
218    [mRecordProgressIndicator stopAnimation:self];
219  }
220
221  // record fetch result or selected item
222  NSString *recordResultStr = @"";
223  if (mRecordFetchError) {
224    recordResultStr = [mRecordFetchError description];
225  } else {
226    GDataEntrySpreadsheetRecord *record = [self selectedRecord];
227    if (record) {
228      recordResultStr = [record description];
229    }
230  }
231  [mRecordResultTextField setString:recordResultStr];
232
233  BOOL isWorksheetSelected = ([self selectedWorksheet] != nil);
234  BOOL canPostToTable = ([mTableFeed postLink] != nil);
235  BOOL canAddTableToWorksheet = (isWorksheetSelected && canPostToTable);
236  [mAddTableButton setEnabled:canAddTableToWorksheet];
237
238  BOOL isTableSelected = ([self selectedTable] != nil);
239  [mDeleteTableButton setEnabled:isTableSelected];
240
241  BOOL hasEditableRecordFeed = ([mRecordFeed postLink] != nil);
242  [mRandomizeTableButton setEnabled:hasEditableRecordFeed];
243}
244
245#pragma mark IBActions
246
247- (IBAction)getSpreadsheetClicked:(id)sender {
248
249  NSCharacterSet *wsSet = [NSCharacterSet whitespaceAndNewlineCharacterSet];
250
251  NSString *username = [mUsernameField stringValue];
252  username = [username stringByTrimmingCharactersInSet:wsSet];
253
254  if ([username rangeOfString:@"@"].location == NSNotFound) {
255    // if no domain was supplied, add @gmail.com
256    username = [username stringByAppendingString:@"@gmail.com"];
257  }
258
259  [mUsernameField setStringValue:username];
260
261  [self fetchFeedOfSpreadsheets];
262}
263
264- (IBAction)addTableClicked:(id)sender {
265  [self addTableToSelectedWorksheet];
266}
267
268- (IBAction)deleteTableClicked:(id)sender {
269
270  GDataEntrySpreadsheetTable *table = [self selectedTable];
271
272  NSBeginAlertSheet(@"Delete", nil, @"Cancel", nil,
273                    [self window], self,
274                    @selector(deleteSheetDidEnd:returnCode:contextInfo:),
275                    nil, nil, @"Delete table \"%@\"?",
276                    [[table title] stringValue]);
277}
278
279- (void)deleteSheetDidEnd:(NSWindow *)sheet
280               returnCode:(int)returnCode
281              contextInfo:(void  *)contextInfo {
282  if (returnCode == NSOKButton) {
283    [self deleteSelectedTable];
284  }
285}
286
287
288- (IBAction)randomizeTableClicked:(id)sender {
289  GDataEntrySpreadsheetTable *table = [self selectedTable];
290
291  NSBeginAlertSheet(@"Randomize", nil, @"Cancel", nil,
292                    [self window], self,
293                    @selector(randomizeSheetDidEnd:returnCode:contextInfo:),
294                    nil, nil, @"Load random data into table \"%@\"?",
295                    [[table title] stringValue]);
296}
297
298- (void)randomizeSheetDidEnd:(NSWindow *)sheet
299                  returnCode:(int)returnCode
300                 contextInfo:(void  *)contextInfo {
301  if (returnCode == NSOKButton) {
302    [self randomizeSelectedTable];
303  }
304}
305
306- (IBAction)loggingCheckboxClicked:(id)sender {
307  [GTMHTTPFetcher setLoggingEnabled:[sender state]];
308}
309
310#pragma mark -
311
312// get a spreadsheet service object with the current username/password
313//
314// A "service" object handles networking tasks.  Service objects
315// contain user authentication information as well as networking
316// state information (such as cookies and the "last modified" date for
317// fetched data.)
318
319- (GDataServiceGoogleSpreadsheet *)spreadsheetService {
320
321  static GDataServiceGoogleSpreadsheet* service = nil;
322
323  if (!service) {
324    service = [[GDataServiceGoogleSpreadsheet alloc] init];
325
326    [service setShouldCacheResponseData:YES];
327    [service setServiceShouldFollowNextLinks:YES];
328
329    // iPhone apps will typically disable caching dated data or will call
330    // clearLastModifiedDates after done fetching to avoid wasting
331    // memory.
332  }
333
334  // username/password may change
335  NSString *username = [mUsernameField stringValue];
336  NSString *password = [mPasswordField stringValue];
337
338  [service setUserAgent:@"MyCompany-SampleSpreadsheetApp-1.0"]; // set this to yourName-appName-appVersion
339  [service setUserCredentialsWithUsername:username
340                                 password:password];
341
342  return service;
343}
344
345// get the spreadsheet selected in the top list, or nil if none
346- (GDataEntrySpreadsheet *)selectedSpreadsheet {
347
348  NSArray *spreadsheets = [mSpreadsheetFeed entries];
349  int rowIndex = [mSpreadsheetTable selectedRow];
350  if ([spreadsheets count] > 0 && rowIndex > -1) {
351
352    GDataEntrySpreadsheet *spreadsheet = [spreadsheets objectAtIndex:rowIndex];
353    return spreadsheet;
354  }
355  return nil;
356}
357
358// get the worksheet selected in the second list, or nil if none
359- (GDataEntryWorksheet *)selectedWorksheet {
360
361  NSArray *worksheets = [mWorksheetFeed entries];
362  int rowIndex = [mWorksheetTable selectedRow];
363  if ([worksheets count] > 0 && rowIndex > -1) {
364
365    GDataEntryWorksheet *worksheet = [worksheets objectAtIndex:rowIndex];
366    return worksheet;
367  }
368  return nil;
369}
370
371// get the table selected in the third list, or nil if none
372- (GDataEntrySpreadsheetTable *)selectedTable {
373
374  NSArray *tables = [mTableFeed entries];
375  int rowIndex = [mTableTable selectedRow];
376  if ([tables count] > 0 && rowIndex > -1) {
377
378    GDataEntrySpreadsheetTable *table = [tables objectAtIndex:rowIndex];
379    return table;
380  }
381  return nil;
382}
383
384// get the record selected in the bottom list
385- (GDataEntrySpreadsheetRecord *)selectedRecord {
386
387  NSArray *records = [mRecordFeed entries];
388
389  int rowIndex = [mRecordTable selectedRow];
390  if ([records count] > 0 && rowIndex > -1) {
391
392    GDataEntrySpreadsheetRecord *record = [records objectAtIndex:rowIndex];
393    return record;
394  }
395  return nil;
396}
397
398#pragma mark Fetch feed of all of the user's spreadsheets
399
400// begin retrieving the list of the user's spreadsheets
401- (void)fetchFeedOfSpreadsheets {
402
403  [self setSpreadsheetFeed:nil];
404  [self setSpreadsheetFetchError:nil];
405
406  [self setWorksheetFeed:nil];
407  [self setWorksheetFeedTicket:nil];
408  [self setWorksheetFetchError:nil];
409
410  [self setTableFeed:nil];
411  [self setTableFeedTicket:nil];
412  [self setTableFetchError:nil];
413
414  [self setRecordFeed:nil];
415  [self setRecordFeedTicket:nil];
416  [self setRecordFetchError:nil];
417
418  GDataServiceGoogleSpreadsheet *service = [self spreadsheetService];
419  NSURL *feedURL = [NSURL URLWithString:kGDataGoogleSpreadsheetsPrivateFullFeed];
420
421  GDataServiceTicket *ticket;
422  ticket = [service fetchFeedWithURL:feedURL
423                            delegate:self
424                   didFinishSelector:@selector(spreadsheetsTicket:finishedWithFeed:error:)];
425  [self setSpreadsheetFeedTicket:ticket];
426
427  [self updateUI];
428}
429
430// spreadsheet feed fetch callback
431- (void)spreadsheetsTicket:(GDataServiceTicket *)ticket
432          finishedWithFeed:(GDataFeedSpreadsheet *)feed
433                     error:(NSError *)error {
434
435  [self setSpreadsheetFeed:feed];
436  [self setSpreadsheetFetchError:error];
437  [self setSpreadsheetFeedTicket:nil];
438
439  [self updateUI];
440}
441
442#pragma mark Fetch a spreadsheet's worksheets and tables
443
444// for the spreadsheet selected in the top list, begin retrieving the lists of
445// worksheets and tables
446- (void)fetchSelectedSpreadsheet {
447
448  GDataEntrySpreadsheet *spreadsheet = [self selectedSpreadsheet];
449  if (spreadsheet) {
450
451    GDataServiceGoogleSpreadsheet *service = [self spreadsheetService];
452
453    // fetch the feed of worksheets
454    NSURL *worksheetsFeedURL = [spreadsheet worksheetsFeedURL];
455    if (worksheetsFeedURL) {
456
457      [self setWorksheetFeed:nil];
458      [self setWorksheetFetchError:nil];
459
460      GDataServiceTicket *ticket;
461      ticket = [service fetchFeedWithURL:worksheetsFeedURL
462                                delegate:self
463                       didFinishSelector:@selector(worksheetsTicket:finishedWithFeed:error:)];
464      [self setWorksheetFeedTicket:ticket];
465    }
466
467    // fetch the feed of tables
468    NSURL *tablesFeedURL = [[spreadsheet tablesFeedLink] URL];
469
470    // TODO - temporary code -
471    // rely just on the link to the tables feed once that finally is available
472    if (tablesFeedURL == nil) {
473      NSString *key = [[spreadsheet identifier] lastPathComponent];
474      NSString *template = @"http://spreadsheets.google.com/feeds/%@/tables";
475      NSString *tableFeedURLString = [NSString stringWithFormat:template, key];
476      tablesFeedURL = [NSURL URLWithString:tableFeedURLString];
477    }
478
479    if (tablesFeedURL) {
480
481      [self setTableFeed:nil];
482      [self setTableFetchError:nil];
483
484      // clear the record feed, since the user will need to select a table again
485      // and the record feed will be refetched
486      [self setRecordFeed:nil];
487      [self setRecordFetchError:nil];
488
489      GDataServiceTicket *ticket;
490      ticket = [service fetchFeedWithURL:tablesFeedURL
491                                delegate:self
492                       didFinishSelector:@selector(tablesTicket:finishedWithFeed:error:)];
493      [self setTableFeedTicket:ticket];
494    }
495
496    [self updateUI];
497  }
498}
499
500// worksheets feed fetch callback
501- (void)worksheetsTicket:(GDataServiceTicket *)ticket
502        finishedWithFeed:(GDataFeedWorksheet *)feed
503                   error:(NSError *)error {
504
505  [self setWorksheetFeed:feed];
506  [self setWorksheetFetchError:error];
507  [self setWorksheetFeedTicket:nil];
508
509  [self updateUI];
510}
511
512// tables feed fetch callback
513- (void)tablesTicket:(GDataServiceTicket *)ticket
514    finishedWithFeed:(GDataFeedSpreadsheetTable *)feed
515               error:(NSError *)error {
516
517  [self setTableFeed:feed];
518  [self setTableFetchError:error];
519  [self setTableFeedTicket:nil];
520
521  [self updateUI];
522}
523
524#pragma mark Fetch a table's records
525
526- (void)fetchSelectedTable {
527
528  GDataEntrySpreadsheetTable *table = [self selectedTable];
529  if (table) {
530
531    GDataServiceGoogleSpreadsheet *service = [self spreadsheetService];
532
533    // fetch the feed of records
534    NSURL *recordFeedURL = [table recordFeedURL];
535    if (recordFeedURL) {
536
537      [self setRecordFeed:nil];
538      [self setRecordFetchError:nil];
539
540      GDataServiceTicket *ticket;
541      ticket = [service fetchFeedWithURL:recordFeedURL
542                                delegate:self
543                       didFinishSelector:@selector(recordsTicket:finishedWithFeed:error:)];
544      [self setRecordFeedTicket:ticket];
545
546      [self updateUI];
547    }
548  }
549}
550
551// records feed fetch callback
552- (void)recordsTicket:(GDataServiceTicket *)ticket
553     finishedWithFeed:(GDataFeedSpreadsheetRecord *)feed
554                error:(NSError *)error {
555
556  [self setRecordFeed:feed];
557  [self setRecordFetchError:error];
558  [self setRecordFeedTicket:nil];
559
560  [self updateUI];
561}
562
563#pragma mark Add a table to the selected worksheet
564
565- (void)addTableToSelectedWorksheet {
566
567  GDataEntryWorksheet *selectedWorksheet = [self selectedWorksheet];
568  NSString *worksheetName = [[selectedWorksheet title] stringValue];
569
570  NSURL *postURL = [[mTableFeed postLink] URL];
571
572  if (worksheetName != nil && postURL != nil) {
573
574    // add a 2-column, 3-row table to the selected worksheet
575    GDataEntrySpreadsheetTable *newEntry;
576    newEntry = [GDataEntrySpreadsheetTable tableEntry];
577
578    NSString *title = [NSString stringWithFormat:@"Table Created %@",
579                       [NSDate date]];
580    [newEntry setTitleWithString:title];
581    [newEntry setWorksheetNameWithString:worksheetName];
582    [newEntry setSpreadsheetHeaderWithRow:3];
583
584    GDataSpreadsheetData *spData;
585    spData = [GDataSpreadsheetData spreadsheetDataWithStartIndex:4
586                                                    numberOfRows:3
587                                                   insertionMode:kGDataSpreadsheetModeInsert];
588    [spData addColumn:[GDataSpreadsheetColumn columnWithIndexString:@"A"
589                                                               name:@"Column Alpha"]];
590    [spData addColumn:[GDataSpreadsheetColumn columnWithIndexString:@"B"
591                                                               name:@"Column Beta"]];
592    [newEntry setSpreadsheetData:spData];
593
594    GDataServiceGoogleSpreadsheet *service = [self spreadsheetService];
595    GDataServiceTicket *ticket;
596
597    ticket = [service fetchEntryByInsertingEntry:newEntry
598                                      forFeedURL:postURL
599                                        delegate:self
600                               didFinishSelector:@selector(addTableTicket:finishedWithEntry:error:)];
601  }
602}
603
604- (void)addTableTicket:(GDataServiceTicket *)ticket
605     finishedWithEntry:(GDataEntrySpreadsheetTable *)entry
606                 error:(NSError *)error {
607  if (error == nil) {
608    NSBeginAlertSheet(@"Table added", nil, nil, nil,
609                      [self window], nil, nil,
610                      nil, nil, @"Added table \"%@\"",
611                      [[entry title] stringValue]);
612
613    [self fetchSelectedSpreadsheet];
614  } else {
615    NSBeginAlertSheet(@"Add Table Error", nil, nil, nil,
616                      [self window], nil, nil,
617                      nil, nil, @"%@", error);
618  }
619}
620
621#pragma mark Delete the selected table
622
623- (void)deleteSelectedTable {
624
625  GDataEntrySpreadsheetTable *selectedTable = [self selectedTable];
626  if (selectedTable) {
627
628    GDataServiceGoogleSpreadsheet *service = [self spreadsheetService];
629    GDataServiceTicket *ticket;
630
631    ticket = [service deleteEntry:selectedTable
632                         delegate:self
633                didFinishSelector:@selector(deleteTableTicket:finishedWithNil:error:)];
634    // save the name in the ticket
635    [ticket setProperty:[[selectedTable title] stringValue]
636                 forKey:@"tableName"];
637  }
638}
639
640- (void)deleteTableTicket:(GDataServiceTicket *)ticket
641          finishedWithNil:(GDataObject *)nilObj
642                    error:(NSError *)error {
643  if (error == nil) {
644    // succeeded
645    NSString *tableName = [ticket propertyForKey:@"tableName"];
646
647    NSBeginAlertSheet(@"Table deleted", nil, nil, nil,
648                      [self window], nil, nil,
649                      nil, nil, @"Deleted table \"%@\"", tableName);
650
651    [self fetchSelectedSpreadsheet];
652  } else {
653    // failed
654    NSBeginAlertSheet(@"Delete Table Error", nil, nil, nil,
655                      [self window], nil, nil,
656                      nil, nil, @"%@", error);
657  }
658}
659
660#pragma mark Randomize the data in the records of the selected table
661
662- (void)randomizeSelectedTable {
663
664  if (mRecordFeed != nil) {
665
666    NSArray *words = [NSArray arrayWithObjects:@"cat", @"dog", @"unicycle",
667                      @"airplane", @"boat", @"treehouse", @"doghouse",
668                      @"clouds", @"moon", @"sun", @"mars", @"venus", nil];
669
670    // for each field in each record, assign a random word
671    for (GDataEntrySpreadsheetRecord *recordEntry in mRecordFeed) {
672
673      for (GDataSpreadsheetField *field in [recordEntry fields]) {
674
675        NSString *word = [words objectAtIndex:(random() % [words count])];
676        [field setValue:word];
677      }
678
679      // if this API supported batch updates, we wouldn't have to fetch
680      // once per record here
681      GDataServiceGoogleSpreadsheet *service = [self spreadsheetService];
682      GDataServiceTicket *ticket;
683
684      ticket = [service fetchEntryByUpdatingEntry:recordEntry
685                                         delegate:self
686                                didFinishSelector:@selector(editRecordTicket:finishedWithEntry:error:)];
687
688      if (mRecordUpdateTickets == nil) {
689        mRecordUpdateTickets = [[NSMutableArray alloc] init];
690      }
691      [mRecordUpdateTickets addObject:ticket];
692    }
693  }
694}
695
696- (void)editRecordTicket:(GDataServiceTicket *)ticket
697       finishedWithEntry:(GDataEntrySpreadsheetTable *)entry
698                   error:(NSError *)error {
699
700  [mRecordUpdateTickets removeObject:ticket];
701  if (error == nil) {
702    // succeeded
703    if ([mRecordUpdateTickets count] == 0) {
704      // no more udpate tickets pending, so refresh the table's list of records
705      [self fetchSelectedTable];
706    }
707  } else {
708    // failed
709    NSLog(@"record update error: %@", error);
710  }
711
712  [self updateUI];
713}
714
715#pragma mark Global fetch progress indicator
716
717- (void)fetchStateChanged:(NSNotification *)note {
718
719  // This notification observer is invoked whenever fetching starts or stops.
720  //
721  // If we turn on fetch retries in the service or the ticket, we can also
722  // display an indicator of fetch retry delays by observing
723  // kGTMHTTPFetcherRetryDelayStartedNotification and
724  // kGTMHTTPFetcherRetryDelayStoppedNotification
725
726  static int gCounter = 0;
727  if ([[note name] isEqual:kGTMHTTPFetcherStartedNotification]) {
728    // started
729    ++gCounter;
730  } else {
731    // stopped
732    --gCounter;
733  }
734
735  if (gCounter > 0) {
736    [mGlobalFetchProgressIndicator startAnimation:self];
737  } else {
738    [mGlobalFetchProgressIndicator stopAnimation:self];
739  }
740}
741
742#pragma mark TableView delegate and data source methods
743
744- (void)tableViewSelectionDidChange:(NSNotification *)notification {
745  id obj = [notification object];
746  if (obj == mSpreadsheetTable) {
747    // the user clicked on a spreadsheet, so fetch its worksheets and tables
748    [self fetchSelectedSpreadsheet];
749  } else if (obj == mTableTable) {
750    // the user clicked on a table, so fetch its records
751    [self fetchSelectedTable];
752  } else {
753    // just update the results view for the selected item
754    [self updateUI];
755  }
756}
757
758// table view data source methods
759- (GDataFeedBase *)feedForTableView:(NSTableView *)tableView {
760  if (tableView == mSpreadsheetTable) return mSpreadsheetFeed;
761  if (tableView == mWorksheetTable)   return mWorksheetFeed;
762  if (tableView == mTableTable)       return mTableFeed;
763  if (tableView == mRecordTable)      return mRecordFeed;
764  return nil;
765}
766
767- (int)numberOfRowsInTableView:(NSTableView *)tableView {
768  GDataFeedBase *feed = [self feedForTableView:tableView];
769  return [[feed entries] count];
770}
771
772- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(int)row {
773  GDataFeedBase *feed = [self feedForTableView:tableView];
774  GDataEntryBase *entry = [[feed entries] objectAtIndex:row];
775
776  if (tableView == mRecordTable) {
777    // for records, return the content, since it conveniently summarizes all
778    // fields of the record
779    return [[entry content] stringValue];
780  } else {
781    return [[entry title] stringValue];
782  }
783}
784
785#pragma mark Setters and Getters
786
787- (GDataFeedSpreadsheet *)spreadsheetFeed {
788  return mSpreadsheetFeed;
789}
790
791- (void)setSpreadsheetFeed:(GDataFeedSpreadsheet *)feed {
792  [mSpreadsheetFeed autorelease];
793  mSpreadsheetFeed = [feed retain];
794}
795
796- (NSError *)spreadsheetFetchError {
797  return mSpreadsheetFetchError;
798}
799
800- (void)setSpreadsheetFetchError:(NSError *)error {
801  [mSpreadsheetFetchError release];
802  mSpreadsheetFetchError = [error retain];
803}
804
805- (GDataServiceTicket *)spreadsheetFeedTicket {
806  return mSpreadsheetFeedTicket;
807}
808
809- (void)setSpreadsheetFeedTicket:(GDataServiceTicket *)obj {
810  [mSpreadsheetFeedTicket autorelease];
811  mSpreadsheetFeedTicket = [obj retain];
812}
813
814
815- (GDataFeedWorksheet *)worksheetFeed {
816  return mWorksheetFeed;
817}
818
819- (void)setWorksheetFeed:(GDataFeedWorksheet *)feed {
820  [mWorksheetFeed autorelease];
821  mWorksheetFeed = [feed retain];
822}
823
824- (NSError *)worksheetFetchError {
825  return mWorksheetFetchError;
826}
827
828- (void)setWorksheetFetchError:(NSError *)error {
829  [mWorksheetFetchError release];
830  mWorksheetFetchError = [error retain];
831}
832
833- (GDataServiceTicket *)worksheetFeedTicket {
834  return mWorksheetFeedTicket;
835}
836
837- (void)setWorksheetFeedTicket:(GDataServiceTicket *)obj {
838  [mWorksheetFeedTicket autorelease];
839  mWorksheetFeedTicket = [obj retain];
840}
841
842
843- (GDataFeedSpreadsheetTable *)tableFeed {
844  return mTableFeed;
845}
846
847- (void)setTableFeed:(GDataFeedSpreadsheetTable *)feed {
848  [mTableFeed autorelease];
849  mTableFeed = [feed retain];
850}
851
852- (NSError *)tableFetchError {
853  return mTableFetchError;
854}
855
856- (void)setTableFetchError:(NSError *)error {
857  [mTableFetchError release];
858  mTableFetchError = [error retain];
859}
860
861- (GDataServiceTicket *)tableFeedTicket {
862  return mTableFeedTicket;
863}
864
865- (void)setTableFeedTicket:(GDataServiceTicket *)obj {
866  [mTableFeedTicket autorelease];
867  mTableFeedTicket = [obj retain];
868}
869
870
871- (GDataFeedSpreadsheetRecord *)recordFeed {
872  return mRecordFeed;
873}
874
875- (void)setRecordFeed:(GDataFeedSpreadsheetRecord *)feed {
876  [mRecordFeed autorelease];
877  mRecordFeed = [feed retain];
878}
879
880- (NSError *)recordFetchError {
881  return mRecordFetchError;
882}
883
884- (void)setRecordFetchError:(NSError *)error {
885  [mRecordFetchError release];
886  mRecordFetchError = [error retain];
887}
888
889- (GDataServiceTicket *)recordFeedTicket {
890  return mRecordFeedTicket;
891}
892
893- (void)setRecordFeedTicket:(GDataServiceTicket *)obj {
894  [mRecordFeedTicket autorelease];
895  mRecordFeedTicket = [obj retain];
896}
897
898@end