93324842d0e278126c96a292907d8dafe30d8d3b
[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 != UIInterfaceOrientationPortraitUpsideDown);
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 {
305   [self clearImageView];
306   [self.imageView setImage:image];
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 #ifdef DEBUG
403   NSLog(@"picked image size = (%f, %f)", image.size.width, image.size.height);
404 #endif
405   if (editingInfo) {
406     UIImage *originalImage = [editingInfo objectForKey:UIImagePickerControllerOriginalImage];
407     if (originalImage) {
408 #ifdef DEBUG
409       NSLog(@"original image size = (%f, %f)", originalImage.size.width, originalImage.size.height);
410 #endif
411       NSValue *cropRectValue = [editingInfo objectForKey:UIImagePickerControllerCropRect];
412       if (cropRectValue) {
413         CGRect cropRect = [cropRectValue CGRectValue];
414 #ifdef DEBUG
415         NSLog(@"crop rect = (%f, %f) x (%f, %f)", CGRectGetMinX(cropRect), CGRectGetMinY(cropRect), CGRectGetWidth(cropRect), CGRectGetHeight(cropRect));
416 #endif
417         UIGraphicsBeginImageContext(cropRect.size);
418         
419         [originalImage drawAtPoint:CGPointMake(-CGRectGetMinX(cropRect),
420                                                -CGRectGetMinY(cropRect))];
421         
422         imageToDecode = UIGraphicsGetImageFromCurrentImageContext();
423         UIGraphicsEndImageContext();
424       }
425     }
426   }
427   
428   [[picker parentViewController] dismissModalViewControllerAnimated:YES];
429   [imageToDecode retain];
430   [picker release];
431   [self.decoder decodeImage:imageToDecode];
432   [imageToDecode release];
433   [self updateToolbar];
434 }
435
436 - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker
437 {
438   [picker dismissModalViewControllerAnimated:YES];
439   [picker release];
440   [self updateToolbar];
441 }
442
443 - (void)navigationController:(UINavigationController *)navigationController 
444        didShowViewController:(UIViewController *)viewController 
445                     animated:(BOOL)animated {
446   // no-op
447 }
448
449 - (void)navigationController:(UINavigationController *)navigationController 
450       willShowViewController:(UIViewController *)viewController 
451                     animated:(BOOL)animated {
452   // no-op
453 }
454
455 - (void)performAction:(ResultAction *)action {
456   [action performActionWithController:self shouldConfirm:NO];
457 }
458
459 - (void)confirmAndPerformAction:(ResultAction *)action {
460   [action performActionWithController:self shouldConfirm:YES];
461 }
462
463
464 - (IBAction)performResultAction:(id)sender {
465   if (self.result == nil) {
466     NSLog(@"no result to perform an action on!");
467     return;
468   }
469   
470   if (self.actions == nil || self.actions.count == 0) {
471     NSLog(@"result has no actions to perform!");
472     return;
473   }
474   
475   if (self.actions.count == 1) {
476     ResultAction *action = [self.actions lastObject];
477 #ifdef DEBUG
478     NSLog(@"Result has the single action, (%@)  '%@', performing it",
479           NSStringFromClass([action class]), [action title]);
480 #endif
481     [self performSelector:@selector(confirmAndPerformAction:) 
482                  withObject:action 
483                  afterDelay:0.0];
484   } else {
485 #ifdef DEBUG
486     NSLog(@"Result has multiple actions, popping up an action sheet");
487 #endif
488     UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithFrame:self.view.bounds];
489         
490     for (ResultAction *action in self.actions) {
491       [actionSheet addButtonWithTitle:[action title]];
492     }
493     
494     int cancelIndex = [actionSheet addButtonWithTitle:NSLocalizedString(@"DecoderViewController cancel button title", @"Cancel")];
495     actionSheet.cancelButtonIndex = cancelIndex;
496     
497     actionSheet.delegate = self;
498     
499     [actionSheet showFromToolbar:self.toolbar];
500   }
501 }
502
503 - (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
504   if (buttonIndex < self.actions.count) {
505     int actionIndex = buttonIndex;
506     ResultAction *action = [self.actions objectAtIndex:actionIndex];
507     [self performSelector:@selector(performAction:) 
508                  withObject:action 
509                  afterDelay:0.0];
510   }
511 }
512
513 - (IBAction)showArchive:(id)sender {
514   ArchiveController *archiveController = [[ArchiveController alloc] initWithDecoderViewController:self];
515   [[self navigationController] pushViewController:archiveController animated:true];
516   [archiveController release];
517 }
518
519 - (void)showScan:(Scan *)scan {
520   [self clearImageView];
521   [self presentResultForString:scan.text];
522   [[self navigationController] popToViewController:self animated:YES];
523 }
524
525 @end