Issue 412
[zxing.git] / iphone / Classes / DecoderViewController.m
1 //
2 //  DecoderViewController.m
3 //  ZXing
4 //
5 //  Created by Christian Brunschen on 22/05/2008.
6 /*
7  * Copyright 2008 ZXing authors
8  *
9  * Licensed under the Apache License, Version 2.0 (the "License");
10  * you may not use this file except in compliance with the License.
11  * You may obtain a copy of the License at
12  *
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  *
15  * Unless required by applicable law or agreed to in writing, software
16  * distributed under the License is distributed on an "AS IS" BASIS,
17  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18  * See the License for the specific language governing permissions and
19  * limitations under the License.
20  */
21
22 #import "DecoderViewController.h"
23 #import "Decoder.h"
24 #import "NSString+HTML.h"
25 #import "ResultParser.h"
26 #import "ParsedResult.h"
27 #import "ResultAction.h"
28
29 #import "Database.h"
30 #import "ArchiveController.h"
31 #import "MessageViewController.h"
32 #import "Scan.h"
33 #import "TwoDDecoderResult.h"
34
35 // Michael Jurewitz, Dec 16, 2009 6:32 PM writes:
36 // https://devforums.apple.com/message/149553
37 // Notice Regarding UIGetScreenImage()
38 // After carefully considering the issue, Apple is now allowing applications to
39 // use the function UIGetScreenImage() to programmatically capture the current
40 // screen contents.
41 // Note that a future release of iPhone OS may provide a public API equivalent
42 // of this functionality.  At such time, all applications using
43 // UIGetScreenImage() will be required to adopt the public API.
44 CGImageRef MyCGImageCopyScreenContents(void) {
45    extern CGImageRef UIGetScreenImage(void);
46    return UIGetScreenImage(); /* already retained */
47 }
48
49 @interface DecoderViewController()
50 - (void)takeScreenshot;
51 @end
52
53 @implementation DecoderViewController
54
55 @synthesize cameraBarItem;
56 @synthesize libraryBarItem;
57 @synthesize savedPhotosBarItem;
58 @synthesize archiveBarItem;
59 @synthesize actionBarItem;
60
61 @synthesize messageView;
62 @synthesize messageTextView;
63 @synthesize messageHelpButton;
64 @synthesize imageView;
65 @synthesize picker;
66 @synthesize toolbar;
67
68 @synthesize decoder;
69 @synthesize result;
70 @synthesize actions;
71
72 @synthesize resultPointViews;
73
74 - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
75   if ((self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil])) {
76     // Initialization code
77     self.title =
78         NSLocalizedString(@"DecoderViewController AppTitle", @"Barcode Scanner");
79
80     Decoder *d = [[Decoder alloc] init];
81     self.decoder = d;
82     d.delegate = self;
83     [d release];
84     resultPointViews = [[NSMutableArray alloc] init];
85   }
86   return self;
87 }
88
89 - (void) messageReady:(id)sender {
90   MessageViewController *messageController = sender;
91   [[self navigationController] pushViewController:messageController animated:true];
92   [messageController release];
93 }
94
95 - (void) messageFailed:(id)sender {
96   MessageViewController *messageController = sender;
97   NSLog(@"Failed to load message!");
98   [messageController release];
99 }
100
101 - (void) showHints:(id)sender {
102   NSLog(@"Showing Hints!");
103
104   MessageViewController *hintsController =
105       [[MessageViewController alloc] initWithMessageFilename:@"Hints"
106                                                       target:self
107                                                    onSuccess:@selector(messageReady:)
108                                                    onFailure:@selector(messageFailed:)];
109   hintsController.title =
110       NSLocalizedString(@"DecoderViewController Hints MessageViewController title", @"Hints");
111   [hintsController view];
112 }
113
114 - (void) showAbout:(id)sender {
115   NSLog(@"Showing About!");
116
117   MessageViewController *aboutController =
118       [[MessageViewController alloc] initWithMessageFilename:@"About"
119                                                       target:self
120                                                    onSuccess:@selector(messageReady:)
121                                                    onFailure:@selector(messageFailed:)];
122   aboutController.title =
123       NSLocalizedString(@"DecoderViewController About MessageViewController title", @"About");
124   [aboutController view];
125 }
126
127
128 #define HELP_BUTTON_WIDTH (44.0)
129 #define HELP_BUTTON_HEIGHT (55.0)
130
131
132 #define FONT_NAME @"TimesNewRomanPSMT"
133 #define FONT_SIZE 16.0
134
135 - (void) reset {
136   self.result = nil;
137   [self clearImageView];
138   [self updateToolbar];
139   [self showMessage:NSLocalizedString(@"DecoderViewController take or choose picture",
140       @"Please take or choose a picture containing a barcode") helpButton:YES];
141 }
142
143 // Implement loadView if you want to create a view hierarchy programmatically
144 - (void)loadView {
145   [super loadView];
146
147   CGRect messageViewFrame = imageView.frame;
148   UIView *mView = [[UIView alloc] initWithFrame:messageViewFrame];
149   mView.backgroundColor = [UIColor darkGrayColor];
150   mView.alpha = 0.9;
151   mView.autoresizingMask = UIViewAutoresizingFlexibleHeight |
152   UIViewAutoresizingFlexibleWidth |
153   UIViewAutoresizingFlexibleTopMargin;
154
155   UITextView *mTextView = [[UITextView alloc] initWithFrame:messageViewFrame];
156   mTextView.autoresizingMask = UIViewAutoresizingFlexibleHeight |
157   UIViewAutoresizingFlexibleWidth;
158   mTextView.editable = false;
159   mTextView.scrollEnabled = true;
160   mTextView.font = [UIFont fontWithName:FONT_NAME size:FONT_SIZE];
161   mTextView.textColor = [UIColor whiteColor];
162   mTextView.backgroundColor = [[UIColor lightGrayColor] colorWithAlphaComponent:0.0];
163   mTextView.textAlignment = UITextAlignmentLeft;
164   mTextView.alpha = 1.0;
165   [mView addSubview:mTextView];
166
167   UIButton *mHelpButton = [[UIButton buttonWithType:UIButtonTypeInfoLight] retain];
168   mHelpButton.frame = CGRectMake(messageViewFrame.size.width - HELP_BUTTON_WIDTH, 0.0, HELP_BUTTON_WIDTH, HELP_BUTTON_HEIGHT);
169
170   mHelpButton.backgroundColor = [UIColor clearColor];
171   [mHelpButton setUserInteractionEnabled:YES];
172   [mHelpButton addTarget:self action:@selector(showHints:) forControlEvents:UIControlEventTouchUpInside];
173
174   self.messageHelpButton = mHelpButton;
175   [mHelpButton release];
176
177   self.messageTextView = mTextView;
178   [mTextView release];
179
180   self.messageView = mView;
181   [mView release];
182
183   [self.view addSubview:self.messageView];
184
185   // add the 'About' button at the top-right of the navigation bar
186   UIBarButtonItem *aboutButton =
187   [[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"DecoderViewController about button title", @"About")
188                                    style:UIBarButtonItemStyleBordered
189                                   target:self
190                                   action:@selector(showAbout:)];
191   self.navigationItem.rightBarButtonItem = aboutButton;
192   [aboutButton release];
193
194   [self reset];
195 }
196
197 - (void) updateToolbar {
198   self.cameraBarItem.enabled = [UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera];
199   self.savedPhotosBarItem.enabled = [UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeSavedPhotosAlbum];
200   self.libraryBarItem.enabled = [UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary];
201   self.archiveBarItem.enabled = true;
202   self.actionBarItem.enabled = (self.result != nil) && ([self.result actions] != nil) && ([self.result actions].count > 0);
203 }
204
205
206 // If you need to do additional setup after loading the view, override viewDidLoad.
207 - (void)viewDidLoad {
208   [super viewDidLoad];
209 }
210
211
212 - (void)clearImageView {
213   imageView.image = nil;
214   for (UIView *view in resultPointViews) {
215     [view removeFromSuperview];
216   }
217   [resultPointViews removeAllObjects];
218 }
219
220 - (void)pickAndDecodeFromSource:(UIImagePickerControllerSourceType) sourceType {
221   [self reset];
222
223   // Create the Image Picker
224   if ([UIImagePickerController isSourceTypeAvailable:sourceType]) {
225     UIImagePickerController* aPicker =
226         [[[UIImagePickerController alloc] init] autorelease];
227     aPicker.sourceType = sourceType;
228     aPicker.delegate = self;
229     self.picker = aPicker;
230
231     // [[NSUserDefaults standardUserDefaults] boolForKey:@"allowEditing"];
232     BOOL isCamera = (sourceType == UIImagePickerControllerSourceTypeCamera);
233     if ([picker respondsToSelector:@selector(setAllowsEditing:)]) {
234       // not in 3.0
235       [picker setAllowsEditing:!isCamera];
236     }
237     if (isCamera) {
238       if ([picker respondsToSelector:@selector(setShowsCameraControls:)]) {
239         [picker setShowsCameraControls:NO];
240         UIButton *cancelButton =
241           [UIButton buttonWithType:UIButtonTypeRoundedRect];
242         NSString *cancelString =
243           NSLocalizedString(@"DecoderViewController cancel button title", @"");
244         CGFloat height = [UIFont systemFontSize];
245         CGSize size =
246           [cancelString sizeWithFont:[UIFont systemFontOfSize:height]];
247         [cancelButton setTitle:cancelString forState:UIControlStateNormal];
248         CGRect appFrame = [[UIScreen mainScreen] bounds];
249         static const int kMargin = 10;
250         static const int kInternalXMargin = 10;
251         static const int kInternalYMargin = 10;
252         CGRect frame = CGRectMake(kMargin,
253           appFrame.size.height - (height + 2*kInternalYMargin + kMargin),
254           2*kInternalXMargin + size.width,
255           height + 2*kInternalYMargin);
256         [cancelButton setFrame:frame];
257         [cancelButton addTarget:self
258                          action:@selector(cancel:)
259                forControlEvents:UIControlEventTouchUpInside];
260         picker.cameraOverlayView = cancelButton;
261         // The camera takes quite a while to start up. Hence the 2 second delay.
262         [self performSelector:@selector(takeScreenshot)
263                    withObject:nil
264                    afterDelay:2.0];
265       }
266     }
267
268     // Picker is displayed asynchronously.
269     [self presentModalViewController:picker animated:YES];
270   } else {
271     NSLog(@"Attempted to pick an image with illegal source type '%d'", sourceType);
272   }
273 }
274
275 - (IBAction)pickAndDecode:(id) sender {
276   UIImagePickerControllerSourceType sourceType;
277   int i = [sender tag];
278
279   switch (i) {
280     case 0: sourceType = UIImagePickerControllerSourceTypeCamera; break;
281     case 1: sourceType = UIImagePickerControllerSourceTypeSavedPhotosAlbum; break;
282     case 2: sourceType = UIImagePickerControllerSourceTypePhotoLibrary; break;
283     default: sourceType = UIImagePickerControllerSourceTypeSavedPhotosAlbum;
284   }
285   [self pickAndDecodeFromSource:sourceType];
286 }
287
288
289 - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
290   // Return YES for supported orientations
291   return (interfaceOrientation == UIInterfaceOrientationPortrait);
292 }
293
294
295 - (void)didReceiveMemoryWarning {
296   [super didReceiveMemoryWarning]; // Releases the view if it doesn't have a superview
297   // Release anything that's not essential, such as cached data
298 }
299
300 - (void)dealloc {
301   [decoder release];
302   [self clearImageView];
303   [imageView release];
304   [actionBarItem release];
305   [cameraBarItem release];
306   [libraryBarItem release];
307   [savedPhotosBarItem release];
308   [archiveBarItem release];
309   [toolbar release];
310   [picker release];
311   [actions release];
312   [resultPointViews release];
313
314   [super dealloc];
315 }
316
317 - (void)showMessage:(NSString *)message helpButton:(BOOL)showHelpButton {
318 #ifdef DEBUG
319   NSLog(@"Showing message '%@' %@ help Button", message, showHelpButton ? @"with" : @"without");
320 #endif
321
322   CGSize imageMaxSize = imageView.bounds.size;
323   if (showHelpButton) {
324     imageMaxSize.width -= messageHelpButton.frame.size.width;
325   }
326   CGSize size = [message sizeWithFont:messageTextView.font constrainedToSize:imageMaxSize lineBreakMode:UILineBreakModeWordWrap];
327   float height = 20.0 + fmin(100.0, size.height);
328   if (showHelpButton) {
329     height = fmax(HELP_BUTTON_HEIGHT, height);
330   }
331
332   CGRect messageFrame = imageView.bounds;
333   messageFrame.origin.y = CGRectGetMaxY(messageFrame) - height;
334   messageFrame.size.height = height;
335   [self.messageView setFrame:messageFrame];
336   messageView.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleWidth;
337   CGRect messageViewBounds = [messageView bounds];
338
339   self.messageTextView.text = message;
340   messageTextView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
341   if (showHelpButton) {
342     CGRect textViewFrame;
343     CGRect helpButtonFrame;
344
345     CGRectDivide(messageViewBounds, &helpButtonFrame, &textViewFrame, HELP_BUTTON_WIDTH, CGRectMaxXEdge);
346     [self.messageTextView setFrame:textViewFrame];
347
348     [messageHelpButton setFrame:helpButtonFrame];
349     messageHelpButton.alpha = 1.0;
350     messageHelpButton.enabled = YES;
351     messageHelpButton.autoresizingMask =
352       UIViewAutoresizingFlexibleLeftMargin |
353       UIViewAutoresizingFlexibleTopMargin;
354     [messageView addSubview:messageHelpButton];
355   } else {
356     [messageHelpButton removeFromSuperview];
357     messageHelpButton.alpha = 0.0;
358     messageHelpButton.enabled = NO;
359
360     [self.messageTextView setFrame:messageViewBounds];
361   }
362 }
363
364 // DecoderDelegate methods
365
366 - (void)decoder:(Decoder *)decoder willDecodeImage:(UIImage *)image usingSubset:(UIImage *)subset{
367   [self clearImageView];
368   [self.imageView setImage:subset];
369   [self showMessage:[NSString stringWithFormat:NSLocalizedString(@"DecoderViewController MessageWhileDecodingWithDimensions", @"Decoding image (%.0fx%.0f) ..."), image.size.width, image.size.height]
370      helpButton:NO];
371 }
372
373 - (void)decoder:(Decoder *)decoder
374   decodingImage:(UIImage *)image
375     usingSubset:(UIImage *)subset
376        progress:(NSString *)message {
377   [self clearImageView];
378   [self.imageView setImage:subset];
379   [self showMessage:message helpButton:NO];
380 }
381
382 - (void)presentResultForString:(NSString *)resultString {
383   self.result = [ResultParser parsedResultForString:resultString];
384   [self showMessage:[self.result stringForDisplay] helpButton:NO];
385   self.actions = self.result.actions;
386 #ifdef DEBUG
387   NSLog(@"result has %d actions", actions ? 0 : actions.count);
388 #endif
389   [self updateToolbar];
390 }
391
392 - (void)presentResultPoints:(NSArray *)resultPoints
393                    forImage:(UIImage *)image
394                 usingSubset:(UIImage *)subset {
395   // simply add the points to the image view
396   imageView.image = subset;
397   for (NSValue *pointValue in resultPoints) {
398     [imageView addResultPoint:[pointValue CGPointValue]];
399   }
400 }
401
402 - (void)decoder:(Decoder *)decoder didDecodeImage:(UIImage *)image usingSubset:(UIImage *)subset withResult:(TwoDDecoderResult *)twoDResult {
403   self.picker = nil;
404   [self presentResultForString:twoDResult.text];
405
406   [self presentResultPoints:twoDResult.points forImage:image usingSubset:subset];
407
408   // save the scan to the shared database
409   [[Database sharedDatabase] addScanWithText:twoDResult.text];
410
411   [self performResultAction:self];
412 }
413
414 - (void)decoder:(Decoder *)decoder failedToDecodeImage:(UIImage *)image usingSubset:(UIImage *)subset reason:(NSString *)reason {
415   if (self.picker && UIImagePickerControllerSourceTypeCamera == self.picker.sourceType) {
416     // If we are using the camera, and the user hasn't manually cancelled,
417     // take another snapshot and try to decode it.
418     [self takeScreenshot];
419   } else {
420     [self showMessage:reason helpButton:YES];
421     [self updateToolbar];
422   }
423 }
424
425
426 - (void)willAnimateFirstHalfOfRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
427   [super willAnimateFirstHalfOfRotationToInterfaceOrientation:toInterfaceOrientation duration:duration];
428
429   if (imageView.image) {
430     /*
431     CGRect viewBounds = imageView.bounds;
432     CGSize imageSize = imageView.image.size;
433     float scale = fmin(viewBounds.size.width / imageSize.width,
434                        viewBounds.size.height / imageSize.height);
435     float xOffset = (viewBounds.size.width - scale * imageSize.width) / 2.0;
436     float yOffset = (viewBounds.size.height - scale * imageSize.height) / 2.0;
437      */
438
439     for (UIView *view in resultPointViews) {
440       view.alpha = 0.0;
441     }
442   }
443 }
444
445 - (void)willAnimateSecondHalfOfRotationFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation duration:(NSTimeInterval)duration {
446   [super willAnimateSecondHalfOfRotationFromInterfaceOrientation:fromInterfaceOrientation duration:duration];
447
448   if (imageView.image) {
449     /*
450     CGRect viewBounds = imageView.bounds;
451     CGSize imageSize = imageView.image.size;
452     float scale = fmin(viewBounds.size.width / imageSize.width,
453                        viewBounds.size.height / imageSize.height);
454     float xOffset = (viewBounds.size.width - scale * imageSize.width) / 2.0;
455     float yOffset = (viewBounds.size.height - scale * imageSize.height) / 2.0;
456      */
457
458     for (UIView *view in resultPointViews) {
459       view.alpha = 1.0;
460     }
461   }
462 }
463
464 - (void)cancel:(id)sender {
465   self.picker = nil;
466 }
467
468 - (void)takeScreenshot {
469   if (picker) {
470     CGImageRef cgScreen = MyCGImageCopyScreenContents();
471     if (cgScreen) {
472       CGRect croppedFrame = CGRectMake(0, 0, CGImageGetWidth(cgScreen),
473           CGImageGetHeight(cgScreen) - (10+toolbar.bounds.size.height));
474       CGImageRef cgCropped = CGImageCreateWithImageInRect(cgScreen, croppedFrame);
475       if (cgCropped) {
476         UIImage *screenshot = [UIImage imageWithCGImage:cgCropped];
477         CGImageRelease(cgCropped);
478         [self.decoder decodeImage:screenshot];
479       }
480       CGImageRelease(cgScreen);
481     }
482   }
483 }
484
485 // UIImagePickerControllerDelegate methods
486
487 - (void)imagePickerController:(UIImagePickerController *)aPicker
488 didFinishPickingMediaWithInfo:(NSDictionary *)info {
489   UIImage *imageToDecode =
490     [info objectForKey:UIImagePickerControllerEditedImage];
491   if (!imageToDecode) {
492     imageToDecode = [info objectForKey:UIImagePickerControllerOriginalImage];
493   }
494   CGSize size = [imageToDecode size];
495   CGRect cropRect = CGRectMake(0.0, 0.0, size.width, size.height);
496   
497 #ifdef DEBUG
498   NSLog(@"picked image size = (%f, %f)", size.width, size.height);
499 #endif
500   NSString *systemVersion = [[UIDevice currentDevice] systemVersion];
501
502   NSValue *cropRectValue = [info objectForKey:UIImagePickerControllerCropRect];
503   if (cropRectValue) {
504     UIImage *originalImage = [info objectForKey:UIImagePickerControllerOriginalImage];
505     if (originalImage) {
506 #ifdef DEBUG
507       NSLog(@"original image size = (%f, %f)", originalImage.size.width, originalImage.size.height);
508 #endif
509        cropRect = [cropRectValue CGRectValue];
510 #ifdef DEBUG
511       NSLog(@"crop rect = (%f, %f) x (%f, %f)", CGRectGetMinX(cropRect), CGRectGetMinY(cropRect), CGRectGetWidth(cropRect), CGRectGetHeight(cropRect));
512 #endif
513       if (([picker sourceType] == UIImagePickerControllerSourceTypeSavedPhotosAlbum) &&
514           [@"2.1" isEqualToString:systemVersion]) {
515         // adjust crop rect to work around bug in iPhone OS 2.1 when selecting from the photo roll
516         cropRect.origin.x *= 2.5;
517         cropRect.origin.y *= 2.5;
518         cropRect.size.width *= 2.5;
519         cropRect.size.height *= 2.5;
520 #ifdef DEBUG
521         NSLog(@"2.1-adjusted crop rect = (%f, %f) x (%f, %f)", CGRectGetMinX(cropRect), CGRectGetMinY(cropRect), CGRectGetWidth(cropRect), CGRectGetHeight(cropRect));
522 #endif
523       }
524
525       imageToDecode = originalImage;
526     }
527   }
528
529   [imageToDecode retain];
530   self.picker = nil;
531   [self.decoder decodeImage:imageToDecode cropRect:cropRect];
532   [imageToDecode release];
533 }
534
535
536 - (void)imagePickerControllerDidCancel:(UIImagePickerController *)aPicker {
537   self.picker = nil;
538 }
539
540 - (void)setPicker:(UIImagePickerController *)aPicker {
541   if (picker != aPicker) {
542     [picker dismissModalViewControllerAnimated:YES];
543     picker = [aPicker retain];
544     [self updateToolbar];
545   }
546 }
547
548 - (void)navigationController:(UINavigationController *)navigationController
549        didShowViewController:(UIViewController *)viewController
550                     animated:(BOOL)animated {
551   // no-op
552 }
553
554 - (void)navigationController:(UINavigationController *)navigationController
555       willShowViewController:(UIViewController *)viewController
556                     animated:(BOOL)animated {
557   // no-op
558 }
559
560 - (void)performAction:(ResultAction *)action {
561   [action performActionWithController:self shouldConfirm:NO];
562 }
563
564 - (void)confirmAndPerformAction:(ResultAction *)action {
565   [action performActionWithController:self shouldConfirm:YES];
566 }
567
568
569 - (IBAction)performResultAction:(id)sender {
570   if (self.result == nil) {
571     NSLog(@"no result to perform an action on!");
572     return;
573   }
574
575   if (self.actions == nil || self.actions.count == 0) {
576     NSLog(@"result has no actions to perform!");
577     return;
578   }
579
580   if (self.actions.count == 1) {
581     ResultAction *action = [self.actions lastObject];
582 #ifdef DEBUG
583     NSLog(@"Result has the single action, (%@)  '%@', performing it",
584           NSStringFromClass([action class]), [action title]);
585 #endif
586     [self performSelector:@selector(confirmAndPerformAction:)
587                  withObject:action
588                  afterDelay:0.0];
589   } else {
590 #ifdef DEBUG
591     NSLog(@"Result has multiple actions, popping up an action sheet");
592 #endif
593     UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithFrame:self.view.bounds];
594
595     for (ResultAction *action in self.actions) {
596       [actionSheet addButtonWithTitle:[action title]];
597     }
598
599     int cancelIndex = [actionSheet addButtonWithTitle:NSLocalizedString(@"DecoderViewController cancel button title", @"Cancel")];
600     actionSheet.cancelButtonIndex = cancelIndex;
601
602     actionSheet.delegate = self;
603
604     [actionSheet showFromToolbar:self.toolbar];
605   }
606 }
607
608 - (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
609   if (buttonIndex < self.actions.count) {
610     int actionIndex = buttonIndex;
611     ResultAction *action = [self.actions objectAtIndex:actionIndex];
612     [self performSelector:@selector(performAction:)
613                  withObject:action
614                  afterDelay:0.0];
615   }
616 }
617
618 - (IBAction)showArchive:(id)sender {
619   ArchiveController *archiveController = [[ArchiveController alloc] initWithDecoderViewController:self];
620   [[self navigationController] pushViewController:archiveController animated:true];
621   [archiveController release];
622 }
623
624 - (void)showScan:(Scan *)scan {
625   [self clearImageView];
626   [self presentResultForString:scan.text];
627   [[self navigationController] popToViewController:self animated:YES];
628 }
629
630 @end