PageRenderTime 154ms CodeModel.GetById 3ms app.highlight 143ms RepoModel.GetById 1ms app.codeStats 1ms

/core/externals/update-engine/externals/gdata-objectivec-client/Examples/BooksSample/BooksSampleWindowController.m

http://macfuse.googlecode.com/
Objective C | 950 lines | 587 code | 237 blank | 126 comment | 93 complexity | 0727e8490084b0a7c1d5a7c18b316e60 MD5 | raw file
  1/* Copyright (c) 2008 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//  BooksSampleWindowController.m
 18//
 19
 20//
 21// IMPORTANT:
 22//
 23// The XML-based API for Google Books has been replaced with a more efficient
 24// and easier-to-use JSON API.  The new API is documented at
 25//
 26//   https://developers.google.com/books/
 27//
 28// See the new Objective-C client library and sample code at
 29//   http://code.google.com/p/google-api-objectivec-client/
 30//
 31// This sample application and library support for the XML-based Books
 32// API will eventually be removed.
 33//
 34
 35#import "BooksSampleWindowController.h"
 36
 37// segmented control indexes
 38const int kLibrarySegment = 0;    // feed of books in user's library
 39const int kAnnotationSegment = 1; // feed of books annotated by the user
 40const int kCollectionSegment = 2; // feed of collections from user's library
 41
 42const int kPreviewSegment = 0;
 43const int kInfoSegment = 1;
 44
 45// tags for viewability pop-up
 46const int kAnyViewability = 0;     // any amount or none is viewable
 47const int kPartialViewability = 1; // some is viewable
 48const int kFullViewability = 2;    // entire book must be viewable
 49
 50// feed properties indicating the source of the feed
 51NSString *kSourceProperty = @"source";
 52NSString *kSearchFeedSource = @"search";
 53NSString *kVolumesFeedSource = @"volumes";
 54NSString *kAnnotationsFeedSource = @"annotations";
 55
 56@interface BooksSampleWindowController (PrivateMethods)
 57- (void)updateUI;
 58
 59- (void)fetchVolumes;
 60- (void)fetchCollections;
 61- (void)searchNow;
 62
 63- (void)addLabelToSelectedVolume;
 64- (void)setReviewForSelectedVolume;
 65- (void)setRatingForSelectedVolume;
 66
 67- (GDataServiceGoogleBooks *)booksService;
 68- (GDataEntryVolume *)selectedVolume;
 69
 70- (GDataFeedVolume *)volumesFeed;
 71- (void)setVolumesFeed:(GDataFeedVolume *)feed;
 72
 73- (GDataServiceTicket *)volumesFetchTicket;
 74- (void)setVolumesFetchTicket:(GDataServiceTicket *)ticket;
 75
 76- (NSError *)volumesFetchError;
 77- (void)setVolumesFetchError:(NSError *)error;
 78
 79- (GDataFeedCollection *)collectionsFeed;
 80- (void)setCollectionsFeed:(GDataFeedCollection *)feed;
 81
 82- (GDataServiceTicket *)collectionsFetchTicket;
 83- (void)setCollectionsFetchTicket:(GDataServiceTicket *)ticket;
 84
 85- (NSError *)collectionsFetchError;
 86- (void)setCollectionsFetchError:(NSError *)error;
 87
 88- (GDataServiceTicket *)annotationsFetchTicket;
 89- (void)setAnnotationsFetchTicket:(GDataServiceTicket *)ticket;
 90
 91- (NSString *)volumeImageURLString;
 92- (void)setVolumeImageURLString:(NSString *)str;
 93
 94- (void)updateImageForVolume:(GDataEntryVolume *)volume;
 95- (void)updateWebViewForVolume:(GDataEntryVolume *)volume;
 96
 97- (void)fetchURLString:(NSString *)urlString forImageView:(NSImageView *)view;
 98
 99- (NSString *)volumeWebURLString;
100- (void)setVolumeWebURLString:(NSString *)str;
101@end
102
103@implementation BooksSampleWindowController
104
105static BooksSampleWindowController* gBooksSampleWindowController = nil;
106
107+ (BooksSampleWindowController *)sharedBooksSampleWindowController {
108
109  if (!gBooksSampleWindowController) {
110    gBooksSampleWindowController = [[BooksSampleWindowController alloc] init];
111  }
112  return gBooksSampleWindowController;
113}
114
115
116- (id)init {
117  return [self initWithWindowNibName:@"BooksSampleWindow"];
118}
119
120- (void)windowDidLoad {
121}
122
123- (void)awakeFromNib {
124  // Set the result text field to have a distinctive color and mono-spaced font
125  // to aid in understanding of each entry.
126  [mVolumesResultTextField setTextColor:[NSColor darkGrayColor]];
127
128  NSFont *resultTextFont = [NSFont fontWithName:@"Monaco" size:9];
129  [mVolumesResultTextField setFont:resultTextFont];
130
131  [self updateUI];
132}
133
134- (void)dealloc {
135  [mVolumesFetchTicket cancelTicket];
136  [mVolumesFeed release];
137  [mVolumesFetchTicket release];
138  [mVolumesFetchError release];
139
140  [mAnnotationsFetchTicket cancelTicket];
141  [mAnnotationsFetchTicket release];
142
143  [mVolumeImageURLString release];
144  [mVolumeWebURLString release];
145
146  [super dealloc];
147}
148
149#pragma mark -
150
151- (void)updateUI {
152
153  // volume entries list display
154  [mVolumesTable reloadData];
155
156  // turn the spinners on during fetches
157  if (mVolumesFetchTicket != nil) {
158    [mVolumesProgressIndicator startAnimation:self];
159  } else {
160    [mVolumesProgressIndicator stopAnimation:self];
161  }
162
163  if (mCollectionsFetchTicket != nil) {
164    [mCollectionProgressIndicator startAnimation:self];
165  } else {
166    [mCollectionProgressIndicator stopAnimation:self];
167  }
168
169  if (mAnnotationsFetchTicket != nil) {
170    [mAnnotationsProgressIndicator startAnimation:self];
171  } else {
172    [mAnnotationsProgressIndicator stopAnimation:self];
173  }
174
175  GDataEntryVolume *selectedVolume = [self selectedVolume];
176
177  // display the volumes fetch result or the selected volume entry
178  NSString *volumesResultStr = @"";
179  if (mVolumesFetchError) {
180    volumesResultStr = [mVolumesFetchError description];
181  } else {
182    if (selectedVolume) {
183      volumesResultStr = [selectedVolume description];
184    }
185  }
186  [mVolumesResultTextField setString:volumesResultStr];
187
188  // update the book thumbnail and the web preview
189  [self updateImageForVolume:selectedVolume];
190  [self updateWebViewForVolume:selectedVolume];
191
192  GDataFeedVolume *volumesFeed = [self volumesFeed];
193  BOOL isAnnotationsFeed =
194    [[volumesFeed propertyForKey:kSourceProperty] isEqual:kAnnotationsFeedSource];
195
196  // enable/disable fetch buttons
197  BOOL hasUsername = ([[mUsernameField stringValue] length] > 0);
198  BOOL hasPassword = ([[mPasswordField stringValue] length] > 0);
199  BOOL canFetchUserFeed = (hasUsername && hasPassword);
200
201  [mGetVolumesButton setEnabled:canFetchUserFeed];
202  [mUserFeedTypeSegments setEnabled:canFetchUserFeed];
203
204  BOOL hasSearchTerm = ([[mSearchField stringValue] length] > 0);
205  [mSearchButton setEnabled:hasSearchTerm];
206
207  // enable/disable collection pop-up
208  BOOL hasCollections = ([[mCollectionsFeed entries] count] > 0);
209  [mCollectionPopup setEnabled:hasCollections];
210
211  // enable/disable cancel buttons
212  [mVolumesCancelButton setEnabled:(mVolumesFetchTicket != nil)];
213  [mAnnotationsCancelButton setEnabled:(mAnnotationsFetchTicket != nil)];
214
215  // enable/disable other buttons
216
217  // "add label" button
218  BOOL isVolumeSelected = ([self selectedVolume] != nil);
219  BOOL isLabelProvided = ([[mLabelField stringValue] length] > 0);
220
221  BOOL canAddLabel = isAnnotationsFeed && isVolumeSelected && isLabelProvided;
222
223  [mAddLabelButton setEnabled:canAddLabel];
224
225  // set rating pop-up button
226  //
227  // if there's no user-set value, we'll use tag 0, "none"
228  GDataRating *rating = [selectedVolume rating];
229  [mRatingPopup selectItemWithTag:[[rating value] intValue]];
230
231  NSString *avgStr = [NSString stringWithFormat:@"Avg: %@", [rating average]];
232  [mAverageRatingField setStringValue:avgStr];
233
234  BOOL canSetRating = isAnnotationsFeed && isVolumeSelected;
235  [mRatingPopup setEnabled:canSetRating];
236
237  // "save review" button
238  NSString *updatedReviewStr = [mReviewField stringValue];
239
240  NSString *oldReviewStr = [[selectedVolume review] stringValue];
241  if (oldReviewStr == nil) oldReviewStr = @"";
242
243  BOOL hasReviewChanged = ![oldReviewStr isEqual:updatedReviewStr];
244  BOOL canSaveReview = isAnnotationsFeed && hasReviewChanged;
245  [mSaveReviewButton setEnabled:canSaveReview];
246}
247
248
249#pragma mark IBActions
250
251- (IBAction)getVolumesClicked:(id)sender {
252
253  NSCharacterSet *whitespace = [NSCharacterSet whitespaceAndNewlineCharacterSet];
254
255  NSString *username = [mUsernameField stringValue];
256  username = [username stringByTrimmingCharactersInSet:whitespace];
257
258  if ([username rangeOfString:@"@"].location == NSNotFound) {
259    // if no domain was supplied, add @gmail.com
260    username = [username stringByAppendingString:@"@gmail.com"];
261  }
262
263  [mUsernameField setStringValue:username];
264
265  if ([mUserFeedTypeSegments selectedSegment] == kCollectionSegment
266      && mCollectionsFeed == nil) {
267    [self fetchCollections];
268  } else {
269    [self fetchVolumes];
270  }
271}
272
273- (IBAction)collectionPopupClicked:(id)sender {
274  [mUserFeedTypeSegments setSelectedSegment:kCollectionSegment];
275  [self getVolumesClicked:sender];
276}
277
278- (IBAction)searchClicked:(id)sender {
279  [self searchNow];
280}
281
282- (IBAction)userFeedTypeSegmentClicked:(id)sender {
283  [self getVolumesClicked:nil];
284}
285
286
287- (IBAction)webViewSegmentClicked:(id)sender {
288  [self updateUI];
289}
290
291- (IBAction)cancelVolumeFetchClicked:(id)sender {
292  [mVolumesFetchTicket cancelTicket];
293  [self setVolumesFetchTicket:nil];
294  [self updateUI];
295}
296
297- (IBAction)cancelAnnotationsFetchClicked:(id)sender {
298  [mAnnotationsFetchTicket cancelTicket];
299  [self setAnnotationsFetchTicket:nil];
300  [self updateUI];
301}
302
303- (IBAction)addLabelClicked:(id)sender {
304  [self addLabelToSelectedVolume];
305}
306
307- (IBAction)ratingPopupClicked:(id)sender {
308  [self setRatingForSelectedVolume];
309}
310
311- (IBAction)saveReviewClicked:(id)sender {
312  [self setReviewForSelectedVolume];
313}
314
315- (IBAction)loggingCheckboxClicked:(id)sender {
316  [GTMHTTPFetcher setLoggingEnabled:[sender state]];
317}
318
319#pragma mark -
320
321// get a volume service object with the current username/password
322//
323// A "service" object handles networking tasks.  Service objects
324// contain user authentication information as well as networking
325// state information (such as cookies and the "last modified" date for
326// fetched data.)
327
328- (GDataServiceGoogleBooks *)booksService {
329
330  static GDataServiceGoogleBooks* service = nil;
331
332  if (!service) {
333    // The service object handles networking cookies and the results cache,
334    // so we want just one instance in the application
335
336    service = [[GDataServiceGoogleBooks alloc] init];
337
338    [service setShouldCacheResponseData:YES];
339    [service setServiceShouldFollowNextLinks:YES];
340  }
341
342  // update the username/password each time the service is requested
343  NSString *username = [mUsernameField stringValue];
344  NSString *password = [mPasswordField stringValue];
345
346  if ([username length] > 0 && [password length] > 0) {
347    [service setUserCredentialsWithUsername:username
348                                   password:password];
349  } else {
350    [service setUserCredentialsWithUsername:nil
351                                   password:nil];
352  }
353
354  return service;
355}
356
357// get the volume selected in the top list, or nil if none
358- (GDataEntryVolume *)selectedVolume {
359
360  NSArray *volumes = [mVolumesFeed entries];
361  int rowIndex = [mVolumesTable selectedRow];
362  if ([volumes count] > 0 && rowIndex > -1) {
363
364    GDataEntryVolume *volume = [volumes objectAtIndex:rowIndex];
365    return volume;
366  }
367  return nil;
368}
369
370#pragma mark Fetch volumes
371
372// begin retrieving the list of the user's annotated volumes
373// or library volumes
374- (void)fetchVolumes {
375
376  [self setVolumesFeed:nil];
377  [self setVolumesFetchError:nil];
378  [self setVolumesFetchTicket:nil];
379
380  GDataServiceGoogleBooks *service = [self booksService];
381  GDataServiceTicket *ticket;
382
383  NSURL *feedURL;
384  NSString *feedType;
385
386  NSInteger segmentIndex = [mUserFeedTypeSegments selectedSegment];
387  if (segmentIndex == kLibrarySegment) {
388    // feed of user's library
389    feedURL = [GDataServiceGoogleBooks booksURLForCollectionID:kGDataGoogleBooksLibraryCollection];
390    feedType = kVolumesFeedSource;
391  } else if (segmentIndex == kAnnotationSegment) {
392    // feed of books annotated by the user
393    feedURL = [GDataServiceGoogleBooks booksURLForVolumeID:nil];
394    feedType = kAnnotationsFeedSource;
395  } else {
396    // collection from user's library
397    NSMenuItem *menuItem = [mCollectionPopup selectedItem];
398    feedURL = [menuItem representedObject];
399    feedType = kVolumesFeedSource;
400  }
401
402  ticket = [service fetchFeedWithURL:feedURL
403                            delegate:self
404                   didFinishSelector:@selector(volumeListFetchTicket:finishedWithFeed:error:)];
405  [self setVolumesFetchTicket:ticket];
406
407  // preserve the source of the feed
408  // (editable annotations vs read-only volumes)
409  [ticket setProperty:feedType forKey:kSourceProperty];
410
411  [self updateUI];
412}
413
414// fetched volume list callback
415- (void)volumeListFetchTicket:(GDataServiceTicket *)ticket
416             finishedWithFeed:(GDataFeedVolume *)feed
417                        error:(NSError *)error {
418
419  [self setVolumesFeed:feed];
420  [self setVolumesFetchError:error];
421  [self setVolumesFetchTicket:nil];
422
423  // transfer the feed source to a property in the feed object
424  if (error == nil) {
425    NSString *sourceProp = [ticket propertyForKey:kSourceProperty];
426    [feed setProperty:sourceProp forKey:kSourceProperty];
427  }
428
429  [self updateUI];
430}
431
432#pragma mark Fetch collections
433
434// begin retrieving the list of the user's collections
435- (void)fetchCollections {
436
437  [self setCollectionsFeed:nil];
438  [self setCollectionsFetchError:nil];
439  [self setCollectionsFetchTicket:nil];
440
441  GDataServiceGoogleBooks *service = [self booksService];
442  GDataServiceTicket *ticket;
443
444  NSURL *collectionsFeedURL = [GDataServiceGoogleBooks collectionsURL];
445  ticket = [service fetchFeedWithURL:collectionsFeedURL
446                            delegate:self
447                   didFinishSelector:@selector(collectionListFetchTicket:finishedWithFeed:error:)];
448  [self setCollectionsFetchTicket:ticket];
449
450  [self updateUI];
451}
452
453// fetched volume list callback
454- (void)collectionListFetchTicket:(GDataServiceTicket *)ticket
455                 finishedWithFeed:(GDataFeedCollection *)feed
456                            error:(NSError *)error {
457
458  [self setCollectionsFeed:feed];
459  [self setCollectionsFetchError:error];
460  [self setCollectionsFetchTicket:nil];
461
462  if (error == nil) {
463    // load the pop-up menu of collections
464    [mCollectionPopup removeAllItems];
465
466    for (GDataEntryCollection *entry in [feed entries]) {
467      NSString *collectionName = [[entry title] stringValue];
468      NSMenuItem *newMenuItem = [[mCollectionPopup menu] addItemWithTitle:collectionName
469                                                                   action:NULL
470                                                            keyEquivalent:@""];
471      // have the menu item remember its feed's URL
472      NSURL *collectionFeedURL = [[entry feedLink] URL];
473      [newMenuItem setRepresentedObject:collectionFeedURL];
474    }
475  } else {
476    // failed to fetch collections
477    NSBeginAlertSheet(@"Error", nil, nil, nil,
478                      [self window], nil, nil,
479                      nil, nil, @"Error fetching collection list: %@",
480                      error);
481
482    [mUserFeedTypeSegments setSelectedSegment:kLibrarySegment];
483  }
484
485  [self fetchVolumes];
486}
487
488#pragma mark Search the query string
489
490// begin searching for the user-specified term
491- (void)searchNow {
492
493  [self setVolumesFeed:nil];
494  [self setVolumesFetchError:nil];
495  [self setVolumesFetchTicket:nil];
496
497  // set the viewability parameter from the user's pop-up menu setting
498  int viewabilityIndex = [mViewabilityPopUp selectedTag];
499  NSString* viewability;
500
501  if (viewabilityIndex == kAnyViewability) {
502    viewability = kGDataGoogleBooksMinViewabilityNone;
503  } else if (viewabilityIndex == kPartialViewability) {
504    viewability = kGDataGoogleBooksMinViewabilityPartial;
505  } else {
506    viewability = kGDataGoogleBooksMinViewabilityFull;
507  }
508
509  NSString *searchTerm = [mSearchField stringValue];
510
511  NSURL *feedURL = [NSURL URLWithString:kGDataGoogleBooksVolumeFeed];
512
513  GDataQueryBooks *query = [GDataQueryBooks booksQueryWithFeedURL:feedURL];
514  [query setFullTextQueryString:searchTerm];
515  [query setMinimumViewability:viewability];
516
517  // we'll reuse the volume list fetch's callbacks for the search query callback
518  GDataServiceGoogleBooks *service = [self booksService];
519  GDataServiceTicket *ticket;
520
521  ticket = [service fetchFeedWithQuery:query
522                              delegate:self
523                     didFinishSelector:@selector(volumeListFetchTicket:finishedWithFeed:error:)];
524
525  [self setVolumesFetchTicket:ticket];
526
527  // searches can have way too many results; don't try to accumulate them all
528  [ticket setShouldFollowNextLinks:NO];
529
530  [ticket setProperty:kSearchFeedSource forKey:kSourceProperty];
531
532  [self updateUI];
533}
534
535#pragma mark Fetch a volume's thumbnail
536
537// fetch or clear the thumbnail for this specified volume entry
538- (void)updateImageForVolume:(GDataEntryVolume *)volume {
539
540  // if there's a thumbnail and it's different from the one being shown,
541  // fetch it now
542  if (!volume) {
543    // clear the image
544    [mVolumeImageView setImage:nil];
545    [self setVolumeImageURLString:nil];
546
547  } else {
548    // if the new thumbnail URL string is different from the previous one,
549    // save the new one, clear the image and fetch the new image
550
551    NSString *imageURLString = [[volume thumbnailLink] href];
552    if (!imageURLString || ![mVolumeImageURLString isEqual:imageURLString]) {
553
554      [self setVolumeImageURLString:imageURLString];
555      [mVolumeImageView setImage:nil];
556
557      if (imageURLString) {
558        [self fetchURLString:imageURLString forImageView:mVolumeImageView];
559      }
560    }
561  }
562}
563
564- (void)fetchURLString:(NSString *)urlString forImageView:(NSImageView *)view {
565  GTMHTTPFetcher *fetcher = [GTMHTTPFetcher fetcherWithURLString:urlString];
566
567  // use the fetcher's userData to remember which view we'll display
568  // this in once the fetch completes
569  [fetcher setUserData:view];
570
571  [fetcher beginFetchWithDelegate:self
572                didFinishSelector:@selector(imageFetcher:finishedWithData:error:)];
573}
574
575- (void)imageFetcher:(GTMHTTPFetcher *)fetcher finishedWithData:(NSData *)data  error:(NSError *)error {
576  if (error == nil) {
577    // got the data; display it in the image view
578    NSImage *image = [[[NSImage alloc] initWithData:data] autorelease];
579
580    NSImageView *view = (NSImageView *)[fetcher userData];
581
582    [view setImage:image];
583  } else {
584    NSLog(@"imageFetcher:%@ failedWithError:%@", fetcher,  error);
585  }
586}
587
588#pragma mark Update Web View
589
590// fetch or clear the thumbnail for this specified album
591- (void)updateWebViewForVolume:(GDataEntryVolume *)volume {
592
593  // if there's a web URL and it's different from the one being shown,
594  // fetch it now
595
596  BOOL shouldClearWebView = NO;
597  if (volume == nil) {
598
599    // no volume is selected
600    shouldClearWebView = YES;
601
602  } else {
603
604    // if the new web URL string is different from the previous one,
605    // save the new one, clear the web page and fetch the new page
606
607    // the user may view either the the book preview or the book
608    // info page
609    NSInteger segmentIndex = [mWebViewSegments selectedSegment];
610    NSString *urlString;
611    if (segmentIndex == 0) {
612      urlString = [[volume previewLink] href];
613    } else {
614      urlString = [[volume infoLink] href];
615    }
616
617    if ([urlString length] == 0) {
618
619      shouldClearWebView = YES;
620
621    } else if (![mVolumeWebURLString isEqual:urlString]) {
622
623      // URL has changed
624      [self setVolumeWebURLString:urlString];
625
626      NSURL *url = [NSURL URLWithString:urlString];
627      NSURLRequest *request = [NSURLRequest requestWithURL:url];
628
629      [[mWebView mainFrame] loadRequest:request];
630    }
631  }
632
633  if (shouldClearWebView) {
634    // clear the image
635    [[mWebView mainFrame] loadHTMLString:@"" baseURL:nil];
636    [self setVolumeWebURLString:nil];
637  }
638}
639
640#pragma mark Add Label
641
642- (void)addLabelToSelectedVolume {
643
644  GDataEntryVolume *volume = [self selectedVolume];
645  if (volume) {
646
647    NSString *label = [mLabelField stringValue];
648
649    GDataCategory *cat = [GDataCategory categoryWithScheme:kGDataBooksLabelsScheme
650                                                      term:label];
651
652    // to avoid changing the original, which is being displayed,
653    // we'll add the category to a copy of the volume entry
654    GDataEntryVolume *volCopy = [[volume copy] autorelease];
655    [volCopy addCategory:cat];
656
657    GDataServiceGoogleBooks *service = [self booksService];
658    GDataServiceTicket *ticket;
659    ticket = [service fetchEntryByUpdatingEntry:volCopy
660                                       delegate:self
661                              didFinishSelector:@selector(addLabelTicket:finishedWithEntry:error:)];
662    [self setAnnotationsFetchTicket:ticket];
663
664    // save the label so we can display it in the success callback
665    [ticket setUserData:label];
666
667    [self updateUI];
668  }
669}
670
671- (void)addLabelTicket:(GDataServiceTicket *)ticket
672     finishedWithEntry:(GDataEntryVolume *)entry
673                 error:(NSError *)error {
674
675  [self setAnnotationsFetchTicket:nil];
676
677  if (error == nil) {
678    NSString *label = [ticket userData];
679
680    NSBeginAlertSheet(@"Added label", nil, nil, nil,
681                      [self window], nil, nil,
682                      nil, nil, @"Added label \"%@\" to volume %@",
683                      label,
684                      [[entry title] stringValue]);
685
686    [self fetchVolumes];
687  } else {
688    // add label failed
689    NSBeginAlertSheet(@"Add label failed", nil, nil, nil,
690                      [self window], nil, nil,
691                      nil, nil, @"Label add failed: %@", error);
692
693    [self updateUI];
694  }
695}
696
697#pragma mark Set Review
698
699- (void)setReviewForSelectedVolume {
700
701  GDataEntryVolume *volume = [self selectedVolume];
702  if (volume) {
703    NSString *reviewStr = [mReviewField stringValue];
704
705    // to avoid changing the original, which is being displayed,
706    // we'll set the review element in a copy of the volume entry
707    GDataEntryVolume *volCopy = [[volume copy] autorelease];
708
709    GDataVolumeReview *review;
710
711    if ([reviewStr length] > 0) {
712      // set the review
713      review = [GDataVolumeReview textConstructWithString:reviewStr];
714    } else {
715      // delete the review
716      review = nil;
717    }
718    [volCopy setReview:review];
719
720    GDataServiceGoogleBooks *service = [self booksService];
721    GDataServiceTicket *ticket;
722    ticket = [service fetchEntryByUpdatingEntry:volCopy
723                                       delegate:self
724                              didFinishSelector:@selector(setReviewTicket:finishedWithEntry:error:)];
725    [self setAnnotationsFetchTicket:ticket];
726
727    [self updateUI];
728  }
729}
730
731- (void)setReviewTicket:(GDataServiceTicket *)ticket
732      finishedWithEntry:(GDataEntryVolume *)entry
733                  error:(NSError *)error {
734
735  [self setAnnotationsFetchTicket:nil];
736
737  if (error == nil) {
738    NSBeginAlertSheet(@"Review set", nil, nil, nil,
739                      [self window], nil, nil,
740                      nil, nil, @"Updated review for volume %@",
741                      [[entry title] stringValue]);
742
743    [self fetchVolumes];
744  } else {
745    // fetch failed
746    NSBeginAlertSheet(@"Set review failed", nil, nil, nil,
747                      [self window], nil, nil,
748                      nil, nil, @"Review set failed: %@", error);
749
750    [self updateUI];
751  }
752}
753
754#pragma mark Set Rating
755
756- (void)setRatingForSelectedVolume {
757
758  GDataEntryVolume *volume = [self selectedVolume];
759  if (volume) {
760    // to avoid changing the original, which is being displayed,
761    // we'll set the rating on a copy of the volume entry
762    GDataEntryVolume *volCopy = [[volume copy] autorelease];
763
764    int newRating = [mRatingPopup selectedTag];
765
766    if (newRating == 0) {
767      // if the user wants no rating set, remove the rating element from the
768      // volume entry
769      [volCopy setRating:nil];
770    } else {
771      // set the rating element to have a value matching the pop-up item's tag
772      [volCopy setRating:[GDataRating ratingWithValue:newRating max:5 min:1]];
773    }
774
775    GDataServiceGoogleBooks *service = [self booksService];
776    GDataServiceTicket *ticket;
777    ticket = [service fetchEntryByUpdatingEntry:volCopy
778                                       delegate:self
779                              didFinishSelector:@selector(setRatingTicket:finishedWithEntry:error:)];
780    [self setAnnotationsFetchTicket:ticket];
781
782    [self updateUI];
783  }
784}
785
786- (void)setRatingTicket:(GDataServiceTicket *)ticket
787      finishedWithEntry:(GDataEntryVolume *)entry
788                  error:(NSError *)error {
789
790  [self setAnnotationsFetchTicket:nil];
791
792  if (error == nil) {
793    NSBeginAlertSheet(@"Set rating", nil, nil, nil,
794                      [self window], nil, nil,
795                      nil, nil, @"Set rating \"%@\" to volume %@",
796                      [[entry rating] value],
797                      [[entry title] stringValue]);
798
799    [self fetchVolumes];
800  } else {
801    // fetch failed
802    NSBeginAlertSheet(@"Set rating failed", nil, nil, nil,
803                      [self window], nil, nil,
804                      nil, nil, @"Set rating failed: %@", error);
805
806    [self updateUI];
807  }
808}
809
810////////////////////////////////////////////////////////
811#pragma mark Text field delegate methods
812
813- (void)controlTextDidChange:(NSNotification *)note {
814
815  [self updateUI]; // enabled/disable buttons
816}
817
818
819#pragma mark TableView Delegate and Data Source Methods
820//
821// table view delegate methods
822//
823// there is only one table, mVolumesTable
824//
825
826- (void)tableViewSelectionDidChange:(NSNotification *)notification {
827
828  // when the user clicks on a volume, we load the review field,
829  // which they can then edit.  We don't want to reload the edit
830  // field every time updateUI is called, since that could too easily
831  // wipe out the user's edits
832  GDataEntryVolume *selectedVolume = [self selectedVolume];
833
834  NSString *reviewStr = [[selectedVolume review] stringValue];
835  if (reviewStr == nil) reviewStr = @"";
836
837  [mReviewField setStringValue:reviewStr];
838
839  [self updateUI];
840}
841
842// table view data source methods
843
844- (int)numberOfRowsInTableView:(NSTableView *)tableView {
845  return [[mVolumesFeed entries] count];
846}
847
848- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(int)row {
849
850  // get the volume entry's title in a comma-separated list
851  GDataEntryVolume *volume = [[mVolumesFeed entries] objectAtIndex:row];
852  NSString *title = [[volume title] stringValue];
853
854  // append any labels
855  NSArray *labelCats = [volume categoriesWithScheme:kGDataBooksLabelsScheme];
856  if ([labelCats count] > 0) {
857
858    NSArray *labels = [labelCats valueForKeyPath:@"term"];
859    NSString *labelStr = [labels componentsJoinedByString:@", "];
860
861    title = [NSString stringWithFormat:@"%@ (%@)", title, labelStr];
862  }
863  return title;
864}
865
866#pragma mark Setters and Getters
867
868- (GDataFeedVolume *)volumesFeed {
869  return mVolumesFeed;
870}
871
872- (void)setVolumesFeed:(GDataFeedVolume *)feed {
873  [mVolumesFeed autorelease];
874  mVolumesFeed = [feed retain];
875}
876
877- (NSError *)volumesFetchError {
878  return mVolumesFetchError;
879}
880
881- (void)setVolumesFetchError:(NSError *)error {
882  [mVolumesFetchError release];
883  mVolumesFetchError = [error retain];
884}
885
886- (GDataServiceTicket *)volumesFetchTicket {
887  return mVolumesFetchTicket;
888}
889
890- (void)setVolumesFetchTicket:(GDataServiceTicket *)ticket {
891  [mVolumesFetchTicket release];
892  mVolumesFetchTicket = [ticket retain];
893}
894
895- (GDataFeedCollection *)collectionsFeed {
896  return mCollectionsFeed;
897}
898
899- (void)setCollectionsFeed:(GDataFeedCollection *)feed {
900  [mCollectionsFeed autorelease];
901  mCollectionsFeed = [feed retain];
902}
903
904- (NSError *)collectionsFetchError {
905  return mCollectionsFetchError;
906}
907
908- (void)setCollectionsFetchError:(NSError *)error {
909  [mCollectionsFetchError release];
910  mCollectionsFetchError = [error retain];
911}
912
913- (GDataServiceTicket *)collectionsFetchTicket {
914  return mCollectionsFetchTicket;
915}
916
917- (void)setCollectionsFetchTicket:(GDataServiceTicket *)ticket {
918  [mCollectionsFetchTicket release];
919  mCollectionsFetchTicket = [ticket retain];
920}
921
922- (GDataServiceTicket *)annotationsFetchTicket {
923  return mAnnotationsFetchTicket;
924}
925
926- (void)setAnnotationsFetchTicket:(GDataServiceTicket *)ticket {
927  [mAnnotationsFetchTicket release];
928  mAnnotationsFetchTicket = [ticket retain];
929}
930
931- (NSString *)volumeImageURLString {
932  return mVolumeImageURLString;
933}
934
935- (void)setVolumeImageURLString:(NSString *)str {
936  [mVolumeImageURLString autorelease];
937  mVolumeImageURLString = [str copy];
938}
939
940- (NSString *)volumeWebURLString {
941  return mVolumeWebURLString;
942}
943
944- (void)setVolumeWebURLString:(NSString *)str {
945  [mVolumeWebURLString autorelease];
946  mVolumeWebURLString = [str copy];
947}
948
949@end
950// thank you for reading this sample code