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 // Michael Jurewitz, Dec 16, 2009 6:32 PM writes:
36 // https://devforums.apple.com/message/149553
37 // Notice Regarding UIGetScreenImage()
38 // After carefully considering the issue, Apple is now allowing applications to
39 // use the function UIGetScreenImage() to programmatically capture the current
41 // Note that a future release of iPhone OS may provide a public API equivalent
42 // of this functionality. At such time, all applications using
43 // UIGetScreenImage() will be required to adopt the public API.
44 CGImageRef MyCGImageCopyScreenContents(void) {
45 extern CGImageRef UIGetScreenImage(void);
46 return UIGetScreenImage(); /* already retained */
49 @interface DecoderViewController()
50 - (void)takeScreenshot;
53 @implementation DecoderViewController
55 @synthesize cameraBarItem;
56 @synthesize libraryBarItem;
57 @synthesize savedPhotosBarItem;
58 @synthesize archiveBarItem;
59 @synthesize actionBarItem;
61 @synthesize messageView;
62 @synthesize messageTextView;
63 @synthesize messageHelpButton;
64 @synthesize imageView;
72 @synthesize resultPointViews;
74 - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
75 if ((self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil])) {
76 // Initialization code
78 NSLocalizedString(@"DecoderViewController AppTitle", @"Barcode Scanner");
80 Decoder *d = [[Decoder alloc] init];
84 resultPointViews = [[NSMutableArray alloc] init];
89 - (void) messageReady:(id)sender {
90 MessageViewController *messageController = sender;
91 [[self navigationController] pushViewController:messageController animated:true];
92 [messageController release];
95 - (void) messageFailed:(id)sender {
96 MessageViewController *messageController = sender;
97 NSLog(@"Failed to load message!");
98 [messageController release];
101 - (void) showHints:(id)sender {
102 NSLog(@"Showing Hints!");
104 MessageViewController *hintsController =
105 [[MessageViewController alloc] initWithMessageFilename:@"Hints"
107 onSuccess:@selector(messageReady:)
108 onFailure:@selector(messageFailed:)];
109 hintsController.title =
110 NSLocalizedString(@"DecoderViewController Hints MessageViewController title", @"Hints");
111 [hintsController view];
114 - (void) showAbout:(id)sender {
115 NSLog(@"Showing About!");
117 MessageViewController *aboutController =
118 [[MessageViewController alloc] initWithMessageFilename:@"About"
120 onSuccess:@selector(messageReady:)
121 onFailure:@selector(messageFailed:)];
122 aboutController.title =
123 NSLocalizedString(@"DecoderViewController About MessageViewController title", @"About");
124 [aboutController view];
128 #define HELP_BUTTON_WIDTH (44.0)
129 #define HELP_BUTTON_HEIGHT (55.0)
132 #define FONT_NAME @"TimesNewRomanPSMT"
133 #define FONT_SIZE 16.0
137 [self clearImageView];
138 [self updateToolbar];
139 [self showMessage:NSLocalizedString(@"DecoderViewController take or choose picture",
140 @"Please take or choose a picture containing a barcode") helpButton:YES];
143 // Implement loadView if you want to create a view hierarchy programmatically
147 CGRect messageViewFrame = imageView.frame;
148 UIView *mView = [[UIView alloc] initWithFrame:messageViewFrame];
149 mView.backgroundColor = [UIColor darkGrayColor];
151 mView.autoresizingMask = UIViewAutoresizingFlexibleHeight |
152 UIViewAutoresizingFlexibleWidth |
153 UIViewAutoresizingFlexibleTopMargin;
155 UITextView *mTextView = [[UITextView alloc] initWithFrame:messageViewFrame];
156 mTextView.autoresizingMask = UIViewAutoresizingFlexibleHeight |
157 UIViewAutoresizingFlexibleWidth;
158 mTextView.editable = false;
159 mTextView.scrollEnabled = true;
160 mTextView.font = [UIFont fontWithName:FONT_NAME size:FONT_SIZE];
161 mTextView.textColor = [UIColor whiteColor];
162 mTextView.backgroundColor = [[UIColor lightGrayColor] colorWithAlphaComponent:0.0];
163 mTextView.textAlignment = UITextAlignmentLeft;
164 mTextView.alpha = 1.0;
165 [mView addSubview:mTextView];
167 UIButton *mHelpButton = [[UIButton buttonWithType:UIButtonTypeInfoLight] retain];
168 mHelpButton.frame = CGRectMake(messageViewFrame.size.width - HELP_BUTTON_WIDTH, 0.0, HELP_BUTTON_WIDTH, HELP_BUTTON_HEIGHT);
170 mHelpButton.backgroundColor = [UIColor clearColor];
171 [mHelpButton setUserInteractionEnabled:YES];
172 [mHelpButton addTarget:self action:@selector(showHints:) forControlEvents:UIControlEventTouchUpInside];
174 self.messageHelpButton = mHelpButton;
175 [mHelpButton release];
177 self.messageTextView = mTextView;
180 self.messageView = mView;
183 [self.view addSubview:self.messageView];
185 // add the 'About' button at the top-right of the navigation bar
186 UIBarButtonItem *aboutButton =
187 [[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"DecoderViewController about button title", @"About")
188 style:UIBarButtonItemStyleBordered
190 action:@selector(showAbout:)];
191 self.navigationItem.rightBarButtonItem = aboutButton;
192 [aboutButton release];
197 - (void) updateToolbar {
198 self.cameraBarItem.enabled = [UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera];
199 self.savedPhotosBarItem.enabled = [UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeSavedPhotosAlbum];
200 self.libraryBarItem.enabled = [UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary];
201 self.archiveBarItem.enabled = true;
202 self.actionBarItem.enabled = (self.result != nil) && ([self.result actions] != nil) && ([self.result actions].count > 0);
206 // If you need to do additional setup after loading the view, override viewDidLoad.
207 - (void)viewDidLoad {
212 - (void)clearImageView {
213 imageView.image = nil;
214 for (UIView *view in resultPointViews) {
215 [view removeFromSuperview];
217 [resultPointViews removeAllObjects];
220 - (void)pickAndDecodeFromSource:(UIImagePickerControllerSourceType) sourceType {
223 // Create the Image Picker
224 if ([UIImagePickerController isSourceTypeAvailable:sourceType]) {
225 UIImagePickerController* aPicker =
226 [[[UIImagePickerController alloc] init] autorelease];
227 aPicker.sourceType = sourceType;
228 aPicker.delegate = self;
229 self.picker = aPicker;
231 // [[NSUserDefaults standardUserDefaults] boolForKey:@"allowEditing"];
232 BOOL isCamera = (sourceType == UIImagePickerControllerSourceTypeCamera);
233 if ([picker respondsToSelector:@selector(setAllowsEditing:)]) {
235 [picker setAllowsEditing:!isCamera];
238 if ([picker respondsToSelector:@selector(setShowsCameraControls:)]) {
239 [picker setShowsCameraControls:NO];
240 UIButton *cancelButton =
241 [UIButton buttonWithType:UIButtonTypeRoundedRect];
242 NSString *cancelString =
243 NSLocalizedString(@"DecoderViewController cancel button title", @"");
244 CGFloat height = [UIFont systemFontSize];
246 [cancelString sizeWithFont:[UIFont systemFontOfSize:height]];
247 [cancelButton setTitle:cancelString forState:UIControlStateNormal];
248 CGRect appFrame = [[UIScreen mainScreen] bounds];
249 static const int kMargin = 10;
250 static const int kInternalXMargin = 10;
251 static const int kInternalYMargin = 10;
252 CGRect frame = CGRectMake(kMargin,
253 appFrame.size.height - (height + 2*kInternalYMargin + kMargin),
254 2*kInternalXMargin + size.width,
255 height + 2*kInternalYMargin);
256 [cancelButton setFrame:frame];
257 [cancelButton addTarget:self
258 action:@selector(cancel:)
259 forControlEvents:UIControlEventTouchUpInside];
260 picker.cameraOverlayView = cancelButton;
261 // The camera takes quite a while to start up. Hence the 2 second delay.
262 [self performSelector:@selector(takeScreenshot)
268 // Picker is displayed asynchronously.
269 [self presentModalViewController:picker animated:YES];
271 NSLog(@"Attempted to pick an image with illegal source type '%d'", sourceType);
275 - (IBAction)pickAndDecode:(id) sender {
276 UIImagePickerControllerSourceType sourceType;
277 int i = [sender tag];
280 case 0: sourceType = UIImagePickerControllerSourceTypeCamera; break;
281 case 1: sourceType = UIImagePickerControllerSourceTypeSavedPhotosAlbum; break;
282 case 2: sourceType = UIImagePickerControllerSourceTypePhotoLibrary; break;
283 default: sourceType = UIImagePickerControllerSourceTypeSavedPhotosAlbum;
285 [self pickAndDecodeFromSource:sourceType];
289 - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
290 // Return YES for supported orientations
291 return (interfaceOrientation == UIInterfaceOrientationPortrait);
295 - (void)didReceiveMemoryWarning {
296 [super didReceiveMemoryWarning]; // Releases the view if it doesn't have a superview
297 // Release anything that's not essential, such as cached data
302 [self clearImageView];
304 [actionBarItem release];
305 [cameraBarItem release];
306 [libraryBarItem release];
307 [savedPhotosBarItem release];
308 [archiveBarItem release];
312 [resultPointViews release];
317 - (void)showMessage:(NSString *)message helpButton:(BOOL)showHelpButton {
319 NSLog(@"Showing message '%@' %@ help Button", message, showHelpButton ? @"with" : @"without");
322 CGSize imageMaxSize = imageView.bounds.size;
323 if (showHelpButton) {
324 imageMaxSize.width -= messageHelpButton.frame.size.width;
326 CGSize size = [message sizeWithFont:messageTextView.font constrainedToSize:imageMaxSize lineBreakMode:UILineBreakModeWordWrap];
327 float height = 20.0 + fmin(100.0, size.height);
328 if (showHelpButton) {
329 height = fmax(HELP_BUTTON_HEIGHT, height);
332 CGRect messageFrame = imageView.bounds;
333 messageFrame.origin.y = CGRectGetMaxY(messageFrame) - height;
334 messageFrame.size.height = height;
335 [self.messageView setFrame:messageFrame];
336 messageView.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleWidth;
337 CGRect messageViewBounds = [messageView bounds];
339 self.messageTextView.text = message;
340 messageTextView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
341 if (showHelpButton) {
342 CGRect textViewFrame;
343 CGRect helpButtonFrame;
345 CGRectDivide(messageViewBounds, &helpButtonFrame, &textViewFrame, HELP_BUTTON_WIDTH, CGRectMaxXEdge);
346 [self.messageTextView setFrame:textViewFrame];
348 [messageHelpButton setFrame:helpButtonFrame];
349 messageHelpButton.alpha = 1.0;
350 messageHelpButton.enabled = YES;
351 messageHelpButton.autoresizingMask =
352 UIViewAutoresizingFlexibleLeftMargin |
353 UIViewAutoresizingFlexibleTopMargin;
354 [messageView addSubview:messageHelpButton];
356 [messageHelpButton removeFromSuperview];
357 messageHelpButton.alpha = 0.0;
358 messageHelpButton.enabled = NO;
360 [self.messageTextView setFrame:messageViewBounds];
364 // DecoderDelegate methods
366 - (void)decoder:(Decoder *)decoder willDecodeImage:(UIImage *)image usingSubset:(UIImage *)subset{
367 [self clearImageView];
368 [self.imageView setImage:subset];
369 [self showMessage:[NSString stringWithFormat:NSLocalizedString(@"DecoderViewController MessageWhileDecodingWithDimensions", @"Decoding image (%.0fx%.0f) ..."), image.size.width, image.size.height]
373 - (void)decoder:(Decoder *)decoder
374 decodingImage:(UIImage *)image
375 usingSubset:(UIImage *)subset
376 progress:(NSString *)message {
377 [self clearImageView];
378 [self.imageView setImage:subset];
379 [self showMessage:message helpButton:NO];
382 - (void)presentResultForString:(NSString *)resultString {
383 self.result = [ResultParser parsedResultForString:resultString];
384 [self showMessage:[self.result stringForDisplay] helpButton:NO];
385 self.actions = self.result.actions;
387 NSLog(@"result has %d actions", actions ? 0 : actions.count);
389 [self updateToolbar];
392 - (void)presentResultPoints:(NSArray *)resultPoints
393 forImage:(UIImage *)image
394 usingSubset:(UIImage *)subset {
395 // simply add the points to the image view
396 imageView.image = subset;
397 for (NSValue *pointValue in resultPoints) {
398 [imageView addResultPoint:[pointValue CGPointValue]];
402 - (void)decoder:(Decoder *)decoder didDecodeImage:(UIImage *)image usingSubset:(UIImage *)subset withResult:(TwoDDecoderResult *)twoDResult {
404 [self presentResultForString:twoDResult.text];
406 [self presentResultPoints:twoDResult.points forImage:image usingSubset:subset];
408 // save the scan to the shared database
409 [[Database sharedDatabase] addScanWithText:twoDResult.text];
411 [self performResultAction:self];
414 - (void)decoder:(Decoder *)decoder failedToDecodeImage:(UIImage *)image usingSubset:(UIImage *)subset reason:(NSString *)reason {
415 if (self.picker && UIImagePickerControllerSourceTypeCamera == self.picker.sourceType) {
416 // If we are using the camera, and the user hasn't manually cancelled,
417 // take another snapshot and try to decode it.
418 [self takeScreenshot];
420 [self showMessage:reason helpButton:YES];
421 [self updateToolbar];
426 - (void)willAnimateFirstHalfOfRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
427 [super willAnimateFirstHalfOfRotationToInterfaceOrientation:toInterfaceOrientation duration:duration];
429 if (imageView.image) {
431 CGRect viewBounds = imageView.bounds;
432 CGSize imageSize = imageView.image.size;
433 float scale = fmin(viewBounds.size.width / imageSize.width,
434 viewBounds.size.height / imageSize.height);
435 float xOffset = (viewBounds.size.width - scale * imageSize.width) / 2.0;
436 float yOffset = (viewBounds.size.height - scale * imageSize.height) / 2.0;
439 for (UIView *view in resultPointViews) {
445 - (void)willAnimateSecondHalfOfRotationFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation duration:(NSTimeInterval)duration {
446 [super willAnimateSecondHalfOfRotationFromInterfaceOrientation:fromInterfaceOrientation duration:duration];
448 if (imageView.image) {
450 CGRect viewBounds = imageView.bounds;
451 CGSize imageSize = imageView.image.size;
452 float scale = fmin(viewBounds.size.width / imageSize.width,
453 viewBounds.size.height / imageSize.height);
454 float xOffset = (viewBounds.size.width - scale * imageSize.width) / 2.0;
455 float yOffset = (viewBounds.size.height - scale * imageSize.height) / 2.0;
458 for (UIView *view in resultPointViews) {
464 - (void)cancel:(id)sender {
468 - (void)takeScreenshot {
470 CGImageRef cgScreen = MyCGImageCopyScreenContents();
472 CGRect croppedFrame = CGRectMake(0, 0, CGImageGetWidth(cgScreen),
473 CGImageGetHeight(cgScreen) - (10+toolbar.bounds.size.height));
474 CGImageRef cgCropped = CGImageCreateWithImageInRect(cgScreen, croppedFrame);
476 UIImage *screenshot = [UIImage imageWithCGImage:cgCropped];
477 CGImageRelease(cgCropped);
478 [self.decoder decodeImage:screenshot];
480 CGImageRelease(cgScreen);
485 // UIImagePickerControllerDelegate methods
487 - (void)imagePickerController:(UIImagePickerController *)aPicker
488 didFinishPickingMediaWithInfo:(NSDictionary *)info {
489 UIImage *imageToDecode =
490 [info objectForKey:UIImagePickerControllerEditedImage];
491 if (!imageToDecode) {
492 imageToDecode = [info objectForKey:UIImagePickerControllerOriginalImage];
494 CGSize size = [imageToDecode size];
495 CGRect cropRect = CGRectMake(0.0, 0.0, size.width, size.height);
498 NSLog(@"picked image size = (%f, %f)", size.width, size.height);
500 NSString *systemVersion = [[UIDevice currentDevice] systemVersion];
502 NSValue *cropRectValue = [info objectForKey:UIImagePickerControllerCropRect];
504 UIImage *originalImage = [info objectForKey:UIImagePickerControllerOriginalImage];
507 NSLog(@"original image size = (%f, %f)", originalImage.size.width, originalImage.size.height);
509 cropRect = [cropRectValue CGRectValue];
511 NSLog(@"crop rect = (%f, %f) x (%f, %f)", CGRectGetMinX(cropRect), CGRectGetMinY(cropRect), CGRectGetWidth(cropRect), CGRectGetHeight(cropRect));
513 if (([picker sourceType] == UIImagePickerControllerSourceTypeSavedPhotosAlbum) &&
514 [@"2.1" isEqualToString:systemVersion]) {
515 // adjust crop rect to work around bug in iPhone OS 2.1 when selecting from the photo roll
516 cropRect.origin.x *= 2.5;
517 cropRect.origin.y *= 2.5;
518 cropRect.size.width *= 2.5;
519 cropRect.size.height *= 2.5;
521 NSLog(@"2.1-adjusted crop rect = (%f, %f) x (%f, %f)", CGRectGetMinX(cropRect), CGRectGetMinY(cropRect), CGRectGetWidth(cropRect), CGRectGetHeight(cropRect));
525 imageToDecode = originalImage;
529 [imageToDecode retain];
531 [self.decoder decodeImage:imageToDecode cropRect:cropRect];
532 [imageToDecode release];
536 - (void)imagePickerControllerDidCancel:(UIImagePickerController *)aPicker {
540 - (void)setPicker:(UIImagePickerController *)aPicker {
541 if (picker != aPicker) {
542 [picker dismissModalViewControllerAnimated:YES];
543 picker = [aPicker retain];
544 [self updateToolbar];
548 - (void)navigationController:(UINavigationController *)navigationController
549 didShowViewController:(UIViewController *)viewController
550 animated:(BOOL)animated {
554 - (void)navigationController:(UINavigationController *)navigationController
555 willShowViewController:(UIViewController *)viewController
556 animated:(BOOL)animated {
560 - (void)performAction:(ResultAction *)action {
561 [action performActionWithController:self shouldConfirm:NO];
564 - (void)confirmAndPerformAction:(ResultAction *)action {
565 [action performActionWithController:self shouldConfirm:YES];
569 - (IBAction)performResultAction:(id)sender {
570 if (self.result == nil) {
571 NSLog(@"no result to perform an action on!");
575 if (self.actions == nil || self.actions.count == 0) {
576 NSLog(@"result has no actions to perform!");
580 if (self.actions.count == 1) {
581 ResultAction *action = [self.actions lastObject];
583 NSLog(@"Result has the single action, (%@) '%@', performing it",
584 NSStringFromClass([action class]), [action title]);
586 [self performSelector:@selector(confirmAndPerformAction:)
591 NSLog(@"Result has multiple actions, popping up an action sheet");
593 UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithFrame:self.view.bounds];
595 for (ResultAction *action in self.actions) {
596 [actionSheet addButtonWithTitle:[action title]];
599 int cancelIndex = [actionSheet addButtonWithTitle:NSLocalizedString(@"DecoderViewController cancel button title", @"Cancel")];
600 actionSheet.cancelButtonIndex = cancelIndex;
602 actionSheet.delegate = self;
604 [actionSheet showFromToolbar:self.toolbar];
608 - (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
609 if (buttonIndex < self.actions.count) {
610 int actionIndex = buttonIndex;
611 ResultAction *action = [self.actions objectAtIndex:actionIndex];
612 [self performSelector:@selector(performAction:)
618 - (IBAction)showArchive:(id)sender {
619 ArchiveController *archiveController = [[ArchiveController alloc] initWithDecoderViewController:self];
620 [[self navigationController] pushViewController:archiveController animated:true];
621 [archiveController release];
624 - (void)showScan:(Scan *)scan {
625 [self clearImageView];
626 [self presentResultForString:scan.text];
627 [[self navigationController] popToViewController:self animated:YES];