UI improvements
[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 "Scan.h"
32 #import "TwoDDecoderResult.h"
33
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 imageView;
45 @synthesize toolbar;
46
47 @synthesize decoder;
48 @synthesize result;
49 @synthesize actions;
50
51 @synthesize resultPointViews;
52
53 - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
54         if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
55                 // Initialization code
56     self.title = @"ZXing";
57     
58     Decoder *d = [[Decoder alloc] init];
59     self.decoder = d;
60     d.delegate = self;
61     [d release];
62     resultPointViews = [[NSMutableArray alloc] init];
63         }
64         return self;
65 }
66
67 #define FONT_NAME @"TimesNewRomanPSMT"
68 #define FONT_SIZE 16.0
69
70 // Implement loadView if you want to create a view hierarchy programmatically
71 - (void)loadView {
72   [super loadView];
73   
74   CGRect messageViewFrame = imageView.frame;
75   UITextView *mView = [[UITextView alloc] initWithFrame:messageViewFrame];
76   mView.backgroundColor = [UIColor darkGrayColor];
77   mView.alpha = 0.9;
78   mView.editable = false;
79   mView.scrollEnabled = true;
80   mView.font = [UIFont fontWithName:FONT_NAME size:FONT_SIZE];
81   mView.autoresizingMask = UIViewAutoresizingFlexibleHeight | 
82                            UIViewAutoresizingFlexibleWidth |
83                            UIViewAutoresizingFlexibleTopMargin;
84   mView.textColor = [UIColor whiteColor];
85   mView.textAlignment = UITextAlignmentLeft;
86   self.messageView = mView;
87   [mView release];
88   
89   [self.view addSubview:self.messageView];
90   [self updateToolbar];
91   [self showMessage:NSLocalizedString(@"Please take or choose a picture containing a barcode", @"")];
92 }
93
94 - (void) updateToolbar {
95   self.cameraBarItem.enabled = [UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera];
96   self.savedPhotosBarItem.enabled = [UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeSavedPhotosAlbum];
97   self.libraryBarItem.enabled = [UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary];
98   self.archiveBarItem.enabled = true;
99   self.actionBarItem.enabled = (self.result != nil) && ([self.result actions] != nil) && ([self.result actions].count > 0);
100 }
101
102
103 // If you need to do additional setup after loading the view, override viewDidLoad.
104 - (void)viewDidLoad {
105   [super viewDidLoad];
106 }
107
108
109 - (void)clearImageView {
110   imageView.image = nil;
111   for (UIView *view in resultPointViews) {
112     [view removeFromSuperview];
113   }
114   [resultPointViews removeAllObjects];
115 }
116
117 - (void)pickAndDecodeFromSource:(UIImagePickerControllerSourceType) sourceType {
118   self.result = nil;
119   [self clearImageView];
120   // Create the Image Picker
121   if ([UIImagePickerController isSourceTypeAvailable:sourceType]) {
122     UIImagePickerController* picker = [[UIImagePickerController alloc] init];
123     picker.sourceType = sourceType;
124     picker.delegate = self;
125     picker.allowsImageEditing = YES; // [[NSUserDefaults standardUserDefaults] boolForKey:@"allowEditing"];
126     
127     // Picker is displayed asynchronously.
128     [self presentModalViewController:picker animated:YES];
129   } else {
130     NSLog(@"Attempted to pick an image with illegal source type '%d'", sourceType);
131   }
132 }
133
134 - (IBAction)pickAndDecode:(id) sender {
135   UIImagePickerControllerSourceType sourceType;
136   int i = [sender tag];
137   
138   switch (i) {
139     case 0: sourceType = UIImagePickerControllerSourceTypeCamera; break;
140     case 1: sourceType = UIImagePickerControllerSourceTypeSavedPhotosAlbum; break;
141     case 2: sourceType = UIImagePickerControllerSourceTypePhotoLibrary; break;
142     default: sourceType = UIImagePickerControllerSourceTypeSavedPhotosAlbum;
143   }
144   [self pickAndDecodeFromSource:sourceType];
145 }
146
147
148 - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
149         // Return YES for supported orientations
150         return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
151 }
152
153
154 - (void)didReceiveMemoryWarning {
155         [super didReceiveMemoryWarning]; // Releases the view if it doesn't have a superview
156         // Release anything that's not essential, such as cached data
157 }
158
159 - (void)dealloc {
160   [decoder release];
161   [self clearImageView];
162   [imageView release];
163   [actionBarItem release];
164   [cameraBarItem release];
165   [libraryBarItem release];
166   [savedPhotosBarItem release];
167   [archiveBarItem release];
168   [toolbar release];
169   [actions dealloc];
170   [resultPointViews dealloc];
171   
172         [super dealloc];
173 }
174
175 - (void)showMessage:(NSString *)message {
176 #ifdef DEBUG
177   NSLog(@"Showing message '%@'", message);
178 #endif
179   
180   CGSize maxSize = imageView.bounds.size;
181   CGSize size = [message sizeWithFont:messageView.font constrainedToSize:maxSize lineBreakMode:UILineBreakModeWordWrap];
182   float height = 20.0 + fmin(100.0, size.height);
183
184   CGRect messageFrame = imageView.bounds;
185   messageFrame.origin.y = CGRectGetMaxY(messageFrame) - height;
186   messageFrame.size.height = height;
187   self.messageView.text = message;
188   [self.messageView setFrame:messageFrame];
189 }
190
191 // DecoderDelegate methods
192
193 - (void)decoder:(Decoder *)decoder willDecodeImage:(UIImage *)image {
194   [self clearImageView];
195   [self.imageView setImage:image];
196   [self showMessage:[NSString stringWithFormat:NSLocalizedString(@"Decoding image (%.0fx%.0f) ...", @"shown while image is decoding"), image.size.width, image.size.height]];
197 }
198
199 - (void)decoder:(Decoder *)decoder 
200   decodingImage:(UIImage *)image 
201     usingSubset:(UIImage *)subset
202        progress:(NSString *)message {
203   [self clearImageView];
204   [self.imageView setImage:subset];
205   [self showMessage:message];
206 }
207
208 - (void)presentResultForString:(NSString *)resultString {
209   self.result = [ResultParser parsedResultForString:resultString];
210   [self showMessage:[self.result stringForDisplay]];
211   self.actions = self.result.actions;
212 #ifdef DEBUG
213   NSLog(@"result has %d actions", actions ? 0 : actions.count);
214 #endif
215   [self updateToolbar];
216
217
218 - (void)presentResultPoints:(NSArray *)resultPoints 
219                    forImage:(UIImage *)image
220                 usingSubset:(UIImage *)subset {
221   // simply add the points to the image view
222   imageView.image = subset;
223   for (NSValue *pointValue in resultPoints) {
224     [imageView addResultPoint:[pointValue CGPointValue]];
225   }
226 }
227
228 - (void)decoder:(Decoder *)decoder didDecodeImage:(UIImage *)image usingSubset:(UIImage *)subset withResult:(TwoDDecoderResult *)twoDResult {
229   [self presentResultForString:twoDResult.text];
230   
231   [self presentResultPoints:twoDResult.points forImage:image usingSubset:subset];
232   
233   // save the scan to the shared database
234   [[Database sharedDatabase] addScanWithText:twoDResult.text];
235   
236   [self performResultAction:self];
237 }
238
239 - (void)decoder:(Decoder *)decoder failedToDecodeImage:(UIImage *)image usingSubset:(UIImage *)subset reason:(NSString *)reason {
240   [self showMessage:reason];
241   [self updateToolbar];
242 }
243
244
245 - (void)willAnimateFirstHalfOfRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
246   [super willAnimateFirstHalfOfRotationToInterfaceOrientation:toInterfaceOrientation duration:duration];
247   
248   if (imageView.image) {
249     /*
250     CGRect viewBounds = imageView.bounds;
251     CGSize imageSize = imageView.image.size;
252     float scale = fmin(viewBounds.size.width / imageSize.width,
253                        viewBounds.size.height / imageSize.height);
254     float xOffset = (viewBounds.size.width - scale * imageSize.width) / 2.0;
255     float yOffset = (viewBounds.size.height - scale * imageSize.height) / 2.0;
256      */
257     
258     for (UIView *view in resultPointViews) {
259       view.alpha = 0.0;
260     }
261   }  
262 }
263
264 - (void)willAnimateSecondHalfOfRotationFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation duration:(NSTimeInterval)duration {
265   [super willAnimateSecondHalfOfRotationFromInterfaceOrientation:fromInterfaceOrientation duration:duration];
266
267   if (imageView.image) {
268     /*
269     CGRect viewBounds = imageView.bounds;
270     CGSize imageSize = imageView.image.size;
271     float scale = fmin(viewBounds.size.width / imageSize.width,
272                        viewBounds.size.height / imageSize.height);
273     float xOffset = (viewBounds.size.width - scale * imageSize.width) / 2.0;
274     float yOffset = (viewBounds.size.height - scale * imageSize.height) / 2.0;
275      */
276     
277     for (UIView *view in resultPointViews) {
278       view.alpha = 1.0;
279     }
280   }  
281 }
282
283 // UIImagePickerControllerDelegate methods
284
285 - (void)imagePickerController:(UIImagePickerController *)picker
286         didFinishPickingImage:(UIImage *)image
287                   editingInfo:(NSDictionary *)editingInfo
288 {
289   UIImage *imageToDecode = image;
290 #ifdef DEBUG
291   NSLog(@"picked image size = (%f, %f)", image.size.width, image.size.height);
292 #endif
293   if (editingInfo) {
294     UIImage *originalImage = [editingInfo objectForKey:UIImagePickerControllerOriginalImage];
295     if (originalImage) {
296 #ifdef DEBUG
297       NSLog(@"original image size = (%f, %f)", originalImage.size.width, originalImage.size.height);
298 #endif
299       NSValue *cropRectValue = [editingInfo objectForKey:UIImagePickerControllerCropRect];
300       if (cropRectValue) {
301         CGRect cropRect = [cropRectValue CGRectValue];
302 #ifdef DEBUG
303         NSLog(@"crop rect = (%f, %f) x (%f, %f)", CGRectGetMinX(cropRect), CGRectGetMinY(cropRect), CGRectGetWidth(cropRect), CGRectGetHeight(cropRect));
304 #endif
305         UIGraphicsBeginImageContext(cropRect.size);
306         
307         [originalImage drawAtPoint:CGPointMake(-CGRectGetMinX(cropRect),
308                                                -CGRectGetMinY(cropRect))];
309         
310         imageToDecode = UIGraphicsGetImageFromCurrentImageContext();
311         UIGraphicsEndImageContext();
312       }
313     }
314   }
315   
316   [[picker parentViewController] dismissModalViewControllerAnimated:YES];
317   [imageToDecode retain];
318   [picker release];
319   [self.decoder decodeImage:imageToDecode];
320   [imageToDecode release];
321   [self updateToolbar];
322 }
323
324 - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker
325 {
326   [picker dismissModalViewControllerAnimated:YES];
327   [picker release];
328   [self updateToolbar];
329 }
330
331 - (void)navigationController:(UINavigationController *)navigationController 
332        didShowViewController:(UIViewController *)viewController 
333                     animated:(BOOL)animated {
334   // no-op
335 }
336
337 - (void)navigationController:(UINavigationController *)navigationController 
338       willShowViewController:(UIViewController *)viewController 
339                     animated:(BOOL)animated {
340   // no-op
341 }
342
343 - (void)performAction:(ResultAction *)action {
344   [action performActionWithController:self shouldConfirm:NO];
345 }
346
347 - (void)confirmAndPerformAction:(ResultAction *)action {
348   [action performActionWithController:self shouldConfirm:YES];
349 }
350
351
352 - (IBAction)performResultAction:(id)sender {
353   if (self.result == nil) {
354     NSLog(@"no result to perform an action on!");
355     return;
356   }
357   
358   if (self.actions == nil || self.actions.count == 0) {
359     NSLog(@"result has no actions to perform!");
360     return;
361   }
362   
363   if (self.actions.count == 1) {
364     ResultAction *action = [self.actions lastObject];
365 #ifdef DEBUG
366     NSLog(@"Result has the single action, (%@)  '%@', performing it",
367           NSStringFromClass([action class]), [action title]);
368 #endif
369     [self performSelector:@selector(confirmAndPerformAction:) 
370                  withObject:action 
371                  afterDelay:0.0];
372   } else {
373 #ifdef DEBUG
374     NSLog(@"Result has multiple actions, popping up an action sheet");
375 #endif
376     UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithFrame:self.view.bounds];
377         
378     for (ResultAction *action in self.actions) {
379       [actionSheet addButtonWithTitle:[action title]];
380     }
381     
382     int cancelIndex = [actionSheet addButtonWithTitle:NSLocalizedString(@"Cancel", @"")];
383     actionSheet.cancelButtonIndex = cancelIndex;
384     
385     actionSheet.delegate = self;
386     
387     [actionSheet showFromToolbar:self.toolbar];
388   }
389 }
390
391 - (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
392   if (buttonIndex < self.actions.count) {
393     int actionIndex = buttonIndex;
394     ResultAction *action = [self.actions objectAtIndex:actionIndex];
395     [self performSelector:@selector(performAction:) 
396                  withObject:action 
397                  afterDelay:0.0];
398   }
399 }
400
401 - (IBAction)showArchive:(id)sender {
402   ArchiveController *archiveController = [[ArchiveController alloc] initWithDecoderViewController:self];
403   [[self navigationController] pushViewController:archiveController animated:true];
404   [archiveController release];
405 }
406
407 - (void)showScan:(Scan *)scan {
408   [self clearImageView];
409   [self presentResultForString:scan.text];
410   [[self navigationController] popToViewController:self animated:YES];
411 }
412
413 @end