OSDN Git Service

MacGui: Fix issue where renaming a preset does not reflect the new name immediately...
[handbrake-jp/handbrake-jp-git.git] / macosx / PictureController.mm
1 /* $Id: PictureController.mm,v 1.11 2005/08/01 15:10:44 titer Exp $
2
3    This file is part of the HandBrake source code.
4    Homepage: <http://handbrake.m0k.org/>.
5    It may be used under the terms of the GNU General Public License. */
6
7 #include "PictureController.h"
8
9 @interface PictureController (Private)
10
11 - (NSSize)optimalViewSizeForImageSize: (NSSize)imageSize;
12 - (void)resizeSheetForViewSize: (NSSize)viewSize;
13 - (void)setViewSize: (NSSize)viewSize;
14 - (BOOL)viewNeedsToResizeToSize: (NSSize)newSize;
15
16 @end
17
18 @implementation PictureController
19
20 - (id)initWithDelegate:(id)del
21 {
22         if (self = [super init])
23         {
24                 delegate = del;
25         [self loadMyNibFile];
26         fPicturePreviews = [[NSMutableDictionary dictionaryWithCapacity: HB_NUM_HBLIB_PICTURES] retain];
27         }
28         return self;
29 }
30
31 - (void) dealloc
32 {
33     [fPicturePreviews release];
34     [super dealloc];
35 }
36
37 - (void) SetHandle: (hb_handle_t *) handle
38 {
39     fHandle = handle;
40
41     [fWidthStepper  setValueWraps: NO];
42     [fWidthStepper  setIncrement: 16];
43     [fWidthStepper  setMinValue: 64];
44     [fHeightStepper setValueWraps: NO];
45     [fHeightStepper setIncrement: 16];
46     [fHeightStepper setMinValue: 64];
47
48     [fCropTopStepper    setIncrement: 2];
49     [fCropTopStepper    setMinValue:  0];
50     [fCropBottomStepper setIncrement: 2];
51     [fCropBottomStepper setMinValue:  0];
52     [fCropLeftStepper   setIncrement: 2];
53     [fCropLeftStepper   setMinValue:  0];
54     [fCropRightStepper  setIncrement: 2];
55     [fCropRightStepper  setMinValue:  0];
56 }
57
58 - (void) SetTitle: (hb_title_t *) title
59 {
60     hb_job_t * job = title->job;
61
62     fTitle = title;
63
64     [fWidthStepper      setMaxValue: title->width];
65     [fWidthStepper      setIntValue: job->width];
66     [fWidthField        setIntValue: job->width];
67     [fHeightStepper     setMaxValue: title->height];
68     [fHeightStepper     setIntValue: job->height];
69     [fHeightField       setIntValue: job->height];
70     [fRatioCheck        setState:    job->keep_ratio ? NSOnState : NSOffState];
71     [fCropTopStepper    setMaxValue: title->height/2-2];
72     [fCropBottomStepper setMaxValue: title->height/2-2];
73     [fCropLeftStepper   setMaxValue: title->width/2-2];
74     [fCropRightStepper  setMaxValue: title->width/2-2];
75
76     /* Populate the Anamorphic NSPopUp button here */
77     [fAnamorphicPopUp removeAllItems];
78     [fAnamorphicPopUp addItemWithTitle: @"None"];
79     [fAnamorphicPopUp addItemWithTitle: @"Strict"];
80     if (allowLooseAnamorphic)
81     {
82     [fAnamorphicPopUp addItemWithTitle: @"Loose"];
83     }
84     [fAnamorphicPopUp selectItemAtIndex: job->pixel_ratio];
85     
86     /* We initially set the previous state of keep ar to on */
87     keepAspectRatioPreviousState = 1;
88         if (!autoCrop)
89         {
90         [fCropMatrix  selectCellAtRow: 1 column:0];
91         /* If auto, lets set the crop steppers according to current job->crop values */
92         [fCropTopStepper    setIntValue: job->crop[0]];
93         [fCropTopField      setIntValue: job->crop[0]];
94         [fCropBottomStepper setIntValue: job->crop[1]];
95         [fCropBottomField   setIntValue: job->crop[1]];
96         [fCropLeftStepper   setIntValue: job->crop[2]];
97         [fCropLeftField     setIntValue: job->crop[2]];
98         [fCropRightStepper  setIntValue: job->crop[3]];
99         [fCropRightField    setIntValue: job->crop[3]];
100         }
101         else
102         {
103         [fCropMatrix  selectCellAtRow: 0 column:0];
104         }
105         
106         /* Set filters widgets according to the filters struct */
107         [fVFRCheck setState:fPictureFilterSettings.vfr];
108     [fDetelecineCheck setState:fPictureFilterSettings.detelecine];
109     [fDeinterlacePopUp selectItemAtIndex: fPictureFilterSettings.deinterlace];
110     [fDenoisePopUp selectItemAtIndex: fPictureFilterSettings.denoise];
111     [fDeblockCheck setState: fPictureFilterSettings.deblock];
112     
113     fPicture = 0;
114     MaxOutputWidth = title->width - job->crop[2] - job->crop[3];
115     MaxOutputHeight = title->height - job->crop[0] - job->crop[1];
116     [self SettingsChanged: nil];
117 }
118
119 /* we use this to setup the initial picture filters upon first launch, after that their states
120 are maintained across different sources */
121 - (void) setInitialPictureFilters
122 {
123         /* we use a popup to show the deinterlace settings */
124         [fDeinterlacePopUp removeAllItems];
125     [fDeinterlacePopUp addItemWithTitle: @"None"];
126     [fDeinterlacePopUp addItemWithTitle: @"Fast"];
127     [fDeinterlacePopUp addItemWithTitle: @"Slow"];
128         [fDeinterlacePopUp addItemWithTitle: @"Slower"];
129     
130         /* Set deinterlaces level according to the integer in the main window */
131         [fDeinterlacePopUp selectItemAtIndex: fPictureFilterSettings.deinterlace];
132
133         /* we use a popup to show the denoise settings */
134         [fDenoisePopUp removeAllItems];
135     [fDenoisePopUp addItemWithTitle: @"None"];
136     [fDenoisePopUp addItemWithTitle: @"Weak"];
137         [fDenoisePopUp addItemWithTitle: @"Medium"];
138     [fDenoisePopUp addItemWithTitle: @"Strong"];
139         /* Set denoises level according to the integer in the main window */
140         [fDenoisePopUp selectItemAtIndex: fPictureFilterSettings.denoise];
141
142 }
143
144 // Adjusts the window to draw the current picture (fPicture) adjusting its size as
145 // necessary to display as much of the picture as possible.
146 - (void) displayPreview
147 {
148     [fPictureView setImage: [self imageForPicture: fPicture]];
149         
150         NSSize displaySize = NSMakeSize( (float)fTitle->width, (float)fTitle->height );
151     /* Set the picture size display fields below the Preview Picture*/
152     if( fTitle->job->pixel_ratio == 1 ) // Original PAR Implementation
153     {
154         output_width = fTitle->width-fTitle->job->crop[2]-fTitle->job->crop[3];
155         output_height = fTitle->height-fTitle->job->crop[0]-fTitle->job->crop[1];
156         display_width = output_width * fTitle->job->pixel_aspect_width / fTitle->job->pixel_aspect_height;
157         [fInfoField setStringValue:[NSString stringWithFormat:
158                                     @"Source: %dx%d, Output: %dx%d, Anamorphic: %dx%d",
159                                     fTitle->width, fTitle->height, output_width, output_height, display_width, output_height]];
160         displaySize.width *= ((float)fTitle->job->pixel_aspect_width) / ((float)fTitle->job->pixel_aspect_height);   
161     }
162     else if (fTitle->job->pixel_ratio == 2) // Loose Anamorphic
163     {
164         display_width = output_width * output_par_width / output_par_height;
165         [fInfoField setStringValue:[NSString stringWithFormat:
166                                     @"Source: %dx%d, Output: %dx%d, Anamorphic: %dx%d",
167                                     fTitle->width, fTitle->height, output_width, output_height, display_width, output_height]];
168         
169         /* FIXME: needs to be fixed so that the picture window does not resize itself on the first
170          anamorphic width drop
171          */
172         if (fTitle->width - 8 < output_width)
173         {
174             displaySize.width *= ((float)output_par_width) / ((float)output_par_height);
175         }
176     }
177     else // No Anamorphic
178     {
179         [fInfoField setStringValue: [NSString stringWithFormat:
180                                      @"Source: %dx%d, Output: %dx%d", fTitle->width, fTitle->height,
181                                      fTitle->job->width, fTitle->job->height]];
182     }
183     
184     NSSize viewSize = [self optimalViewSizeForImageSize:displaySize];
185     if( [self viewNeedsToResizeToSize:viewSize] )
186     {
187         [self resizeSheetForViewSize:viewSize];
188         [self setViewSize:viewSize];
189     }
190     
191     // Show the scaled text (use the height to check since the width can vary
192     // with anamorphic video).
193     if( ((int)viewSize.height) != fTitle->height )
194     {
195         float scale = viewSize.width / ((float)fTitle->width);
196         NSString *scaleString = [NSString stringWithFormat:
197                                  NSLocalizedString( @" (Preview scaled to %.0f%% actual size)",
198                                                    @"String shown when a preview is scaled" ),
199                                  scale * 100.0];
200         [fInfoField setStringValue:
201          [[fInfoField stringValue] stringByAppendingString:scaleString]];
202     }
203     
204     [fPrevButton setEnabled: ( fPicture > 0 )];
205     [fNextButton setEnabled: ( fPicture < 9 )];
206 }
207
208 - (IBAction) SettingsChanged: (id) sender
209 {
210     hb_job_t * job = fTitle->job;
211     
212         if( [fAnamorphicPopUp indexOfSelectedItem] > 0 )
213         {
214         if ([fAnamorphicPopUp indexOfSelectedItem] == 2) // Loose anamorphic
215         {
216             job->pixel_ratio = 2;
217             [fWidthStepper setEnabled: YES];
218             [fWidthField setEnabled: YES];
219             /* We set job->width and call hb_set_anamorphic_size in libhb to do a "dry run" to get
220              * the values to be used by libhb for loose anamorphic
221              */
222             job->width       = [fWidthStepper  intValue];
223             hb_set_anamorphic_size(job, &output_width, &output_height, &output_par_width, &output_par_height);
224             [fHeightStepper      setIntValue: output_height];
225             [fHeightField        setIntValue: output_height];
226             job->height      = [fHeightStepper intValue];
227             
228         }
229         else // must be "1" or strict anamorphic
230         {
231             [fWidthStepper      setIntValue: fTitle->width-fTitle->job->crop[2]-fTitle->job->crop[3]];
232             [fWidthField        setIntValue: fTitle->width-fTitle->job->crop[2]-fTitle->job->crop[3]];
233             
234             /* This will show correct anamorphic height values, but
235              show distorted preview picture ratio */
236             [fHeightStepper      setIntValue: fTitle->height-fTitle->job->crop[0]-fTitle->job->crop[1]];
237             [fHeightField        setIntValue: fTitle->height-fTitle->job->crop[0]-fTitle->job->crop[1]];
238             job->width       = [fWidthStepper  intValue];
239             job->height      = [fHeightStepper intValue];
240             
241             job->pixel_ratio = 1;
242             [fWidthStepper setEnabled: NO];
243             [fWidthField setEnabled: NO];
244         }
245         
246         /* if the sender is the Anamorphic checkbox, record the state
247          of KeepAspect Ratio so it can be reset if Anamorphic is unchecked again */
248         if (sender == fAnamorphicPopUp)
249         {
250             keepAspectRatioPreviousState = [fRatioCheck state];
251         }
252         [fRatioCheck setState:NSOffState];
253         [fRatioCheck setEnabled: NO];
254         
255         
256         [fHeightStepper setEnabled: NO];
257         [fHeightField setEnabled: NO];
258         
259     }
260     else
261         {
262         job->width       = [fWidthStepper  intValue];
263         job->height      = [fHeightStepper intValue];
264         job->pixel_ratio = 0;
265         [fWidthStepper setEnabled: YES];
266         [fWidthField setEnabled: YES];
267         [fHeightStepper setEnabled: YES];
268         [fHeightField setEnabled: YES];
269         [fRatioCheck setEnabled: YES];
270         /* if the sender is the Anamorphic checkbox, we return the
271          keep AR checkbox to its previous state */
272         if (sender == fAnamorphicPopUp)
273         {
274             [fRatioCheck setState:keepAspectRatioPreviousState];
275         }
276         
277         }
278         
279     
280     job->keep_ratio  = ( [fRatioCheck state] == NSOnState );
281     
282         fPictureFilterSettings.deinterlace = [fDeinterlacePopUp indexOfSelectedItem];
283     /* if the gui deinterlace settings are fast through slowest, the job->deinterlace
284      value needs to be set to one, for the job as well as the previews showing deinterlacing
285      otherwise set job->deinterlace to 0 or "off" */
286     if (fPictureFilterSettings.deinterlace > 0)
287     {
288         job->deinterlace  = 1;
289     }
290     else
291     {
292         job->deinterlace  = 0;
293     }
294     fPictureFilterSettings.denoise     = [fDenoisePopUp indexOfSelectedItem];
295     fPictureFilterSettings.vfr  = [fVFRCheck state];
296     if (fPictureFilterSettings.vfr > 0)
297     {
298         [fDetelecineCheck setState:NSOnState];
299         [fDetelecineCheck setEnabled: NO];
300     }
301     else
302     {
303         [fDetelecineCheck setEnabled: YES];
304     }
305     fPictureFilterSettings.detelecine  = [fDetelecineCheck state];
306     fPictureFilterSettings.deblock  = [fDeblockCheck state];
307         //job->pixel_ratio = ( [fPARCheck state] == NSOnState );
308     
309     autoCrop = ( [fCropMatrix selectedRow] == 0 );
310     [fCropTopStepper    setEnabled: !autoCrop];
311     [fCropBottomStepper setEnabled: !autoCrop];
312     [fCropLeftStepper   setEnabled: !autoCrop];
313     [fCropRightStepper  setEnabled: !autoCrop];
314     
315     if( autoCrop )
316     {
317         memcpy( job->crop, fTitle->crop, 4 * sizeof( int ) );
318     }
319     else
320     {
321         job->crop[0] = [fCropTopStepper    intValue];
322         job->crop[1] = [fCropBottomStepper intValue];
323         job->crop[2] = [fCropLeftStepper   intValue];
324         job->crop[3] = [fCropRightStepper  intValue];
325     }
326     
327     if( job->keep_ratio )
328     {
329         if( sender == fWidthStepper || sender == fRatioCheck ||
330            sender == fCropTopStepper || sender == fCropBottomStepper )
331         {
332             hb_fix_aspect( job, HB_KEEP_WIDTH );
333             if( job->height > fTitle->height )
334             {
335                 job->height = fTitle->height;
336                 hb_fix_aspect( job, HB_KEEP_HEIGHT );
337             }
338         }
339         else
340         {
341             hb_fix_aspect( job, HB_KEEP_HEIGHT );
342             if( job->width > fTitle->width )
343             {
344                 job->width = fTitle->width;
345                 hb_fix_aspect( job, HB_KEEP_WIDTH );
346             }
347         }
348     }
349     
350     [fWidthStepper      setIntValue: job->width];
351     [fWidthField        setIntValue: job->width];
352     if( [fAnamorphicPopUp indexOfSelectedItem] < 2 )
353         {
354         [fHeightStepper     setIntValue: job->height];
355         [fHeightField       setIntValue: job->height];
356     }
357     [fCropTopStepper    setIntValue: job->crop[0]];
358     [fCropTopField      setIntValue: job->crop[0]];
359     [fCropBottomStepper setIntValue: job->crop[1]];
360     [fCropBottomField   setIntValue: job->crop[1]];
361     [fCropLeftStepper   setIntValue: job->crop[2]];
362     [fCropLeftField     setIntValue: job->crop[2]];
363     [fCropRightStepper  setIntValue: job->crop[3]];
364     [fCropRightField    setIntValue: job->crop[3]];
365     /* Sanity Check Here for < 16 px preview to avoid
366      crashing hb_get_preview. In fact, just for kicks
367      lets getting previews at a min limit of 32, since
368      no human can see any meaningful detail below that */
369     if (job->width >= 64 && job->height >= 64)
370     {
371         // Purge the existing picture previews so they get recreated the next time
372         // they are needed.
373         [self purgeImageCache];
374         [self displayPreview];
375     }
376 }
377
378 - (IBAction) PreviousPicture: (id) sender
379 {   
380     if( fPicture <= 0 )
381     {
382         return;
383     }
384     fPicture--;
385     [self displayPreview];
386 }
387
388 - (IBAction) NextPicture: (id) sender
389 {
390     if( fPicture >= 9 )
391     {
392         return;
393     }
394     fPicture++;
395     [self displayPreview];
396 }
397
398 - (IBAction) ClosePanel: (id) sender
399 {
400     if ([delegate respondsToSelector:@selector(pictureSettingsDidChange)])
401         [delegate pictureSettingsDidChange];
402         
403     [NSApp endSheet: fPicturePanel];
404     [fPicturePanel orderOut: self];
405 }
406
407 - (BOOL) autoCrop
408 {
409     return autoCrop;
410 }
411 - (void) setAutoCrop: (BOOL) setting
412 {
413     autoCrop = setting;
414 }
415
416 - (BOOL) allowLooseAnamorphic
417 {
418     return allowLooseAnamorphic;
419 }
420
421 - (void) setAllowLooseAnamorphic: (BOOL) setting
422 {
423     allowLooseAnamorphic = setting;
424 }
425
426 - (int) detelecine
427 {
428     return fPictureFilterSettings.detelecine;
429 }
430
431 - (void) setDetelecine: (int) setting
432 {
433     fPictureFilterSettings.detelecine = setting;
434 }
435
436 - (int) vfr
437 {
438     return fPictureFilterSettings.vfr;
439 }
440
441 - (void) setVFR: (int) setting
442 {
443     fPictureFilterSettings.vfr = setting;
444 }
445
446 - (int) deinterlace
447 {
448     return fPictureFilterSettings.deinterlace;
449 }
450
451 - (void) setDeinterlace: (int) setting {
452     fPictureFilterSettings.deinterlace = setting;
453 }
454
455 - (int) denoise
456 {
457     return fPictureFilterSettings.denoise;
458 }
459
460 - (void) setDenoise: (int) setting
461 {
462     fPictureFilterSettings.denoise = setting;
463 }
464
465 - (int) deblock
466 {
467     return fPictureFilterSettings.deblock;
468 }
469
470 - (void) setDeblock: (int) setting
471 {
472     fPictureFilterSettings.deblock = setting;
473 }
474
475 - (void)showPanelInWindow: (NSWindow *)fWindow forTitle: (hb_title_t *)title
476 {
477     [self SetTitle:title];
478     
479     [NSApp beginSheet:fPicturePanel
480        modalForWindow:fWindow
481         modalDelegate:nil
482        didEndSelector:nil
483           contextInfo:NULL];
484 }
485
486
487 // This function converts an image created by libhb (specified via pictureIndex) into
488 // an NSImage suitable for the GUI code to use. If removeBorders is YES,
489 // makeImageForPicture crops the image generated by libhb stripping off the gray
490 // border around the content. This is the low-level method that generates the image.
491 // -imageForPicture calls this function whenever it can't find an image in its cache.
492 + (NSImage *) makeImageForPicture: (int)pictureIndex
493                 libhb:(hb_handle_t*)handle
494                 title:(hb_title_t*)title
495                 removeBorders:(BOOL)removeBorders
496 {
497     if (removeBorders)
498     {
499         //     |<---------- title->width ----------->|
500         //     |   |<---- title->job->width ---->|   |
501         //     |   |                             |   |
502         //     .......................................
503         //     ....+-----------------------------+....
504         //     ....|                             |....<-- gray border
505         //     ....|                             |....
506         //     ....|                             |....
507         //     ....|                             |<------- image
508         //     ....|                             |....
509         //     ....|                             |....
510         //     ....|                             |....
511         //     ....|                             |....
512         //     ....|                             |....
513         //     ....+-----------------------------+....
514         //     .......................................
515
516         static uint8_t * buffer;
517         static int bufferSize;
518
519         // Make sure we have a big enough buffer to receive the image from libhb. libhb
520         // creates images with a one-pixel border around the original content. Hence we
521         // add 2 pixels horizontally and vertically to the buffer size.
522         int srcWidth = title->width + 2;
523         int srcHeight= title->height + 2;
524         int newSize;
525         newSize = srcWidth * srcHeight * 4;
526         if( bufferSize < newSize )
527         {
528             bufferSize = newSize;
529             buffer     = (uint8_t *) realloc( buffer, bufferSize );
530         }
531
532         hb_get_preview( handle, title, pictureIndex, buffer );
533
534         // Create an NSBitmapImageRep and copy the libhb image into it, converting it from
535         // libhb's format to one suitable for NSImage. Along the way, we'll strip off the
536         // border around libhb's image.
537         
538         // The image data returned by hb_get_preview is 4 bytes per pixel, BGRA format.
539         // Alpha is ignored.
540         
541         int dstWidth = title->job->width;
542         int dstHeight = title->job->height;
543         NSBitmapFormat bitmapFormat = (NSBitmapFormat)NSAlphaFirstBitmapFormat;
544         NSBitmapImageRep * imgrep = [[[NSBitmapImageRep alloc]
545                 initWithBitmapDataPlanes:nil
546                 pixelsWide:dstWidth
547                 pixelsHigh:dstHeight
548                 bitsPerSample:8
549                 samplesPerPixel:3   // ignore alpha
550                 hasAlpha:NO
551                 isPlanar:NO
552                 colorSpaceName:NSCalibratedRGBColorSpace
553                 bitmapFormat:bitmapFormat
554                 bytesPerRow:dstWidth * 4
555                 bitsPerPixel:32] autorelease];
556
557         int borderTop = (srcHeight - dstHeight) / 2;
558         int borderLeft = (srcWidth - dstWidth) / 2;
559         
560         UInt32 * src = (UInt32 *)buffer;
561         UInt32 * dst = (UInt32 *)[imgrep bitmapData];
562         src += borderTop * srcWidth;    // skip top rows in src to get to first row of dst
563         src += borderLeft;              // skip left pixels in src to get to first pixel of dst
564         for (int r = 0; r < dstHeight; r++)
565         {
566             for (int c = 0; c < dstWidth; c++)
567 #if TARGET_RT_LITTLE_ENDIAN
568                 *dst++ = Endian32_Swap(*src++);
569 #else
570                 *dst++ = *src++;
571 #endif
572             src += (srcWidth - dstWidth);   // skip to next row in src
573         }
574
575         NSImage * img = [[[NSImage alloc] initWithSize: NSMakeSize(dstWidth, dstHeight)] autorelease];
576         [img addRepresentation:imgrep];
577
578         return img;
579     }
580     else
581     {
582         // Make sure we have big enough buffer
583         static uint8_t * buffer;
584         static int bufferSize;
585
586         int newSize;
587         newSize = ( title->width + 2 ) * (title->height + 2 ) * 4;
588         if( bufferSize < newSize )
589         {
590             bufferSize = newSize;
591             buffer     = (uint8_t *) realloc( buffer, bufferSize );
592         }
593
594         hb_get_preview( handle, title, pictureIndex, buffer );
595
596         // The image data returned by hb_get_preview is 4 bytes per pixel, BGRA format.
597         // We'll copy that into an NSImage swapping it to ARGB in the process. Alpha is
598         // ignored.
599         int width = title->width + 2;      // hblib adds a one-pixel border to the image
600         int height = title->height + 2;
601         int numPixels = width * height;
602         NSBitmapFormat bitmapFormat = (NSBitmapFormat)NSAlphaFirstBitmapFormat;
603         NSBitmapImageRep * imgrep = [[[NSBitmapImageRep alloc]
604                 initWithBitmapDataPlanes:nil
605                 pixelsWide:width
606                 pixelsHigh:height
607                 bitsPerSample:8
608                 samplesPerPixel:3   // ignore alpha
609                 hasAlpha:NO
610                 isPlanar:NO
611                 colorSpaceName:NSCalibratedRGBColorSpace
612                 bitmapFormat:bitmapFormat
613                 bytesPerRow:width * 4
614                 bitsPerPixel:32] autorelease];
615
616         UInt32 * src = (UInt32 *)buffer;
617         UInt32 * dst = (UInt32 *)[imgrep bitmapData];
618         for (int i = 0; i < numPixels; i++)
619 #if TARGET_RT_LITTLE_ENDIAN
620             *dst++ = Endian32_Swap(*src++);
621 #else
622             *dst++ = *src++;
623 #endif
624
625         NSImage * img = [[[NSImage alloc] initWithSize: NSMakeSize(width, height)] autorelease];
626         [img addRepresentation:imgrep];
627
628         return img;
629     }
630 }
631
632 // Returns the preview image for the specified index, retrieving it from its internal
633 // cache or by calling makeImageForPicture if it is not cached. Generally, you should
634 // use imageForPicture so that images are cached. Calling makeImageForPicture will
635 // always generate a new copy of the image.
636 - (NSImage *) imageForPicture: (int) pictureIndex
637 {
638     // The preview for the specified index may not currently exist, so this method
639     // generates it if necessary.
640     NSString * key = [NSString stringWithFormat:@"%d", pictureIndex];
641     NSImage * theImage = [fPicturePreviews objectForKey:key];
642     if (!theImage)
643     {
644         theImage = [PictureController makeImageForPicture:pictureIndex libhb:fHandle title:fTitle removeBorders: NO];
645         [fPicturePreviews setObject:theImage forKey:key];
646     }
647     return theImage;
648 }
649
650 // Purges all images from the cache. The next call to imageForPicture will cause a new
651 // image to be generated.
652 - (void) purgeImageCache
653 {
654     [fPicturePreviews removeAllObjects];
655 }
656
657 - (BOOL) loadMyNibFile
658 {
659     if(![NSBundle loadNibNamed:@"PictureSettings" owner:self])
660     {
661         NSLog(@"Warning! Could not load myNib file.\n");
662         return NO;
663     }
664     
665     return YES;
666 }
667
668 @end
669
670 @implementation PictureController (Private)
671
672 //
673 // -[PictureController(Private) optimalViewSizeForImageSize:]
674 //
675 // Given the size of the preview image to be shown, returns the best possible
676 // size for the OpenGL view.
677 //
678 - (NSSize)optimalViewSizeForImageSize: (NSSize)imageSize
679 {
680     // The min size is 320x240
681     float minWidth = 320.0;
682     float minHeight = 240.0;
683     
684     // The max size of the view is when the sheet is taking up 85% of the screen.
685     NSSize screenSize = [[NSScreen mainScreen] frame].size;
686     NSSize sheetSize = [fPicturePanel frame].size;
687     NSSize viewAreaSize = [fPictureViewArea frame].size;
688     float paddingX = sheetSize.width - viewAreaSize.width;
689     float paddingY = sheetSize.height - viewAreaSize.height;
690     float maxWidth = (0.85 * screenSize.width) - paddingX;
691     float maxHeight = (0.85 * screenSize.height) - paddingY;
692     
693     NSSize resultSize = imageSize;
694     
695     // Its better to have a view that's too small than a view that's too big, so
696     // apply the maximum constraints last.
697     if( resultSize.width < minWidth )
698     {
699         resultSize.height *= (minWidth / resultSize.width);
700         resultSize.width = minWidth;
701     }
702     if( resultSize.height < minHeight )
703     {
704         resultSize.width *= (minHeight / resultSize.height);
705         resultSize.height = minHeight;
706     }
707     if( resultSize.width > maxWidth )
708     {
709         resultSize.height *= (maxWidth / resultSize.width);
710         resultSize.width = maxWidth;
711     }
712     if( resultSize.height > maxHeight )
713     {
714         resultSize.width *= (maxHeight / resultSize.height);
715         resultSize.height = maxHeight;
716     }
717     
718     return resultSize;
719 }
720
721 //
722 // -[PictureController(Private) resizePanelForViewSize:animate:]
723 //
724 // Resizes the entire sheet to accomodate an OpenGL view of a particular size.
725 //
726 - (void)resizeSheetForViewSize: (NSSize)viewSize
727 {
728     // Figure out the deltas for the new frame area
729     NSSize currentSize = [fPictureViewArea frame].size;
730     float deltaX = viewSize.width - currentSize.width;
731     float deltaY = viewSize.height - currentSize.height;
732     
733     // Now resize the whole panel by those same deltas, but don't exceed the min
734     NSRect frame = [fPicturePanel frame];
735     NSSize maxSize = [fPicturePanel maxSize];
736     NSSize minSize = [fPicturePanel minSize];
737     frame.size.width += deltaX;
738     frame.size.height += deltaY;
739     if( frame.size.width < minSize.width )
740     {
741         frame.size.width = minSize.width;
742     }
743     if( frame.size.height < minSize.height )
744     {
745         frame.size.height = minSize.height;
746     }
747
748     // But now the sheet is off-center, so also shift the origin to center it and
749     // keep the top aligned.
750     frame.origin.x -= (deltaX / 2.0);
751     frame.origin.y -= deltaY;
752
753     [fPicturePanel setFrame:frame display:YES animate:YES];
754 }
755
756 //
757 // -[PictureController(Private) setViewSize:]
758 //
759 // Changes the OpenGL view's size and centers it vertially inside of its area.
760 // Assumes resizeSheetForViewSize: has already been called.
761 //
762 - (void)setViewSize: (NSSize)viewSize
763 {
764     [fPictureView setFrameSize:viewSize];
765     
766     // center it vertically
767     NSPoint origin = [fPictureViewArea frame].origin;
768     origin.y += ([fPictureViewArea frame].size.height -
769                  [fPictureView frame].size.height) / 2.0;
770     [fPictureView setFrameOrigin:origin];
771 }
772
773 //
774 // -[PictureController(Private) viewNeedsToResizeToSize:]
775 //
776 // Returns YES if the view will need to resize to match the given size.
777 //
778 - (BOOL)viewNeedsToResizeToSize: (NSSize)newSize
779 {
780     NSSize viewSize = [fPictureView frame].size;
781     return (newSize.width != viewSize.width || newSize.height != viewSize.height);
782 }
783
784
785 @end