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