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