PageRenderTime 53ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 1ms

/ios/main.m

https://bitbucket.org/sumidasignage/mupdf
Objective C | 1661 lines | 1376 code | 260 blank | 25 comment | 156 complexity | 37defb9f823869aee7dbf05af83af310 MD5 | raw file
  1. #import <UIKit/UIKit.h>
  2. #undef ABS
  3. #undef MIN
  4. #undef MAX
  5. #include "fitz/fitz.h"
  6. #define GAP 20
  7. #define INDICATOR_Y -44-24
  8. #define SLIDER_W (width - GAP - 24)
  9. #define SEARCH_W (width - GAP - 170)
  10. static dispatch_queue_t queue;
  11. static float screenScale = 1;
  12. static fz_context *ctx = NULL;
  13. @interface MuLibraryController : UITableViewController <UIActionSheetDelegate>
  14. {
  15. NSArray *files;
  16. NSTimer *timer;
  17. fz_document *_doc; // temporaries for juggling password dialog
  18. NSString *_filename;
  19. }
  20. - (void) openDocument: (NSString*)filename;
  21. - (void) askForPassword: (NSString*)prompt;
  22. - (void) onPasswordOkay;
  23. - (void) onPasswordCancel;
  24. - (void) reload;
  25. @end
  26. @interface MuOutlineController : UITableViewController
  27. {
  28. id target;
  29. NSMutableArray *titles;
  30. NSMutableArray *pages;
  31. }
  32. - (id) initWithTarget: (id)aTarget titles: (NSMutableArray*)aTitles pages: (NSMutableArray*)aPages;
  33. @end
  34. @interface MuHitView : UIView
  35. {
  36. CGSize pageSize;
  37. int hitCount;
  38. CGRect hitRects[500];
  39. int linkPage[500];
  40. char *linkUrl[500];
  41. UIColor *color;
  42. }
  43. - (id) initWithSearchResults: (int)n forDocument: (fz_document *)doc;
  44. - (id) initWithLinks: (fz_link*)links forDocument: (fz_document *)doc;
  45. - (void) setPageSize: (CGSize)s;
  46. @end
  47. @interface MuPageView : UIScrollView <UIScrollViewDelegate>
  48. {
  49. fz_document *doc;
  50. fz_page *page;
  51. int number;
  52. UIActivityIndicatorView *loadingView;
  53. UIImageView *imageView;
  54. UIImageView *tileView;
  55. MuHitView *hitView;
  56. MuHitView *linkView;
  57. CGSize pageSize;
  58. CGRect tileFrame;
  59. float tileScale;
  60. BOOL cancel;
  61. }
  62. - (id) initWithFrame: (CGRect)frame document: (fz_document*)aDoc page: (int)aNumber;
  63. - (void) displayImage: (UIImage*)image;
  64. - (void) resizeImage;
  65. - (void) loadPage;
  66. - (void) loadTile;
  67. - (void) willRotate;
  68. - (void) resetZoomAnimated: (BOOL)animated;
  69. - (void) showSearchResults: (int)count;
  70. - (void) clearSearchResults;
  71. - (void) showLinks;
  72. - (void) hideLinks;
  73. - (int) number;
  74. @end
  75. @interface MuDocumentController : UIViewController <UIScrollViewDelegate, UISearchBarDelegate>
  76. {
  77. fz_document *doc;
  78. NSString *key;
  79. MuOutlineController *outline;
  80. UIScrollView *canvas;
  81. UILabel *indicator;
  82. UISlider *slider;
  83. UISearchBar *searchBar;
  84. UIBarButtonItem *nextButton, *prevButton, *cancelButton, *searchButton, *outlineButton, *linkButton;
  85. UIBarButtonItem *sliderWrapper;
  86. int searchPage;
  87. int cancelSearch;
  88. int showLinks;
  89. int width; // current screen size
  90. int height;
  91. int current; // currently visible page
  92. int scroll_animating; // stop view updates during scrolling animations
  93. }
  94. - (id) initWithFilename: (NSString*)nsfilename document: (fz_document *)aDoc;
  95. - (void) createPageView: (int)number;
  96. - (void) gotoPage: (int)number animated: (BOOL)animated;
  97. - (void) onShowOutline: (id)sender;
  98. - (void) onShowSearch: (id)sender;
  99. - (void) onCancelSearch: (id)sender;
  100. - (void) resetSearch;
  101. - (void) showSearchResults: (int)count forPage: (int)number;
  102. - (void) onSlide: (id)sender;
  103. - (void) onTap: (UITapGestureRecognizer*)sender;
  104. - (void) showNavigationBar;
  105. - (void) hideNavigationBar;
  106. @end
  107. @interface MuAppDelegate : NSObject <UIApplicationDelegate, UINavigationControllerDelegate>
  108. {
  109. UIWindow *window;
  110. UINavigationController *navigator;
  111. MuLibraryController *library;
  112. }
  113. @end
  114. #pragma mark -
  115. static int hit_count = 0;
  116. static fz_rect hit_bbox[500];
  117. static int
  118. search_page(fz_document *doc, int number, char *needle, fz_cookie *cookie)
  119. {
  120. fz_page *page = fz_load_page(doc, number);
  121. fz_text_sheet *sheet = fz_new_text_sheet(ctx);
  122. fz_text_page *text = fz_new_text_page(ctx, &fz_empty_rect);
  123. fz_device *dev = fz_new_text_device(ctx, sheet, text);
  124. fz_run_page(doc, page, dev, &fz_identity, cookie);
  125. fz_free_device(dev);
  126. hit_count = fz_search_text_page(ctx, text, needle, hit_bbox, nelem(hit_bbox));;
  127. fz_free_text_page(ctx, text);
  128. fz_free_text_sheet(ctx, sheet);
  129. fz_free_page(doc, page);
  130. return hit_count;
  131. }
  132. static fz_rect
  133. search_result_bbox(fz_document *doc, int i)
  134. {
  135. return hit_bbox[i];
  136. }
  137. static void showAlert(NSString *msg, NSString *filename)
  138. {
  139. UIAlertView *alert = [[UIAlertView alloc]
  140. initWithTitle: msg
  141. message: filename
  142. delegate: nil
  143. cancelButtonTitle: @"Okay"
  144. otherButtonTitles: nil];
  145. [alert show];
  146. [alert release];
  147. }
  148. static void flattenOutline(NSMutableArray *titles, NSMutableArray *pages, fz_outline *outline, int level)
  149. {
  150. char indent[8*4+1];
  151. if (level > 8)
  152. level = 8;
  153. memset(indent, ' ', level * 4);
  154. indent[level * 4] = 0;
  155. while (outline)
  156. {
  157. if (outline->dest.kind == FZ_LINK_GOTO)
  158. {
  159. int page = outline->dest.ld.gotor.page;
  160. if (page >= 0 && outline->title)
  161. {
  162. NSString *title = [NSString stringWithUTF8String: outline->title];
  163. [titles addObject: [NSString stringWithFormat: @"%s%@", indent, title]];
  164. [pages addObject: [NSNumber numberWithInt: page]];
  165. }
  166. }
  167. flattenOutline(titles, pages, outline->down, level + 1);
  168. outline = outline->next;
  169. }
  170. }
  171. static void releasePixmap(void *info, const void *data, size_t size)
  172. {
  173. fz_drop_pixmap(ctx, info);
  174. }
  175. static UIImage *newImageWithPixmap(fz_pixmap *pix)
  176. {
  177. unsigned char *samples = fz_pixmap_samples(ctx, pix);
  178. int w = fz_pixmap_width(ctx, pix);
  179. int h = fz_pixmap_height(ctx, pix);
  180. CGDataProviderRef cgdata = CGDataProviderCreateWithData(pix, samples, w * 4 * h, releasePixmap);
  181. CGColorSpaceRef cgcolor = CGColorSpaceCreateDeviceRGB();
  182. CGImageRef cgimage = CGImageCreate(w, h, 8, 32, 4 * w,
  183. cgcolor, kCGBitmapByteOrderDefault,
  184. cgdata, NULL, NO, kCGRenderingIntentDefault);
  185. UIImage *image = [[UIImage alloc]
  186. initWithCGImage: cgimage
  187. scale: screenScale
  188. orientation: UIImageOrientationUp];
  189. CGDataProviderRelease(cgdata);
  190. CGColorSpaceRelease(cgcolor);
  191. CGImageRelease(cgimage);
  192. return image;
  193. }
  194. static CGSize fitPageToScreen(CGSize page, CGSize screen)
  195. {
  196. float hscale = screen.width / page.width;
  197. float vscale = screen.height / page.height;
  198. float scale = fz_min(hscale, vscale);
  199. hscale = floorf(page.width * scale) / page.width;
  200. vscale = floorf(page.height * scale) / page.height;
  201. return CGSizeMake(hscale, vscale);
  202. }
  203. static CGSize measurePage(fz_document *doc, fz_page *page)
  204. {
  205. CGSize pageSize;
  206. fz_rect bounds;
  207. fz_bound_page(doc, page, &bounds);
  208. pageSize.width = bounds.x1 - bounds.x0;
  209. pageSize.height = bounds.y1 - bounds.y0;
  210. return pageSize;
  211. }
  212. static UIImage *renderPage(fz_document *doc, fz_page *page, CGSize screenSize)
  213. {
  214. CGSize pageSize;
  215. fz_irect bbox;
  216. fz_matrix ctm;
  217. fz_device *dev;
  218. fz_pixmap *pix;
  219. CGSize scale;
  220. screenSize.width *= screenScale;
  221. screenSize.height *= screenScale;
  222. pageSize = measurePage(doc, page);
  223. scale = fitPageToScreen(pageSize, screenSize);
  224. fz_scale(&ctm, scale.width, scale.height);
  225. bbox = (fz_irect){0, 0, pageSize.width * scale.width, pageSize.height * scale.height};
  226. pix = fz_new_pixmap_with_bbox(ctx, fz_device_rgb, &bbox);
  227. fz_clear_pixmap_with_value(ctx, pix, 255);
  228. dev = fz_new_draw_device(ctx, pix);
  229. fz_run_page(doc, page, dev, &ctm, NULL);
  230. fz_free_device(dev);
  231. return newImageWithPixmap(pix);
  232. }
  233. static UIImage *renderTile(fz_document *doc, fz_page *page, CGSize screenSize, CGRect tileRect, float zoom)
  234. {
  235. CGSize pageSize;
  236. fz_irect bbox;
  237. fz_matrix ctm;
  238. fz_device *dev;
  239. fz_pixmap *pix;
  240. CGSize scale;
  241. screenSize.width *= screenScale;
  242. screenSize.height *= screenScale;
  243. tileRect.origin.x *= screenScale;
  244. tileRect.origin.y *= screenScale;
  245. tileRect.size.width *= screenScale;
  246. tileRect.size.height *= screenScale;
  247. pageSize = measurePage(doc, page);
  248. scale = fitPageToScreen(pageSize, screenSize);
  249. fz_scale(&ctm, scale.width * zoom, scale.height * zoom);
  250. bbox.x0 = tileRect.origin.x;
  251. bbox.y0 = tileRect.origin.y;
  252. bbox.x1 = tileRect.origin.x + tileRect.size.width;
  253. bbox.y1 = tileRect.origin.y + tileRect.size.height;
  254. pix = fz_new_pixmap_with_bbox(ctx, fz_device_rgb, &bbox);
  255. fz_clear_pixmap_with_value(ctx, pix, 255);
  256. dev = fz_new_draw_device(ctx, pix);
  257. fz_run_page(doc, page, dev, &ctm, NULL);
  258. fz_free_device(dev);
  259. return newImageWithPixmap(pix);
  260. }
  261. #pragma mark -
  262. @implementation MuLibraryController
  263. - (void) viewWillAppear: (BOOL)animated
  264. {
  265. [self setTitle: @"PDF, XPS and CBZ Documents"];
  266. [self reload];
  267. printf("library viewWillAppear (starting reload timer)\n");
  268. timer = [NSTimer timerWithTimeInterval: 3
  269. target: self selector: @selector(reload) userInfo: nil
  270. repeats: YES];
  271. [[NSRunLoop currentRunLoop] addTimer: timer forMode: NSDefaultRunLoopMode];
  272. }
  273. - (void) viewWillDisappear: (BOOL)animated
  274. {
  275. printf("library viewWillDisappear (stopping reload timer)\n");
  276. [timer invalidate];
  277. timer = nil;
  278. }
  279. - (void) reload
  280. {
  281. if (files) {
  282. [files release];
  283. files = nil;
  284. }
  285. NSFileManager *fileman = [NSFileManager defaultManager];
  286. NSString *docdir = [NSString stringWithFormat: @"%@/Documents", NSHomeDirectory()];
  287. NSMutableArray *outfiles = [[NSMutableArray alloc] init];
  288. NSDirectoryEnumerator *direnum = [fileman enumeratorAtPath:docdir];
  289. NSString *file;
  290. BOOL isdir;
  291. while (file = [direnum nextObject]) {
  292. NSString *filepath = [docdir stringByAppendingPathComponent:file];
  293. NSLog(@"file %@\n", file);
  294. if ([fileman fileExistsAtPath:filepath isDirectory:&isdir] && !isdir) {
  295. [outfiles addObject:file];
  296. }
  297. }
  298. files = outfiles;
  299. [[self tableView] reloadData];
  300. }
  301. - (void) dealloc
  302. {
  303. [files release];
  304. [super dealloc];
  305. }
  306. - (BOOL) shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation)o
  307. {
  308. return YES;
  309. }
  310. - (NSInteger) numberOfSectionsInTableView: (UITableView*)tableView
  311. {
  312. return 1;
  313. }
  314. - (NSInteger) tableView: (UITableView*)tableView numberOfRowsInSection: (NSInteger)section
  315. {
  316. return [files count] + 1;
  317. }
  318. - (void) actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
  319. {
  320. if (buttonIndex == [actionSheet destructiveButtonIndex])
  321. {
  322. char filename[PATH_MAX];
  323. int row = [actionSheet tag];
  324. dispatch_sync(queue, ^{});
  325. strcpy(filename, [NSHomeDirectory() UTF8String]);
  326. strcat(filename, "/Documents/");
  327. strcat(filename, [[files objectAtIndex: row - 1] UTF8String]);
  328. printf("delete document '%s'\n", filename);
  329. unlink(filename);
  330. [self reload];
  331. }
  332. }
  333. - (void) onTapDelete: (UIControl*)sender
  334. {
  335. int row = [sender tag];
  336. NSString *title = [NSString stringWithFormat: @"Delete %@?", [files objectAtIndex: row - 1]];
  337. UIActionSheet *sheet = [[UIActionSheet alloc]
  338. initWithTitle: title
  339. delegate: self
  340. cancelButtonTitle: @"Cancel"
  341. destructiveButtonTitle: @"Delete"
  342. otherButtonTitles: nil];
  343. [sheet setTag: row];
  344. [sheet showInView: [self tableView]];
  345. [sheet release];
  346. }
  347. - (UITableViewCell*) tableView: (UITableView*)tableView cellForRowAtIndexPath: (NSIndexPath*)indexPath
  348. {
  349. static NSString *cellid = @"MuCellIdent";
  350. UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier: cellid];
  351. if (!cell)
  352. cell = [[[UITableViewCell alloc] initWithStyle: UITableViewCellStyleDefault reuseIdentifier: cellid] autorelease];
  353. int row = [indexPath row];
  354. if (row == 0) {
  355. [[cell textLabel] setText: @"About MuPDF"];
  356. [[cell textLabel] setFont: [UIFont systemFontOfSize: 20]];
  357. } else {
  358. [[cell textLabel] setText: [files objectAtIndex: row - 1]];
  359. [[cell textLabel] setFont: [UIFont systemFontOfSize: 20]];
  360. }
  361. if (row > 0)
  362. {
  363. UIButton *deleteButton = [UIButton buttonWithType:UIButtonTypeCustom];
  364. [deleteButton setImage: [UIImage imageNamed: @"x_alt_blue.png"] forState: UIControlStateNormal];
  365. [deleteButton setFrame: CGRectMake(0, 0, 35, 35)];
  366. [deleteButton addTarget: self action: @selector(onTapDelete:) forControlEvents: UIControlEventTouchUpInside];
  367. [deleteButton setTag: row];
  368. [cell setAccessoryView: deleteButton];
  369. }
  370. else
  371. {
  372. [cell setAccessoryView: nil];
  373. }
  374. return cell;
  375. }
  376. - (void) tableView: (UITableView*)tableView didSelectRowAtIndexPath: (NSIndexPath*)indexPath
  377. {
  378. int row = [indexPath row];
  379. if (row == 0)
  380. [self openDocument: @"../MuPDF.app/About.xps"];
  381. else
  382. [self openDocument: [files objectAtIndex: row - 1]];
  383. }
  384. - (void) openDocument: (NSString*)nsfilename
  385. {
  386. char filename[PATH_MAX];
  387. dispatch_sync(queue, ^{});
  388. strcpy(filename, [NSHomeDirectory() UTF8String]);
  389. strcat(filename, "/Documents/");
  390. strcat(filename, [nsfilename UTF8String]);
  391. printf("open document '%s'\n", filename);
  392. _filename = [nsfilename retain];
  393. _doc = fz_open_document(ctx, filename);
  394. if (!_doc) {
  395. showAlert(@"Cannot open document", nsfilename);
  396. return;
  397. }
  398. if (fz_needs_password(_doc))
  399. [self askForPassword: @"'%@' needs a password:"];
  400. else
  401. [self onPasswordOkay];
  402. }
  403. - (void) askForPassword: (NSString*)prompt
  404. {
  405. UIAlertView *passwordAlertView = [[UIAlertView alloc]
  406. initWithTitle: @"Password Protected"
  407. message: [NSString stringWithFormat: prompt, [_filename lastPathComponent]]
  408. delegate: self
  409. cancelButtonTitle: @"Cancel"
  410. otherButtonTitles: @"Done", nil];
  411. [passwordAlertView setAlertViewStyle: UIAlertViewStyleSecureTextInput];
  412. [passwordAlertView show];
  413. [passwordAlertView release];
  414. }
  415. - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
  416. {
  417. char *password = (char*) [[[alertView textFieldAtIndex: 0] text] UTF8String];
  418. [alertView dismissWithClickedButtonIndex: buttonIndex animated: TRUE];
  419. if (buttonIndex == 1) {
  420. if (fz_authenticate_password(_doc, password))
  421. [self onPasswordOkay];
  422. else
  423. [self askForPassword: @"Wrong password for '%@'. Try again:"];
  424. } else {
  425. [self onPasswordCancel];
  426. }
  427. }
  428. - (void) onPasswordOkay
  429. {
  430. MuDocumentController *document = [[MuDocumentController alloc] initWithFilename: _filename document: _doc];
  431. if (document) {
  432. [self setTitle: @"Library"];
  433. [[self navigationController] pushViewController: document animated: YES];
  434. [document release];
  435. }
  436. [_filename release];
  437. _doc = NULL;
  438. }
  439. - (void) onPasswordCancel
  440. {
  441. [_filename release];
  442. printf("close document (password cancel)\n");
  443. fz_close_document(_doc);
  444. _doc = NULL;
  445. }
  446. @end
  447. #pragma mark -
  448. @implementation MuOutlineController
  449. - (id) initWithTarget: (id)aTarget titles: (NSMutableArray*)aTitles pages: (NSMutableArray*)aPages
  450. {
  451. self = [super initWithStyle: UITableViewStylePlain];
  452. if (self) {
  453. [self setTitle: @"Table of Contents"];
  454. target = aTarget; // only keep a weak reference, to avoid retain cycles
  455. titles = [aTitles retain];
  456. pages = [aPages retain];
  457. [[self tableView] setSeparatorStyle: UITableViewCellSeparatorStyleNone];
  458. }
  459. return self;
  460. }
  461. - (void) dealloc
  462. {
  463. [titles release];
  464. [pages release];
  465. [super dealloc];
  466. }
  467. - (BOOL) shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation)o
  468. {
  469. return YES;
  470. }
  471. - (NSInteger) numberOfSectionsInTableView: (UITableView*)tableView
  472. {
  473. return 1;
  474. }
  475. - (NSInteger) tableView: (UITableView*)tableView numberOfRowsInSection: (NSInteger)section
  476. {
  477. return [titles count];
  478. }
  479. - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
  480. {
  481. return 28;
  482. }
  483. - (UITableViewCell*) tableView: (UITableView*)tableView cellForRowAtIndexPath: (NSIndexPath*)indexPath
  484. {
  485. static NSString *cellid = @"MuCellIdent";
  486. UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier: cellid];
  487. if (!cell)
  488. {
  489. cell = [[[UITableViewCell alloc] initWithStyle: UITableViewCellStyleValue1 reuseIdentifier: cellid] autorelease];
  490. [[cell textLabel] setFont: [UIFont systemFontOfSize: 16]];
  491. [[cell detailTextLabel] setFont: [UIFont systemFontOfSize: 16]];
  492. }
  493. NSString *title = [titles objectAtIndex: [indexPath row]];
  494. NSString *page = [pages objectAtIndex: [indexPath row]];
  495. [[cell textLabel] setText: title];
  496. [[cell detailTextLabel] setText: [NSString stringWithFormat: @"%d", [page intValue]+1]];
  497. return cell;
  498. }
  499. - (void) tableView: (UITableView*)tableView didSelectRowAtIndexPath: (NSIndexPath*)indexPath
  500. {
  501. NSNumber *page = [pages objectAtIndex: [indexPath row]];
  502. [target gotoPage: [page intValue] animated: NO];
  503. [[self navigationController] popViewControllerAnimated: YES];
  504. }
  505. @end
  506. #pragma mark -
  507. @implementation MuHitView
  508. - (id) initWithSearchResults: (int)n forDocument: (fz_document *)doc
  509. {
  510. self = [super initWithFrame: CGRectMake(0,0,100,100)];
  511. if (self) {
  512. [self setOpaque: NO];
  513. color = [[UIColor colorWithRed: 0x25/255.0 green: 0x72/255.0 blue: 0xAC/255.0 alpha: 0.5] retain];
  514. pageSize = CGSizeMake(100,100);
  515. for (int i = 0; i < n && i < nelem(hitRects); i++) {
  516. fz_rect bbox = search_result_bbox(doc, i); // this is thread-safe enough
  517. hitRects[i].origin.x = bbox.x0;
  518. hitRects[i].origin.y = bbox.y0;
  519. hitRects[i].size.width = bbox.x1 - bbox.x0;
  520. hitRects[i].size.height = bbox.y1 - bbox.y0;
  521. }
  522. hitCount = n;
  523. }
  524. return self;
  525. }
  526. - (id) initWithLinks: (fz_link*)link forDocument: (fz_document *)doc
  527. {
  528. self = [super initWithFrame: CGRectMake(0,0,100,100)];
  529. if (self) {
  530. [self setOpaque: NO];
  531. color = [[UIColor colorWithRed: 0xAC/255.0 green: 0x72/255.0 blue: 0x25/255.0 alpha: 0.5] retain];
  532. pageSize = CGSizeMake(100,100);
  533. while (link && hitCount < nelem(hitRects)) {
  534. if (link->dest.kind == FZ_LINK_GOTO || link->dest.kind == FZ_LINK_URI) {
  535. fz_rect bbox = link->rect;
  536. hitRects[hitCount].origin.x = bbox.x0;
  537. hitRects[hitCount].origin.y = bbox.y0;
  538. hitRects[hitCount].size.width = bbox.x1 - bbox.x0;
  539. hitRects[hitCount].size.height = bbox.y1 - bbox.y0;
  540. linkPage[hitCount] = link->dest.kind == FZ_LINK_GOTO ? link->dest.ld.gotor.page : -1;
  541. linkUrl[hitCount] = link->dest.kind == FZ_LINK_URI ? strdup(link->dest.ld.uri.uri) : nil;
  542. hitCount++;
  543. }
  544. link = link->next;
  545. }
  546. }
  547. return self;
  548. }
  549. - (void) setPageSize: (CGSize)s
  550. {
  551. pageSize = s;
  552. // if page takes a long time to load we may have drawn at the initial (wrong) size
  553. [self setNeedsDisplay];
  554. }
  555. - (void) drawRect: (CGRect)r
  556. {
  557. CGSize scale = fitPageToScreen(pageSize, self.bounds.size);
  558. [color set];
  559. for (int i = 0; i < hitCount; i++) {
  560. CGRect rect = hitRects[i];
  561. rect.origin.x *= scale.width;
  562. rect.origin.y *= scale.height;
  563. rect.size.width *= scale.width;
  564. rect.size.height *= scale.height;
  565. UIRectFill(rect);
  566. }
  567. }
  568. - (void) dealloc
  569. {
  570. int i;
  571. [color release];
  572. for (i = 0; i < hitCount; i++)
  573. free(linkUrl[i]);
  574. [super dealloc];
  575. }
  576. @end
  577. @implementation MuPageView
  578. - (id) initWithFrame: (CGRect)frame document: (fz_document*)aDoc page: (int)aNumber
  579. {
  580. self = [super initWithFrame: frame];
  581. if (self) {
  582. doc = aDoc;
  583. number = aNumber;
  584. cancel = NO;
  585. [self setShowsVerticalScrollIndicator: NO];
  586. [self setShowsHorizontalScrollIndicator: NO];
  587. [self setDecelerationRate: UIScrollViewDecelerationRateFast];
  588. [self setDelegate: self];
  589. // zoomDidFinish/Begin events fire before bounce animation completes,
  590. // making a mess when we rearrange views during the animation.
  591. [self setBouncesZoom: NO];
  592. [self resetZoomAnimated: NO];
  593. // TODO: use a one shot timer to delay the display of this?
  594. loadingView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
  595. [loadingView startAnimating];
  596. [self addSubview: loadingView];
  597. [self loadPage];
  598. }
  599. return self;
  600. }
  601. - (void) dealloc
  602. {
  603. // dealloc can trigger in background thread when the queued block is
  604. // our last owner, and releases us on completion.
  605. // Send the dealloc back to the main thread so we don't mess up UIKit.
  606. if (dispatch_get_current_queue() != dispatch_get_main_queue()) {
  607. __block id block_self = self; // don't auto-retain self!
  608. dispatch_async(dispatch_get_main_queue(), ^{ [block_self dealloc]; });
  609. } else {
  610. __block fz_page *block_page = page;
  611. __block fz_document *block_doc = doc;
  612. dispatch_async(queue, ^{
  613. if (block_page)
  614. fz_free_page(block_doc, block_page);
  615. block_page = nil;
  616. });
  617. [linkView release];
  618. [hitView release];
  619. [tileView release];
  620. [loadingView release];
  621. [imageView release];
  622. [super dealloc];
  623. }
  624. }
  625. - (int) number
  626. {
  627. return number;
  628. }
  629. - (void) showLinks
  630. {
  631. if (!linkView) {
  632. dispatch_async(queue, ^{
  633. if (!page)
  634. page = fz_load_page(doc, number);
  635. fz_link *links = fz_load_links(doc, page);
  636. dispatch_async(dispatch_get_main_queue(), ^{
  637. linkView = [[MuHitView alloc] initWithLinks: links forDocument: doc];
  638. dispatch_async(queue, ^{
  639. fz_drop_link(ctx, links);
  640. });
  641. if (imageView) {
  642. [linkView setFrame: [imageView frame]];
  643. [linkView setPageSize: pageSize];
  644. }
  645. [self addSubview: linkView];
  646. });
  647. });
  648. }
  649. }
  650. - (void) hideLinks
  651. {
  652. [linkView removeFromSuperview];
  653. [linkView release];
  654. linkView = nil;
  655. }
  656. - (void) showSearchResults: (int)count
  657. {
  658. if (hitView) {
  659. [hitView removeFromSuperview];
  660. [hitView release];
  661. hitView = nil;
  662. }
  663. hitView = [[MuHitView alloc] initWithSearchResults: count forDocument: doc];
  664. if (imageView) {
  665. [hitView setFrame: [imageView frame]];
  666. [hitView setPageSize: pageSize];
  667. }
  668. [self addSubview: hitView];
  669. }
  670. - (void) clearSearchResults
  671. {
  672. if (hitView) {
  673. [hitView removeFromSuperview];
  674. [hitView release];
  675. hitView = nil;
  676. }
  677. }
  678. - (void) resetZoomAnimated: (BOOL)animated
  679. {
  680. // discard tile and any pending tile jobs
  681. tileFrame = CGRectZero;
  682. tileScale = 1;
  683. if (tileView) {
  684. [tileView removeFromSuperview];
  685. [tileView release];
  686. tileView = nil;
  687. }
  688. [self setMinimumZoomScale: 1];
  689. [self setMaximumZoomScale: 5];
  690. [self setZoomScale: 1 animated: animated];
  691. }
  692. - (void) removeFromSuperview
  693. {
  694. cancel = YES;
  695. [super removeFromSuperview];
  696. }
  697. - (void) loadPage
  698. {
  699. if (number < 0 || number >= fz_count_pages(doc))
  700. return;
  701. dispatch_async(queue, ^{
  702. if (!cancel) {
  703. printf("render page %d\n", number);
  704. if (!page)
  705. page = fz_load_page(doc, number);
  706. CGSize size = measurePage(doc, page);
  707. UIImage *image = renderPage(doc, page, self.bounds.size);
  708. dispatch_async(dispatch_get_main_queue(), ^{
  709. pageSize = size;
  710. [self displayImage: image];
  711. [image release];
  712. });
  713. } else {
  714. printf("cancel page %d\n", number);
  715. }
  716. });
  717. }
  718. - (void) displayImage: (UIImage*)image
  719. {
  720. if (loadingView) {
  721. [loadingView removeFromSuperview];
  722. [loadingView release];
  723. loadingView = nil;
  724. }
  725. if (hitView)
  726. [hitView setPageSize: pageSize];
  727. if (!imageView) {
  728. imageView = [[UIImageView alloc] initWithImage: image];
  729. imageView.opaque = YES;
  730. [self addSubview: imageView];
  731. if (hitView)
  732. [self bringSubviewToFront: hitView];
  733. } else {
  734. [imageView setImage: image];
  735. }
  736. [self resizeImage];
  737. }
  738. - (void) resizeImage
  739. {
  740. if (imageView) {
  741. CGSize imageSize = imageView.image.size;
  742. CGSize scale = fitPageToScreen(imageSize, self.bounds.size);
  743. if (fabs(scale.width - 1) > 0.1) {
  744. CGRect frame = [imageView frame];
  745. frame.size.width = imageSize.width * scale.width;
  746. frame.size.height = imageSize.height * scale.height;
  747. [imageView setFrame: frame];
  748. printf("resized view; queuing up a reload (%d)\n", number);
  749. dispatch_async(queue, ^{
  750. dispatch_async(dispatch_get_main_queue(), ^{
  751. CGSize scale = fitPageToScreen(imageView.image.size, self.bounds.size);
  752. if (fabs(scale.width - 1) > 0.01)
  753. [self loadPage];
  754. });
  755. });
  756. } else {
  757. [imageView sizeToFit];
  758. }
  759. [self setContentSize: imageView.frame.size];
  760. [self layoutIfNeeded];
  761. }
  762. }
  763. - (void) willRotate
  764. {
  765. if (imageView) {
  766. [self resetZoomAnimated: NO];
  767. [self resizeImage];
  768. }
  769. }
  770. - (void) layoutSubviews
  771. {
  772. [super layoutSubviews];
  773. // center the image as it becomes smaller than the size of the screen
  774. CGSize boundsSize = self.bounds.size;
  775. CGRect frameToCenter = loadingView ? loadingView.frame : imageView.frame;
  776. // center horizontally
  777. if (frameToCenter.size.width < boundsSize.width)
  778. frameToCenter.origin.x = floor((boundsSize.width - frameToCenter.size.width) / 2);
  779. else
  780. frameToCenter.origin.x = 0;
  781. // center vertically
  782. if (frameToCenter.size.height < boundsSize.height)
  783. frameToCenter.origin.y = floor((boundsSize.height - frameToCenter.size.height) / 2);
  784. else
  785. frameToCenter.origin.y = 0;
  786. if (loadingView)
  787. loadingView.frame = frameToCenter;
  788. else
  789. imageView.frame = frameToCenter;
  790. if (hitView && imageView)
  791. [hitView setFrame: [imageView frame]];
  792. }
  793. - (UIView*) viewForZoomingInScrollView: (UIScrollView*)scrollView
  794. {
  795. return imageView;
  796. }
  797. - (void) loadTile
  798. {
  799. CGSize screenSize = self.bounds.size;
  800. tileFrame.origin = self.contentOffset;
  801. tileFrame.size = self.bounds.size;
  802. tileFrame = CGRectIntersection(tileFrame, imageView.frame);
  803. tileScale = self.zoomScale;
  804. CGRect frame = tileFrame;
  805. float scale = tileScale;
  806. CGRect viewFrame = frame;
  807. if (self.contentOffset.x < imageView.frame.origin.x)
  808. viewFrame.origin.x = 0;
  809. if (self.contentOffset.y < imageView.frame.origin.y)
  810. viewFrame.origin.y = 0;
  811. if (scale < 1.01)
  812. return;
  813. dispatch_async(queue, ^{
  814. __block BOOL isValid;
  815. dispatch_sync(dispatch_get_main_queue(), ^{
  816. isValid = CGRectEqualToRect(frame, tileFrame) && scale == tileScale;
  817. });
  818. if (!isValid) {
  819. printf("cancel tile\n");
  820. return;
  821. }
  822. if (!page)
  823. page = fz_load_page(doc, number);
  824. printf("render tile\n");
  825. UIImage *image = renderTile(doc, page, screenSize, viewFrame, scale);
  826. dispatch_async(dispatch_get_main_queue(), ^{
  827. isValid = CGRectEqualToRect(frame, tileFrame) && scale == tileScale;
  828. if (isValid) {
  829. tileFrame = CGRectZero;
  830. tileScale = 1;
  831. if (tileView) {
  832. [tileView removeFromSuperview];
  833. [tileView release];
  834. tileView = nil;
  835. }
  836. tileView = [[UIImageView alloc] initWithFrame: frame];
  837. [tileView setImage: image];
  838. [self addSubview: tileView];
  839. if (hitView)
  840. [self bringSubviewToFront: hitView];
  841. } else {
  842. printf("discard tile\n");
  843. }
  844. [image release];
  845. });
  846. });
  847. }
  848. - (void) scrollViewDidScrollToTop:(UIScrollView *)scrollView { [self loadTile]; }
  849. - (void) scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView { [self loadTile]; }
  850. - (void) scrollViewDidEndDecelerating:(UIScrollView *)scrollView { [self loadTile]; }
  851. - (void) scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
  852. {
  853. if (!decelerate)
  854. [self loadTile];
  855. }
  856. - (void) scrollViewWillBeginZooming: (UIScrollView*)scrollView withView: (UIView*)view
  857. {
  858. // discard tile and any pending tile jobs
  859. tileFrame = CGRectZero;
  860. tileScale = 1;
  861. if (tileView) {
  862. [tileView removeFromSuperview];
  863. [tileView release];
  864. tileView = nil;
  865. }
  866. }
  867. - (void) scrollViewDidEndZooming: (UIScrollView*)scrollView withView: (UIView*)view atScale: (float)scale
  868. {
  869. [self loadTile];
  870. }
  871. - (void) scrollViewDidZoom: (UIScrollView*)scrollView
  872. {
  873. if (hitView && imageView)
  874. [hitView setFrame: [imageView frame]];
  875. }
  876. @end
  877. #pragma mark -
  878. @implementation MuDocumentController
  879. - (id) initWithFilename: (NSString*)filename document: (fz_document *)aDoc
  880. {
  881. self = [super init];
  882. if (!self)
  883. return nil;
  884. key = [filename retain];
  885. doc = aDoc;
  886. dispatch_sync(queue, ^{});
  887. fz_outline *root = fz_load_outline(doc);
  888. if (root) {
  889. NSMutableArray *titles = [[NSMutableArray alloc] init];
  890. NSMutableArray *pages = [[NSMutableArray alloc] init];
  891. flattenOutline(titles, pages, root, 0);
  892. if ([titles count])
  893. outline = [[MuOutlineController alloc] initWithTarget: self titles: titles pages: pages];
  894. [titles release];
  895. [pages release];
  896. fz_free_outline(ctx, root);
  897. }
  898. return self;
  899. }
  900. - (void) loadView
  901. {
  902. [[NSUserDefaults standardUserDefaults] setObject: key forKey: @"OpenDocumentKey"];
  903. current = [[NSUserDefaults standardUserDefaults] integerForKey: key];
  904. if (current < 0 || current >= fz_count_pages(doc))
  905. current = 0;
  906. UIView *view = [[UIView alloc] initWithFrame: CGRectZero];
  907. [view setAutoresizingMask: UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
  908. [view setAutoresizesSubviews: YES];
  909. canvas = [[UIScrollView alloc] initWithFrame: CGRectMake(0,0,GAP,0)];
  910. [canvas setAutoresizingMask: UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
  911. [canvas setPagingEnabled: YES];
  912. [canvas setShowsHorizontalScrollIndicator: NO];
  913. [canvas setShowsVerticalScrollIndicator: NO];
  914. [canvas setDelegate: self];
  915. [canvas addGestureRecognizer: [[[UITapGestureRecognizer alloc] initWithTarget: self action: @selector(onTap:)] autorelease]];
  916. scroll_animating = NO;
  917. indicator = [[UILabel alloc] initWithFrame: CGRectZero];
  918. [indicator setAutoresizingMask: UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin];
  919. [indicator setText: @"0000 of 9999"];
  920. [indicator sizeToFit];
  921. [indicator setCenter: CGPointMake(0, INDICATOR_Y)];
  922. [indicator setTextAlignment: UITextAlignmentCenter];
  923. [indicator setBackgroundColor: [[UIColor blackColor] colorWithAlphaComponent: 0.5]];
  924. [indicator setTextColor: [UIColor whiteColor]];
  925. [view addSubview: canvas];
  926. [view addSubview: indicator];
  927. slider = [[UISlider alloc] initWithFrame: CGRectZero];
  928. [slider setMinimumValue: 0];
  929. [slider setMaximumValue: fz_count_pages(doc) - 1];
  930. [slider addTarget: self action: @selector(onSlide:) forControlEvents: UIControlEventValueChanged];
  931. sliderWrapper = [[UIBarButtonItem alloc] initWithCustomView: slider];
  932. [self setToolbarItems: [NSArray arrayWithObjects: sliderWrapper, nil]];
  933. // Set up the buttons on the navigation and search bar
  934. if (outline) {
  935. outlineButton = [[UIBarButtonItem alloc]
  936. initWithBarButtonSystemItem: UIBarButtonSystemItemBookmarks
  937. target:self action:@selector(onShowOutline:)];
  938. }
  939. linkButton = [[UIBarButtonItem alloc]
  940. initWithBarButtonSystemItem: UIBarButtonSystemItemAction
  941. target:self action:@selector(onToggleLinks:)];
  942. cancelButton = [[UIBarButtonItem alloc]
  943. initWithTitle: @"Cancel" style: UIBarButtonItemStyleBordered
  944. target:self action:@selector(onCancelSearch:)];
  945. searchButton = [[UIBarButtonItem alloc]
  946. initWithBarButtonSystemItem: UIBarButtonSystemItemSearch
  947. target:self action:@selector(onShowSearch:)];
  948. prevButton = [[UIBarButtonItem alloc]
  949. initWithBarButtonSystemItem: UIBarButtonSystemItemRewind
  950. target:self action:@selector(onSearchPrev:)];
  951. nextButton = [[UIBarButtonItem alloc]
  952. initWithBarButtonSystemItem: UIBarButtonSystemItemFastForward
  953. target:self action:@selector(onSearchNext:)];
  954. searchBar = [[UISearchBar alloc] initWithFrame: CGRectMake(0,0,50,32)];
  955. [searchBar setPlaceholder: @"Search"];
  956. [searchBar setDelegate: self];
  957. // HACK to make transparent background
  958. [[searchBar.subviews objectAtIndex:0] removeFromSuperview];
  959. [prevButton setEnabled: NO];
  960. [nextButton setEnabled: NO];
  961. [[self navigationItem] setRightBarButtonItems:
  962. [NSArray arrayWithObjects: searchButton, linkButton, outlineButton, nil]];
  963. // TODO: add activityindicator to search bar
  964. [self setView: view];
  965. [view release];
  966. }
  967. - (void) dealloc
  968. {
  969. if (doc) {
  970. fz_document *self_doc = doc; // don't auto-retain self here!
  971. dispatch_async(queue, ^{
  972. printf("close document\n");
  973. fz_close_document(self_doc);
  974. });
  975. }
  976. [indicator release]; indicator = nil;
  977. [slider release]; slider = nil;
  978. [sliderWrapper release]; sliderWrapper = nil;
  979. [searchBar release]; searchBar = nil;
  980. [outlineButton release]; outlineButton = nil;
  981. [searchButton release]; searchButton = nil;
  982. [cancelButton release]; cancelButton = nil;
  983. [prevButton release]; prevButton = nil;
  984. [nextButton release]; nextButton = nil;
  985. [canvas release]; canvas = nil;
  986. [outline release];
  987. [key release];
  988. [super dealloc];
  989. }
  990. - (void) viewWillAppear: (BOOL)animated
  991. {
  992. [self setTitle: [key lastPathComponent]];
  993. [slider setValue: current];
  994. [indicator setText: [NSString stringWithFormat: @" %d of %d ", current+1, fz_count_pages(doc)]];
  995. [[self navigationController] setToolbarHidden: NO animated: animated];
  996. }
  997. - (void) viewWillLayoutSubviews
  998. {
  999. CGSize size = [canvas frame].size;
  1000. int max_width = fz_max(width, size.width);
  1001. width = size.width;
  1002. height = size.height;
  1003. [canvas setContentInset: UIEdgeInsetsZero];
  1004. [canvas setContentSize: CGSizeMake(fz_count_pages(doc) * width, height)];
  1005. [canvas setContentOffset: CGPointMake(current * width, 0)];
  1006. [sliderWrapper setWidth: SLIDER_W];
  1007. [searchBar setFrame: CGRectMake(0,0,SEARCH_W,32)];
  1008. [[[self navigationController] toolbar] setNeedsLayout]; // force layout!
  1009. // use max_width so we don't clamp the content offset too early during animation
  1010. [canvas setContentSize: CGSizeMake(fz_count_pages(doc) * max_width, height)];
  1011. [canvas setContentOffset: CGPointMake(current * width, 0)];
  1012. for (MuPageView *view in [canvas subviews]) {
  1013. if ([view number] == current) {
  1014. [view setFrame: CGRectMake([view number] * width, 0, width-GAP, height)];
  1015. [view willRotate];
  1016. }
  1017. }
  1018. for (MuPageView *view in [canvas subviews]) {
  1019. if ([view number] != current) {
  1020. [view setFrame: CGRectMake([view number] * width, 0, width-GAP, height)];
  1021. [view willRotate];
  1022. }
  1023. }
  1024. }
  1025. - (void) viewDidAppear: (BOOL)animated
  1026. {
  1027. [self scrollViewDidScroll: canvas];
  1028. }
  1029. - (void) viewWillDisappear: (BOOL)animated
  1030. {
  1031. [self setTitle: @"Resume"];
  1032. [[NSUserDefaults standardUserDefaults] removeObjectForKey: @"OpenDocumentKey"];
  1033. [[self navigationController] setToolbarHidden: YES animated: animated];
  1034. }
  1035. - (void) showNavigationBar
  1036. {
  1037. if ([[self navigationController] isNavigationBarHidden]) {
  1038. [[self navigationController] setNavigationBarHidden: NO];
  1039. [[self navigationController] setToolbarHidden: NO];
  1040. [indicator setHidden: NO];
  1041. [UIView beginAnimations: @"MuNavBar" context: NULL];
  1042. [[[self navigationController] navigationBar] setAlpha: 1];
  1043. [[[self navigationController] toolbar] setAlpha: 1];
  1044. [indicator setAlpha: 1];
  1045. [UIView commitAnimations];
  1046. }
  1047. }
  1048. - (void) hideNavigationBar
  1049. {
  1050. if (![[self navigationController] isNavigationBarHidden]) {
  1051. [searchBar resignFirstResponder];
  1052. [UIView beginAnimations: @"MuNavBar" context: NULL];
  1053. [UIView setAnimationDelegate: self];
  1054. [UIView setAnimationDidStopSelector: @selector(onHideNavigationBarFinished)];
  1055. [[[self navigationController] navigationBar] setAlpha: 0];
  1056. [[[self navigationController] toolbar] setAlpha: 0];
  1057. [indicator setAlpha: 0];
  1058. [UIView commitAnimations];
  1059. }
  1060. }
  1061. - (void) onHideNavigationBarFinished
  1062. {
  1063. [[self navigationController] setNavigationBarHidden: YES];
  1064. [[self navigationController] setToolbarHidden: YES];
  1065. [indicator setHidden: YES];
  1066. }
  1067. - (void) onShowOutline: (id)sender
  1068. {
  1069. [[self navigationController] pushViewController: outline animated: YES];
  1070. }
  1071. - (void) onToggleLinks: (id)sender
  1072. {
  1073. showLinks = !showLinks;
  1074. for (MuPageView *view in [canvas subviews])
  1075. {
  1076. if (showLinks)
  1077. [view showLinks];
  1078. else
  1079. [view hideLinks];
  1080. }
  1081. }
  1082. - (void) onShowSearch: (id)sender
  1083. {
  1084. [[self navigationItem] setTitleView: searchBar];
  1085. [[self navigationItem] setRightBarButtonItems:
  1086. [NSArray arrayWithObjects: nextButton, prevButton, nil]];
  1087. [[self navigationItem] setLeftBarButtonItem: cancelButton];
  1088. [searchBar becomeFirstResponder];
  1089. }
  1090. - (void) onCancelSearch: (id)sender
  1091. {
  1092. cancelSearch = YES;
  1093. [searchBar resignFirstResponder];
  1094. [[self navigationItem] setTitleView: nil];
  1095. [[self navigationItem] setRightBarButtonItems:
  1096. [NSArray arrayWithObjects: searchButton, linkButton, outlineButton, nil]];
  1097. [[self navigationItem] setLeftBarButtonItem: nil];
  1098. [self resetSearch];
  1099. }
  1100. - (void) resetSearch
  1101. {
  1102. searchPage = -1;
  1103. for (MuPageView *view in [canvas subviews])
  1104. [view clearSearchResults];
  1105. }
  1106. - (void) showSearchResults: (int)count forPage: (int)number
  1107. {
  1108. printf("search found match on page %d\n", number);
  1109. searchPage = number;
  1110. [self gotoPage: number animated: NO];
  1111. for (MuPageView *view in [canvas subviews])
  1112. if ([view number] == number)
  1113. [view showSearchResults: count];
  1114. else
  1115. [view clearSearchResults];
  1116. }
  1117. - (void) searchInDirection: (int)dir
  1118. {
  1119. UITextField *searchField;
  1120. char *needle;
  1121. int start;
  1122. [searchBar resignFirstResponder];
  1123. if (searchPage == current)
  1124. start = current + dir;
  1125. else
  1126. start = current;
  1127. needle = strdup([[searchBar text] UTF8String]);
  1128. searchField = nil;
  1129. for (id view in [searchBar subviews])
  1130. if ([view isKindOfClass: [UITextField class]])
  1131. searchField = view;
  1132. [prevButton setEnabled: NO];
  1133. [nextButton setEnabled: NO];
  1134. [searchField setEnabled: NO];
  1135. cancelSearch = NO;
  1136. dispatch_async(queue, ^{
  1137. for (int i = start; i >= 0 && i < fz_count_pages(doc); i += dir) {
  1138. int n = search_page(doc, i, needle, NULL);
  1139. if (n) {
  1140. dispatch_async(dispatch_get_main_queue(), ^{
  1141. [prevButton setEnabled: YES];
  1142. [nextButton setEnabled: YES];
  1143. [searchField setEnabled: YES];
  1144. [self showSearchResults: n forPage: i];
  1145. free(needle);
  1146. });
  1147. return;
  1148. }
  1149. if (cancelSearch) {
  1150. dispatch_async(dispatch_get_main_queue(), ^{
  1151. [prevButton setEnabled: YES];
  1152. [nextButton setEnabled: YES];
  1153. [searchField setEnabled: YES];
  1154. free(needle);
  1155. });
  1156. return;
  1157. }
  1158. }
  1159. dispatch_async(dispatch_get_main_queue(), ^{
  1160. printf("no search results found\n");
  1161. [prevButton setEnabled: YES];
  1162. [nextButton setEnabled: YES];
  1163. [searchField setEnabled: YES];
  1164. UIAlertView *alert = [[UIAlertView alloc]
  1165. initWithTitle: @"No matches found for:"
  1166. message: [NSString stringWithUTF8String: needle]
  1167. delegate: nil
  1168. cancelButtonTitle: @"Close"
  1169. otherButtonTitles: nil];
  1170. [alert show];
  1171. [alert release];
  1172. free(needle);
  1173. });
  1174. });
  1175. }
  1176. - (void) onSearchPrev: (id)sender
  1177. {
  1178. [self searchInDirection: -1];
  1179. }
  1180. - (void) onSearchNext: (id)sender
  1181. {
  1182. [self searchInDirection: 1];
  1183. }
  1184. - (void) searchBarSearchButtonClicked: (UISearchBar*)sender
  1185. {
  1186. [self onSearchNext: sender];
  1187. }
  1188. - (void) searchBar: (UISearchBar*)sender textDidChange: (NSString*)searchText
  1189. {
  1190. [self resetSearch];
  1191. if ([[searchBar text] length] > 0) {
  1192. [prevButton setEnabled: YES];
  1193. [nextButton setEnabled: YES];
  1194. } else {
  1195. [prevButton setEnabled: NO];
  1196. [nextButton setEnabled: NO];
  1197. }
  1198. }
  1199. - (void) onSlide: (id)sender
  1200. {
  1201. int number = [slider value];
  1202. if ([slider isTracking])
  1203. [indicator setText: [NSString stringWithFormat: @" %d of %d ", number+1, fz_count_pages(doc)]];
  1204. else
  1205. [self gotoPage: number animated: NO];
  1206. }
  1207. - (void) onTap: (UITapGestureRecognizer*)sender
  1208. {
  1209. CGPoint p = [sender locationInView: canvas];
  1210. CGPoint ofs = [canvas contentOffset];
  1211. float x0 = (width - GAP) / 5;
  1212. float x1 = (width - GAP) - x0;
  1213. p.x -= ofs.x;
  1214. p.y -= ofs.y;
  1215. if (p.x < x0) {
  1216. [self gotoPage: current-1 animated: YES];
  1217. } else if (p.x > x1) {
  1218. [self gotoPage: current+1 animated: YES];
  1219. } else {
  1220. if ([[self navigationController] isNavigationBarHidden])
  1221. [self showNavigationBar];
  1222. else
  1223. [self hideNavigationBar];
  1224. }
  1225. }
  1226. - (void) scrollViewWillBeginDragging: (UIScrollView *)scrollView
  1227. {
  1228. [self hideNavigationBar];
  1229. }
  1230. - (void) scrollViewDidScroll: (UIScrollView*)scrollview
  1231. {
  1232. if (width == 0)
  1233. return; // not visible yet
  1234. if (scroll_animating)
  1235. return; // don't mess with layout during animations
  1236. float x = [canvas contentOffset].x + width * 0.5f;
  1237. current = x / width;
  1238. [[NSUserDefaults standardUserDefaults] setInteger: current forKey: key];
  1239. [indicator setText: [NSString stringWithFormat: @" %d of %d ", current+1, fz_count_pages(doc)]];
  1240. [slider setValue: current];
  1241. // swap the distant page views out
  1242. NSMutableSet *invisiblePages = [[NSMutableSet alloc] init];
  1243. for (MuPageView *view in [canvas subviews]) {
  1244. if ([view number] != current)
  1245. [view resetZoomAnimated: YES];
  1246. if ([view number] < current - 2 || [view number] > current + 2)
  1247. [invisiblePages addObject: view];
  1248. }
  1249. for (MuPageView *view in invisiblePages)
  1250. [view removeFromSuperview];
  1251. [invisiblePages release]; // don't bother recycling them...
  1252. [self createPageView: current];
  1253. [self createPageView: current - 1];
  1254. [self createPageView: current + 1];
  1255. // reset search results when page has flipped
  1256. if (current != searchPage)
  1257. [self resetSearch];
  1258. }
  1259. - (void) createPageView: (int)number
  1260. {
  1261. if (number < 0 || number >= fz_count_pages(doc))
  1262. return;
  1263. int found = 0;
  1264. for (MuPageView *view in [canvas subviews])
  1265. if ([view number] == number)
  1266. found = 1;
  1267. if (!found) {
  1268. MuPageView *view = [[MuPageView alloc] initWithFrame: CGRectMake(number * width, 0, width-GAP, height) document: doc page: number];
  1269. [canvas addSubview: view];
  1270. if (showLinks)
  1271. [view showLinks];
  1272. [view release];
  1273. }
  1274. }
  1275. - (void) gotoPage: (int)number animated: (BOOL)animated
  1276. {
  1277. if (number < 0)
  1278. number = 0;
  1279. if (number >= fz_count_pages(doc))
  1280. number = fz_count_pages(doc) - 1;
  1281. if (current == number)
  1282. return;
  1283. if (animated) {
  1284. // setContentOffset:animated: does not use the normal animation
  1285. // framework. It also doesn't play nice with the tap gesture
  1286. // recognizer. So we do our own page flipping animation here.
  1287. // We must set the scroll_animating flag so that we don't create
  1288. // or remove subviews until after the animation, or they'll
  1289. // swoop in from origo during the animation.
  1290. scroll_animating = YES;
  1291. [UIView beginAnimations: @"MuScroll" context: NULL];
  1292. [UIView setAnimationDuration: 0.4];
  1293. [UIView setAnimationBeginsFromCurrentState: YES];
  1294. [UIView setAnimationDelegate: self];
  1295. [UIView setAnimationDidStopSelector: @selector(onGotoPageFinished)];
  1296. for (MuPageView *view in [canvas subviews])
  1297. [view resetZoomAnimated: NO];
  1298. [canvas setContentOffset: CGPointMake(number * width, 0)];
  1299. [slider setValue: number];
  1300. [indicator setText: [NSString stringWithFormat: @" %d of %d ", number+1, fz_count_pages(doc)]];
  1301. [UIView commitAnimations];
  1302. } else {
  1303. for (MuPageView *view in [canvas subviews])
  1304. [view resetZoomAnimated: NO];
  1305. [canvas setContentOffset: CGPointMake(number * width, 0)];
  1306. }
  1307. current = number;
  1308. }
  1309. - (void) onGotoPageFinished
  1310. {
  1311. scroll_animating = NO;
  1312. [self scrollViewDidScroll: canvas];
  1313. }
  1314. - (BOOL) shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation)o
  1315. {
  1316. return YES;
  1317. }
  1318. - (void) didRotateFromInterfaceOrientation: (UIInterfaceOrientation)o
  1319. {
  1320. [canvas setContentSize: CGSizeMake(fz_count_pages(doc) * width, height)];
  1321. [canvas setContentOffset: CGPointMake(current * width, 0)];
  1322. }
  1323. @end
  1324. #pragma mark -
  1325. @implementation MuAppDelegate
  1326. - (BOOL) application: (UIApplication*)application didFinishLaunchingWithOptions: (NSDictionary*)launchOptions
  1327. {
  1328. NSString *filename;
  1329. queue = dispatch_queue_create("com.artifex.mupdf.queue", NULL);
  1330. // use at most 128M for resource cache
  1331. ctx = fz_new_context(NULL, NULL, 128<<20);
  1332. screenScale = [[UIScreen mainScreen] scale];
  1333. library = [[MuLibraryController alloc] initWithStyle: UITableViewStylePlain];
  1334. navigator = [[UINavigationController alloc] initWithRootViewController: library];
  1335. [[navigator navigationBar] setTranslucent: YES];
  1336. [[navigator toolbar] setTranslucent: YES];
  1337. [navigator setDelegate: self];
  1338. window = [[UIWindow alloc] initWithFrame: [[UIScreen mainScreen] bounds]];
  1339. [window setBackgroundColor: [UIColor scrollViewTexturedBackgroundColor]];
  1340. [window setRootViewController: navigator];
  1341. [window makeKeyAndVisible];
  1342. filename = [[NSUserDefaults standardUserDefaults] objectForKey: @"OpenDocumentKey"];
  1343. if (filename)
  1344. [library openDocument: filename];
  1345. filename = [launchOptions objectForKey: UIApplicationLaunchOptionsURLKey];
  1346. NSLog(@"urlkey = %@\n", filename);
  1347. return YES;
  1348. }
  1349. - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
  1350. {
  1351. NSLog(@"openURL: %@\n", url);
  1352. if ([url isFileURL]) {
  1353. NSString *path = [url path];
  1354. NSString *dir = [NSString stringWithFormat: @"%@/Documents/", NSHomeDirectory()];
  1355. path = [path stringByReplacingOccurrencesOfString:@"/private" withString:@""];
  1356. path = [path stringByReplacingOccurrencesOfString:dir withString:@""];
  1357. NSLog(@"file relative path: %@\n", path);
  1358. [library openDocument:path];
  1359. return YES;
  1360. }
  1361. return NO;
  1362. }
  1363. - (void)applicationDidEnterBackground:(UIApplication *)application
  1364. {
  1365. printf("applicationDidEnterBackground!\n");
  1366. [[NSUserDefaults standardUserDefaults] synchronize];
  1367. }
  1368. - (void)applicationWillEnterForeground:(UIApplication *)application
  1369. {
  1370. printf("applicationWillEnterForeground!\n");
  1371. }
  1372. - (void)applicationDidBecomeActive:(UIApplication *)application
  1373. {
  1374. printf("applicationDidBecomeActive!\n");
  1375. }
  1376. - (void)applicationWillTerminate:(UIApplication *)application
  1377. {
  1378. printf("applicationWillTerminate!\n");
  1379. [[NSUserDefaults standardUserDefaults] synchronize];
  1380. }
  1381. - (void)applicationDidReceiveMemoryWarning:(UIApplication *)application
  1382. {
  1383. printf("applicationDidReceiveMemoryWarning\n");
  1384. }
  1385. - (void) dealloc
  1386. {
  1387. dispatch_release(queue);
  1388. [library release];
  1389. [navigator release];
  1390. [window release];
  1391. [super dealloc];
  1392. }
  1393. @end
  1394. #pragma mark -
  1395. int main(int argc, char *argv[])
  1396. {
  1397. NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  1398. int retVal = UIApplicationMain(argc, argv, nil, @"MuAppDelegate");
  1399. [pool release];
  1400. return retVal;
  1401. }