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