Issue 370, allow custom response URLs
[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 @implementation DecoderViewController
36
37 @synthesize cameraBarItem;
38 @synthesize libraryBarItem;
39 @synthesize savedPhotosBarItem;
40 @synthesize archiveBarItem;
41 @synthesize actionBarItem;
42
43 @synthesize messageView;
44 @synthesize messageTextView;
45 @synthesize messageHelpButton;
46 @synthesize imageView;
47 @synthesize toolbar;
48
49 @synthesize decoder;
50 @synthesize result;
51 @synthesize actions;
52
53 @synthesize resultPointViews;
54
55 - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
56         if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
57                 // Initialization code
58     self.title = NSLocalizedString(@"DecoderViewController AppTitle", @"Barcode Scanner");
59     
60     Decoder *d = [[Decoder alloc] init];
61     self.decoder = d;
62     d.delegate = self;
63     [d release];
64     resultPointViews = [[NSMutableArray alloc] init];
65         }
66         return self;
67 }
68
69 - (void) messageReady:(id)sender {
70   MessageViewController *messageController = sender;
71   [[self navigationController] pushViewController:messageController animated:true];
72   [messageController release];
73 }
74
75 - (void) messageFailed:(id)sender {
76   MessageViewController *messageController = sender;
77   NSLog(@"Failed to load message!");
78   [messageController release];
79 }
80
81 - (void) showHints:(id)sender {
82   NSLog(@"Showing Hints!");
83   
84   MessageViewController *hintsController = 
85   [[MessageViewController alloc] initWithMessageFilename:@"Hints"
86                                                   target:self 
87                                                onSuccess:@selector(messageReady:) 
88                                                onFailure:@selector(messageFailed:)];
89   hintsController.title = NSLocalizedString(@"DecoderViewController Hints MessageViewController title", @"Hints");
90   hintsController.view;
91 }
92
93 - (void) showAbout:(id)sender {
94   NSLog(@"Showing About!");
95   
96   MessageViewController *aboutController = 
97   [[MessageViewController alloc] initWithMessageFilename:@"About"
98                                                   target:self 
99                                                onSuccess:@selector(messageReady:) 
100                                                onFailure:@selector(messageFailed:)];
101   aboutController.title = NSLocalizedString(@"DecoderViewController About MessageViewController title", @"About");
102   aboutController.view;
103 }
104
105   
106 #define HELP_BUTTON_WIDTH (44.0)
107 #define HELP_BUTTON_HEIGHT (55.0)
108
109
110 #define FONT_NAME @"TimesNewRomanPSMT"
111 #define FONT_SIZE 16.0
112
113 - (void) reset {
114   self.result = nil;
115   [self clearImageView];
116   [self updateToolbar];
117   [self showMessage:NSLocalizedString(@"DecoderViewController take or choose picture", @"Please take or choose a picture containing a barcode") helpButton:YES];
118 }
119
120 // Implement loadView if you want to create a view hierarchy programmatically
121 - (void)loadView {
122   [super loadView];
123   
124   CGRect messageViewFrame = imageView.frame;
125   UIView *mView = [[UIView alloc] initWithFrame:messageViewFrame];
126   mView.backgroundColor = [UIColor darkGrayColor];
127   mView.alpha = 0.9;
128   mView.autoresizingMask = UIViewAutoresizingFlexibleHeight | 
129   UIViewAutoresizingFlexibleWidth |
130   UIViewAutoresizingFlexibleTopMargin;
131   
132   UITextView *mTextView = [[UITextView alloc] initWithFrame:messageViewFrame];
133   mTextView.autoresizingMask = UIViewAutoresizingFlexibleHeight | 
134   UIViewAutoresizingFlexibleWidth;
135   mTextView.editable = false;
136   mTextView.scrollEnabled = true;
137   mTextView.font = [UIFont fontWithName:FONT_NAME size:FONT_SIZE];
138   mTextView.textColor = [UIColor whiteColor];
139   mTextView.backgroundColor = [[UIColor lightGrayColor] colorWithAlphaComponent:0.0];
140   mTextView.textAlignment = UITextAlignmentLeft;
141   mTextView.alpha = 1.0;
142   [mView addSubview:mTextView];
143
144   UIButton *mHelpButton = [[UIButton buttonWithType:UIButtonTypeInfoLight] retain];
145   mHelpButton.frame = CGRectMake(messageViewFrame.size.width - HELP_BUTTON_WIDTH, 0.0, HELP_BUTTON_WIDTH, HELP_BUTTON_HEIGHT);
146   
147   mHelpButton.backgroundColor = [UIColor clearColor];
148   [mHelpButton setUserInteractionEnabled:YES];
149   [mHelpButton addTarget:self action:@selector(showHints:) forControlEvents:UIControlEventTouchUpInside];
150
151   self.messageHelpButton = mHelpButton;
152   [mHelpButton release];
153   
154   self.messageTextView = mTextView;
155   [mTextView release];
156   
157   self.messageView = mView;
158   [mView release];
159   
160   [self.view addSubview:self.messageView];
161   
162   // add the 'About' button at the top-right of the navigation bar
163   UIBarButtonItem *aboutButton = 
164   [[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"DecoderViewController about button title", @"About") 
165                                    style:UIBarButtonItemStyleBordered
166                                   target:self 
167                                   action:@selector(showAbout:)];
168   self.navigationItem.rightBarButtonItem = aboutButton;
169   [aboutButton release];
170   
171   [self reset];
172 }
173
174 - (void) updateToolbar {
175   self.cameraBarItem.enabled = [UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera];
176   self.savedPhotosBarItem.enabled = [UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeSavedPhotosAlbum];
177   self.libraryBarItem.enabled = [UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary];
178   self.archiveBarItem.enabled = true;
179   self.actionBarItem.enabled = (self.result != nil) && ([self.result actions] != nil) && ([self.result actions].count > 0);
180 }
181
182
183 // If you need to do additional setup after loading the view, override viewDidLoad.
184 - (void)viewDidLoad {
185   [super viewDidLoad];
186 }
187
188
189 - (void)clearImageView {
190   imageView.image = nil;
191   for (UIView *view in resultPointViews) {
192     [view removeFromSuperview];
193   }
194   [resultPointViews removeAllObjects];
195 }
196
197 - (void)pickAndDecodeFromSource:(UIImagePickerControllerSourceType) sourceType {
198   [self reset];
199
200   // Create the Image Picker
201   if ([UIImagePickerController isSourceTypeAvailable:sourceType]) {
202     UIImagePickerController* picker = [[UIImagePickerController alloc] init];
203     picker.sourceType = sourceType;
204     picker.delegate = self;
205     picker.allowsImageEditing = YES; // [[NSUserDefaults standardUserDefaults] boolForKey:@"allowEditing"];
206     
207     // Picker is displayed asynchronously.
208     [self presentModalViewController:picker animated:YES];
209   } else {
210     NSLog(@"Attempted to pick an image with illegal source type '%d'", sourceType);
211   }
212 }
213
214 - (IBAction)pickAndDecode:(id) sender {
215   UIImagePickerControllerSourceType sourceType;
216   int i = [sender tag];
217   
218   switch (i) {
219     case 0: sourceType = UIImagePickerControllerSourceTypeCamera; break;
220     case 1: sourceType = UIImagePickerControllerSourceTypeSavedPhotosAlbum; break;
221     case 2: sourceType = UIImagePickerControllerSourceTypePhotoLibrary; break;
222     default: sourceType = UIImagePickerControllerSourceTypeSavedPhotosAlbum;
223   }
224   [self pickAndDecodeFromSource:sourceType];
225 }
226
227
228 - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
229   // Return YES for supported orientations
230   return (interfaceOrientation == UIInterfaceOrientationPortrait);
231 }
232
233
234 - (void)didReceiveMemoryWarning {
235   [super didReceiveMemoryWarning]; // Releases the view if it doesn't have a superview
236   // Release anything that's not essential, such as cached data
237 }
238
239 - (void)dealloc {
240   [decoder release];
241   [self clearImageView];
242   [imageView release];
243   [actionBarItem release];
244   [cameraBarItem release];
245   [libraryBarItem release];
246   [savedPhotosBarItem release];
247   [archiveBarItem release];
248   [toolbar release];
249   [actions dealloc];
250   [resultPointViews dealloc];
251   
252         [super dealloc];
253 }
254
255 - (void)showMessage:(NSString *)message helpButton:(BOOL)showHelpButton {
256 #ifdef DEBUG
257   NSLog(@"Showing message '%@' %@ help Button", message, showHelpButton ? @"with" : @"without");
258 #endif
259   
260   CGSize maxSize = imageView.bounds.size;
261   if (showHelpButton) {
262     maxSize.width -= messageHelpButton.frame.size.width;
263   }
264   CGSize size = [message sizeWithFont:messageTextView.font constrainedToSize:maxSize lineBreakMode:UILineBreakModeWordWrap];
265   float height = 20.0 + fmin(100.0, size.height);
266   if (showHelpButton) {
267     height = fmax(HELP_BUTTON_HEIGHT, height);
268   }
269
270   CGRect messageFrame = imageView.bounds;
271   messageFrame.origin.y = CGRectGetMaxY(messageFrame) - height;
272   messageFrame.size.height = height;
273   [self.messageView setFrame:messageFrame];
274   messageView.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleWidth;
275   CGRect messageViewBounds = [messageView bounds];
276
277   self.messageTextView.text = message;
278   messageTextView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
279   if (showHelpButton) {
280     CGRect textViewFrame;
281     CGRect helpButtonFrame;
282     
283     CGRectDivide(messageViewBounds, &helpButtonFrame, &textViewFrame, HELP_BUTTON_WIDTH, CGRectMaxXEdge);
284     [self.messageTextView setFrame:textViewFrame];
285     
286     [messageHelpButton setFrame:helpButtonFrame];
287     messageHelpButton.alpha = 1.0;
288     messageHelpButton.enabled = YES;
289     messageHelpButton.autoresizingMask = 
290       UIViewAutoresizingFlexibleLeftMargin | 
291       UIViewAutoresizingFlexibleTopMargin;
292     [messageView addSubview:messageHelpButton];
293   } else {
294     [messageHelpButton removeFromSuperview];
295     messageHelpButton.alpha = 0.0;
296     messageHelpButton.enabled = NO;
297
298     [self.messageTextView setFrame:messageViewBounds];
299   }
300 }
301
302 // DecoderDelegate methods
303
304 - (void)decoder:(Decoder *)decoder willDecodeImage:(UIImage *)image usingSubset:(UIImage *)subset{
305   [self clearImageView];
306   [self.imageView setImage:subset];
307   [self showMessage:[NSString stringWithFormat:NSLocalizedString(@"DecoderViewController MessageWhileDecodingWithDimensions", @"Decoding image (%.0fx%.0f) ..."), image.size.width, image.size.height]
308      helpButton:NO];
309 }
310
311 - (void)decoder:(Decoder *)decoder 
312   decodingImage:(UIImage *)image 
313     usingSubset:(UIImage *)subset
314        progress:(NSString *)message {
315   [self clearImageView];
316   [self.imageView setImage:subset];
317   [self showMessage:message helpButton:NO];
318 }
319
320 - (void)presentResultForString:(NSString *)resultString {
321   self.result = [ResultParser parsedResultForString:resultString];
322   [self showMessage:[self.result stringForDisplay] helpButton:NO];
323   self.actions = self.result.actions;
324 #ifdef DEBUG
325   NSLog(@"result has %d actions", actions ? 0 : actions.count);
326 #endif
327   [self updateToolbar];
328
329
330 - (void)presentResultPoints:(NSArray *)resultPoints 
331                    forImage:(UIImage *)image
332                 usingSubset:(UIImage *)subset {
333   // simply add the points to the image view
334   imageView.image = subset;
335   for (NSValue *pointValue in resultPoints) {
336     [imageView addResultPoint:[pointValue CGPointValue]];
337   }
338 }
339
340 - (void)decoder:(Decoder *)decoder didDecodeImage:(UIImage *)image usingSubset:(UIImage *)subset withResult:(TwoDDecoderResult *)twoDResult {
341   [self presentResultForString:twoDResult.text];
342   
343   [self presentResultPoints:twoDResult.points forImage:image usingSubset:subset];
344   
345   // save the scan to the shared database
346   [[Database sharedDatabase] addScanWithText:twoDResult.text];
347   
348   [self performResultAction:self];
349 }
350
351 - (void)decoder:(Decoder *)decoder failedToDecodeImage:(UIImage *)image usingSubset:(UIImage *)subset reason:(NSString *)reason {
352   [self showMessage:reason helpButton:YES];
353   [self updateToolbar];
354 }
355
356
357 - (void)willAnimateFirstHalfOfRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
358   [super willAnimateFirstHalfOfRotationToInterfaceOrientation:toInterfaceOrientation duration:duration];
359   
360   if (imageView.image) {
361     /*
362     CGRect viewBounds = imageView.bounds;
363     CGSize imageSize = imageView.image.size;
364     float scale = fmin(viewBounds.size.width / imageSize.width,
365                        viewBounds.size.height / imageSize.height);
366     float xOffset = (viewBounds.size.width - scale * imageSize.width) / 2.0;
367     float yOffset = (viewBounds.size.height - scale * imageSize.height) / 2.0;
368      */
369     
370     for (UIView *view in resultPointViews) {
371       view.alpha = 0.0;
372     }
373   }  
374 }
375
376 - (void)willAnimateSecondHalfOfRotationFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation duration:(NSTimeInterval)duration {
377   [super willAnimateSecondHalfOfRotationFromInterfaceOrientation:fromInterfaceOrientation duration:duration];
378
379   if (imageView.image) {
380     /*
381     CGRect viewBounds = imageView.bounds;
382     CGSize imageSize = imageView.image.size;
383     float scale = fmin(viewBounds.size.width / imageSize.width,
384                        viewBounds.size.height / imageSize.height);
385     float xOffset = (viewBounds.size.width - scale * imageSize.width) / 2.0;
386     float yOffset = (viewBounds.size.height - scale * imageSize.height) / 2.0;
387      */
388     
389     for (UIView *view in resultPointViews) {
390       view.alpha = 1.0;
391     }
392   }  
393 }
394
395 // UIImagePickerControllerDelegate methods
396
397 - (void)imagePickerController:(UIImagePickerController *)picker
398         didFinishPickingImage:(UIImage *)image
399                   editingInfo:(NSDictionary *)editingInfo
400 {
401   UIImage *imageToDecode = image;
402         CGSize size = [image size];
403         CGRect cropRect = CGRectMake(0.0, 0.0, size.width, size.height);
404         
405 #ifdef DEBUG
406   NSLog(@"picked image size = (%f, %f)", size.width, size.height);
407 #endif
408   NSString *systemVersion = [[UIDevice currentDevice] systemVersion];
409   
410   if (editingInfo) {
411     UIImage *originalImage = [editingInfo objectForKey:UIImagePickerControllerOriginalImage];
412     if (originalImage) {
413 #ifdef DEBUG
414       NSLog(@"original image size = (%f, %f)", originalImage.size.width, originalImage.size.height);
415 #endif
416       NSValue *cropRectValue = [editingInfo objectForKey:UIImagePickerControllerCropRect];
417       if (cropRectValue) {
418         cropRect = [cropRectValue CGRectValue];
419 #ifdef DEBUG
420         NSLog(@"crop rect = (%f, %f) x (%f, %f)", CGRectGetMinX(cropRect), CGRectGetMinY(cropRect), CGRectGetWidth(cropRect), CGRectGetHeight(cropRect));
421 #endif
422         if (([picker sourceType] == UIImagePickerControllerSourceTypeSavedPhotosAlbum) &&
423                                                 [@"2.1" isEqualToString:systemVersion]) {
424           // adjust crop rect to work around bug in iPhone OS 2.1 when selecting from the photo roll
425           cropRect.origin.x *= 2.5;
426           cropRect.origin.y *= 2.5;
427           cropRect.size.width *= 2.5;
428           cropRect.size.height *= 2.5;
429 #ifdef DEBUG
430           NSLog(@"2.1-adjusted crop rect = (%f, %f) x (%f, %f)", CGRectGetMinX(cropRect), CGRectGetMinY(cropRect), CGRectGetWidth(cropRect), CGRectGetHeight(cropRect));
431 #endif
432         }
433                                 
434                                 imageToDecode = originalImage;
435       }
436     }
437   }
438   
439   [[picker parentViewController] dismissModalViewControllerAnimated:YES];
440   [imageToDecode retain];
441   [picker release];
442   [self.decoder decodeImage:imageToDecode cropRect:cropRect];
443   [imageToDecode release];
444   [self updateToolbar];
445 }
446
447 - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker
448 {
449   [picker dismissModalViewControllerAnimated:YES];
450   [picker release];
451   [self updateToolbar];
452 }
453
454 - (void)navigationController:(UINavigationController *)navigationController 
455        didShowViewController:(UIViewController *)viewController 
456                     animated:(BOOL)animated {
457   // no-op
458 }
459
460 - (void)navigationController:(UINavigationController *)navigationController 
461       willShowViewController:(UIViewController *)viewController 
462                     animated:(BOOL)animated {
463   // no-op
464 }
465
466 - (void)performAction:(ResultAction *)action {
467   [action performActionWithController:self shouldConfirm:NO];
468 }
469
470 - (void)confirmAndPerformAction:(ResultAction *)action {
471   [action performActionWithController:self shouldConfirm:YES];
472 }
473
474
475 - (IBAction)performResultAction:(id)sender {
476   if (self.result == nil) {
477     NSLog(@"no result to perform an action on!");
478     return;
479   }
480   
481   if (self.actions == nil || self.actions.count == 0) {
482     NSLog(@"result has no actions to perform!");
483     return;
484   }
485   
486   if (self.actions.count == 1) {
487     ResultAction *action = [self.actions lastObject];
488 #ifdef DEBUG
489     NSLog(@"Result has the single action, (%@)  '%@', performing it",
490           NSStringFromClass([action class]), [action title]);
491 #endif
492     [self performSelector:@selector(confirmAndPerformAction:) 
493                  withObject:action 
494                  afterDelay:0.0];
495   } else {
496 #ifdef DEBUG
497     NSLog(@"Result has multiple actions, popping up an action sheet");
498 #endif
499     UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithFrame:self.view.bounds];
500         
501     for (ResultAction *action in self.actions) {
502       [actionSheet addButtonWithTitle:[action title]];
503     }
504     
505     int cancelIndex = [actionSheet addButtonWithTitle:NSLocalizedString(@"DecoderViewController cancel button title", @"Cancel")];
506     actionSheet.cancelButtonIndex = cancelIndex;
507     
508     actionSheet.delegate = self;
509     
510     [actionSheet showFromToolbar:self.toolbar];
511   }
512 }
513
514 - (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
515   if (buttonIndex < self.actions.count) {
516     int actionIndex = buttonIndex;
517     ResultAction *action = [self.actions objectAtIndex:actionIndex];
518     [self performSelector:@selector(performAction:) 
519                  withObject:action 
520                  afterDelay:0.0];
521   }
522 }
523
524 - (IBAction)showArchive:(id)sender {
525   ArchiveController *archiveController = [[ArchiveController alloc] initWithDecoderViewController:self];
526   [[self navigationController] pushViewController:archiveController animated:true];
527   [archiveController release];
528 }
529
530 - (void)showScan:(Scan *)scan {
531   [self clearImageView];
532   [self presentResultForString:scan.text];
533   [[self navigationController] popToViewController:self animated:YES];
534 }
535
536 @end