Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
7640 views
1
#include "common.h"
2
3
#import "MuPageViewNormal.h"
4
#import "MuPageViewReflow.h"
5
#import "MuDocumentController.h"
6
#import "MuTextFieldController.h"
7
#import "MuChoiceFieldController.h"
8
#import "MuPrintPageRenderer.h"
9
10
#define GAP 20
11
#define INDICATOR_Y -44-24
12
#define SLIDER_W (width - GAP - 24)
13
#define SEARCH_W (width - GAP - 170)
14
#define MIN_SCALE (1.0)
15
#define MAX_SCALE (5.0)
16
17
static NSString *const AlertTitle = @"Save Document?";
18
// Correct functioning of the app relies on CloseAlertMessage and ShareAlertMessage differing
19
static NSString *const CloseAlertMessage = @"Changes have been made to the document that will be lost if not saved";
20
static NSString *const ShareAlertMessage = @"Your changes will not be shared unless the document is first saved";
21
22
static void flattenOutline(NSMutableArray *titles, NSMutableArray *pages, fz_outline *outline, int level)
23
{
24
char indent[8*4+1];
25
if (level > 8)
26
level = 8;
27
memset(indent, ' ', level * 4);
28
indent[level * 4] = 0;
29
while (outline)
30
{
31
if (outline->dest.kind == FZ_LINK_GOTO)
32
{
33
int page = outline->dest.ld.gotor.page;
34
if (page >= 0 && outline->title)
35
{
36
NSString *title = [NSString stringWithUTF8String: outline->title];
37
[titles addObject: [NSString stringWithFormat: @"%s%@", indent, title]];
38
[pages addObject: [NSNumber numberWithInt: page]];
39
}
40
}
41
flattenOutline(titles, pages, outline->down, level + 1);
42
outline = outline->next;
43
}
44
}
45
46
static char *tmp_path(char *path)
47
{
48
int f;
49
char *buf = malloc(strlen(path) + 6 + 1);
50
if (!buf)
51
return NULL;
52
53
strcpy(buf, path);
54
strcat(buf, "XXXXXX");
55
56
f = mkstemp(buf);
57
58
if (f >= 0)
59
{
60
close(f);
61
return buf;
62
}
63
else
64
{
65
free(buf);
66
return NULL;
67
}
68
}
69
70
static void saveDoc(char *current_path, fz_document *doc)
71
{
72
char *tmp;
73
fz_write_options opts;
74
opts.do_incremental = 1;
75
opts.do_ascii = 0;
76
opts.do_expand = 0;
77
opts.do_garbage = 0;
78
opts.do_linear = 0;
79
80
tmp = tmp_path(current_path);
81
if (tmp)
82
{
83
int written = 0;
84
85
fz_var(written);
86
fz_try(ctx)
87
{
88
FILE *fin = fopen(current_path, "rb");
89
FILE *fout = fopen(tmp, "wb");
90
char buf[256];
91
size_t n;
92
int err = 1;
93
94
if (fin && fout)
95
{
96
while ((n = fread(buf, 1, sizeof(buf), fin)) > 0)
97
fwrite(buf, 1, n, fout);
98
err = (ferror(fin) || ferror(fout));
99
}
100
101
if (fin)
102
fclose(fin);
103
if (fout)
104
fclose(fout);
105
106
if (!err)
107
{
108
fz_write_document(ctx, doc, tmp, &opts);
109
written = 1;
110
}
111
}
112
fz_catch(ctx)
113
{
114
written = 0;
115
}
116
117
if (written)
118
{
119
rename(tmp, current_path);
120
}
121
122
free(tmp);
123
}
124
}
125
126
@implementation MuDocumentController
127
{
128
fz_document *doc;
129
MuDocRef *docRef;
130
NSString *key;
131
char *filePath;
132
BOOL reflowMode;
133
MuOutlineController *outline;
134
UIScrollView *canvas;
135
UILabel *indicator;
136
UISlider *slider;
137
UISearchBar *searchBar;
138
UIBarButtonItem *nextButton, *prevButton, *cancelButton, *searchButton, *outlineButton, *linkButton;
139
UIBarButtonItem *moreButton;
140
UIBarButtonItem *shareButton, *printButton, *annotButton;
141
UIBarButtonItem *highlightButton, *underlineButton, *strikeoutButton;
142
UIBarButtonItem *inkButton;
143
UIBarButtonItem *tickButton;
144
UIBarButtonItem *deleteButton;
145
UIBarButtonItem *reflowButton;
146
UIBarButtonItem *backButton;
147
UIBarButtonItem *sliderWrapper;
148
int barmode;
149
int searchPage;
150
int cancelSearch;
151
int showLinks;
152
int width; // current screen size
153
int height;
154
int current; // currently visible page
155
int scroll_animating; // stop view updates during scrolling animations
156
float scale; // scale applied to views (only used in reflow mode)
157
BOOL _isRotating;
158
}
159
160
- (id) initWithFilename: (NSString*)filename path:(char *)cstr document: (MuDocRef *)aDoc
161
{
162
self = [super init];
163
if (!self)
164
return nil;
165
166
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000
167
if ([self respondsToSelector:@selector(automaticallyAdjustsScrollViewInsets)])
168
self.automaticallyAdjustsScrollViewInsets = NO;
169
#endif
170
key = [filename retain];
171
docRef = [aDoc retain];
172
doc = docRef->doc;
173
filePath = strdup(cstr);
174
175
dispatch_sync(queue, ^{});
176
177
fz_outline *root = fz_load_outline(ctx, doc);
178
if (root) {
179
NSMutableArray *titles = [[NSMutableArray alloc] init];
180
NSMutableArray *pages = [[NSMutableArray alloc] init];
181
flattenOutline(titles, pages, root, 0);
182
if ([titles count])
183
outline = [[MuOutlineController alloc] initWithTarget: self titles: titles pages: pages];
184
[titles release];
185
[pages release];
186
fz_drop_outline(ctx, root);
187
}
188
189
return self;
190
}
191
192
- (UIBarButtonItem *) newResourceBasedButton:(NSString *)resource withAction:(SEL)selector
193
{
194
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
195
{
196
return [[UIBarButtonItem alloc] initWithImage:[UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:resource ofType:@"png"]] style:UIBarButtonItemStylePlain target:self action:selector];
197
}
198
else
199
{
200
UIView *buttonView;
201
BOOL iOS7Style = ([[UIDevice currentDevice].systemVersion floatValue] >= 7.0f);
202
UIButton *button = [UIButton buttonWithType:iOS7Style ? UIButtonTypeSystem : UIButtonTypeCustom];
203
[button setImage:[UIImage imageNamed:resource] forState:UIControlStateNormal];
204
[button addTarget:self action:selector forControlEvents:UIControlEventTouchUpInside];
205
[button sizeToFit];
206
buttonView = button;
207
208
return [[UIBarButtonItem alloc] initWithCustomView:buttonView];
209
}
210
}
211
212
- (void) addMainMenuButtons
213
{
214
NSMutableArray *array = [NSMutableArray arrayWithCapacity:3];
215
[array addObject:moreButton];
216
[array addObject:searchButton];
217
if (outlineButton)
218
[array addObject:outlineButton];
219
[array addObject:reflowButton];
220
[array addObject:linkButton];
221
[[self navigationItem] setRightBarButtonItems: array ];
222
[[self navigationItem] setLeftBarButtonItem:backButton];
223
}
224
225
- (void) loadView
226
{
227
[[NSUserDefaults standardUserDefaults] setObject: key forKey: @"OpenDocumentKey"];
228
229
current = (int)[[NSUserDefaults standardUserDefaults] integerForKey: key];
230
if (current < 0 || current >= fz_count_pages(ctx, doc))
231
current = 0;
232
233
UIView *view = [[UIView alloc] initWithFrame: CGRectZero];
234
[view setAutoresizingMask: UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
235
[view setAutoresizesSubviews: YES];
236
view.backgroundColor = [UIColor grayColor];
237
238
canvas = [[UIScrollView alloc] initWithFrame: CGRectMake(0,0,GAP,0)];
239
[canvas setAutoresizingMask: UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
240
[canvas setPagingEnabled: YES];
241
[canvas setShowsHorizontalScrollIndicator: NO];
242
[canvas setShowsVerticalScrollIndicator: NO];
243
[canvas setDelegate: self];
244
245
UITapGestureRecognizer *tapRecog = [[UITapGestureRecognizer alloc] initWithTarget: self action: @selector(onTap:)];
246
tapRecog.delegate = self;
247
[canvas addGestureRecognizer: tapRecog];
248
[tapRecog release];
249
// In reflow mode, we need to track pinch gestures on the canvas and pass
250
// the scale changes to the subviews.
251
UIPinchGestureRecognizer *pinchRecog = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(onPinch:)];
252
pinchRecog.delegate = self;
253
[canvas addGestureRecognizer:pinchRecog];
254
[pinchRecog release];
255
256
scale = 1.0;
257
258
scroll_animating = NO;
259
260
indicator = [[UILabel alloc] initWithFrame: CGRectZero];
261
[indicator setAutoresizingMask: UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin];
262
[indicator setText: @"0000 of 9999"];
263
[indicator sizeToFit];
264
[indicator setCenter: CGPointMake(0, INDICATOR_Y)];
265
[indicator setTextAlignment: NSTextAlignmentCenter];
266
[indicator setBackgroundColor: [[UIColor blackColor] colorWithAlphaComponent: 0.5]];
267
[indicator setTextColor: [UIColor whiteColor]];
268
269
[view addSubview: canvas];
270
[view addSubview: indicator];
271
272
slider = [[UISlider alloc] initWithFrame: CGRectZero];
273
[slider setMinimumValue: 0];
274
[slider setMaximumValue: fz_count_pages(ctx, doc) - 1];
275
[slider addTarget: self action: @selector(onSlide:) forControlEvents: UIControlEventValueChanged];
276
277
if ([[[UIDevice currentDevice] systemVersion] floatValue] < 7.0)
278
{
279
sliderWrapper = [[UIBarButtonItem alloc] initWithCustomView: slider];
280
281
[self setToolbarItems: [NSArray arrayWithObjects: sliderWrapper, nil]];
282
}
283
284
// Set up the buttons on the navigation and search bar
285
286
if (outline) {
287
outlineButton = [self newResourceBasedButton:@"ic_list" withAction:@selector(onShowOutline:)];
288
}
289
linkButton = [self newResourceBasedButton:@"ic_link" withAction:@selector(onToggleLinks:)];
290
cancelButton = [self newResourceBasedButton:@"ic_cancel" withAction:@selector(onCancel:)];
291
searchButton = [self newResourceBasedButton:@"ic_magnifying_glass" withAction:@selector(onShowSearch:)];
292
prevButton = [self newResourceBasedButton:@"ic_arrow_left" withAction:@selector(onSearchPrev:)];
293
nextButton = [self newResourceBasedButton:@"ic_arrow_right" withAction:@selector(onSearchNext:)];
294
reflowButton = [self newResourceBasedButton:@"ic_reflow" withAction:@selector(onToggleReflow:)];
295
moreButton = [self newResourceBasedButton:@"ic_more" withAction:@selector(onMore:)];
296
annotButton = [self newResourceBasedButton:@"ic_annotation" withAction:@selector(onAnnot:)];
297
shareButton = [self newResourceBasedButton:@"ic_share" withAction:@selector(onShare:)];
298
printButton = [self newResourceBasedButton:@"ic_print" withAction:@selector(onPrint:)];
299
highlightButton = [self newResourceBasedButton:@"ic_highlight" withAction:@selector(onHighlight:)];
300
underlineButton = [self newResourceBasedButton:@"ic_underline" withAction:@selector(onUnderline:)];
301
strikeoutButton = [self newResourceBasedButton:@"ic_strike" withAction:@selector(onStrikeout:)];
302
inkButton = [self newResourceBasedButton:@"ic_pen" withAction:@selector(onInk:)];
303
tickButton = [self newResourceBasedButton:@"ic_check" withAction:@selector(onTick:)];
304
deleteButton = [self newResourceBasedButton:@"ic_trash" withAction:@selector(onDelete:)];
305
searchBar = [[UISearchBar alloc] initWithFrame: CGRectMake(0,0,50,32)];
306
backButton = [self newResourceBasedButton:@"ic_arrow_left" withAction:@selector(onBack:)];
307
[searchBar setPlaceholder: @"Search"];
308
[searchBar setDelegate: self];
309
310
[prevButton setEnabled: NO];
311
[nextButton setEnabled: NO];
312
313
[self addMainMenuButtons];
314
315
// TODO: add activityindicator to search bar
316
317
[self setView: view];
318
[view release];
319
}
320
321
- (void) dealloc
322
{
323
[docRef release]; docRef = nil; doc = NULL;
324
[indicator release]; indicator = nil;
325
[slider release]; slider = nil;
326
[sliderWrapper release]; sliderWrapper = nil;
327
[reflowButton release]; reflowButton = nil;
328
[backButton release]; backButton = nil;
329
[moreButton release]; moreButton = nil;
330
[searchBar release]; searchBar = nil;
331
[outlineButton release]; outlineButton = nil;
332
[linkButton release]; linkButton = nil;
333
[searchButton release]; searchButton = nil;
334
[cancelButton release]; cancelButton = nil;
335
[prevButton release]; prevButton = nil;
336
[nextButton release]; nextButton = nil;
337
[shareButton release]; shareButton = nil;
338
[printButton release]; printButton = nil;
339
[annotButton release]; annotButton = nil;
340
[highlightButton release]; highlightButton = nil;
341
[underlineButton release]; underlineButton = nil;
342
[strikeoutButton release]; strikeoutButton = nil;
343
[inkButton release]; inkButton = nil;
344
[tickButton release]; tickButton = nil;
345
[deleteButton release]; deleteButton = nil;
346
[canvas release]; canvas = nil;
347
free(filePath); filePath = NULL;
348
349
[outline release];
350
[key release];
351
[super dealloc];
352
}
353
354
- (void) viewWillAppear: (BOOL)animated
355
{
356
[super viewWillAppear:animated];
357
[self setTitle: [key lastPathComponent]];
358
359
[slider setValue: current];
360
361
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0)
362
[[[self navigationController] toolbar] addSubview:slider];
363
364
[indicator setText: [NSString stringWithFormat: @" %d of %d ", current+1, fz_count_pages(ctx, doc)]];
365
366
[[self navigationController] setToolbarHidden: NO animated: animated];
367
}
368
369
- (void) viewWillLayoutSubviews
370
{
371
CGSize size = [canvas frame].size;
372
int max_width = fz_max(width, size.width);
373
374
width = size.width;
375
height = size.height;
376
377
[canvas setContentInset: UIEdgeInsetsZero];
378
[canvas setContentSize: CGSizeMake(fz_count_pages(ctx, doc) * width, height)];
379
[canvas setContentOffset: CGPointMake(current * width, 0)];
380
381
[sliderWrapper setWidth: SLIDER_W];
382
[searchBar setFrame: CGRectMake(0,0,SEARCH_W,32)];
383
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0)
384
{
385
CGRect r = [[self navigationController] toolbar].frame;
386
r.origin.x = 0;
387
r.origin.y = 0;
388
[slider setFrame:r];
389
}
390
391
[[[self navigationController] toolbar] setNeedsLayout]; // force layout!
392
393
// use max_width so we don't clamp the content offset too early during animation
394
[canvas setContentSize: CGSizeMake(fz_count_pages(ctx, doc) * max_width, height)];
395
[canvas setContentOffset: CGPointMake(current * width, 0)];
396
397
for (UIView<MuPageView> *view in [canvas subviews]) {
398
if ([view number] == current) {
399
[view setFrame: CGRectMake([view number] * width, 0, width-GAP, height)];
400
[view willRotate];
401
}
402
}
403
for (UIView<MuPageView> *view in [canvas subviews]) {
404
if ([view number] != current) {
405
[view setFrame: CGRectMake([view number] * width, 0, width-GAP, height)];
406
[view willRotate];
407
}
408
}
409
}
410
411
- (void) viewDidAppear: (BOOL)animated
412
{
413
[super viewDidAppear:animated];
414
[self scrollViewDidScroll: canvas];
415
}
416
417
- (void) viewWillDisappear: (BOOL)animated
418
{
419
[super viewWillDisappear:animated];
420
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0)
421
[slider removeFromSuperview];
422
423
[self setTitle: @"Resume"];
424
[[NSUserDefaults standardUserDefaults] removeObjectForKey: @"OpenDocumentKey"];
425
[[self navigationController] setToolbarHidden: YES animated: animated];
426
}
427
428
- (void) showNavigationBar
429
{
430
if ([[self navigationController] isNavigationBarHidden]) {
431
[sliderWrapper setWidth: SLIDER_W];
432
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0)
433
{
434
CGRect r = [[self navigationController] toolbar].frame;
435
r.origin.x = 0;
436
r.origin.y = 0;
437
[slider setFrame:r];
438
}
439
[[self navigationController] setNavigationBarHidden: NO];
440
[[self navigationController] setToolbarHidden: NO];
441
[indicator setHidden: NO];
442
443
[UIView beginAnimations: @"MuNavBar" context: NULL];
444
445
[[[self navigationController] navigationBar] setAlpha: 1];
446
[[[self navigationController] toolbar] setAlpha: 1];
447
[indicator setAlpha: 1];
448
449
[UIView commitAnimations];
450
}
451
}
452
453
- (void) hideNavigationBar
454
{
455
if (![[self navigationController] isNavigationBarHidden]) {
456
[searchBar resignFirstResponder];
457
458
[UIView beginAnimations: @"MuNavBar" context: NULL];
459
[UIView setAnimationDelegate: self];
460
[UIView setAnimationDidStopSelector: @selector(onHideNavigationBarFinished)];
461
462
[[[self navigationController] navigationBar] setAlpha: 0];
463
[[[self navigationController] toolbar] setAlpha: 0];
464
[indicator setAlpha: 0];
465
466
[UIView commitAnimations];
467
}
468
}
469
470
- (void) onHideNavigationBarFinished
471
{
472
[[self navigationController] setNavigationBarHidden: YES];
473
[[self navigationController] setToolbarHidden: YES];
474
[indicator setHidden: YES];
475
}
476
477
- (void) onShowOutline: (id)sender
478
{
479
[[self navigationController] pushViewController: outline animated: YES];
480
}
481
482
- (void) onToggleLinks: (id)sender
483
{
484
showLinks = !showLinks;
485
for (UIView<MuPageView> *view in [canvas subviews])
486
{
487
if (showLinks)
488
[view showLinks];
489
else
490
[view hideLinks];
491
}
492
}
493
494
- (void) onToggleReflow: (id)sender
495
{
496
reflowMode = !reflowMode;
497
498
[annotButton setEnabled:!reflowMode];
499
[searchButton setEnabled:!reflowMode];
500
[linkButton setEnabled:!reflowMode];
501
[moreButton setEnabled:!reflowMode];
502
503
[[canvas subviews] makeObjectsPerformSelector:@selector(removeFromSuperview)];
504
[self scrollViewDidScroll:canvas];
505
}
506
507
- (void) showMoreMenu
508
{
509
NSMutableArray *rightbuttons = [NSMutableArray arrayWithObjects:printButton, shareButton, nil];
510
if (docRef->interactive)
511
[rightbuttons insertObject:annotButton atIndex:0];
512
[[self navigationItem] setRightBarButtonItems:rightbuttons];
513
[[self navigationItem] setLeftBarButtonItem:cancelButton];
514
515
barmode = BARMODE_MORE;
516
}
517
518
- (void) showAnnotationMenu
519
{
520
[[self navigationItem] setRightBarButtonItems:[NSArray arrayWithObjects:inkButton, strikeoutButton, underlineButton, highlightButton, nil]];
521
[[self navigationItem] setLeftBarButtonItem:cancelButton];
522
523
for (UIView<MuPageView> *view in [canvas subviews])
524
{
525
if ([view number] == current)
526
[view deselectAnnotation];
527
}
528
529
barmode = BARMODE_ANNOTATION;
530
}
531
532
- (void) update
533
{
534
for (UIView<MuPageView> *view in [canvas subviews])
535
[view update];
536
}
537
538
- (void) onMore: (id)sender
539
{
540
[self showMoreMenu];
541
}
542
543
- (void) onAnnot: (id)sender
544
{
545
[self showAnnotationMenu];
546
}
547
548
- (void) onPrint: (id)sender
549
{
550
UIPrintInteractionController *pic = [UIPrintInteractionController sharedPrintController];
551
if (pic) {
552
UIPrintInfo *printInfo = [UIPrintInfo printInfo];
553
printInfo.outputType = UIPrintInfoOutputGeneral;
554
printInfo.jobName = key;
555
printInfo.duplex = UIPrintInfoDuplexLongEdge;
556
pic.printInfo = printInfo;
557
pic.showsPageRange = YES;
558
pic.printPageRenderer = [[[MuPrintPageRenderer alloc] initWithDocRef:docRef] autorelease];
559
560
void (^completionHandler)(UIPrintInteractionController *, BOOL, NSError *) =
561
^(UIPrintInteractionController *pic, BOOL completed, NSError *error) {
562
if (!completed && error)
563
NSLog(@"FAILED! due to error in domain %@ with error code %u",
564
error.domain, (unsigned int)error.code);
565
};
566
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
567
[pic presentFromBarButtonItem:printButton animated:YES
568
completionHandler:completionHandler];
569
} else {
570
[pic presentAnimated:YES completionHandler:completionHandler];
571
}
572
}
573
}
574
575
- (void) shareDocument
576
{
577
NSURL *url = [NSURL fileURLWithPath:[NSString stringWithUTF8String:filePath]];
578
UIActivityViewController *cont = [[UIActivityViewController alloc] initWithActivityItems:[NSArray arrayWithObject:url] applicationActivities:nil];
579
cont.popoverPresentationController.barButtonItem = shareButton;
580
[self presentViewController:cont animated:YES completion:nil];
581
[cont release];
582
}
583
584
- (void) onShare: (id)sender
585
{
586
pdf_document *idoc = pdf_specifics(ctx, doc);
587
if (idoc && pdf_has_unsaved_changes(ctx, idoc))
588
{
589
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:AlertTitle message:ShareAlertMessage delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:@"Save and Share", nil];
590
[alertView show];
591
[alertView release];
592
}
593
else
594
{
595
[self shareDocument];
596
}
597
}
598
599
- (void) textSelectModeOn
600
{
601
[[self navigationItem] setRightBarButtonItems:[NSArray arrayWithObject:tickButton]];
602
for (UIView<MuPageView> *view in [canvas subviews])
603
{
604
if ([view number] == current)
605
[view textSelectModeOn];
606
}
607
}
608
609
- (void) textSelectModeOff
610
{
611
for (UIView<MuPageView> *view in [canvas subviews])
612
{
613
[view textSelectModeOff];
614
}
615
}
616
617
- (void) inkModeOn
618
{
619
[[self navigationItem] setRightBarButtonItems:[NSArray arrayWithObject:tickButton]];
620
for (UIView<MuPageView> *view in [canvas subviews])
621
{
622
if ([view number] == current)
623
[view inkModeOn];
624
}
625
}
626
627
- (void) deleteModeOn
628
{
629
[[self navigationItem] setRightBarButtonItems:[NSArray arrayWithObject:deleteButton]];
630
barmode = BARMODE_DELETE;
631
}
632
633
- (void) inkModeOff
634
{
635
for (UIView<MuPageView> *view in [canvas subviews])
636
{
637
[view inkModeOff];
638
}
639
}
640
641
- (void) onHighlight: (id)sender
642
{
643
barmode = BARMODE_HIGHLIGHT;
644
[self textSelectModeOn];
645
}
646
647
- (void) onUnderline: (id)sender
648
{
649
barmode = BARMODE_UNDERLINE;
650
[self textSelectModeOn];
651
}
652
653
- (void) onStrikeout: (id)sender
654
{
655
barmode = BARMODE_STRIKE;
656
[self textSelectModeOn];
657
}
658
659
- (void) onInk: (id)sender
660
{
661
barmode = BARMODE_INK;
662
[self inkModeOn];
663
}
664
665
- (void) onShowSearch: (id)sender
666
{
667
[[self navigationItem] setRightBarButtonItems:
668
[NSArray arrayWithObjects: nextButton, prevButton, nil]];
669
[[self navigationItem] setLeftBarButtonItem: cancelButton];
670
[[self navigationItem] setTitleView: searchBar];
671
[searchBar becomeFirstResponder];
672
barmode = BARMODE_SEARCH;
673
}
674
675
- (void) onTick: (id)sender
676
{
677
678
for (UIView<MuPageView> *view in [canvas subviews])
679
{
680
if ([view number] == current)
681
{
682
switch (barmode)
683
{
684
case BARMODE_HIGHLIGHT:
685
[view saveSelectionAsMarkup:FZ_ANNOT_HIGHLIGHT];
686
break;
687
688
case BARMODE_UNDERLINE:
689
[view saveSelectionAsMarkup:FZ_ANNOT_UNDERLINE];
690
break;
691
692
case BARMODE_STRIKE:
693
[view saveSelectionAsMarkup:FZ_ANNOT_STRIKEOUT];
694
break;
695
696
case BARMODE_INK:
697
[view saveInk];
698
}
699
}
700
}
701
702
[self showAnnotationMenu];
703
}
704
705
- (void) onDelete: (id)sender
706
{
707
for (UIView<MuPageView> *view in [canvas subviews])
708
{
709
if ([view number] == current)
710
[view deleteSelectedAnnotation];
711
}
712
[self showAnnotationMenu];
713
}
714
715
- (void) onCancel: (id)sender
716
{
717
switch (barmode)
718
{
719
case BARMODE_SEARCH:
720
cancelSearch = YES;
721
[searchBar resignFirstResponder];
722
[self resetSearch];
723
/* fallthrough */
724
case BARMODE_ANNOTATION:
725
case BARMODE_MORE:
726
[[self navigationItem] setTitleView: nil];
727
[self addMainMenuButtons];
728
barmode = BARMODE_MAIN;
729
break;
730
731
case BARMODE_HIGHLIGHT:
732
case BARMODE_UNDERLINE:
733
case BARMODE_STRIKE:
734
case BARMODE_DELETE:
735
[self showAnnotationMenu];
736
[self textSelectModeOff];
737
break;
738
739
case BARMODE_INK:
740
[self showAnnotationMenu];
741
[self inkModeOff];
742
break;
743
}
744
}
745
746
- (void) onBack: (id)sender
747
{
748
pdf_document *idoc = pdf_specifics(ctx, doc);
749
if (idoc && pdf_has_unsaved_changes(ctx, idoc))
750
{
751
UIAlertView *saveAlert = [[UIAlertView alloc]
752
initWithTitle:AlertTitle message:CloseAlertMessage delegate:self
753
cancelButtonTitle:@"Discard" otherButtonTitles:@"Save", nil];
754
[saveAlert show];
755
[saveAlert release];
756
}
757
else
758
{
759
[[self navigationController] popViewControllerAnimated:YES];
760
}
761
}
762
763
- (void) alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
764
{
765
if ([CloseAlertMessage isEqualToString:alertView.message])
766
{
767
if (buttonIndex == 1)
768
saveDoc(filePath, doc);
769
770
[alertView dismissWithClickedButtonIndex:buttonIndex animated:YES];
771
[[self navigationController] popViewControllerAnimated:YES];
772
}
773
774
if ([ShareAlertMessage isEqualToString:alertView.message])
775
{
776
[alertView dismissWithClickedButtonIndex:buttonIndex animated:NO];
777
if (buttonIndex == 1)
778
{
779
saveDoc(filePath, doc);
780
[self shareDocument];
781
}
782
}
783
}
784
785
- (void) resetSearch
786
{
787
searchPage = -1;
788
for (UIView<MuPageView> *view in [canvas subviews])
789
[view clearSearchResults];
790
}
791
792
- (void) showSearchResults: (int)count forPage: (int)number
793
{
794
printf("search found match on page %d\n", number);
795
searchPage = number;
796
[self gotoPage: number animated: NO];
797
for (UIView<MuPageView> *view in [canvas subviews])
798
if ([view number] == number)
799
[view showSearchResults: count];
800
else
801
[view clearSearchResults];
802
}
803
804
- (void) searchInDirection: (int)dir
805
{
806
UITextField *searchField;
807
char *needle;
808
int start;
809
810
[searchBar resignFirstResponder];
811
812
if (searchPage == current)
813
start = current + dir;
814
else
815
start = current;
816
817
needle = strdup([[searchBar text] UTF8String]);
818
819
searchField = nil;
820
for (id view in [searchBar subviews])
821
if ([view isKindOfClass: [UITextField class]])
822
searchField = view;
823
824
[prevButton setEnabled: NO];
825
[nextButton setEnabled: NO];
826
[searchField setEnabled: NO];
827
828
cancelSearch = NO;
829
830
dispatch_async(queue, ^{
831
for (int i = start; i >= 0 && i < fz_count_pages(ctx, doc); i += dir) {
832
int n = search_page(doc, i, needle, NULL);
833
if (n) {
834
dispatch_async(dispatch_get_main_queue(), ^{
835
[prevButton setEnabled: YES];
836
[nextButton setEnabled: YES];
837
[searchField setEnabled: YES];
838
[self showSearchResults: n forPage: i];
839
free(needle);
840
});
841
return;
842
}
843
if (cancelSearch) {
844
dispatch_async(dispatch_get_main_queue(), ^{
845
[prevButton setEnabled: YES];
846
[nextButton setEnabled: YES];
847
[searchField setEnabled: YES];
848
free(needle);
849
});
850
return;
851
}
852
}
853
dispatch_async(dispatch_get_main_queue(), ^{
854
printf("no search results found\n");
855
[prevButton setEnabled: YES];
856
[nextButton setEnabled: YES];
857
[searchField setEnabled: YES];
858
UIAlertView *alert = [[UIAlertView alloc]
859
initWithTitle: @"No matches found for:"
860
message: [NSString stringWithUTF8String: needle]
861
delegate: nil
862
cancelButtonTitle: @"Close"
863
otherButtonTitles: nil];
864
[alert show];
865
[alert release];
866
free(needle);
867
});
868
});
869
}
870
871
- (void) onSearchPrev: (id)sender
872
{
873
[self searchInDirection: -1];
874
}
875
876
- (void) onSearchNext: (id)sender
877
{
878
[self searchInDirection: 1];
879
}
880
881
- (void) searchBarSearchButtonClicked: (UISearchBar*)sender
882
{
883
[self onSearchNext: sender];
884
}
885
886
- (void) searchBar: (UISearchBar*)sender textDidChange: (NSString*)searchText
887
{
888
[self resetSearch];
889
if ([[searchBar text] length] > 0) {
890
[prevButton setEnabled: YES];
891
[nextButton setEnabled: YES];
892
} else {
893
[prevButton setEnabled: NO];
894
[nextButton setEnabled: NO];
895
}
896
}
897
898
- (void) onSlide: (id)sender
899
{
900
int number = [slider value];
901
if ([slider isTracking])
902
[indicator setText: [NSString stringWithFormat: @" %d of %d ", number+1, fz_count_pages(ctx, doc)]];
903
else
904
[self gotoPage: number animated: NO];
905
}
906
907
- (BOOL) gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
908
{
909
// For reflow mode, we load UIWebViews into the canvas. Returning YES
910
// here prevents them stealing our tap and pinch events.
911
return YES;
912
}
913
914
- (void) onTap: (UITapGestureRecognizer*)sender
915
{
916
CGPoint p = [sender locationInView: canvas];
917
CGPoint ofs = [canvas contentOffset];
918
float x0 = (width - GAP) / 5;
919
float x1 = (width - GAP) - x0;
920
p.x -= ofs.x;
921
p.y -= ofs.y;
922
__block BOOL tapHandled = NO;
923
for (UIView<MuPageView> *view in [canvas subviews])
924
{
925
CGPoint pp = [sender locationInView:view];
926
if (CGRectContainsPoint(view.bounds, pp))
927
{
928
MuTapResult *result = [view handleTap:pp];
929
__block BOOL hitAnnot = NO;
930
[result switchCaseInternal:^(MuTapResultInternalLink *link) {
931
[self gotoPage:link.pageNumber animated:NO];
932
tapHandled = YES;
933
} caseExternal:^(MuTapResultExternalLink *link) {
934
// Not currently supported
935
} caseRemote:^(MuTapResultRemoteLink *link) {
936
// Not currently supported
937
} caseWidget:^(MuTapResultWidget *widget) {
938
tapHandled = YES;
939
} caseAnnotation:^(MuTapResultAnnotation *annot) {
940
hitAnnot = YES;
941
}];
942
943
switch (barmode)
944
{
945
case BARMODE_ANNOTATION:
946
if (hitAnnot)
947
[self deleteModeOn];
948
tapHandled = YES;
949
break;
950
951
case BARMODE_DELETE:
952
if (!hitAnnot)
953
[self showAnnotationMenu];
954
tapHandled = YES;
955
break;
956
957
default:
958
if (hitAnnot)
959
{
960
// Annotation will have been selected, which is wanted
961
// only in annotation-editing mode
962
[view deselectAnnotation];
963
}
964
break;
965
}
966
967
if (tapHandled)
968
break;
969
}
970
}
971
if (tapHandled) {
972
// Do nothing further
973
} else if (p.x < x0) {
974
[self gotoPage: current-1 animated: YES];
975
} else if (p.x > x1) {
976
[self gotoPage: current+1 animated: YES];
977
} else {
978
if ([[self navigationController] isNavigationBarHidden])
979
[self showNavigationBar];
980
else if (barmode == BARMODE_MAIN)
981
[self hideNavigationBar];
982
}
983
}
984
985
- (void) onPinch:(UIPinchGestureRecognizer*)sender
986
{
987
if (sender.state == UIGestureRecognizerStateBegan)
988
sender.scale = scale;
989
990
if (sender.scale < MIN_SCALE)
991
sender.scale = MIN_SCALE;
992
993
if (sender.scale > MAX_SCALE)
994
sender.scale = MAX_SCALE;
995
996
if (sender.state == UIGestureRecognizerStateEnded)
997
scale = sender.scale;
998
999
for (UIView<MuPageView> *view in [canvas subviews])
1000
{
1001
// Zoom only the visible page until end of gesture
1002
if (view.number == current || sender.state == UIGestureRecognizerStateEnded)
1003
[view setScale:sender.scale];
1004
}
1005
}
1006
1007
- (void) scrollViewWillBeginDragging: (UIScrollView *)scrollView
1008
{
1009
if (barmode == BARMODE_MAIN)
1010
[self hideNavigationBar];
1011
}
1012
1013
- (void) scrollViewDidScroll: (UIScrollView*)scrollview
1014
{
1015
// scrollViewDidScroll seems to get called part way through a screen rotation.
1016
// (This is possibly a UIScrollView bug - see
1017
// http://stackoverflow.com/questions/4123991/uiscrollview-disable-scrolling-while-rotating-on-iphone-ipad/8141423#8141423 ).
1018
// This ends up corrupting the current page number, because the calculation
1019
// 'current = x / width' is using the new value of 'width' before the
1020
// pages have been resized/repositioned. To avoid this problem, we filter out
1021
// calls to scrollViewDidScroll during rotation.
1022
if (_isRotating)
1023
return;
1024
1025
if (width == 0)
1026
return; // not visible yet
1027
1028
if (scroll_animating)
1029
return; // don't mess with layout during animations
1030
1031
float x = [canvas contentOffset].x + width * 0.5f;
1032
current = x / width;
1033
1034
[[NSUserDefaults standardUserDefaults] setInteger: current forKey: key];
1035
1036
[indicator setText: [NSString stringWithFormat: @" %d of %d ", current+1, fz_count_pages(ctx, doc)]];
1037
[slider setValue: current];
1038
1039
// swap the distant page views out
1040
1041
NSMutableSet *invisiblePages = [[NSMutableSet alloc] init];
1042
for (UIView<MuPageView> *view in [canvas subviews]) {
1043
if ([view number] != current)
1044
[view resetZoomAnimated: YES];
1045
if ([view number] < current - 2 || [view number] > current + 2)
1046
[invisiblePages addObject: view];
1047
}
1048
for (UIView<MuPageView> *view in invisiblePages)
1049
[view removeFromSuperview];
1050
[invisiblePages release]; // don't bother recycling them...
1051
1052
[self createPageView: current];
1053
[self createPageView: current - 1];
1054
[self createPageView: current + 1];
1055
1056
// reset search results when page has flipped
1057
if (current != searchPage)
1058
[self resetSearch];
1059
}
1060
1061
- (void) createPageView: (int)number
1062
{
1063
if (number < 0 || number >= fz_count_pages(ctx, doc))
1064
return;
1065
int found = 0;
1066
for (UIView<MuPageView> *view in [canvas subviews])
1067
if ([view number] == number)
1068
found = 1;
1069
if (!found) {
1070
UIView<MuPageView> *view
1071
= reflowMode
1072
? [[MuPageViewReflow alloc] initWithFrame:CGRectMake(number * width, 0, width-GAP, height) document:docRef page:number]
1073
: [[MuPageViewNormal alloc] initWithFrame:CGRectMake(number * width, 0, width-GAP, height) dialogCreator:self updater:self document:docRef page:number];
1074
[view setScale:scale];
1075
[canvas addSubview: view];
1076
if (showLinks)
1077
[view showLinks];
1078
[view release];
1079
}
1080
}
1081
1082
- (void) gotoPage: (int)number animated: (BOOL)animated
1083
{
1084
if (number < 0)
1085
number = 0;
1086
if (number >= fz_count_pages(ctx, doc))
1087
number = fz_count_pages(ctx, doc) - 1;
1088
if (current == number)
1089
return;
1090
if (animated) {
1091
// setContentOffset:animated: does not use the normal animation
1092
// framework. It also doesn't play nice with the tap gesture
1093
// recognizer. So we do our own page flipping animation here.
1094
// We must set the scroll_animating flag so that we don't create
1095
// or remove subviews until after the animation, or they'll
1096
// swoop in from origo during the animation.
1097
1098
scroll_animating = YES;
1099
[UIView beginAnimations: @"MuScroll" context: NULL];
1100
[UIView setAnimationDuration: 0.4];
1101
[UIView setAnimationBeginsFromCurrentState: YES];
1102
[UIView setAnimationDelegate: self];
1103
[UIView setAnimationDidStopSelector: @selector(onGotoPageFinished)];
1104
1105
for (UIView<MuPageView> *view in [canvas subviews])
1106
[view resetZoomAnimated: NO];
1107
1108
[canvas setContentOffset: CGPointMake(number * width, 0)];
1109
[slider setValue: number];
1110
[indicator setText: [NSString stringWithFormat: @" %d of %d ", number+1, fz_count_pages(ctx, doc)]];
1111
1112
[UIView commitAnimations];
1113
} else {
1114
for (UIView<MuPageView> *view in [canvas subviews])
1115
[view resetZoomAnimated: NO];
1116
[canvas setContentOffset: CGPointMake(number * width, 0)];
1117
}
1118
current = number;
1119
}
1120
1121
- (void) invokeTextDialog:(NSString *)aString okayAction:(void (^)(NSString *))block
1122
{
1123
MuTextFieldController *tf = [[MuTextFieldController alloc] initWithText:aString okayAction:block];
1124
tf.modalPresentationStyle = UIModalPresentationFormSheet;
1125
[self presentViewController:tf animated:YES completion:nil];
1126
[tf release];
1127
}
1128
1129
- (void) invokeChoiceDialog:(NSArray *)anArray okayAction:(void (^)(NSArray *))block
1130
{
1131
MuChoiceFieldController *cf = [[MuChoiceFieldController alloc] initWithChoices:anArray okayAction:block];
1132
cf.modalPresentationStyle = UIModalPresentationFormSheet;
1133
[self presentViewController:cf animated:YES completion:nil];
1134
[cf release];
1135
}
1136
1137
- (void) onGotoPageFinished
1138
{
1139
scroll_animating = NO;
1140
[self scrollViewDidScroll: canvas];
1141
}
1142
1143
- (BOOL) shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation)o
1144
{
1145
return YES;
1146
}
1147
1148
- (void) willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
1149
{
1150
_isRotating = YES;
1151
}
1152
1153
- (void) didRotateFromInterfaceOrientation: (UIInterfaceOrientation)o
1154
{
1155
_isRotating = NO;
1156
1157
// We need to set these here, because during the animation we may use a wider
1158
// size (the maximum of the landscape/portrait widths), to avoid clipping during
1159
// the rotation.
1160
[canvas setContentSize: CGSizeMake(fz_count_pages(ctx, doc) * width, height)];
1161
[canvas setContentOffset: CGPointMake(current * width, 0)];
1162
}
1163
1164
@end
1165
1166