2 // DecoderViewController.m
5 // Created by Christian Brunschen on 22/05/2008.
7 * Copyright 2008 ZXing authors
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
13 * http://www.apache.org/licenses/LICENSE-2.0
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.
22 #import "DecoderViewController.h"
24 #import "NSString+HTML.h"
25 #import "ResultParser.h"
26 #import "ParsedResult.h"
27 #import "ResultAction.h"
30 #import "ArchiveController.h"
31 #import "MessageViewController.h"
33 #import "TwoDDecoderResult.h"
35 @implementation DecoderViewController
37 @synthesize cameraBarItem;
38 @synthesize libraryBarItem;
39 @synthesize savedPhotosBarItem;
40 @synthesize archiveBarItem;
41 @synthesize actionBarItem;
43 @synthesize messageView;
44 @synthesize messageTextView;
45 @synthesize messageHelpButton;
46 @synthesize imageView;
53 @synthesize resultPointViews;
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");
60 Decoder *d = [[Decoder alloc] init];
64 resultPointViews = [[NSMutableArray alloc] init];
69 - (void) messageReady:(id)sender {
70 MessageViewController *messageController = sender;
71 [[self navigationController] pushViewController:messageController animated:true];
72 [messageController release];
75 - (void) messageFailed:(id)sender {
76 MessageViewController *messageController = sender;
77 NSLog(@"Failed to load message!");
78 [messageController release];
81 - (void) showHints:(id)sender {
82 NSLog(@"Showing Hints!");
84 MessageViewController *hintsController =
85 [[MessageViewController alloc] initWithMessageFilename:@"Hints"
87 onSuccess:@selector(messageReady:)
88 onFailure:@selector(messageFailed:)];
89 hintsController.title = NSLocalizedString(@"DecoderViewController Hints MessageViewController title", @"Hints");
93 - (void) showAbout:(id)sender {
94 NSLog(@"Showing About!");
96 MessageViewController *aboutController =
97 [[MessageViewController alloc] initWithMessageFilename:@"About"
99 onSuccess:@selector(messageReady:)
100 onFailure:@selector(messageFailed:)];
101 aboutController.title = NSLocalizedString(@"DecoderViewController About MessageViewController title", @"About");
102 aboutController.view;
106 #define HELP_BUTTON_WIDTH (44.0)
107 #define HELP_BUTTON_HEIGHT (55.0)
110 #define FONT_NAME @"TimesNewRomanPSMT"
111 #define FONT_SIZE 16.0
115 [self clearImageView];
116 [self updateToolbar];
117 [self showMessage:NSLocalizedString(@"DecoderViewController take or choose picture", @"Please take or choose a picture containing a barcode") helpButton:YES];
120 // Implement loadView if you want to create a view hierarchy programmatically
124 CGRect messageViewFrame = imageView.frame;
125 UIView *mView = [[UIView alloc] initWithFrame:messageViewFrame];
126 mView.backgroundColor = [UIColor darkGrayColor];
128 mView.autoresizingMask = UIViewAutoresizingFlexibleHeight |
129 UIViewAutoresizingFlexibleWidth |
130 UIViewAutoresizingFlexibleTopMargin;
132 UITextView *mTextView = [[UITextView alloc] initWithFrame:messageViewFrame];
133 mTextView.autoresizingMask = UIViewAutoresizingFlexibleHeight |
134 UIViewAutoresizingFlexibleWidth;
135 mTextView.editable = false;
136 mTextView.scrollEnabled = true;
137 mTextView.font = [UIFont fontWithName:FONT_NAME size:FONT_SIZE];
138 mTextView.textColor = [UIColor whiteColor];
139 mTextView.backgroundColor = [[UIColor lightGrayColor] colorWithAlphaComponent:0.0];
140 mTextView.textAlignment = UITextAlignmentLeft;
141 mTextView.alpha = 1.0;
142 [mView addSubview:mTextView];
144 UIButton *mHelpButton = [[UIButton buttonWithType:UIButtonTypeInfoLight] retain];
145 mHelpButton.frame = CGRectMake(messageViewFrame.size.width - HELP_BUTTON_WIDTH, 0.0, HELP_BUTTON_WIDTH, HELP_BUTTON_HEIGHT);
147 mHelpButton.backgroundColor = [UIColor clearColor];
148 [mHelpButton setUserInteractionEnabled:YES];
149 [mHelpButton addTarget:self action:@selector(showHints:) forControlEvents:UIControlEventTouchUpInside];
151 self.messageHelpButton = mHelpButton;
152 [mHelpButton release];
154 self.messageTextView = mTextView;
157 self.messageView = mView;
160 [self.view addSubview:self.messageView];
162 // add the 'About' button at the top-right of the navigation bar
163 UIBarButtonItem *aboutButton =
164 [[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"DecoderViewController about button title", @"About")
165 style:UIBarButtonItemStyleBordered
167 action:@selector(showAbout:)];
168 self.navigationItem.rightBarButtonItem = aboutButton;
169 [aboutButton release];
174 - (void) updateToolbar {
175 self.cameraBarItem.enabled = [UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera];
176 self.savedPhotosBarItem.enabled = [UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeSavedPhotosAlbum];
177 self.libraryBarItem.enabled = [UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary];
178 self.archiveBarItem.enabled = true;
179 self.actionBarItem.enabled = (self.result != nil) && ([self.result actions] != nil) && ([self.result actions].count > 0);
183 // If you need to do additional setup after loading the view, override viewDidLoad.
184 - (void)viewDidLoad {
189 - (void)clearImageView {
190 imageView.image = nil;
191 for (UIView *view in resultPointViews) {
192 [view removeFromSuperview];
194 [resultPointViews removeAllObjects];
197 - (void)pickAndDecodeFromSource:(UIImagePickerControllerSourceType) sourceType {
200 // Create the Image Picker
201 if ([UIImagePickerController isSourceTypeAvailable:sourceType]) {
202 UIImagePickerController* picker = [[UIImagePickerController alloc] init];
203 picker.sourceType = sourceType;
204 picker.delegate = self;
205 picker.allowsImageEditing = YES; // [[NSUserDefaults standardUserDefaults] boolForKey:@"allowEditing"];
207 // Picker is displayed asynchronously.
208 [self presentModalViewController:picker animated:YES];
210 NSLog(@"Attempted to pick an image with illegal source type '%d'", sourceType);
214 - (IBAction)pickAndDecode:(id) sender {
215 UIImagePickerControllerSourceType sourceType;
216 int i = [sender tag];
219 case 0: sourceType = UIImagePickerControllerSourceTypeCamera; break;
220 case 1: sourceType = UIImagePickerControllerSourceTypeSavedPhotosAlbum; break;
221 case 2: sourceType = UIImagePickerControllerSourceTypePhotoLibrary; break;
222 default: sourceType = UIImagePickerControllerSourceTypeSavedPhotosAlbum;
224 [self pickAndDecodeFromSource:sourceType];
228 - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
229 // Return YES for supported orientations
230 return (interfaceOrientation == UIInterfaceOrientationPortrait);
234 - (void)didReceiveMemoryWarning {
235 [super didReceiveMemoryWarning]; // Releases the view if it doesn't have a superview
236 // Release anything that's not essential, such as cached data
241 [self clearImageView];
243 [actionBarItem release];
244 [cameraBarItem release];
245 [libraryBarItem release];
246 [savedPhotosBarItem release];
247 [archiveBarItem release];
250 [resultPointViews dealloc];
255 - (void)showMessage:(NSString *)message helpButton:(BOOL)showHelpButton {
257 NSLog(@"Showing message '%@' %@ help Button", message, showHelpButton ? @"with" : @"without");
260 CGSize maxSize = imageView.bounds.size;
261 if (showHelpButton) {
262 maxSize.width -= messageHelpButton.frame.size.width;
264 CGSize size = [message sizeWithFont:messageTextView.font constrainedToSize:maxSize lineBreakMode:UILineBreakModeWordWrap];
265 float height = 20.0 + fmin(100.0, size.height);
266 if (showHelpButton) {
267 height = fmax(HELP_BUTTON_HEIGHT, height);
270 CGRect messageFrame = imageView.bounds;
271 messageFrame.origin.y = CGRectGetMaxY(messageFrame) - height;
272 messageFrame.size.height = height;
273 [self.messageView setFrame:messageFrame];
274 messageView.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleWidth;
275 CGRect messageViewBounds = [messageView bounds];
277 self.messageTextView.text = message;
278 messageTextView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
279 if (showHelpButton) {
280 CGRect textViewFrame;
281 CGRect helpButtonFrame;
283 CGRectDivide(messageViewBounds, &helpButtonFrame, &textViewFrame, HELP_BUTTON_WIDTH, CGRectMaxXEdge);
284 [self.messageTextView setFrame:textViewFrame];
286 [messageHelpButton setFrame:helpButtonFrame];
287 messageHelpButton.alpha = 1.0;
288 messageHelpButton.enabled = YES;
289 messageHelpButton.autoresizingMask =
290 UIViewAutoresizingFlexibleLeftMargin |
291 UIViewAutoresizingFlexibleTopMargin;
292 [messageView addSubview:messageHelpButton];
294 [messageHelpButton removeFromSuperview];
295 messageHelpButton.alpha = 0.0;
296 messageHelpButton.enabled = NO;
298 [self.messageTextView setFrame:messageViewBounds];
302 // DecoderDelegate methods
304 - (void)decoder:(Decoder *)decoder willDecodeImage:(UIImage *)image usingSubset:(UIImage *)subset{
305 [self clearImageView];
306 [self.imageView setImage:subset];
307 [self showMessage:[NSString stringWithFormat:NSLocalizedString(@"DecoderViewController MessageWhileDecodingWithDimensions", @"Decoding image (%.0fx%.0f) ..."), image.size.width, image.size.height]
311 - (void)decoder:(Decoder *)decoder
312 decodingImage:(UIImage *)image
313 usingSubset:(UIImage *)subset
314 progress:(NSString *)message {
315 [self clearImageView];
316 [self.imageView setImage:subset];
317 [self showMessage:message helpButton:NO];
320 - (void)presentResultForString:(NSString *)resultString {
321 self.result = [ResultParser parsedResultForString:resultString];
322 [self showMessage:[self.result stringForDisplay] helpButton:NO];
323 self.actions = self.result.actions;
325 NSLog(@"result has %d actions", actions ? 0 : actions.count);
327 [self updateToolbar];
330 - (void)presentResultPoints:(NSArray *)resultPoints
331 forImage:(UIImage *)image
332 usingSubset:(UIImage *)subset {
333 // simply add the points to the image view
334 imageView.image = subset;
335 for (NSValue *pointValue in resultPoints) {
336 [imageView addResultPoint:[pointValue CGPointValue]];
340 - (void)decoder:(Decoder *)decoder didDecodeImage:(UIImage *)image usingSubset:(UIImage *)subset withResult:(TwoDDecoderResult *)twoDResult {
341 [self presentResultForString:twoDResult.text];
343 [self presentResultPoints:twoDResult.points forImage:image usingSubset:subset];
345 // save the scan to the shared database
346 [[Database sharedDatabase] addScanWithText:twoDResult.text];
348 [self performResultAction:self];
351 - (void)decoder:(Decoder *)decoder failedToDecodeImage:(UIImage *)image usingSubset:(UIImage *)subset reason:(NSString *)reason {
352 [self showMessage:reason helpButton:YES];
353 [self updateToolbar];
357 - (void)willAnimateFirstHalfOfRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
358 [super willAnimateFirstHalfOfRotationToInterfaceOrientation:toInterfaceOrientation duration:duration];
360 if (imageView.image) {
362 CGRect viewBounds = imageView.bounds;
363 CGSize imageSize = imageView.image.size;
364 float scale = fmin(viewBounds.size.width / imageSize.width,
365 viewBounds.size.height / imageSize.height);
366 float xOffset = (viewBounds.size.width - scale * imageSize.width) / 2.0;
367 float yOffset = (viewBounds.size.height - scale * imageSize.height) / 2.0;
370 for (UIView *view in resultPointViews) {
376 - (void)willAnimateSecondHalfOfRotationFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation duration:(NSTimeInterval)duration {
377 [super willAnimateSecondHalfOfRotationFromInterfaceOrientation:fromInterfaceOrientation duration:duration];
379 if (imageView.image) {
381 CGRect viewBounds = imageView.bounds;
382 CGSize imageSize = imageView.image.size;
383 float scale = fmin(viewBounds.size.width / imageSize.width,
384 viewBounds.size.height / imageSize.height);
385 float xOffset = (viewBounds.size.width - scale * imageSize.width) / 2.0;
386 float yOffset = (viewBounds.size.height - scale * imageSize.height) / 2.0;
389 for (UIView *view in resultPointViews) {
395 // UIImagePickerControllerDelegate methods
397 - (void)imagePickerController:(UIImagePickerController *)picker
398 didFinishPickingImage:(UIImage *)image
399 editingInfo:(NSDictionary *)editingInfo
401 UIImage *imageToDecode = image;
402 CGSize size = [image size];
403 CGRect cropRect = CGRectMake(0.0, 0.0, size.width, size.height);
406 NSLog(@"picked image size = (%f, %f)", size.width, size.height);
408 NSString *systemVersion = [[UIDevice currentDevice] systemVersion];
411 UIImage *originalImage = [editingInfo objectForKey:UIImagePickerControllerOriginalImage];
414 NSLog(@"original image size = (%f, %f)", originalImage.size.width, originalImage.size.height);
416 NSValue *cropRectValue = [editingInfo objectForKey:UIImagePickerControllerCropRect];
418 cropRect = [cropRectValue CGRectValue];
420 NSLog(@"crop rect = (%f, %f) x (%f, %f)", CGRectGetMinX(cropRect), CGRectGetMinY(cropRect), CGRectGetWidth(cropRect), CGRectGetHeight(cropRect));
422 if (([picker sourceType] == UIImagePickerControllerSourceTypeSavedPhotosAlbum) &&
423 [@"2.1" isEqualToString:systemVersion]) {
424 // adjust crop rect to work around bug in iPhone OS 2.1 when selecting from the photo roll
425 cropRect.origin.x *= 2.5;
426 cropRect.origin.y *= 2.5;
427 cropRect.size.width *= 2.5;
428 cropRect.size.height *= 2.5;
430 NSLog(@"2.1-adjusted crop rect = (%f, %f) x (%f, %f)", CGRectGetMinX(cropRect), CGRectGetMinY(cropRect), CGRectGetWidth(cropRect), CGRectGetHeight(cropRect));
434 imageToDecode = originalImage;
439 [[picker parentViewController] dismissModalViewControllerAnimated:YES];
440 [imageToDecode retain];
442 [self.decoder decodeImage:imageToDecode cropRect:cropRect];
443 [imageToDecode release];
444 [self updateToolbar];
447 - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker
449 [picker dismissModalViewControllerAnimated:YES];
451 [self updateToolbar];
454 - (void)navigationController:(UINavigationController *)navigationController
455 didShowViewController:(UIViewController *)viewController
456 animated:(BOOL)animated {
460 - (void)navigationController:(UINavigationController *)navigationController
461 willShowViewController:(UIViewController *)viewController
462 animated:(BOOL)animated {
466 - (void)performAction:(ResultAction *)action {
467 [action performActionWithController:self shouldConfirm:NO];
470 - (void)confirmAndPerformAction:(ResultAction *)action {
471 [action performActionWithController:self shouldConfirm:YES];
475 - (IBAction)performResultAction:(id)sender {
476 if (self.result == nil) {
477 NSLog(@"no result to perform an action on!");
481 if (self.actions == nil || self.actions.count == 0) {
482 NSLog(@"result has no actions to perform!");
486 if (self.actions.count == 1) {
487 ResultAction *action = [self.actions lastObject];
489 NSLog(@"Result has the single action, (%@) '%@', performing it",
490 NSStringFromClass([action class]), [action title]);
492 [self performSelector:@selector(confirmAndPerformAction:)
497 NSLog(@"Result has multiple actions, popping up an action sheet");
499 UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithFrame:self.view.bounds];
501 for (ResultAction *action in self.actions) {
502 [actionSheet addButtonWithTitle:[action title]];
505 int cancelIndex = [actionSheet addButtonWithTitle:NSLocalizedString(@"DecoderViewController cancel button title", @"Cancel")];
506 actionSheet.cancelButtonIndex = cancelIndex;
508 actionSheet.delegate = self;
510 [actionSheet showFromToolbar:self.toolbar];
514 - (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
515 if (buttonIndex < self.actions.count) {
516 int actionIndex = buttonIndex;
517 ResultAction *action = [self.actions objectAtIndex:actionIndex];
518 [self performSelector:@selector(performAction:)
524 - (IBAction)showArchive:(id)sender {
525 ArchiveController *archiveController = [[ArchiveController alloc] initWithDecoderViewController:self];
526 [[self navigationController] pushViewController:archiveController animated:true];
527 [archiveController release];
530 - (void)showScan:(Scan *)scan {
531 [self clearImageView];
532 [self presentResultForString:scan.text];
533 [[self navigationController] popToViewController:self animated:YES];