1/* 2 Project: LaternaMagica 3 AppController.m 4 5 Copyright (C) 2006-2017 Riccardo Mottola 6 7 Author: Riccardo Mottola 8 9 Created: 2006-01-16 10 11 This application is free software; you can redistribute it and/or 12 modify it under the terms of the GNU General Public 13 License as published by the Free Software Foundation; either 14 version 2 of the License, or (at your option) any later version. 15 16 This application is distributed in the hope that it will be useful, 17 but WITHOUT ANY WARRANTY; without even the implied warranty of 18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 19 Library General Public License for more details. 20 21 You should have received a copy of the GNU General Public 22 License along with this library; if not, write to the Free 23 Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 24*/ 25 26#if defined(__MINGW32__) 27#define srandom srand 28#define random rand 29#endif 30 31#include <stdlib.h> 32#include <time.h> 33 34#import "AppController.h" 35#import "LMImage.h" 36#import "PRScale.h" 37 38#if !defined (GNUSTEP) && (MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_4) 39#define NSInteger int 40#if MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_3 41#define NSImageGamma @"NSImageGamma" 42#define NSImageEXIFData @"NSImageEXIFData" 43#endif 44#endif 45 46 47#define LM_KEY_DESTROYRECYCLE @"DestroyOrRecycle" 48#define LM_KEY_ASKDELETING @"AskBeforeDeleting" 49 50@implementation AppController 51 52- (id) init 53{ 54 if ((self = [super init])) 55 { 56 /* initialize random number generator */ 57 srandom(time(NULL)); 58 } 59 return self; 60} 61 62- (void)awakeFromNib 63{ 64 NSRect frame; 65 66 window = smallWindow; 67 view = smallView; 68 69 frame = [[NSScreen mainScreen] frame]; 70 fullWindow = [[LMWindow alloc] initWithContentRect: frame 71 styleMask: NSBorderlessWindowMask 72 backing: NSBackingStoreBuffered 73 defer: NO]; 74 [fullWindow setAutodisplay:YES]; 75 [fullWindow setExcludedFromWindowsMenu: YES]; 76 [fullWindow setBackgroundColor: [NSColor blackColor]]; 77 78 [smallView setFrame:[scrollView documentVisibleRect]]; 79 [smallView setImageAlignment:NSImageAlignTopLeft]; 80 [smallWindow setDelegate:smallView]; 81 82 fullView = [[LMFlipView alloc] initWithFrame:[fullWindow frame]]; 83 [fullView setImageScaling: NSScaleNone]; 84 [fullView setImageAlignment:NSImageAlignCenter]; 85 [fullView setAutoresizingMask: NSViewWidthSizable | NSViewHeightSizable]; 86 [fullView setController:self]; 87 88 89 /* avoid replacing the contentview with a NSControl subclass, thus add a subview instead */ 90 [[fullWindow contentView] addSubview: fullView]; 91 [fullWindow setInitialFirstResponder:fullView]; 92 [fullWindow setDelegate:fullView]; 93 94 /* register the file view as drag destionation */ 95 [fileListView registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, nil]]; 96 97 /* add an observer for the file table view */ 98 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_selectionDidChange:) name:NSTableViewSelectionDidChangeNotification object:fileListView]; 99 100 /* add an observer for the window resize */ 101 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_windowDidResize:) name:NSWindowDidResizeNotification object:window]; 102} 103 104- (void)dealloc 105{ 106 [[NSNotificationCenter defaultCenter] removeObserver:self]; 107 [super dealloc]; 108} 109 110 111- (void)addFile:(NSString *)filename 112{ 113 if([fileListData addPathAndRecurse:filename]) 114 [fileListView reloadData]; 115} 116 117- (void)addFiles:(id)sender 118{ 119 NSOpenPanel *openPanel; 120 NSArray *files; 121 NSEnumerator *e; 122 NSString *filename; 123 NSFileManager *fmgr; 124 NSDictionary *attrs; 125 126 openPanel = [NSOpenPanel openPanel]; 127 [openPanel setAllowsMultipleSelection:YES]; 128 [openPanel setCanChooseDirectories:YES]; 129 [openPanel setCanChooseFiles:YES]; 130 if ([openPanel runModalForTypes:NULL] != NSOKButton) 131 return; 132 133 files = [openPanel filenames]; 134 e = [files objectEnumerator]; 135 fmgr = [NSFileManager defaultManager]; 136 while ((filename = (NSString*)[e nextObject])) 137 { 138 attrs = [fmgr fileAttributesAtPath:filename traverseLink:YES]; 139 if (attrs) 140 { 141 [self addFile:filename]; 142 } 143 else 144 { 145 NSLog(@"open panel did not return a valid path"); 146 } 147 } 148 [fileListView selectRow: [fileListView numberOfRows]-1 byExtendingSelection: NO]; 149} 150 151// scale image view according to options 152- (void)scaleView:(NSImage *) image 153{ 154 NSPoint rectOrigin; 155 NSSize rectSize; 156 157 if (image == nil) 158 return; 159 160 rectSize = [window frame].size; 161 if (scaleToFit) 162 { 163 NSSize imageSize; 164 NSAffineTransform *at; 165 float scaleH, scaleW; 166 float scale; 167 168 169 imageSize = [image size]; 170 171 scaleW = rectSize.width / imageSize.width; 172 scaleH = rectSize.height / imageSize.height; 173 174 if (scaleW < scaleH) 175 scale = scaleW; 176 else 177 scale = scaleH; 178 179 at = [NSAffineTransform transform]; 180 [at scaleBy:scale]; 181 [view setFrameSize:[at transformSize:imageSize]]; 182 } else 183 { 184 [view setFrameSize:[image size]]; 185 } 186 rectOrigin = NSMakePoint((rectSize.width - [view frame].size.width)/2, (rectSize.height - [view frame].size.height)/2); 187 [view setFrameOrigin:rectOrigin]; 188} 189 190- (void)changeImage:(LMImage *) image 191{ 192 NSImage *nsImage; 193 NSImage *img; 194 195 nsImage = [[NSImage alloc] initByReferencingFile:[image path]]; 196 197 [nsImage autorelease]; 198 img = nsImage; 199 200 if ([image rotation] > 0) 201 { 202 NSImage *destImage; 203 204 destImage = [self rotate: nsImage byAngle:[image rotation]]; 205 img = destImage; 206 } 207 [self scaleView: img]; 208 [view setImage: img]; 209 210 [view setNeedsDisplay:YES]; 211 [[view superview] setNeedsDisplay:YES]; 212 213 /* we don't need to update the full-screen window title */ 214 [smallWindow setTitleWithRepresentedFilename:[image name]]; 215} 216 217- (IBAction)setScaleToFit:(id)sender 218{ 219 if ([fitButton state] == NSOnState) 220 { 221 scaleToFit = YES; 222 [view setImageScaling:NSScaleToFit]; 223 } else 224 { 225 scaleToFit = NO; 226 [view setImageScaling:NSScaleNone]; 227 } 228 [scrollView setHasVerticalScroller:!scaleToFit]; 229 [scrollView setHasHorizontalScroller:!scaleToFit]; 230 [self scaleView:[view image]]; 231 [view setNeedsDisplay:YES]; 232} 233 234/** method called as a notification from the selection change */ 235- (void)_selectionDidChange :(NSNotification *)notif 236{ 237 NSTableView *table; 238 int selectedRow; 239 240 table = [notif object]; 241 selectedRow = [table selectedRow]; 242 if (selectedRow >= 0) 243 [self changeImage:[fileListData imageAtIndex:selectedRow]]; 244} 245 246/** method called as a notification from the window resize 247 or if scale preferences changed */ 248- (void)_windowDidResize :(NSNotification *)notif 249{ 250 [self scaleView: [view image]]; 251} 252 253 254- (IBAction)setFullScreen: (id)sender 255{ 256 NSImage *image; 257 258 /* trick for GS so that we don't have order problems with GWorkspace */ 259 [window makeKeyAndOrderFront: self]; 260 261 /* we choose not to respond to key events if not in fullscreen */ 262 if ([sender isKindOfClass:[NSEvent class]] && [fullScreenMenuItem state] == NSOffState) 263 return; 264 265 image = [view image]; 266 [image retain]; 267 268 /* check the sender and set the other item accordingly */ 269 if (sender == fullScreenButton) 270 [fullScreenMenuItem setState:[fullScreenButton state]]; 271 else 272 { 273 if ([fullScreenMenuItem state] == NSOnState) 274 [fullScreenMenuItem setState:NSOffState]; 275 else 276 [fullScreenMenuItem setState:NSOnState]; 277 [fullScreenButton setState:[fullScreenMenuItem state]]; 278 } 279 280 if ([fullScreenButton state] == NSOnState) 281 { 282 [smallView setImage: nil]; 283 [fullWindow setLevel: NSScreenSaverWindowLevel]; 284 window = fullWindow; 285 view = fullView; 286 } else 287 { 288 [fullView setImage: nil]; 289 [fullWindow orderOut:self]; 290 window = smallWindow; 291 view = smallView; 292 } 293 [view setImage: image]; 294 [image release]; 295 [self setScaleToFit: self]; 296 [view setNeedsDisplay:YES]; 297 [[view superview] setNeedsDisplay:YES]; 298 [window makeKeyAndOrderFront: self]; 299} 300 301- (IBAction)prevImage:(id)sender 302{ 303 int sr; 304 int rows; 305 306 rows = [fileListView numberOfRows]; 307 if (rows > 0) 308 { 309 sr = [fileListView selectedRow]; 310 311 if (sr > 0) 312 sr--; 313 else 314 sr = (rows - 1); 315 316 [fileListView selectRow: sr byExtendingSelection: NO]; 317 } 318} 319 320- (IBAction)nextImage:(id)sender 321{ 322 int sr; 323 int rows; 324 325 rows = [fileListView numberOfRows]; 326 327 if (rows > 0) 328 { 329 sr = [fileListView selectedRow]; 330 331 if (sr < (rows - 1)) 332 sr++; 333 else 334 sr = 0; 335 336 [fileListView selectRow: sr byExtendingSelection: NO]; 337 } 338} 339 340- (IBAction)removeAllImages:(id)sender 341{ 342 NSInteger rows; 343 NSInteger i; 344 345 rows = [fileListView numberOfRows]; 346 for (i = (rows - 1); i >= 0 ; i--) 347 [fileListData removeObjectAtIndex: i]; 348 [fileListView reloadData]; 349 [view setImage: nil]; 350 [window setTitle:@"None"]; 351} 352 353- (IBAction)scrambleList:(id)sender 354{ 355 NSInteger selRow; 356 [fileListData scrambleObjects]; 357 [fileListView reloadData]; 358 359 /* we reload the image directly without calling selectRow, 360 the selected row did not actually change and no notification triggers */ 361 selRow = [fileListView selectedRow]; 362 if (selRow > 0) 363 [self changeImage: [fileListData imageAtIndex: (NSUInteger)selRow]]; 364} 365 366- (IBAction)removeImage:(id)sender 367{ 368 int sr; 369 int rows; 370 371 rows = [fileListView numberOfRows]; 372 if (rows >= 0) 373 { 374 sr = [fileListView selectedRow]; 375 if (sr >= 0) 376 { 377 [fileListData removeObjectAtIndex: sr]; 378 [fileListView reloadData]; 379 380 rows = [fileListView numberOfRows]; 381 if (rows > 0) 382 { 383 // if we remove the last image, the selection changes 384 // otherwise no selection change notification is generated 385 // and thus we update simply the image 386 if (sr >= rows) 387 [fileListView selectRow: rows-1 byExtendingSelection: NO]; 388 else 389 [self changeImage: [fileListData imageAtIndex: sr]]; 390 391 } else 392 { 393 // no image to select, we clear the display 394 [view setImage: nil]; 395 [window setTitle:@"None"]; 396 } 397 } 398 } 399} 400 401- (IBAction)eraseImage:(id)sender 402{ 403 int sr; 404 BOOL shallErase; 405 NSUserDefaults *defaults; 406 407 defaults = [NSUserDefaults standardUserDefaults]; 408 sr = [fileListView selectedRow]; 409 if (sr >= 0) 410 { 411 if ([defaults boolForKey:LM_KEY_ASKDELETING]) 412 { 413 int result; 414 415 result = NSRunAlertPanel(nil, @"Really delete the image from disk?", @"Delete", @"Abort", nil); 416 shallErase = NO; 417 if (result == NSAlertDefaultReturn) 418 shallErase = YES; 419 } 420 else 421 shallErase = YES; 422 if (shallErase) 423 { 424 NSWorkspace *ws; 425 NSString *fileOperation; 426 NSInteger opTag; 427 NSString *folder; 428 NSString *file; 429 430 if ([defaults boolForKey:LM_KEY_DESTROYRECYCLE]) 431 fileOperation = NSWorkspaceDestroyOperation; 432 else 433 fileOperation = NSWorkspaceRecycleOperation; 434 435 folder = [[fileListData pathAtIndex:sr]stringByDeletingLastPathComponent]; 436 file = [[fileListData pathAtIndex:sr] lastPathComponent]; 437 ws = [NSWorkspace sharedWorkspace]; 438 opTag = 1; 439 [ws performFileOperation:fileOperation 440 source:folder 441 destination:nil 442 files:[NSArray arrayWithObject: file] 443 tag:&opTag]; 444 445 [self removeImage:self]; 446 } 447 } 448} 449 450 451- (IBAction)rotateImage90:(id)sender 452{ 453 NSImage *destImage; 454 455 LMImage *imageInfo; 456 457 imageInfo = [fileListData imageAtIndex:[fileListView selectedRow]]; 458 [imageInfo setRotation: 90]; 459 460 destImage = [self rotate: [view image] byAngle:90]; 461 462 463 [self scaleView:destImage]; 464 [view setImage: destImage]; 465 [view setNeedsDisplay:YES]; 466 [[view superview] setNeedsDisplay:YES]; 467} 468 469- (IBAction)rotateImage180:(id)sender 470{ 471 NSImage *destImage; 472 473 LMImage *imageInfo; 474 475 imageInfo = [fileListData imageAtIndex:[fileListView selectedRow]]; 476 [imageInfo setRotation: 180]; 477 478 destImage = [self rotate: [view image] byAngle:180]; 479 480 [self scaleView:destImage]; 481 [view setImage: destImage]; 482 [view setNeedsDisplay:YES]; 483 [[view superview] setNeedsDisplay:YES]; 484} 485 486 487- (IBAction)rotateImage270:(id)sender 488{ 489 NSImage *destImage; 490 491 LMImage *imageInfo; 492 493 imageInfo = [fileListData imageAtIndex:[fileListView selectedRow]]; 494 [imageInfo setRotation: 270]; 495 496 destImage = [self rotate: [view image] byAngle: 270]; 497 498 [self scaleView:destImage]; 499 [view setImage: destImage]; 500 [[view superview] setNeedsDisplay:YES]; 501} 502 503- (NSImage *)rotate: (NSImage *)image byAngle:(unsigned)angle 504{ 505 NSBitmapImageRep *srcImageRep; 506 NSImage *destImage; 507 NSBitmapImageRep *destImageRep; 508 NSMutableDictionary *imgProps; 509 NSInteger x, y; 510 NSInteger w, h; 511 NSInteger newW, newH; 512 NSInteger srcSamplesPerPixel; 513 NSInteger destSamplesPerPixel; 514 unsigned char *srcData; 515 unsigned char *destData; 516 unsigned char *p1, *p2; 517 NSSize oldRepSize, newRepSize; 518 NSSize oldImgSize, newImgSize; 519 register NSInteger srcBytesPerPixel; 520 register NSInteger destBytesPerPixel; 521 register NSInteger srcBytesPerRow; 522 register NSInteger destBytesPerRow; 523 524 /* get source image representation and associated information */ 525 oldImgSize = [image size]; 526 srcImageRep = [NSBitmapImageRep imageRepWithData:[image TIFFRepresentation]]; 527 oldRepSize = [srcImageRep size]; 528 w = [srcImageRep pixelsWide]; 529 h = [srcImageRep pixelsHigh]; 530 if (angle == 90 || angle == 270) 531 { 532 newH = w; 533 newW = h; 534 newImgSize = NSMakeSize(oldImgSize.height, oldImgSize.width); 535 newRepSize = NSMakeSize(oldRepSize.height, oldRepSize.width); 536 } 537 else 538 { 539 newH = h; 540 newW = w; 541 newImgSize = oldImgSize; 542 newRepSize = oldRepSize; 543 } 544 545 srcSamplesPerPixel = [srcImageRep samplesPerPixel]; 546 destSamplesPerPixel = srcSamplesPerPixel; 547 srcBytesPerRow = [srcImageRep bytesPerRow]; 548 srcBytesPerPixel = [srcImageRep bitsPerPixel] / 8; srcSamplesPerPixel = [srcImageRep samplesPerPixel]; 549 550 imgProps = [[NSMutableDictionary alloc] init]; 551 [imgProps setValue:[srcImageRep valueForProperty:NSImageCompressionMethod] forKey:NSImageCompressionMethod]; 552 [imgProps setValue:[srcImageRep valueForProperty:NSImageCompressionFactor] forKey:NSImageCompressionFactor]; 553 [imgProps setValue:[srcImageRep valueForProperty:NSImageEXIFData] forKey:NSImageEXIFData]; 554 [imgProps setValue:[srcImageRep valueForProperty:NSImageGamma] forKey:NSImageGamma]; 555 [imgProps setValue:[srcImageRep valueForProperty:NSImageColorSyncProfileData] forKey:NSImageColorSyncProfileData]; 556 NSLog(@"Properties: %@", imgProps); 557 558 destSamplesPerPixel = srcSamplesPerPixel; 559 destImage = [[NSImage alloc] initWithSize:newImgSize]; 560 destImageRep = [[NSBitmapImageRep alloc] 561 initWithBitmapDataPlanes:NULL 562 pixelsWide:newW 563 pixelsHigh:newH 564 bitsPerSample:[srcImageRep bitsPerSample] 565 samplesPerPixel:[srcImageRep samplesPerPixel] 566 hasAlpha:[srcImageRep hasAlpha] 567 isPlanar:[srcImageRep isPlanar] 568 colorSpaceName:[srcImageRep colorSpaceName] 569 bytesPerRow:0 570 bitsPerPixel:0]; 571 [destImageRep setSize:newRepSize]; 572 [destImageRep setProperty:NSImageEXIFData withValue:[imgProps valueForKey:NSImageEXIFData]]; 573 [destImageRep setProperty:NSImageColorSyncProfileData withValue:[imgProps valueForKey:NSImageColorSyncProfileData]]; 574 [destImageRep setProperty:NSImageGamma withValue:[imgProps valueForKey:NSImageGamma]]; 575 [imgProps release]; 576 577 srcData = [srcImageRep bitmapData]; 578 destData = [destImageRep bitmapData]; 579 destBytesPerRow = [destImageRep bytesPerRow]; 580 destBytesPerPixel = [destImageRep bitsPerPixel] / 8; 581 582 if (angle == 90) 583 { 584 for (y = 0; y < h; y++) 585 for (x = 0; x < w; x++) 586 { 587 unsigned s; 588 589 p1 = srcData + srcBytesPerRow * y + srcBytesPerPixel * x; 590 p2 = destData + destBytesPerRow * (w-x-1) + destBytesPerPixel * y; 591 for (s = 0; s < srcSamplesPerPixel; s++) 592 p2[s] = p1[s]; 593 } 594 } 595 else if (angle == 180) 596 { 597 for (y = 0; y < h; y++) 598 for (x = 0; x < w; x++) 599 { 600 unsigned s; 601 602 p1 = srcData + srcBytesPerRow * y + srcBytesPerPixel * x; 603 p2 = destData + destBytesPerRow * (h-y-1) + destBytesPerPixel * (w-x-1); 604 for (s = 0; s < srcSamplesPerPixel; s++) 605 p2[s] = p1[s]; 606 } 607 } 608 else if (angle == 270) 609 { 610 for (y = 0; y < h; y++) 611 for (x = 0; x < w; x++) 612 { 613 unsigned s; 614 615 p1 = srcData + srcBytesPerRow * y + srcBytesPerPixel * x; 616 p2 = destData + destBytesPerRow * x + destBytesPerPixel * (h-y-1); 617 for (s = 0; s < srcSamplesPerPixel; s++) 618 p2[s] = p1[s]; 619 } 620 } 621 [destImage addRepresentation:destImageRep]; 622 [destImageRep release]; 623 624 [destImage autorelease]; 625 return destImage; 626} 627 628- (BOOL)validateMenuItem:(id)sender 629{ 630 /* the menu item returned is not the same object, so we use isEqual */ 631 if ([sender isEqual:saveAsMenuItem]) 632 { 633 if ([view image] == nil) 634 return NO; 635 } 636 return YES; 637} 638 639- (void)updateImageCount 640{ 641 [fieldImageCount setIntValue:(int)[fileListData imageCount]]; 642} 643 644- (IBAction)saveImageAs:(id)sender 645{ 646 NSImage *srcImage; 647 NSBitmapImageRep *srcImageRep; 648 NSData *dataOfRep = nil; 649 NSDictionary *repProperties; 650 NSString *origFileName; 651 NSString *filenameNoExtension; 652 653 654 origFileName = [[fileListData pathAtIndex:[fileListView selectedRow]] lastPathComponent]; 655 filenameNoExtension = [origFileName stringByDeletingPathExtension]; 656 savePanel = [NSSavePanel savePanel]; 657 [savePanel setDelegate: self]; 658 /* if the accessory view comes from a window it needs a retain */ 659 [savePanel setAccessoryView: [saveOptionsView retain]]; 660 661 /* simulate clicks to be sure interface is consistent */ 662 [jpegCompressionSlider performClick:nil]; 663 [fileTypePopUp sendAction:@selector(setCompressionType:) to:self]; 664 665 if ([savePanel runModalForDirectory:@"" file:filenameNoExtension] == NSFileHandlingPanelOKButton) 666 { 667 NSString *fileName; 668 int selItem; 669 670 srcImage = [view image]; 671 srcImageRep = [NSBitmapImageRep imageRepWithData:[srcImage TIFFRepresentation]]; 672 673 674 fileName = [savePanel filename]; 675 676 selItem = [fileTypePopUp indexOfSelectedItem]; 677 if (selItem == 0) 678 { 679 NSLog(@"Tiff"); 680 repProperties = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:NSTIFFCompressionLZW] forKey:NSImageCompressionMethod]; 681 dataOfRep = [srcImageRep representationUsingType: NSTIFFFileType properties:repProperties]; 682 683 } else if (selItem == 1) 684 { 685 NSLog(@"Jpeg"); 686 repProperties = [NSDictionary dictionaryWithObject:[NSNumber numberWithFloat:[jpegCompressionSlider floatValue]/100] forKey:NSImageCompressionFactor]; 687 dataOfRep = [srcImageRep representationUsingType: NSJPEGFileType properties:repProperties]; 688 } 689 [dataOfRep writeToFile:fileName atomically:NO]; 690 } 691} 692 693 694/* ===== delegates =====*/ 695- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename 696{ 697 [self addFile:filename]; 698 [fileListView selectRow: [fileListView numberOfRows]-1 byExtendingSelection: NO]; 699 return YES; 700} 701 702/* save panel delegates */ 703/** change the file type */ 704- (IBAction)setCompressionType:(id)sender 705{ 706 int selItem; 707 708 selItem = [fileTypePopUp indexOfSelectedItem]; 709 if (selItem == 0) 710 { 711 NSLog(@"Tiff"); 712 [jpegCompressionSlider setEnabled:NO]; 713 [jpegCompressionField setEnabled:NO]; 714#if (MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_2) 715 [savePanel setRequiredFileType:@"tiff"]; 716#else 717 [savePanel setAllowedFileTypes:[NSArray arrayWithObjects: @"tif", @"tiff", nil]]; 718#endif 719 } else if (selItem == 1) 720 { 721 NSLog(@"Jpeg"); 722 [jpegCompressionSlider setEnabled:YES]; 723 [jpegCompressionField setEnabled:YES]; 724#if (MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_2) 725 [savePanel setRequiredFileType:@"jpg"]; 726#else 727 [savePanel setAllowedFileTypes:[NSArray arrayWithObjects: @"jpg", @"jpeg", nil]]; 728#endif 729 } 730} 731 732/** keep the slider and the text view of the compression level in sync */ 733- (IBAction)setCompressionLevel:(id)sender 734{ 735 if (sender == jpegCompressionField) 736 [jpegCompressionSlider takeFloatValueFrom:sender]; 737 else 738 [jpegCompressionField takeFloatValueFrom:sender]; 739} 740 741/* exporter */ 742- (IBAction)exportImages:(id)sender 743{ 744 [exporterPanel makeKeyAndOrderFront:self]; 745 [exportProgress setDoubleValue: 0.0]; 746} 747 748- (IBAction)setExportPath:(id)sender 749{ 750 NSOpenPanel *openPanel; 751 NSString *choosenFilePath; 752 753 openPanel = [NSOpenPanel openPanel]; 754 [openPanel setAllowsMultipleSelection:NO]; 755 [openPanel setCanChooseDirectories:YES]; 756 [openPanel setCanChooseFiles:NO]; 757 if ([openPanel runModalForTypes:NULL] != NSOKButton) 758 { 759 return; 760 } 761 762 choosenFilePath = [openPanel filename]; 763 [fieldOutputPath setStringValue:choosenFilePath]; 764} 765 766- (IBAction)execExportImages:(id)sender 767{ 768 int givenHeight; 769 int givenWidth; 770 NSDictionary *repProperties; 771 NSString *origFileName; 772 NSString *filenameNoExtension; 773 NSImage *srcImage; 774 NSBitmapImageRep *srcImageRep; 775 NSData *dataOfRep; 776 NSString *destFileName; 777 NSString *destFileExtension; 778 NSString *destFolder; 779 NSInteger i; 780 781 givenHeight = [fieldHeight intValue]; 782 givenWidth = [fieldWidth intValue]; 783 784 destFolder = [fieldOutputPath stringValue]; 785 786 if ([popupFileType indexOfSelectedItem] == 0) 787 destFileExtension = @"tiff"; 788 else 789 destFileExtension = @"jpeg"; 790 791 [exportProgress setDoubleValue: 0.0]; 792 [exporterPanel displayIfNeeded]; 793 for (i = 0; i < [fileListView numberOfRows]; i++) 794 { 795 NSInteger newW, newH; 796 double aspectRatio; 797 NSBitmapImageRep *scaledImageRep; 798 NSAutoreleasePool *pool; 799 LMImage *lmImage; 800 801 lmImage = [fileListData imageAtIndex:i]; 802 803 /* create a local pool to avoid the autorelease to grow too much */ 804 pool = [[NSAutoreleasePool alloc] init]; 805 origFileName = [lmImage name]; 806 filenameNoExtension = [origFileName stringByDeletingPathExtension]; 807 808 srcImage = [[NSImage alloc] initByReferencingFile:[lmImage path]]; 809 if ([lmImage rotation] > 0) 810 { 811 NSImage *rotImage; 812 813 rotImage = [self rotate:srcImage byAngle:[lmImage rotation]]; 814 [rotImage retain]; 815 [srcImage release]; 816 srcImage = rotImage; 817 } 818 srcImageRep = [[srcImage representations] objectAtIndex:0];; 819 820#if !defined (GNUSTEP) && (MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_3) 821 /* since 10.4 we have different Alpha format position, let's convert it */ 822 NSMutableDictionary *imgProps; 823 824 if ([srcImageRep bitmapFormat] != 0) 825 { 826 NSInteger x, y; 827 NSInteger w, h; 828 BOOL alphaFirst; 829 NSImage *destImage; 830 NSBitmapImageRep *destImageRep; 831 NSInteger srcBytesPerRow; 832 NSInteger destBytesPerRow; 833 NSInteger srcBytesPerPixel; 834 NSInteger destBytesPerPixel; 835 836 NSLog(@"We have a non-standard format, let's try to convert it"); 837 alphaFirst = [srcImageRep bitmapFormat] & NSAlphaFirstBitmapFormat; 838 839 if ([srcImageRep bitsPerSample] == 8) 840 { 841 unsigned char *srcData; 842 unsigned char *destData; 843 unsigned char *p1; 844 unsigned char *p2; 845 846 /* swap Alpha is hopefully only for chunky images */ 847 if (alphaFirst) 848 { 849 imgProps = [[NSMutableDictionary alloc] init]; 850 [imgProps setValue:[srcImageRep valueForProperty:NSImageCompressionMethod] forKey:NSImageCompressionMethod]; 851 [imgProps setValue:[srcImageRep valueForProperty:NSImageCompressionFactor] forKey:NSImageCompressionFactor]; 852 [imgProps setValue:[srcImageRep valueForProperty:NSImageEXIFData] forKey:NSImageEXIFData]; 853 854 w = [srcImageRep pixelsWide]; 855 h = [srcImageRep pixelsHigh]; 856 857 srcBytesPerRow = [srcImageRep bytesPerRow]; 858 srcBytesPerPixel = [srcImageRep bitsPerPixel] / 8; 859 destImage = [[NSImage alloc] initWithSize:NSMakeSize(w, h)]; 860 destImageRep = [[NSBitmapImageRep alloc] 861 initWithBitmapDataPlanes:NULL 862 pixelsWide:w 863 pixelsHigh:h 864 bitsPerSample:8 865 samplesPerPixel:[srcImageRep samplesPerPixel] 866 hasAlpha:[srcImageRep hasAlpha] 867 isPlanar:NO 868 colorSpaceName:[srcImageRep colorSpaceName] 869 bytesPerRow:0 870 bitsPerPixel:0]; 871 872 destBytesPerRow = [destImageRep bytesPerRow]; 873 destBytesPerPixel = [destImageRep bitsPerPixel] / 8; 874 srcData = [srcImageRep bitmapData]; 875 destData = [destImageRep bitmapData]; 876 if ([srcImageRep samplesPerPixel] == 2) 877 { 878 for (y = 0; y < h; y++) 879 for (x = 0; x < w; x++) 880 { 881 p1 = srcData + srcBytesPerRow * y + srcBytesPerPixel * x; 882 p2 = destData + destBytesPerRow * y + destBytesPerPixel * x; 883 p2[0] = p1[1]; 884 p2[1] = p1[0]; 885 } 886 } 887 else 888 { 889 for (y = 0; y < h; y++) 890 for (x = 0; x < w; x++) 891 { 892 p1 = srcData + srcBytesPerRow * y + srcBytesPerPixel * x; 893 p2 = destData + destBytesPerRow * y + destBytesPerPixel * x; 894 p2[0] = p1[1]; 895 p2[1] = p1[2]; 896 p2[2] = p1[3]; 897 p2[3] = p1[0]; 898 } 899 } 900 [destImageRep setProperty:NSImageEXIFData withValue:[imgProps objectForKey:NSImageEXIFData]]; 901 [destImageRep setSize:[srcImageRep size]]; 902 [destImage addRepresentation:destImageRep]; 903 [destImageRep release]; 904 [srcImage release]; 905 srcImage = destImage; 906 [imgProps release]; 907 } 908 } 909 else /* for 16 bit */ 910 { 911 } 912 } 913#endif 914 915 newW = [srcImageRep pixelsWide]; 916 newH = [srcImageRep pixelsHigh]; 917 aspectRatio = (double)newW / newH; 918 NSLog(@"aspect ratio: %f", aspectRatio); 919 switch([popupConstraints indexOfSelectedItem]) 920 { 921 case 0: /* none */ 922 break; 923 case 1: /* width */ 924 newW = givenWidth; 925 newH = givenWidth / aspectRatio; 926 break; 927 case 2: /* height */ 928 newH = givenHeight; 929 newW = givenHeight * aspectRatio; 930 break; 931 case 3: /* both */ 932 newW = givenWidth; 933 newH = givenWidth / aspectRatio; 934 if (newH > givenHeight) 935 { 936 newH = givenHeight; 937 newW = givenHeight * aspectRatio; 938 } 939 break; 940 case 4: /* largest side */ 941 if ([srcImageRep pixelsHigh] > [srcImageRep pixelsWide]) 942 { 943 newH = givenWidth; 944 newW = givenWidth * aspectRatio; 945 } 946 else 947 { 948 newW = givenWidth; 949 newH = givenWidth / aspectRatio; 950 } 951 break; 952 default: 953 NSLog(@"Unexpected constraint selection value."); 954 } 955 956 if (newW == [srcImageRep pixelsWide]) 957 { 958 NSLog(@"nothing"); 959 scaledImageRep = srcImageRep; 960 } 961 else 962 { 963 NSImage *scaledImage; 964 PRScale *scaleFilter; 965 966 scaleFilter = [[PRScale alloc] init]; 967 scaledImage = [scaleFilter scaleImage:srcImage :newW :newH :BILINEAR :nil]; 968 [scaleFilter release]; 969 scaledImageRep = [NSBitmapImageRep imageRepWithData:[scaledImage TIFFRepresentation]]; 970 } 971 972 if ([popupFileType indexOfSelectedItem] == 0) 973 { 974 repProperties = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:NSTIFFCompressionLZW] forKey:NSImageCompressionMethod]; 975 dataOfRep = [scaledImageRep representationUsingType: NSTIFFFileType properties:repProperties]; 976 } 977 else 978 { 979 float quality; 980 981 switch ([popupFileQuality indexOfSelectedItem]) 982 { 983 case 0: 984 quality = 1.0; 985 break; 986 case 1: 987 quality = 0.75; 988 break; 989 case 2: 990 quality = 0.66; 991 break; 992 case 3: 993 quality = 0.4; 994 break; 995 default: 996 quality = 0.5; 997 } 998 repProperties = [NSDictionary dictionaryWithObject:[NSNumber numberWithFloat:quality] forKey:NSImageCompressionFactor]; 999 dataOfRep = [scaledImageRep representationUsingType: NSJPEGFileType properties:repProperties]; 1000 } 1001 [srcImage release]; 1002 1003 destFileName = [destFolder stringByAppendingPathComponent:[filenameNoExtension stringByAppendingPathExtension: destFileExtension]]; 1004 NSLog(@"%@", destFileName); 1005 if (dataOfRep != nil) 1006 { 1007 [dataOfRep writeToFile:destFileName atomically:NO]; 1008 } 1009 [exportProgress setDoubleValue: ((double)(i+1)*100)/(double)[fileListView numberOfRows]]; 1010 [exporterPanel displayIfNeeded]; 1011 [pool release]; 1012 } 1013} 1014 1015/* preferences */ 1016- (IBAction)showPreferences:(id)sender 1017{ 1018 NSUserDefaults *defaults; 1019 1020 defaults = [NSUserDefaults standardUserDefaults]; 1021 1022 [destroyOrRecycleButton setState:[defaults boolForKey:LM_KEY_DESTROYRECYCLE] ? NSOnState : NSOffState]; 1023 [askBeforeDeletingButton setState:[defaults boolForKey:LM_KEY_ASKDELETING] ? NSOnState : NSOffState]; 1024 [prefPanel makeKeyAndOrderFront: sender]; 1025} 1026 1027- (IBAction)savePreferences:(id)sender 1028{ 1029 NSUserDefaults *defaults; 1030 1031 defaults = [NSUserDefaults standardUserDefaults]; 1032 [defaults setBool:[destroyOrRecycleButton state] forKey:LM_KEY_DESTROYRECYCLE]; 1033 [defaults setBool:[askBeforeDeletingButton state] forKey:LM_KEY_ASKDELETING]; 1034 [prefPanel performClose:self]; 1035} 1036 1037- (IBAction)cancelPreferences:(id)sender 1038{ 1039 [prefPanel performClose:self]; 1040} 1041 1042/* printing */ 1043- (void)print:(id)sender 1044{ 1045 [view print:sender]; 1046} 1047 1048@end 1049