#include "common.h" #import #import "MuPageViewNormal.h" #import "MuPageViewReflow.h" #import "MuDocumentController.h" #import "MuTextFieldController.h" #import "MuChoiceFieldController.h" #define GAP 20 #define INDICATOR_Y -44-24 #define SLIDER_W (width - GAP - 24) #define SEARCH_W (width - GAP - 170) #define MIN_SCALE (1.0) #define MAX_SCALE (5.0) static void flattenOutline(NSMutableArray *titles, NSMutableArray *pages, fz_outline *outline, int level) { char indent[8*4+1]; if (level > 8) level = 8; memset(indent, ' ', level * 4); indent[level * 4] = 0; while (outline) { if (outline->dest.kind == FZ_LINK_GOTO) { int page = outline->dest.ld.gotor.page; if (page >= 0 && outline->title) { NSString *title = [NSString stringWithUTF8String: outline->title]; [titles addObject: [NSString stringWithFormat: @"%s%@", indent, title]]; [pages addObject: [NSNumber numberWithInt: page]]; } } flattenOutline(titles, pages, outline->down, level + 1); outline = outline->next; } } static char *tmp_path(char *path) { int f; char *buf = malloc(strlen(path) + 6 + 1); if (!buf) return NULL; strcpy(buf, path); strcat(buf, "XXXXXX"); f = mkstemp(buf); if (f >= 0) { close(f); return buf; } else { free(buf); return NULL; } } static void saveDoc(char *current_path, fz_document *doc) { char *tmp; fz_write_options opts; opts.do_incremental = 1; opts.do_ascii = 0; opts.do_expand = 0; opts.do_garbage = 0; opts.do_linear = 0; tmp = tmp_path(current_path); if (tmp) { int written = 0; fz_var(written); fz_try(ctx) { FILE *fin = fopen(current_path, "rb"); FILE *fout = fopen(tmp, "wb"); char buf[256]; int n, err = 1; if (fin && fout) { while ((n = fread(buf, 1, sizeof(buf), fin)) > 0) fwrite(buf, 1, n, fout); err = (ferror(fin) || ferror(fout)); } if (fin) fclose(fin); if (fout) fclose(fout); if (!err) { fz_write_document(doc, tmp, &opts); written = 1; } } fz_catch(ctx) { written = 0; } if (written) { rename(tmp, current_path); } free(tmp); } } @implementation MuDocumentController - (id) initWithFilename: (NSString*)filename path:(char *)cstr document: (MuDocRef *)aDoc { self = [super init]; if (!self) return nil; #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000 if ([self respondsToSelector:@selector(automaticallyAdjustsScrollViewInsets)]) self.automaticallyAdjustsScrollViewInsets = NO; #endif key = [filename retain]; docRef = [aDoc retain]; doc = docRef->doc; filePath = strdup(cstr); dispatch_sync(queue, ^{}); fz_outline *root = fz_load_outline(doc); if (root) { NSMutableArray *titles = [[NSMutableArray alloc] init]; NSMutableArray *pages = [[NSMutableArray alloc] init]; flattenOutline(titles, pages, root, 0); if ([titles count]) outline = [[MuOutlineController alloc] initWithTarget: self titles: titles pages: pages]; [titles release]; [pages release]; fz_free_outline(ctx, root); } return self; } - (UIBarButtonItem *) resourceBasedButton:(NSString *)resource withAction:(SEL)selector { if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { return [[UIBarButtonItem alloc] initWithImage:[UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:resource ofType:@"png"]] style:UIBarButtonItemStylePlain target:self action:selector]; } else { UIView *iv = [[TapImage alloc] initWithResource:resource target:self action:selector]; UIBarButtonItem *ib = [[UIBarButtonItem alloc] initWithCustomView:iv]; [iv release]; return ib; } } - (void) addMainMenuButtons { NSMutableArray *array = [NSMutableArray arrayWithCapacity:3]; [array addObject:moreButton]; [array addObject:searchButton]; if (outlineButton) [array addObject:outlineButton]; [array addObject:reflowButton]; [array addObject:linkButton]; [[self navigationItem] setRightBarButtonItems: array ]; [[self navigationItem] setLeftBarButtonItem:backButton]; } - (void) loadView { [[NSUserDefaults standardUserDefaults] setObject: key forKey: @"OpenDocumentKey"]; current = [[NSUserDefaults standardUserDefaults] integerForKey: key]; if (current < 0 || current >= fz_count_pages(doc)) current = 0; UIView *view = [[UIView alloc] initWithFrame: CGRectZero]; [view setAutoresizingMask: UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight]; [view setAutoresizesSubviews: YES]; canvas = [[UIScrollView alloc] initWithFrame: CGRectMake(0,0,GAP,0)]; [canvas setAutoresizingMask: UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight]; [canvas setPagingEnabled: YES]; [canvas setShowsHorizontalScrollIndicator: NO]; [canvas setShowsVerticalScrollIndicator: NO]; [canvas setDelegate: self]; UITapGestureRecognizer *tapRecog = [[UITapGestureRecognizer alloc] initWithTarget: self action: @selector(onTap:)]; tapRecog.delegate = self; [canvas addGestureRecognizer: tapRecog]; [tapRecog release]; // In reflow mode, we need to track pinch gestures on the canvas and pass // the scale changes to the subviews. UIPinchGestureRecognizer *pinchRecog = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(onPinch:)]; pinchRecog.delegate = self; [canvas addGestureRecognizer:pinchRecog]; [pinchRecog release]; scale = 1.0; scroll_animating = NO; indicator = [[UILabel alloc] initWithFrame: CGRectZero]; [indicator setAutoresizingMask: UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin]; [indicator setText: @"0000 of 9999"]; [indicator sizeToFit]; [indicator setCenter: CGPointMake(0, INDICATOR_Y)]; [indicator setTextAlignment: NSTextAlignmentCenter]; [indicator setBackgroundColor: [[UIColor blackColor] colorWithAlphaComponent: 0.5]]; [indicator setTextColor: [UIColor whiteColor]]; [view addSubview: canvas]; [view addSubview: indicator]; slider = [[UISlider alloc] initWithFrame: CGRectZero]; [slider setMinimumValue: 0]; [slider setMaximumValue: fz_count_pages(doc) - 1]; [slider addTarget: self action: @selector(onSlide:) forControlEvents: UIControlEventValueChanged]; sliderWrapper = [[UIBarButtonItem alloc] initWithCustomView: slider]; [self setToolbarItems: [NSArray arrayWithObjects: sliderWrapper, nil]]; // Set up the buttons on the navigation and search bar if (outline) { outlineButton = [self resourceBasedButton:@"ic_list" withAction:@selector(onShowOutline:)]; } linkButton = [self resourceBasedButton:@"ic_link" withAction:@selector(onToggleLinks:)]; cancelButton = [self resourceBasedButton:@"ic_cancel" withAction:@selector(onCancel:)]; searchButton = [self resourceBasedButton:@"ic_magnifying_glass" withAction:@selector(onShowSearch:)]; prevButton = [self resourceBasedButton:@"ic_arrow_left" withAction:@selector(onSearchPrev:)]; nextButton = [self resourceBasedButton:@"ic_arrow_right" withAction:@selector(onSearchNext:)]; reflowButton = [self resourceBasedButton:@"ic_reflow" withAction:@selector(onToggleReflow:)]; moreButton = [self resourceBasedButton:@"ic_more" withAction:@selector(onMore:)]; highlightButton = [self resourceBasedButton:@"ic_highlight" withAction:@selector(onHighlight:)]; underlineButton = [self resourceBasedButton:@"ic_underline" withAction:@selector(onUnderline:)]; strikeoutButton = [self resourceBasedButton:@"ic_strike" withAction:@selector(onStrikeout:)]; inkButton = [self resourceBasedButton:@"ic_pen" withAction:@selector(onInk:)]; tickButton = [self resourceBasedButton:@"ic_check" withAction:@selector(onTick:)]; deleteButton = [self resourceBasedButton:@"ic_trash" withAction:@selector(onDelete:)]; searchBar = [[UISearchBar alloc] initWithFrame: CGRectMake(0,0,50,32)]; backButton = [self resourceBasedButton:@"ic_arrow_left" withAction:@selector(onBack:)]; [searchBar setPlaceholder: @"Search"]; [searchBar setDelegate: self]; [prevButton setEnabled: NO]; [nextButton setEnabled: NO]; [self addMainMenuButtons]; // TODO: add activityindicator to search bar [self setView: view]; [view release]; } - (void) dealloc { [docRef release]; docRef = nil; doc = NULL; [indicator release]; indicator = nil; [slider release]; slider = nil; [sliderWrapper release]; sliderWrapper = nil; [reflowButton release]; reflowButton = nil; [moreButton release]; moreButton = nil; [searchBar release]; searchBar = nil; [outlineButton release]; outlineButton = nil; [searchButton release]; searchButton = nil; [cancelButton release]; cancelButton = nil; [prevButton release]; prevButton = nil; [nextButton release]; nextButton = nil; [highlightButton release]; highlightButton = nil; [underlineButton release]; underlineButton = nil; [strikeoutButton release]; strikeoutButton = nil; [inkButton release]; inkButton = nil; [tickButton release]; tickButton = nil; [deleteButton release]; deleteButton = nil; [canvas release]; canvas = nil; free(filePath); filePath = NULL; [outline release]; [key release]; [super dealloc]; } - (void) viewWillAppear: (BOOL)animated { [self setTitle: [key lastPathComponent]]; [slider setValue: current]; [indicator setText: [NSString stringWithFormat: @" %d of %d ", current+1, fz_count_pages(doc)]]; [[self navigationController] setToolbarHidden: NO animated: animated]; } - (void) viewWillLayoutSubviews { CGSize size = [canvas frame].size; int max_width = fz_max(width, size.width); width = size.width; height = size.height; [canvas setContentInset: UIEdgeInsetsZero]; [canvas setContentSize: CGSizeMake(fz_count_pages(doc) * width, height)]; [canvas setContentOffset: CGPointMake(current * width, 0)]; [sliderWrapper setWidth: SLIDER_W]; [searchBar setFrame: CGRectMake(0,0,SEARCH_W,32)]; [[[self navigationController] toolbar] setNeedsLayout]; // force layout! // use max_width so we don't clamp the content offset too early during animation [canvas setContentSize: CGSizeMake(fz_count_pages(doc) * max_width, height)]; [canvas setContentOffset: CGPointMake(current * width, 0)]; for (UIView *view in [canvas subviews]) { if ([view number] == current) { [view setFrame: CGRectMake([view number] * width, 0, width-GAP, height)]; [view willRotate]; } } for (UIView *view in [canvas subviews]) { if ([view number] != current) { [view setFrame: CGRectMake([view number] * width, 0, width-GAP, height)]; [view willRotate]; } } } - (void) viewDidAppear: (BOOL)animated { [self scrollViewDidScroll: canvas]; } - (void) viewWillDisappear: (BOOL)animated { [self setTitle: @"Resume"]; [[NSUserDefaults standardUserDefaults] removeObjectForKey: @"OpenDocumentKey"]; [[self navigationController] setToolbarHidden: YES animated: animated]; } - (void) showNavigationBar { if ([[self navigationController] isNavigationBarHidden]) { [[self navigationController] setNavigationBarHidden: NO]; [[self navigationController] setToolbarHidden: NO]; [indicator setHidden: NO]; [UIView beginAnimations: @"MuNavBar" context: NULL]; [[[self navigationController] navigationBar] setAlpha: 1]; [[[self navigationController] toolbar] setAlpha: 1]; [indicator setAlpha: 1]; [UIView commitAnimations]; } } - (void) hideNavigationBar { if (![[self navigationController] isNavigationBarHidden]) { [searchBar resignFirstResponder]; [UIView beginAnimations: @"MuNavBar" context: NULL]; [UIView setAnimationDelegate: self]; [UIView setAnimationDidStopSelector: @selector(onHideNavigationBarFinished)]; [[[self navigationController] navigationBar] setAlpha: 0]; [[[self navigationController] toolbar] setAlpha: 0]; [indicator setAlpha: 0]; [UIView commitAnimations]; } } - (void) onHideNavigationBarFinished { [[self navigationController] setNavigationBarHidden: YES]; [[self navigationController] setToolbarHidden: YES]; [indicator setHidden: YES]; } - (void) onShowOutline: (id)sender { [[self navigationController] pushViewController: outline animated: YES]; } - (void) onToggleLinks: (id)sender { showLinks = !showLinks; for (UIView *view in [canvas subviews]) { if (showLinks) [view showLinks]; else [view hideLinks]; } } - (void) onToggleReflow: (id)sender { reflowMode = !reflowMode; [[canvas subviews] makeObjectsPerformSelector:@selector(removeFromSuperview)]; [self scrollViewDidScroll:canvas]; } - (void) showAnnotationMenu { [[self navigationItem] setRightBarButtonItems:[NSArray arrayWithObjects:inkButton, strikeoutButton, underlineButton, highlightButton, nil]]; [[self navigationItem] setLeftBarButtonItem:cancelButton]; for (UIView *view in [canvas subviews]) { if ([view number] == current) [view deselectAnnotation]; } barmode = BARMODE_ANNOTATION; } - (void) onMore: (id)sender { [self showAnnotationMenu]; } - (void) textSelectModeOn { [[self navigationItem] setRightBarButtonItems:[NSArray arrayWithObject:tickButton]]; for (UIView *view in [canvas subviews]) { if ([view number] == current) [view textSelectModeOn]; } } - (void) textSelectModeOff { for (UIView *view in [canvas subviews]) { [view textSelectModeOff]; } } - (void) inkModeOn { [[self navigationItem] setRightBarButtonItems:[NSArray arrayWithObject:tickButton]]; for (UIView *view in [canvas subviews]) { if ([view number] == current) [view inkModeOn]; } } - (void) deleteModeOn { [[self navigationItem] setRightBarButtonItems:[NSArray arrayWithObject:deleteButton]]; barmode = BARMODE_DELETE; } - (void) inkModeOff { for (UIView *view in [canvas subviews]) { [view inkModeOff]; } } - (void) onHighlight: (id)sender { barmode = BARMODE_HIGHLIGHT; [self textSelectModeOn]; } - (void) onUnderline: (id)sender { barmode = BARMODE_UNDERLINE; [self textSelectModeOn]; } - (void) onStrikeout: (id)sender { barmode = BARMODE_STRIKE; [self textSelectModeOn]; } - (void) onInk: (id)sender { barmode = BARMODE_INK; [self inkModeOn]; } - (void) onShowSearch: (id)sender { [[self navigationItem] setRightBarButtonItems: [NSArray arrayWithObjects: nextButton, prevButton, nil]]; [[self navigationItem] setLeftBarButtonItem: cancelButton]; [[self navigationItem] setTitleView: searchBar]; [searchBar becomeFirstResponder]; barmode = BARMODE_SEARCH; } - (void) onTick: (id)sender { for (UIView *view in [canvas subviews]) { if ([view number] == current) { switch (barmode) { case BARMODE_HIGHLIGHT: [view saveSelectionAsMarkup:FZ_ANNOT_HIGHLIGHT]; break; case BARMODE_UNDERLINE: [view saveSelectionAsMarkup:FZ_ANNOT_UNDERLINE]; break; case BARMODE_STRIKE: [view saveSelectionAsMarkup:FZ_ANNOT_STRIKEOUT]; break; case BARMODE_INK: [view saveInk]; } } } [self showAnnotationMenu]; } - (void) onDelete: (id)sender { for (UIView *view in [canvas subviews]) { if ([view number] == current) [view deleteSelectedAnnotation]; } [self showAnnotationMenu]; } - (void) onCancel: (id)sender { switch (barmode) { case BARMODE_SEARCH: cancelSearch = YES; [searchBar resignFirstResponder]; [self resetSearch]; /* fallthrough */ case BARMODE_ANNOTATION: [[self navigationItem] setTitleView: nil]; [self addMainMenuButtons]; barmode = BARMODE_MAIN; break; case BARMODE_HIGHLIGHT: case BARMODE_UNDERLINE: case BARMODE_STRIKE: case BARMODE_DELETE: [self showAnnotationMenu]; [self textSelectModeOff]; break; case BARMODE_INK: [self showAnnotationMenu]; [self inkModeOff]; break; } } - (void) onBack: (id)sender { pdf_document *idoc = pdf_specifics(doc); if (idoc && pdf_has_unsaved_changes(idoc)) { UIAlertView *saveAlert = [[UIAlertView alloc] initWithTitle:@"Save changes?" message:nil delegate:self cancelButtonTitle:@"Discard" otherButtonTitles:@"Save", nil]; [saveAlert show]; } else { [[self navigationController] popViewControllerAnimated:YES]; } } - (void) alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex { if (buttonIndex == 1) saveDoc(filePath, doc); [alertView dismissWithClickedButtonIndex:buttonIndex animated:YES]; [[self navigationController] popViewControllerAnimated:YES]; } - (void) resetSearch { searchPage = -1; for (UIView *view in [canvas subviews]) [view clearSearchResults]; } - (void) showSearchResults: (int)count forPage: (int)number { printf("search found match on page %d\n", number); searchPage = number; [self gotoPage: number animated: NO]; for (UIView *view in [canvas subviews]) if ([view number] == number) [view showSearchResults: count]; else [view clearSearchResults]; } - (void) searchInDirection: (int)dir { UITextField *searchField; char *needle; int start; [searchBar resignFirstResponder]; if (searchPage == current) start = current + dir; else start = current; needle = strdup([[searchBar text] UTF8String]); searchField = nil; for (id view in [searchBar subviews]) if ([view isKindOfClass: [UITextField class]]) searchField = view; [prevButton setEnabled: NO]; [nextButton setEnabled: NO]; [searchField setEnabled: NO]; cancelSearch = NO; dispatch_async(queue, ^{ for (int i = start; i >= 0 && i < fz_count_pages(doc); i += dir) { int n = search_page(doc, i, needle, NULL); if (n) { dispatch_async(dispatch_get_main_queue(), ^{ [prevButton setEnabled: YES]; [nextButton setEnabled: YES]; [searchField setEnabled: YES]; [self showSearchResults: n forPage: i]; free(needle); }); return; } if (cancelSearch) { dispatch_async(dispatch_get_main_queue(), ^{ [prevButton setEnabled: YES]; [nextButton setEnabled: YES]; [searchField setEnabled: YES]; free(needle); }); return; } } dispatch_async(dispatch_get_main_queue(), ^{ printf("no search results found\n"); [prevButton setEnabled: YES]; [nextButton setEnabled: YES]; [searchField setEnabled: YES]; UIAlertView *alert = [[UIAlertView alloc] initWithTitle: @"No matches found for:" message: [NSString stringWithUTF8String: needle] delegate: nil cancelButtonTitle: @"Close" otherButtonTitles: nil]; [alert show]; [alert release]; free(needle); }); }); } - (void) onSearchPrev: (id)sender { [self searchInDirection: -1]; } - (void) onSearchNext: (id)sender { [self searchInDirection: 1]; } - (void) searchBarSearchButtonClicked: (UISearchBar*)sender { [self onSearchNext: sender]; } - (void) searchBar: (UISearchBar*)sender textDidChange: (NSString*)searchText { [self resetSearch]; if ([[searchBar text] length] > 0) { [prevButton setEnabled: YES]; [nextButton setEnabled: YES]; } else { [prevButton setEnabled: NO]; [nextButton setEnabled: NO]; } } - (void) onSlide: (id)sender { int number = [slider value]; if ([slider isTracking]) [indicator setText: [NSString stringWithFormat: @" %d of %d ", number+1, fz_count_pages(doc)]]; else [self gotoPage: number animated: NO]; } - (BOOL) gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer { // For reflow mode, we load UIWebViews into the canvas. Returning YES // here prevents them stealing our tap and pinch events. return YES; } - (void) onTap: (UITapGestureRecognizer*)sender { CGPoint p = [sender locationInView: canvas]; CGPoint ofs = [canvas contentOffset]; float x0 = (width - GAP) / 5; float x1 = (width - GAP) - x0; p.x -= ofs.x; p.y -= ofs.y; __block BOOL tapHandled = NO; for (UIView *view in [canvas subviews]) { CGPoint pp = [sender locationInView:view]; if (CGRectContainsPoint(view.bounds, pp)) { MuTapResult *result = [view handleTap:pp]; __block BOOL hitAnnot = NO; [result switchCaseInternal:^(MuTapResultInternalLink *link) { [self gotoPage:link.pageNumber animated:NO]; tapHandled = YES; } caseExternal:^(MuTapResultExternalLink *link) { // Not currently supported } caseRemote:^(MuTapResultRemoteLink *link) { // Not currently supported } caseWidget:^(MuTapResultWidget *widget) { tapHandled = YES; } caseAnnotation:^(MuTapResultAnnotation *annot) { hitAnnot = YES; }]; switch (barmode) { case BARMODE_ANNOTATION: if (hitAnnot) [self deleteModeOn]; tapHandled = YES; break; case BARMODE_DELETE: if (!hitAnnot) [self showAnnotationMenu]; tapHandled = YES; break; default: if (hitAnnot) { // Annotation will have been selected, which is wanted // only in annotation-editing mode [view deselectAnnotation]; } break; } if (tapHandled) break; } } if (tapHandled) { // Do nothing further } else if (p.x < x0) { [self gotoPage: current-1 animated: YES]; } else if (p.x > x1) { [self gotoPage: current+1 animated: YES]; } else { if ([[self navigationController] isNavigationBarHidden]) [self showNavigationBar]; else if (barmode == BARMODE_MAIN) [self hideNavigationBar]; } } - (void) onPinch:(UIPinchGestureRecognizer*)sender { if (sender.state == UIGestureRecognizerStateBegan) sender.scale = scale; if (sender.scale < MIN_SCALE) sender.scale = MIN_SCALE; if (sender.scale > MAX_SCALE) sender.scale = MAX_SCALE; if (sender.state == UIGestureRecognizerStateEnded) scale = sender.scale; for (UIView *view in [canvas subviews]) { // Zoom only the visible page until end of gesture if (view.number == current || sender.state == UIGestureRecognizerStateEnded) [view setScale:sender.scale]; } } - (void) scrollViewWillBeginDragging: (UIScrollView *)scrollView { if (barmode == BARMODE_MAIN) [self hideNavigationBar]; } - (void) scrollViewDidScroll: (UIScrollView*)scrollview { if (width == 0) return; // not visible yet if (scroll_animating) return; // don't mess with layout during animations float x = [canvas contentOffset].x + width * 0.5f; current = x / width; [[NSUserDefaults standardUserDefaults] setInteger: current forKey: key]; [indicator setText: [NSString stringWithFormat: @" %d of %d ", current+1, fz_count_pages(doc)]]; [slider setValue: current]; // swap the distant page views out NSMutableSet *invisiblePages = [[NSMutableSet alloc] init]; for (UIView *view in [canvas subviews]) { if ([view number] != current) [view resetZoomAnimated: YES]; if ([view number] < current - 2 || [view number] > current + 2) [invisiblePages addObject: view]; } for (UIView *view in invisiblePages) [view removeFromSuperview]; [invisiblePages release]; // don't bother recycling them... [self createPageView: current]; [self createPageView: current - 1]; [self createPageView: current + 1]; // reset search results when page has flipped if (current != searchPage) [self resetSearch]; } - (void) createPageView: (int)number { if (number < 0 || number >= fz_count_pages(doc)) return; int found = 0; for (UIView *view in [canvas subviews]) if ([view number] == number) found = 1; if (!found) { UIView *view = reflowMode ? [[MuPageViewReflow alloc] initWithFrame:CGRectMake(number * width, 0, width-GAP, height) document:docRef page:number] : [[MuPageViewNormal alloc] initWithFrame:CGRectMake(number * width, 0, width-GAP, height) dialogCreator:self document:docRef page:number]; [view setScale:scale]; [canvas addSubview: view]; if (showLinks) [view showLinks]; [view release]; } } - (void) gotoPage: (int)number animated: (BOOL)animated { if (number < 0) number = 0; if (number >= fz_count_pages(doc)) number = fz_count_pages(doc) - 1; if (current == number) return; if (animated) { // setContentOffset:animated: does not use the normal animation // framework. It also doesn't play nice with the tap gesture // recognizer. So we do our own page flipping animation here. // We must set the scroll_animating flag so that we don't create // or remove subviews until after the animation, or they'll // swoop in from origo during the animation. scroll_animating = YES; [UIView beginAnimations: @"MuScroll" context: NULL]; [UIView setAnimationDuration: 0.4]; [UIView setAnimationBeginsFromCurrentState: YES]; [UIView setAnimationDelegate: self]; [UIView setAnimationDidStopSelector: @selector(onGotoPageFinished)]; for (UIView *view in [canvas subviews]) [view resetZoomAnimated: NO]; [canvas setContentOffset: CGPointMake(number * width, 0)]; [slider setValue: number]; [indicator setText: [NSString stringWithFormat: @" %d of %d ", number+1, fz_count_pages(doc)]]; [UIView commitAnimations]; } else { for (UIView *view in [canvas subviews]) [view resetZoomAnimated: NO]; [canvas setContentOffset: CGPointMake(number * width, 0)]; } current = number; } - (void) invokeTextDialog:(NSString *)aString okayAction:(void (^)(NSString *))block { MuTextFieldController *tf = [[MuTextFieldController alloc] initWithText:aString okayAction:block]; tf.modalPresentationStyle = UIModalPresentationFormSheet; [self presentViewController:tf animated:YES completion:nil]; [tf release]; } - (void) invokeChoiceDialog:(NSArray *)anArray okayAction:(void (^)(NSArray *))block { MuChoiceFieldController *cf = [[MuChoiceFieldController alloc] initWithChoices:anArray okayAction:block]; cf.modalPresentationStyle = UIModalPresentationFormSheet; [self presentViewController:cf animated:YES completion:nil]; [cf release]; } - (void) onGotoPageFinished { scroll_animating = NO; [self scrollViewDidScroll: canvas]; } - (BOOL) shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation)o { return YES; } - (void) didRotateFromInterfaceOrientation: (UIInterfaceOrientation)o { [canvas setContentSize: CGSizeMake(fz_count_pages(doc) * width, height)]; [canvas setContentOffset: CGPointMake(current * width, 0)]; } @end