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