Added hints for taking pictures, improved localizations into English, German & Swedish
[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   CGRect messageViewBounds = [messageView bounds];
250
251   self.messageTextView.text = message;
252   if (showHelpButton) {
253     CGRect textViewFrame;
254     CGRect helpButtonFrame;
255     
256     CGRectDivide(messageViewBounds, &helpButtonFrame, &textViewFrame, HELP_BUTTON_WIDTH, CGRectMaxXEdge);
257     [self.messageTextView setFrame:textViewFrame];
258     
259     [messageHelpButton setFrame:helpButtonFrame];
260     messageHelpButton.alpha = 1.0;
261     messageHelpButton.enabled = YES;
262     [messageView addSubview:messageHelpButton];
263   } else {
264     [messageHelpButton removeFromSuperview];
265     messageHelpButton.alpha = 0.0;
266     messageHelpButton.enabled = NO;
267
268     [self.messageTextView setFrame:messageViewBounds];
269   }
270 }
271
272 // DecoderDelegate methods
273
274 - (void)decoder:(Decoder *)decoder willDecodeImage:(UIImage *)image {
275   [self clearImageView];
276   [self.imageView setImage:image];
277   [self showMessage:[NSString stringWithFormat:NSLocalizedString(@"DecoderViewController MessageWhileDecodingWithDimensions", @"Decoding image (%.0fx%.0f) ..."), image.size.width, image.size.height]
278      helpButton:NO];
279 }
280
281 - (void)decoder:(Decoder *)decoder 
282   decodingImage:(UIImage *)image 
283     usingSubset:(UIImage *)subset
284        progress:(NSString *)message {
285   [self clearImageView];
286   [self.imageView setImage:subset];
287   [self showMessage:message helpButton:NO];
288 }
289
290 - (void)presentResultForString:(NSString *)resultString {
291   self.result = [ResultParser parsedResultForString:resultString];
292   [self showMessage:[self.result stringForDisplay] helpButton:NO];
293   self.actions = self.result.actions;
294 #ifdef DEBUG
295   NSLog(@"result has %d actions", actions ? 0 : actions.count);
296 #endif
297   [self updateToolbar];
298
299
300 - (void)presentResultPoints:(NSArray *)resultPoints 
301                    forImage:(UIImage *)image
302                 usingSubset:(UIImage *)subset {
303   // simply add the points to the image view
304   imageView.image = subset;
305   for (NSValue *pointValue in resultPoints) {
306     [imageView addResultPoint:[pointValue CGPointValue]];
307   }
308 }
309
310 - (void)decoder:(Decoder *)decoder didDecodeImage:(UIImage *)image usingSubset:(UIImage *)subset withResult:(TwoDDecoderResult *)twoDResult {
311   [self presentResultForString:twoDResult.text];
312   
313   [self presentResultPoints:twoDResult.points forImage:image usingSubset:subset];
314   
315   // save the scan to the shared database
316   [[Database sharedDatabase] addScanWithText:twoDResult.text];
317   
318   [self performResultAction:self];
319 }
320
321 - (void)decoder:(Decoder *)decoder failedToDecodeImage:(UIImage *)image usingSubset:(UIImage *)subset reason:(NSString *)reason {
322   [self showMessage:reason helpButton:YES];
323   [self updateToolbar];
324 }
325
326
327 - (void)willAnimateFirstHalfOfRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
328   [super willAnimateFirstHalfOfRotationToInterfaceOrientation:toInterfaceOrientation duration:duration];
329   
330   if (imageView.image) {
331     /*
332     CGRect viewBounds = imageView.bounds;
333     CGSize imageSize = imageView.image.size;
334     float scale = fmin(viewBounds.size.width / imageSize.width,
335                        viewBounds.size.height / imageSize.height);
336     float xOffset = (viewBounds.size.width - scale * imageSize.width) / 2.0;
337     float yOffset = (viewBounds.size.height - scale * imageSize.height) / 2.0;
338      */
339     
340     for (UIView *view in resultPointViews) {
341       view.alpha = 0.0;
342     }
343   }  
344 }
345
346 - (void)willAnimateSecondHalfOfRotationFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation duration:(NSTimeInterval)duration {
347   [super willAnimateSecondHalfOfRotationFromInterfaceOrientation:fromInterfaceOrientation duration:duration];
348
349   if (imageView.image) {
350     /*
351     CGRect viewBounds = imageView.bounds;
352     CGSize imageSize = imageView.image.size;
353     float scale = fmin(viewBounds.size.width / imageSize.width,
354                        viewBounds.size.height / imageSize.height);
355     float xOffset = (viewBounds.size.width - scale * imageSize.width) / 2.0;
356     float yOffset = (viewBounds.size.height - scale * imageSize.height) / 2.0;
357      */
358     
359     for (UIView *view in resultPointViews) {
360       view.alpha = 1.0;
361     }
362   }  
363 }
364
365 // UIImagePickerControllerDelegate methods
366
367 - (void)imagePickerController:(UIImagePickerController *)picker
368         didFinishPickingImage:(UIImage *)image
369                   editingInfo:(NSDictionary *)editingInfo
370 {
371   UIImage *imageToDecode = image;
372 #ifdef DEBUG
373   NSLog(@"picked image size = (%f, %f)", image.size.width, image.size.height);
374 #endif
375   if (editingInfo) {
376     UIImage *originalImage = [editingInfo objectForKey:UIImagePickerControllerOriginalImage];
377     if (originalImage) {
378 #ifdef DEBUG
379       NSLog(@"original image size = (%f, %f)", originalImage.size.width, originalImage.size.height);
380 #endif
381       NSValue *cropRectValue = [editingInfo objectForKey:UIImagePickerControllerCropRect];
382       if (cropRectValue) {
383         CGRect cropRect = [cropRectValue CGRectValue];
384 #ifdef DEBUG
385         NSLog(@"crop rect = (%f, %f) x (%f, %f)", CGRectGetMinX(cropRect), CGRectGetMinY(cropRect), CGRectGetWidth(cropRect), CGRectGetHeight(cropRect));
386 #endif
387         UIGraphicsBeginImageContext(cropRect.size);
388         
389         [originalImage drawAtPoint:CGPointMake(-CGRectGetMinX(cropRect),
390                                                -CGRectGetMinY(cropRect))];
391         
392         imageToDecode = UIGraphicsGetImageFromCurrentImageContext();
393         UIGraphicsEndImageContext();
394       }
395     }
396   }
397   
398   [[picker parentViewController] dismissModalViewControllerAnimated:YES];
399   [imageToDecode retain];
400   [picker release];
401   [self.decoder decodeImage:imageToDecode];
402   [imageToDecode release];
403   [self updateToolbar];
404 }
405
406 - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker
407 {
408   [picker dismissModalViewControllerAnimated:YES];
409   [picker release];
410   [self updateToolbar];
411 }
412
413 - (void)navigationController:(UINavigationController *)navigationController 
414        didShowViewController:(UIViewController *)viewController 
415                     animated:(BOOL)animated {
416   // no-op
417 }
418
419 - (void)navigationController:(UINavigationController *)navigationController 
420       willShowViewController:(UIViewController *)viewController 
421                     animated:(BOOL)animated {
422   // no-op
423 }
424
425 - (void)performAction:(ResultAction *)action {
426   [action performActionWithController:self shouldConfirm:NO];
427 }
428
429 - (void)confirmAndPerformAction:(ResultAction *)action {
430   [action performActionWithController:self shouldConfirm:YES];
431 }
432
433
434 - (IBAction)performResultAction:(id)sender {
435   if (self.result == nil) {
436     NSLog(@"no result to perform an action on!");
437     return;
438   }
439   
440   if (self.actions == nil || self.actions.count == 0) {
441     NSLog(@"result has no actions to perform!");
442     return;
443   }
444   
445   if (self.actions.count == 1) {
446     ResultAction *action = [self.actions lastObject];
447 #ifdef DEBUG
448     NSLog(@"Result has the single action, (%@)  '%@', performing it",
449           NSStringFromClass([action class]), [action title]);
450 #endif
451     [self performSelector:@selector(confirmAndPerformAction:) 
452                  withObject:action 
453                  afterDelay:0.0];
454   } else {
455 #ifdef DEBUG
456     NSLog(@"Result has multiple actions, popping up an action sheet");
457 #endif
458     UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithFrame:self.view.bounds];
459         
460     for (ResultAction *action in self.actions) {
461       [actionSheet addButtonWithTitle:[action title]];
462     }
463     
464     int cancelIndex = [actionSheet addButtonWithTitle:NSLocalizedString(@"DecoderViewController cancel button title", @"Cancel")];
465     actionSheet.cancelButtonIndex = cancelIndex;
466     
467     actionSheet.delegate = self;
468     
469     [actionSheet showFromToolbar:self.toolbar];
470   }
471 }
472
473 - (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
474   if (buttonIndex < self.actions.count) {
475     int actionIndex = buttonIndex;
476     ResultAction *action = [self.actions objectAtIndex:actionIndex];
477     [self performSelector:@selector(performAction:) 
478                  withObject:action 
479                  afterDelay:0.0];
480   }
481 }
482
483 - (IBAction)showArchive:(id)sender {
484   ArchiveController *archiveController = [[ArchiveController alloc] initWithDecoderViewController:self];
485   [[self navigationController] pushViewController:archiveController animated:true];
486   [archiveController release];
487 }
488
489 - (void)showScan:(Scan *)scan {
490   [self clearImageView];
491   [self presentResultForString:scan.text];
492   [[self navigationController] popToViewController:self animated:YES];
493 }
494
495 @end