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