OSDN Git Service

MacGui: Do not call pictureSliderChanged in windowWillClose in HBPreviewController...
[handbrake-jp/handbrake-jp-git.git] / macosx / HBPreviewController.m
1 /* $Id: HBPreviewController.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.fr/>.
5    It may be used under the terms of the GNU General Public License. */
6
7 #import "HBPreviewController.h"
8 #import "Controller.h"
9
10 @implementation QTMovieView ( HBQTkitExt )
11 - (void) mouseMoved:(NSEvent *)theEvent
12 {
13     [super mouseMoved:theEvent];
14 }
15 @end
16
17
18
19 @interface PreviewController (Private)
20
21 - (NSSize)optimalViewSizeForImageSize: (NSSize)imageSize;
22 - (void)resizeSheetForViewSize: (NSSize)viewSize;
23 - (void)setViewSize: (NSSize)viewSize;
24 - (BOOL)viewNeedsToResizeToSize: (NSSize)newSize;
25
26 @end
27
28 @implementation PreviewController
29
30 - (id)init
31 {
32         if (self = [super initWithWindowNibName:@"PicturePreview"])
33         {
34         // NSWindowController likes to lazily load its window. However since
35         // this controller tries to set all sorts of outlets before the window
36         // is displayed, we need it to load immediately. The correct way to do
37         // this, according to the documentation, is simply to invoke the window
38         // getter once.
39         //
40         // If/when we switch a lot of this stuff to bindings, this can probably
41         // go away.
42         [self window];
43         
44                 fPicturePreviews = [[NSMutableDictionary dictionaryWithCapacity: HB_NUM_HBLIB_PICTURES] retain];
45         /* Init libhb with check for updates libhb style set to "0" so its ignored and lets sparkle take care of it */
46         int loggingLevel = [[[NSUserDefaults standardUserDefaults] objectForKey:@"LoggingLevel"] intValue];
47         fPreviewLibhb = hb_init(loggingLevel, 0);
48         
49         
50
51         }
52         return self;
53 }
54
55
56 //------------------------------------------------------------------------------------
57 // Displays and brings the picture window to the front
58 //------------------------------------------------------------------------------------
59 - (IBAction) showPreviewWindow: (id)sender
60 {
61     [self showWindow:sender];
62     [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"PreviewWindowIsOpen"];
63     
64     /* lets set the preview window to accept mouse moved events */
65     [fPreviewWindow setAcceptsMouseMovedEvents:YES];
66     hudTimerSeconds = 0;
67     [self pictureSliderChanged:nil];
68     [self startReceivingLibhbNotifications];
69 }
70
71 - (void)setHBController: (HBController *)controller
72 {
73     fHBController = controller;
74 }
75
76 - (void)awakeFromNib
77 {
78     [fPreviewWindow setDelegate:self];
79     if( ![[self window] setFrameUsingName:@"Preview"] )
80         [[self window] center];
81     [self setWindowFrameAutosaveName:@"Preview"];
82     [[self window] setExcludedFromWindowsMenu:YES];
83     
84     /* lets set the preview window to accept mouse moved events */
85     [fPreviewWindow setAcceptsMouseMovedEvents:YES];
86     //[self pictureSliderChanged:nil];
87     [self startReceivingLibhbNotifications];
88     
89     hudTimerSeconds = 0;
90     /* we set the progress indicator to not use threaded animation
91      * as it causes a conflict with the qtmovieview's controllerbar
92     */
93     [fMovieCreationProgressIndicator setUsesThreadedAnimation:NO];
94     
95     /* Setup our layers for core animation */
96     [fPictureViewArea setWantsLayer:YES];
97     [fPictureView setWantsLayer:YES];
98
99     [fCancelPreviewMovieButton setWantsLayer:YES];
100     [fMovieCreationProgressIndicator setWantsLayer:YES];
101
102     [fPictureControlBox setWantsLayer:YES];
103     [fEncodingControlBox setWantsLayer:YES];
104         [fMovieView setWantsLayer:YES];
105         [fMovieView setHidden:YES];
106     [fMovieView setDelegate:self];
107
108     /* Since the xib has everything off center for easy acess
109      * we align our views and windows here we an align to anything
110      * since it will actually change later upon source load, but
111      * for convenience we will use the fPictureViewArea
112      */
113      
114      /* Align the still preview image view to the picture box */
115      [fPictureView setFrameSize:[fPictureViewArea frame].size];
116      [fMovieView setFrameSize:[fPictureViewArea frame].size];
117      //[fPreviewWindow setFrameSize:[fPictureViewArea frame].size];
118     
119     
120 }
121 - (BOOL)acceptsMouseMovedEvents
122 {
123     return YES;
124 }
125
126 - (void)windowWillClose:(NSNotification *)aNotification
127 {
128     /* Upon Closing the picture window, we make sure we clean up any
129      * preview movie that might be playing
130      */
131     hb_stop( fPreviewLibhb );
132     isEncoding = NO;
133     // Show the picture view
134     [fPictureView setHidden:NO];
135     [fMovieView pause:nil];
136     [fMovieTimer invalidate];
137     [fMovieTimer release];
138     [fMovieView setHidden:YES];
139         [fMovieView setMovie:nil];
140
141     hudTimerSeconds = 0;
142     [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"PreviewWindowIsOpen"];
143 }
144
145 - (BOOL)windowShouldClose:(id)fPictureWindow
146 {
147      return YES;
148 }
149
150 - (void) dealloc
151 {
152     hb_stop(fPreviewLibhb);
153     if (fPreviewMoviePath)
154     {
155         [[NSFileManager defaultManager] removeItemAtPath:fPreviewMoviePath error:nil];
156         [fPreviewMoviePath release];
157     }    
158     
159     [fLibhbTimer invalidate];
160     [fLibhbTimer release];
161     
162     [fHudTimer invalidate];
163     [fHudTimer release];
164     
165     [fMovieTimer invalidate];
166     [fMovieTimer release];
167     
168     [fPicturePreviews release];
169     [fFullScreenWindow release];
170     
171     hb_close(&fPreviewLibhb);
172     
173     [self removeMovieCallbacks];
174     
175     [super dealloc];
176 }
177
178 - (void) SetHandle: (hb_handle_t *) handle
179 {
180     fHandle = handle;
181     
182
183     
184     /* we set the preview length popup in seconds */
185     [fPreviewMovieLengthPopUp removeAllItems];
186     [fPreviewMovieLengthPopUp addItemWithTitle: @"5"];
187     [fPreviewMovieLengthPopUp addItemWithTitle: @"10"];
188     [fPreviewMovieLengthPopUp addItemWithTitle: @"15"];
189     [fPreviewMovieLengthPopUp addItemWithTitle: @"20"];
190     [fPreviewMovieLengthPopUp addItemWithTitle: @"25"];
191     [fPreviewMovieLengthPopUp addItemWithTitle: @"30"];
192     [fPreviewMovieLengthPopUp addItemWithTitle: @"35"];
193     [fPreviewMovieLengthPopUp addItemWithTitle: @"40"];
194     [fPreviewMovieLengthPopUp addItemWithTitle: @"45"];
195     [fPreviewMovieLengthPopUp addItemWithTitle: @"50"];
196     [fPreviewMovieLengthPopUp addItemWithTitle: @"55"];
197     [fPreviewMovieLengthPopUp addItemWithTitle: @"60"];
198     
199     /* adjust the preview slider length */
200     /* We use our advance pref to determine how many previews we scanned */
201     int hb_num_previews = [[[NSUserDefaults standardUserDefaults] objectForKey:@"PreviewsNumber"] intValue];
202     [fPictureSlider setMaxValue: hb_num_previews - 1.0];
203     [fPictureSlider setNumberOfTickMarks: hb_num_previews];
204     
205     if ([[NSUserDefaults standardUserDefaults] objectForKey:@"PreviewLength"])
206     {
207         [fPreviewMovieLengthPopUp selectItemWithTitle:[[NSUserDefaults standardUserDefaults] objectForKey:@"PreviewLength"]];
208     }
209     else
210     {
211         /* currently hard set default to 10 seconds */
212         [fPreviewMovieLengthPopUp selectItemAtIndex: 1];
213     }
214 }
215
216 - (void) SetTitle: (hb_title_t *) title
217 {
218     hb_job_t * job = title->job;
219     
220     fTitle = title;
221     fPicture = 0;
222     MaxOutputWidth = title->width - job->crop[2] - job->crop[3];
223     MaxOutputHeight = title->height - job->crop[0] - job->crop[1];
224     
225     [self SettingsChanged: nil];
226
227 }
228
229
230
231 // Adjusts the window to draw the current picture (fPicture) adjusting its size as
232 // necessary to display as much of the picture as possible.
233 - (void) displayPreview 
234 {
235     hb_job_t * job = fTitle->job;
236     /* lets make sure that the still picture view is not hidden and that 
237      * the movie preview is 
238      */
239      aMovie = nil;
240     [fMovieView pause:nil];
241     [fMovieView setHidden:YES];
242         [fMovieView setMovie:nil];
243     [fMovieCreationProgressIndicator stopAnimation: nil];
244     [fMovieCreationProgressIndicator setHidden: YES];
245     [fMoviePlaybackControlBox setHidden: YES];
246     if( fMovieTimer )
247     {
248         [self stopMovieTimer];
249     }
250     [fPictureControlBox setHidden: NO];
251     
252     [fPictureView setHidden:NO];
253     
254     NSImage *fPreviewImage = [self imageForPicture: fPicture];
255     NSSize imageScaledSize = [fPreviewImage size];
256     [fPictureView setImage: fPreviewImage];
257     
258     NSSize displaySize = NSMakeSize( ( CGFloat )fTitle->width, ( CGFloat )fTitle->height );
259     NSString *sizeInfoString;
260     /* Set the picture size display fields below the Preview Picture*/
261     if( fTitle->job->anamorphic.mode == 1 ) // Original PAR Implementation
262     {
263         output_width = fTitle->width-fTitle->job->crop[2]-fTitle->job->crop[3];
264         output_height = fTitle->height-fTitle->job->crop[0]-fTitle->job->crop[1];
265         display_width = output_width * fTitle->job->anamorphic.par_width / fTitle->job->anamorphic.par_height;
266         sizeInfoString = [NSString stringWithFormat:
267                           @"Source: %dx%d, Output: %dx%d, Anamorphic: %dx%d Strict",
268                           fTitle->width, fTitle->height, output_width, output_height, display_width, output_height];
269         
270         displaySize.width = display_width;
271         displaySize.height = fTitle->height;
272         imageScaledSize.width = display_width;
273         imageScaledSize.height = output_height;   
274     }
275     else if (fTitle->job->anamorphic.mode == 2) // Loose Anamorphic
276     {
277         hb_set_anamorphic_size(job, &output_width, &output_height, &output_par_width, &output_par_height);
278         display_width = output_width * output_par_width / output_par_height;
279         sizeInfoString = [NSString stringWithFormat:
280                           @"Source: %dx%d, Output: %dx%d, Anamorphic: %dx%d Loose",
281                           fTitle->width, fTitle->height, output_width, output_height, display_width, output_height];
282         
283         displaySize.width = display_width;
284         displaySize.height = fTitle->height;
285         imageScaledSize.width = display_width;
286         imageScaledSize.height = output_height;
287     }
288     else if (fTitle->job->anamorphic.mode == 3) // Custom Anamorphic
289     {
290         hb_set_anamorphic_size(job, &output_width, &output_height, &output_par_width, &output_par_height);
291         display_width = output_width * output_par_width / output_par_height;
292         sizeInfoString = [NSString stringWithFormat:
293                           @"Source: %dx%d, Output: %dx%d, Anamorphic: %dx%d Custom",
294                           fTitle->width, fTitle->height, output_width, output_height, fTitle->job->anamorphic.dar_width, fTitle->job->anamorphic.dar_height];
295         
296         displaySize.width = fTitle->job->anamorphic.dar_width + fTitle->job->crop[2] + fTitle->job->crop[3] ;
297         displaySize.height = fTitle->job->anamorphic.dar_height + fTitle->job->crop[0] + fTitle->job->crop[1];
298         imageScaledSize.width = (int)fTitle->job->anamorphic.dar_width;
299         imageScaledSize.height = (int)fTitle->job->height;   
300     } 
301     else // No Anamorphic
302     {
303         sizeInfoString = [NSString stringWithFormat:
304                           @"Source: %dx%d, Output: %dx%d", fTitle->width, fTitle->height,
305                           fTitle->job->width, fTitle->job->height];
306        
307         displaySize.width = fTitle->width;
308         displaySize.height = fTitle->height;
309         imageScaledSize.width = fTitle->job->width;
310         imageScaledSize.height = fTitle->job->height;
311     }
312     
313     
314     
315     NSSize viewSize = [self optimalViewSizeForImageSize:displaySize];
316     [self resizeSheetForViewSize:viewSize];
317
318     NSSize windowSize = [[self window] frame].size;    
319     
320     if (scaleToScreen == YES)
321     {
322         /* Note: this should probably become a utility function */
323         /* We are in Scale To Screen mode so, we have to get the ratio for height and width against the window
324          *size so we can scale from there.
325          */
326         CGFloat deltaWidth = imageScaledSize.width / displaySize.width;
327         CGFloat deltaHeight = imageScaledSize.height /displaySize.height;
328         NSSize windowSize = [[self window] frame].size;  
329         CGFloat pictureAspectRatio = imageScaledSize.width / imageScaledSize.height;
330         
331         /* Set our min size to the storage size */
332         NSSize minSize;
333         minSize.width = fTitle->width;
334         minSize.height = fTitle->height;
335
336         /* Set delta's based on minimum size */
337         if (imageScaledSize.width <  minSize.width)
338         {
339             deltaWidth = imageScaledSize.width / minSize.width;
340         }
341         else
342         {
343             deltaWidth = 1.0;
344         }
345         
346         if (imageScaledSize.height <  minSize.height)
347         {
348             deltaHeight =  imageScaledSize.height / minSize.height;
349         }
350         else
351         {
352             deltaHeight = 1.0;
353         }
354         
355         /* Now apply our deltas to the full screen view */
356         if (pictureAspectRatio > 1.0) // we are wider than taller, so expand the width to fill the area and scale the height
357         {
358             viewSize.width = windowSize.width * deltaWidth;
359             viewSize.height = viewSize.width / pictureAspectRatio;
360             
361         }
362         else
363         {
364             viewSize.height = windowSize.height * deltaHeight; 
365             viewSize.width = viewSize.height * pictureAspectRatio;
366         }
367         
368     }
369     else
370     {
371         viewSize.width = viewSize.width - (viewSize.width - imageScaledSize.width);
372         viewSize.height = viewSize.height - (viewSize.height - imageScaledSize.height);
373         
374         if (fTitle->width > windowSize.width || fTitle->height > windowSize.height)
375         {
376             CGFloat viewSizeAspect = viewSize.width / viewSize.height;
377             if (viewSizeAspect > 1.0) // we are wider than taller, so expand the width to fill the area and scale the height
378             {
379                 viewSize.width = viewSize.width * (windowSize.width / fTitle->width) ;
380                 viewSize.height = viewSize.width / viewSizeAspect;
381             }
382             else
383             {
384                 viewSize.height = viewSize.height * (windowSize.height / fTitle->height);
385                 viewSize.width = viewSize.height * viewSizeAspect;
386             }
387         }
388         
389     }
390     
391     [self setViewSize:viewSize];
392     
393     /* relocate our hud origins as per setViewSize */
394     NSPoint hudControlBoxOrigin = [fPictureControlBox frame].origin;
395     hudControlBoxOrigin.y = ([[self window] frame].size.height / 2) - (viewSize.height / 2);
396     hudControlBoxOrigin.x = ([[self window] frame].size.width / 2) - ([fPictureControlBox frame].size.width / 2);
397     [fPictureControlBox setFrameOrigin:hudControlBoxOrigin];
398     [fEncodingControlBox setFrameOrigin:hudControlBoxOrigin];
399     [fMoviePlaybackControlBox setFrameOrigin:hudControlBoxOrigin];
400
401
402     NSString *scaleString;
403     CGFloat scale = ( ( CGFloat )[fPictureView frame].size.width) / ( ( CGFloat )imageScaledSize.width);
404     if (scale * 100.0 != 100)
405     {
406         scaleString = [NSString stringWithFormat:
407                        NSLocalizedString( @" (%.0f%% actual size)",
408                                          @"String shown when a preview is scaled" ), scale * 100.0];
409     }
410     else
411     {
412         scaleString = @"(Actual size)";
413     }
414     
415     if (scaleToScreen == YES)
416     {
417         scaleString = [scaleString stringByAppendingString:@" Scaled To Screen"];
418     }
419     /* Set the info fields in the hud controller */
420     [fInfoField setStringValue: [NSString stringWithFormat:
421                                  @"%@", sizeInfoString]];
422     
423     [fscaleInfoField setStringValue: [NSString stringWithFormat:
424                                       @"%@", scaleString]];
425     /* Set the info field in the window title bar */
426     [[self window] setTitle:[NSString stringWithFormat: @"Preview - %@ %@",sizeInfoString, scaleString]];
427 }
428
429 - (IBAction) previewDurationPopUpChanged: (id) sender
430 {
431     
432     [[NSUserDefaults standardUserDefaults] setObject:[fPreviewMovieLengthPopUp titleOfSelectedItem] forKey:@"PreviewLength"];
433     
434 }    
435     
436 - (IBAction) SettingsChanged: (id) sender
437 {
438          // Purge the existing picture previews so they get recreated the next time
439         // they are needed.
440         [self purgeImageCache];
441         [self pictureSliderChanged:nil];
442 }
443
444 - (IBAction) pictureSliderChanged: (id) sender
445 {
446     // Show the picture view
447     [fPictureView setHidden:NO];
448     [fMovieView pause:nil];
449     [fMovieView setHidden:YES];
450         [fMovieView setMovie:nil];
451     [fEncodingControlBox setHidden: YES];
452     
453     int newPicture = [fPictureSlider intValue];
454     if (newPicture != fPicture)
455     {
456         fPicture = newPicture;
457     }
458     [self displayPreview];
459     
460 }
461
462 - (IBAction)showPreviewPanel: (id)sender forTitle: (hb_title_t *)title
463 {
464     if ([fPreviewWindow isVisible])
465     {
466         [fPreviewWindow close];
467     }
468     else
469     {
470         [self showWindow:sender];
471         [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"PreviewWindowIsOpen"];
472         [fPreviewWindow setAcceptsMouseMovedEvents:YES];
473         scaleToScreen = NO;
474         [self pictureSliderChanged:nil];
475     }
476     
477 }
478
479 - (NSString*) pictureSizeInfoString
480 {
481     return [fInfoField stringValue];
482 }
483
484 - (IBAction)showPictureSettings:(id)sender
485 {
486     [fHBController showPicturePanel:self];
487 }
488
489 #pragma mark Hud Control Overlay
490 - (void) mouseMoved:(NSEvent *)theEvent
491 {
492     [super mouseMoved:theEvent];
493     NSPoint mouseLoc = [theEvent locationInWindow];
494     
495     /* Test for mouse location to show/hide hud controls */
496     if( isEncoding == NO ) 
497     {
498         /* Since we are not encoding, verify which control hud to show
499          * or hide based on aMovie ( aMovie indicates we need movie controls )
500          */
501         NSBox           * hudBoxToShow;
502         if ( aMovie == nil ) // No movie loaded up
503         {
504             hudBoxToShow = fPictureControlBox;
505         }
506         else // We have a movie
507         {
508             hudBoxToShow = fMoviePlaybackControlBox;
509         }
510         
511         if( NSPointInRect( mouseLoc, [fPictureControlBox frame] ) )
512         {
513             [[hudBoxToShow animator] setHidden: NO];
514             [self stopHudTimer];
515         }
516                 else if( NSPointInRect( mouseLoc, [fPictureViewArea frame] ) )
517         {
518             [[hudBoxToShow animator] setHidden: NO];
519             [self startHudTimer];
520         }
521         else
522         {
523             [[hudBoxToShow animator] setHidden: YES];
524         }
525         }
526 }
527
528 - (void) startHudTimer
529 {
530         if( fHudTimer ) {
531                 [fHudTimer invalidate];
532                 [fHudTimer release];
533         }
534     fHudTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(hudTimerFired:) userInfo:nil repeats:YES];
535     [fHudTimer retain];
536 }
537
538 - (void) stopHudTimer
539 {
540     if( fHudTimer )
541     {
542         [fHudTimer invalidate];
543         [fHudTimer release];
544         fHudTimer = nil;
545         hudTimerSeconds = 0;
546     }
547 }
548
549 - (void) hudTimerFired: (NSTimer*)theTimer
550 {
551     hudTimerSeconds++;
552     if( hudTimerSeconds >= 10 ) 
553     {
554         /* Regardless which control box is active, after the timer
555          * period we want either one to fade to hidden.
556          */
557         [[fPictureControlBox animator] setHidden: YES];
558         [[fMoviePlaybackControlBox animator] setHidden: YES];
559         [self stopHudTimer];
560     }
561 }
562
563
564
565 - (IBAction)toggleScaleToScreen:(id)sender
566 {
567     if (scaleToScreen == YES)
568     {
569         scaleToScreen = NO;
570         /* make sure we are set to a still preview */
571         [self pictureSliderChanged:nil];
572         [fScaleToScreenToggleButton setTitle:@"Scale To Screen"];
573     }
574     else
575     {
576         scaleToScreen = YES;
577         /* make sure we are set to a still preview */
578         [self pictureSliderChanged:nil];
579         [fScaleToScreenToggleButton setTitle:@"Actual Scale"];
580     }
581     
582 }
583
584
585
586 // Title-less windows normally don't receive key presses, override this
587 - (BOOL)canBecomeKeyWindow
588 {
589     return YES;
590 }
591
592 // Title-less windows normally can't become main which means that another
593 // non-fullscreen window will have the "active" titlebar in expose. Bad, fix it.
594 - (BOOL)canBecomeMainWindow
595 {
596     return YES;
597 }
598
599
600 - (IBAction)goWindowedScreen:(id)sender
601 {
602     
603     /* Get the screen info to release the display but don't actually do
604      * it until the windowed screen is setup.
605      */
606     scaleToScreen = NO;
607     [self pictureSliderChanged:nil];
608     [fScaleToScreenToggleButton setTitle:@"<->"];
609         
610     NSScreen* mainScreen = [NSScreen mainScreen]; 
611     NSDictionary* screenInfo = [mainScreen deviceDescription]; 
612     NSNumber* screenID = [screenInfo objectForKey:@"NSScreenNumber"];
613     CGDirectDisplayID displayID = (CGDirectDisplayID)[screenID longValue]; 
614     
615     [fFullScreenWindow dealloc];
616     [fFullScreenWindow release];
617     
618     
619     [fPreviewWindow setContentView:fPictureViewArea]; 
620     [fPictureViewArea setNeedsDisplay:YES];
621     [self setWindow:fPreviewWindow];
622     
623     // Show the window. 
624     [fPreviewWindow makeKeyAndOrderFront:self];
625     
626     /* Set the window back to regular level */
627     [fPreviewWindow setLevel:NSNormalWindowLevel];
628     
629     /* Set the isFullScreen flag back to NO */
630     //isFullScreen = NO;
631     scaleToScreen = NO;
632     /* make sure we are set to a still preview */
633     [self pictureSliderChanged:nil];
634     [self showPreviewWindow:nil];
635     
636     /* Change the name of fFullScreenToggleButton appropriately */
637     //[fFullScreenToggleButton setTitle: @"Full Screen"];
638     // [fScaleToScreenToggleButton setHidden:YES];
639     /* set the picture settings pallete back to normal level */
640     [fHBController picturePanelWindowed];
641     
642     /* Release the display now that the we are back in windowed mode */
643     CGDisplayRelease(displayID);
644     
645     [fPreviewWindow setAcceptsMouseMovedEvents:YES];
646     //[fFullScreenWindow setAcceptsMouseMovedEvents:NO];
647     
648     hudTimerSeconds = 0;
649     [self startHudTimer];
650     
651 }
652
653
654 #pragma mark Still Preview Image Processing
655
656
657 // This function converts an image created by libhb (specified via pictureIndex) into
658 // an NSImage suitable for the GUI code to use. If removeBorders is YES,
659 // makeImageForPicture crops the image generated by libhb stripping off the gray
660 // border around the content. This is the low-level method that generates the image.
661 // -imageForPicture calls this function whenever it can't find an image in its cache.
662 + (NSImage *) makeImageForPicture: (int)pictureIndex
663                 libhb:(hb_handle_t*)handle
664                 title:(hb_title_t*)title
665 {
666     static uint8_t * buffer;
667     static int bufferSize;
668
669     // Make sure we have a big enough buffer to receive the image from libhb. libhb
670     int dstWidth = title->job->width;
671     int dstHeight = title->job->height;
672         
673     int newSize;
674     newSize = dstWidth * dstHeight * 4;
675     if( bufferSize < newSize )
676     {
677         bufferSize = newSize;
678         buffer     = (uint8_t *) realloc( buffer, bufferSize );
679     }
680
681     hb_get_preview( handle, title, pictureIndex, buffer );
682
683     // Create an NSBitmapImageRep and copy the libhb image into it, converting it from
684     // libhb's format to one suitable for NSImage. Along the way, we'll strip off the
685     // border around libhb's image.
686         
687     // The image data returned by hb_get_preview is 4 bytes per pixel, BGRA format.
688     // Alpha is ignored.
689         
690     NSBitmapFormat bitmapFormat = (NSBitmapFormat)NSAlphaFirstBitmapFormat;
691     NSBitmapImageRep * imgrep = [[[NSBitmapImageRep alloc]
692             initWithBitmapDataPlanes:nil
693             pixelsWide:dstWidth
694             pixelsHigh:dstHeight
695             bitsPerSample:8
696             samplesPerPixel:3   // ignore alpha
697             hasAlpha:NO
698             isPlanar:NO
699             colorSpaceName:NSCalibratedRGBColorSpace
700             bitmapFormat:bitmapFormat
701             bytesPerRow:dstWidth * 4
702             bitsPerPixel:32] autorelease];
703
704     UInt32 * src = (UInt32 *)buffer;
705     UInt32 * dst = (UInt32 *)[imgrep bitmapData];
706     int r, c;
707     for (r = 0; r < dstHeight; r++)
708     {
709         for (c = 0; c < dstWidth; c++)
710 #if TARGET_RT_LITTLE_ENDIAN
711             *dst++ = Endian32_Swap(*src++);
712 #else
713             *dst++ = *src++;
714 #endif
715     }
716
717     NSImage * img = [[[NSImage alloc] initWithSize: NSMakeSize(dstWidth, dstHeight)] autorelease];
718     [img addRepresentation:imgrep];
719
720     return img;
721 }
722
723 // Returns the preview image for the specified index, retrieving it from its internal
724 // cache or by calling makeImageForPicture if it is not cached. Generally, you should
725 // use imageForPicture so that images are cached. Calling makeImageForPicture will
726 // always generate a new copy of the image.
727 - (NSImage *) imageForPicture: (int) pictureIndex
728 {
729     // The preview for the specified index may not currently exist, so this method
730     // generates it if necessary.
731     NSString * key = [NSString stringWithFormat:@"%d", pictureIndex];
732     NSImage * theImage = [fPicturePreviews objectForKey:key];
733     if (!theImage)
734     {
735         theImage = [PreviewController makeImageForPicture:pictureIndex libhb:fHandle title:fTitle];
736         [fPicturePreviews setObject:theImage forKey:key];
737     }
738     return theImage;
739 }
740
741 // Purges all images from the cache. The next call to imageForPicture will cause a new
742 // image to be generated.
743 - (void) purgeImageCache
744 {
745     [fPicturePreviews removeAllObjects];
746 }
747
748  
749
750 #pragma mark Movie Preview
751
752 - (IBAction) cancelCreateMoviePreview: (id) sender
753 {
754     
755     hb_state_t s;
756     hb_get_state2( fPreviewLibhb, &s );
757     
758     if(isEncoding && (s.state == HB_STATE_WORKING || s.state == HB_STATE_PAUSED))
759     {
760         hb_stop( fPreviewLibhb );
761         [fPictureView setHidden:NO];
762         [fMovieView pause:nil];
763         [fMovieView setHidden:YES];
764                 [fMovieView setMovie:nil];
765         [fPictureSlider setHidden:NO];
766         isEncoding = NO;
767         
768         [self pictureSliderChanged:nil];
769         
770         return;
771     }
772     
773 }
774
775 - (IBAction) createMoviePreview: (id) sender
776 {
777     
778     
779     /* Lets make sure the still picture previews are showing in case
780      * there is currently a movie showing */
781     [self pictureSliderChanged:nil];
782     
783     /* Rip or Cancel ? */
784     hb_state_t s;
785     hb_get_state2( fPreviewLibhb, &s );
786     
787     if(sender == fCancelPreviewMovieButton && (s.state == HB_STATE_WORKING || s.state == HB_STATE_PAUSED))
788     {
789         hb_stop( fPreviewLibhb );
790         [fPictureView setHidden:NO];
791         [fMovieView pause:nil];
792         [fMovieView setHidden:YES];
793                 [fMovieView setMovie:nil];
794         [fPictureSlider setHidden:NO];
795         isEncoding = NO;
796         
797         return;
798     }
799     
800     
801     /* we use controller.mm's prepareJobForPreview to go ahead and set all of our settings
802      * however, we want to use a temporary destination field of course
803      * so that we do not put our temp preview in the users chosen
804      * directory */
805     
806     hb_job_t * job = fTitle->job;
807     
808     /* We run our current setting through prepeareJob in Controller.mm
809      * just as if it were a regular encode */
810     
811     [fHBController prepareJobForPreview];
812     
813     /* Destination file. We set this to our preview directory
814      * changing the extension appropriately.*/
815     if (fTitle->job->mux == HB_MUX_MP4) // MP4 file
816     {
817         /* we use .m4v for our mp4 files so that ac3 and chapters in mp4 will play properly */
818         fPreviewMoviePath = @"~/Library/Application Support/HandBrake/Previews/preview_temp.m4v";
819     }
820     else if (fTitle->job->mux == HB_MUX_MKV) // MKV file
821     {
822         fPreviewMoviePath = @"~/Library/Application Support/HandBrake/Previews/preview_temp.mkv";
823     }
824     else if (fTitle->job->mux == HB_MUX_AVI) // AVI file
825     {
826         fPreviewMoviePath = @"~/Library/Application Support/HandBrake/Previews/preview_temp.avi";
827     }
828     else if (fTitle->job->mux == HB_MUX_OGM) // OGM file
829     {
830         fPreviewMoviePath = @"~/Library/Application Support/HandBrake/Previews/preview_temp.ogm";
831     }
832     
833     fPreviewMoviePath = [[fPreviewMoviePath stringByExpandingTildeInPath]retain];
834     
835     /* See if there is an existing preview file, if so, delete it */
836     if( ![[NSFileManager defaultManager] fileExistsAtPath:fPreviewMoviePath] )
837     {
838         [[NSFileManager defaultManager] removeItemAtPath:fPreviewMoviePath error:nil];
839     }
840     
841     /* We now direct our preview encode to fPreviewMoviePath */
842     fTitle->job->file = [fPreviewMoviePath UTF8String];
843     
844     /* We use our advance pref to determine how many previews to scan */
845     int hb_num_previews = [[[NSUserDefaults standardUserDefaults] objectForKey:@"PreviewsNumber"] intValue];
846     job->start_at_preview = fPicture + 1;
847     job->seek_points = hb_num_previews;
848     
849     /* we use the preview duration popup to get the specified
850      * number of seconds for the preview encode.
851      */
852     
853     job->pts_to_stop = [[fPreviewMovieLengthPopUp titleOfSelectedItem] intValue] * 90000LL;
854     
855     /* lets go ahead and send it off to libhb
856      * Note: unlike a full encode, we only send 1 pass regardless if the final encode calls for 2 passes.
857      * this should suffice for a fairly accurate short preview and cuts our preview generation time in half.
858      * However we also need to take into account the indepth scan for subtitles.
859      */
860     /*
861      * If scanning we need to do some extra setup of the job.
862      */
863     if( job->indepth_scan == 1 )
864     {
865         char *x264opts_tmp;
866         
867         /*
868          * When subtitle scan is enabled do a fast pre-scan job
869          * which will determine which subtitles to enable, if any.
870          */
871         job->pass = -1;
872         x264opts_tmp = job->x264opts;
873         
874         job->x264opts = NULL;
875         job->indepth_scan = 1;  
876         /*
877          * Add the pre-scan job
878          */
879         hb_add( fPreviewLibhb, job );
880         job->x264opts = x264opts_tmp;
881     }                  
882     /* Go ahead and perform the actual encoding preview scan */
883     job->indepth_scan = 0;
884     job->pass = 0;
885     hb_add( fPreviewLibhb, job );
886     
887     [fEncodingControlBox setHidden: NO];
888     [fPictureControlBox setHidden: YES];
889     
890     [fMovieCreationProgressIndicator setHidden: NO];
891     [fPreviewMovieStatusField setHidden: NO];
892     
893     isEncoding = YES;
894
895     /* Let fPreviewLibhb do the job */
896     hb_start( fPreviewLibhb );
897         
898 }
899
900 - (void) startReceivingLibhbNotifications
901 {
902     if (!fLibhbTimer)
903     {
904         fLibhbTimer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(libhbTimerFired:) userInfo:nil repeats:YES];
905         [fLibhbTimer retain];
906     }
907 }
908
909 - (void) stopReceivingLibhbNotifications
910 {
911     if (fLibhbTimer)
912     {
913         [fLibhbTimer invalidate];
914         [fLibhbTimer release];
915         fLibhbTimer = nil;
916     }
917 }
918 - (void) libhbTimerFired: (NSTimer*)theTimer
919 {
920     hb_state_t s;
921     hb_get_state( fPreviewLibhb, &s );
922     [self libhbStateChanged: s];
923     
924 }
925
926 - (void) libhbStateChanged: (hb_state_t)state
927 {
928     switch( state.state )
929     {
930         case HB_STATE_IDLE:
931         case HB_STATE_SCANNING:
932         case HB_STATE_SCANDONE:
933             break;
934             
935         case HB_STATE_WORKING:
936         {
937 #define p state.param.working
938             
939             NSMutableString * string;
940                         /* Update text field */
941                         string = [NSMutableString stringWithFormat: NSLocalizedString( @"Encoding preview:  %.2f %%", @"" ), 100.0 * p.progress];
942             
943                         if( p.seconds > -1 )
944             {
945                 [string appendFormat:
946                  NSLocalizedString( @" (%.2f fps, avg %.2f fps, ETA %02dh%02dm%02ds)", @"" ),
947                  p.rate_cur, p.rate_avg, p.hours, p.minutes, p.seconds];
948             }
949             [fPreviewMovieStatusField setStringValue: string];
950             
951             [fMovieCreationProgressIndicator setIndeterminate: NO];
952             /* Update slider */
953                         [fMovieCreationProgressIndicator setDoubleValue: 100.0 * p.progress];
954             
955             [fCreatePreviewMovieButton setTitle: @"Cancel Preview"];
956             
957             break;
958             
959         }
960 #undef p
961             
962 #define p state.param.muxing            
963         case HB_STATE_MUXING:
964         {
965             // Update fMovieCreationProgressIndicator
966             [fMovieCreationProgressIndicator setIndeterminate: YES];
967             [fMovieCreationProgressIndicator startAnimation: nil];
968             [fPreviewMovieStatusField setStringValue: [NSString stringWithFormat:
969                                          NSLocalizedString( @"Muxing Preview ...", @"" )]];
970             break;
971         }
972 #undef p                        
973         case HB_STATE_PAUSED:
974             [fMovieCreationProgressIndicator stopAnimation: nil];
975             break;
976                         
977         case HB_STATE_WORKDONE:
978         {
979             // Delete all remaining jobs since libhb doesn't do this on its own.
980             hb_job_t * job;
981             while( ( job = hb_job(fPreviewLibhb, 0) ) )
982                 hb_rem( fHandle, job );
983             
984             [fPreviewMovieStatusField setStringValue: @""];
985             [fPreviewMovieStatusField setHidden: YES];
986             
987             [fMovieCreationProgressIndicator stopAnimation: nil];
988             [fMovieCreationProgressIndicator setHidden: YES];
989             [fEncodingControlBox setHidden: YES];
990             [fPictureControlBox setHidden: YES];
991             isEncoding = NO;
992
993             // Show the movie view
994             [self showMoviePreview:fPreviewMoviePath];
995             [fCreatePreviewMovieButton setTitle: @"Live Preview"];
996
997             break;
998         }
999     }
1000 }
1001
1002 - (IBAction) toggleMoviePreviewPlayPause: (id) sender
1003 {
1004     /* make sure a movie is even loaded up */
1005     if (aMovie != nil)
1006     {
1007         /* For some stupid reason there is no "isPlaying" method for a QTMovie
1008          * object, given that, we detect the rate to determine whether the movie
1009          * is playing or not.
1010          */
1011         if ([aMovie rate] != 0) // we are playing 
1012         {
1013             [fMovieView pause:aMovie];
1014             [fPlayPauseButton setTitle: @">"];
1015         }
1016         else // we are paused or stopped
1017         {
1018             [fMovieView play:aMovie];
1019             [fPlayPauseButton setTitle: @"||"];   
1020         }
1021     }
1022     
1023 }
1024
1025 - (IBAction) moviePlaybackGoToBeginning: (id) sender
1026 {
1027     /* make sure a movie is even loaded up */
1028     if (aMovie != nil)
1029     {
1030         [fMovieView gotoBeginning:aMovie];
1031      }
1032     
1033 }
1034
1035 - (IBAction) moviePlaybackGoToEnd: (id) sender
1036 {
1037     /* make sure a movie is even loaded up */
1038     if (aMovie != nil)
1039     {
1040         [fMovieView gotoEnd:aMovie];
1041      }
1042     
1043 }
1044
1045 - (IBAction) moviePlaybackGoBackwardOneFrame: (id) sender
1046 {
1047     /* make sure a movie is even loaded up */
1048     if (aMovie != nil)
1049     {
1050         [fMovieView pause:aMovie]; // Pause the movie
1051         [fMovieView stepBackward:aMovie];
1052      }
1053     
1054 }
1055
1056 - (IBAction) moviePlaybackGoForwardOneFrame: (id) sender
1057 {
1058     /* make sure a movie is even loaded up */
1059     if (aMovie != nil)
1060     {
1061         [fMovieView pause:aMovie]; // Pause the movie
1062         [fMovieView stepForward:aMovie];
1063      }
1064     
1065 }
1066
1067
1068 - (void) startMovieTimer
1069 {
1070         if( fMovieTimer ) {
1071                 [fMovieTimer invalidate];
1072                 [fMovieTimer release];
1073         }
1074     fMovieTimer = [NSTimer scheduledTimerWithTimeInterval:0.10 target:self selector:@selector(movieTimerFired:) userInfo:nil repeats:YES];
1075     [fMovieTimer retain];
1076 }
1077
1078 - (void) stopMovieTimer
1079 {
1080     if( fMovieTimer )
1081     {
1082         [fMovieTimer invalidate];
1083         [fMovieTimer release];
1084         fMovieTimer = nil;
1085     }
1086 }
1087
1088 - (void) movieTimerFired: (NSTimer*)theTimer
1089 {
1090      if (aMovie != nil)
1091     {
1092         [self adjustPreviewScrubberForCurrentMovieTime];
1093         [fMovieInfoField setStringValue: [NSString stringWithFormat:NSLocalizedString( @"%@", @"" ),[self calculatePlaybackSMTPETimecodeForDisplay]]];    
1094     }
1095 }
1096
1097
1098
1099 - (IBAction) showMoviePreview: (NSString *) path
1100 {
1101     /* Since the gray background for the still images is part of
1102      * fPictureView, lets leave the picture view visible and postion
1103      * the fMovieView over the image portion of fPictureView so
1104      * we retain the gray cropping border  we have already established
1105      * with the still previews
1106      */
1107     
1108     /* Load the new movie into fMovieView */
1109     if (path) 
1110     {
1111                 //QTMovie * aMovie;
1112                 NSError  *outError;
1113                 NSURL *movieUrl = [NSURL fileURLWithPath:path];
1114                 NSDictionary *movieAttributes = [NSDictionary dictionaryWithObjectsAndKeys:
1115                                                                                  movieUrl, QTMovieURLAttribute,
1116                                                                                  [NSNumber numberWithBool:NO], QTMovieAskUnresolvedDataRefsAttribute,
1117                                                                                  [NSNumber numberWithBool:YES], @"QTMovieOpenForPlaybackAttribute",
1118                                                                                  [NSNumber numberWithBool:NO], @"QTMovieOpenAsyncRequiredAttribute",                                                            
1119                                                                                  [NSNumber numberWithBool:NO], @"QTMovieOpenAsyncOKAttribute",
1120                                          [NSNumber numberWithBool:YES], @"QTMovieIsSteppableAttribute",
1121                                                                                  QTMovieApertureModeClean, QTMovieApertureModeAttribute,
1122                                                                                  nil];
1123         
1124         aMovie = [[[QTMovie alloc] initWithAttributes:movieAttributes error:&outError] autorelease];
1125         
1126         
1127                 if (!aMovie) 
1128         {
1129                         NSLog(@"Unable to open movie");
1130                 }
1131         else 
1132         {
1133             NSRect movieBounds;
1134             /* we get some size information from the preview movie */
1135             NSSize movieSize= [[aMovie attributeForKey:QTMovieNaturalSizeAttribute] sizeValue];
1136             movieBounds = [fMovieView movieBounds];
1137             movieBounds.size.height = movieSize.height;
1138             /* We also get our view size to use for scaling fMovieView's size */
1139             NSSize scaledMovieViewSize = [fPictureView frame].size;
1140             [fMovieView setControllerVisible:FALSE];
1141             if ([fMovieView isControllerVisible]) 
1142             {
1143                 CGFloat controllerBarHeight = [fMovieView controllerBarHeight];
1144                 if ( controllerBarHeight != 0 ) //Check if QTKit return a real value or not.
1145                 {
1146                     movieBounds.size.height += controllerBarHeight;
1147                     scaledMovieViewSize.height += controllerBarHeight;
1148                 }
1149                 else
1150                 {
1151                     movieBounds.size.height += 15;
1152                     scaledMovieViewSize.height += 15;
1153                 }
1154             }
1155             
1156             movieBounds.size.width = movieSize.width;
1157             
1158             /* we need to account for an issue where the scaledMovieViewSize > the window size */
1159             if (scaledMovieViewSize.height > [[self window] frame].size.height)
1160             {
1161                 [fHBController writeToActivityLog: "showMoviePreview: Our window is not tall enough to show the controller bar ..."];
1162             }
1163             
1164             
1165             
1166             /* Scale the fMovieView to scaledMovieViewSize */
1167             [fMovieView setFrameSize:scaledMovieViewSize];
1168             
1169             /*set our origin try using fPictureViewArea or fPictureView */
1170             NSPoint origin = [fPictureView frame].origin;
1171             origin.x += trunc( ( [fPictureView frame].size.width -
1172                                 [fMovieView frame].size.width ) / 2.0 );
1173             origin.y += trunc( ( ( [fPictureView frame].size.height -
1174                                       [fMovieView frame].size.height ) / 2.0 ) - 7.5 );
1175
1176             [fMovieView setFrameOrigin:origin];
1177             [fMovieView setMovie:aMovie];
1178             [fMovieView setHidden:NO];
1179             [fMoviePlaybackControlBox setHidden: NO];
1180             [fPictureControlBox setHidden: YES];
1181             
1182             // to actually play the movie
1183             
1184             [self initPreviewScrubberForMovie];
1185             [self startMovieTimer];
1186             /* Install amovie notifications */
1187             [aMovie setDelegate:self];
1188             [self installMovieCallbacks];
1189             [fMovieView play:aMovie];
1190
1191         }
1192     }
1193     isEncoding = NO;
1194 }
1195 #pragma mark *** Movie Playback Scrubber and time code methods ***
1196
1197 /* Since MacOSX Leopard QTKit has taken over some responsibility for assessing movie playback
1198  * information from the old QuickTime carbon api ( time code information as well as fps, etc.).
1199  * However, the QTKit devs at apple were not really big on documentation and further ...
1200  * QuickTimes ability to playback HB's largely variable framerate output makes perfectly frame
1201  * accurate information at best convoluted. Still, for the purpose of a custom hud based custom
1202  * playback scrubber slider this has so far proven to be as accurate as I have found. To say it
1203  * could use some better accuracy is not understating it enough probably.
1204  * Most of this was gleaned from this obscure Apple Mail list thread:
1205  * http://www.mailinglistarchive.com/quicktime-api@lists.apple.com/msg05642.html
1206  * Now as we currently do not show a QTKit control bar with scrubber for display sizes > container
1207  * size, this seems to facilitate playback control from the HB custom HUD controller fairly close
1208  * to the built in controller bar.
1209  * Further work needs to be done to try to get accurate frame by frame playback display if we want it.
1210  * Note that the keyboard commands for frame by frame step through etc. work as always.
1211  */ 
1212
1213 // Returns a human readable string from the currentTime of movie playback
1214 - (NSString*) calculatePlaybackSMTPETimecodeForDisplay
1215 {
1216     QTTime time = [aMovie currentTime];
1217     
1218     NSString *smtpeTimeCodeString;
1219     int days, hour, minute, second, frame;
1220     long long result;
1221     
1222     result = time.timeValue / time.timeScale; // second
1223     frame = (time.timeValue % time.timeScale) / 100;
1224     
1225     second = result % 60;
1226     
1227     result = result / 60; // minute
1228     minute = result % 60;
1229     
1230     result = result / 60; // hour
1231     hour = result % 24;  
1232     days = result;
1233     
1234     smtpeTimeCodeString = [NSString stringWithFormat:@"Time: %02d:%02d:%02d", hour, minute, second]; // hh:mm:ss
1235     return smtpeTimeCodeString;
1236     
1237 }
1238
1239
1240 // Initialize the preview scrubber min/max to appropriate values for the current movie
1241 -(void) initPreviewScrubberForMovie
1242 {
1243     if (aMovie)
1244     {
1245         
1246         QTTime duration = [aMovie duration];
1247         float result = duration.timeValue / duration.timeScale;
1248         
1249         [fMovieScrubberSlider setMinValue:0.0];
1250         [fMovieScrubberSlider setMaxValue: (float)result];
1251         [fMovieScrubberSlider setFloatValue: 0.0];
1252     }
1253 }
1254
1255
1256 -(void) adjustPreviewScrubberForCurrentMovieTime
1257 {
1258     if (aMovie)
1259     {
1260         QTTime time = [aMovie currentTime];
1261         
1262         float result = (float)time.timeValue / (float)time.timeScale;;
1263         [fMovieScrubberSlider setFloatValue:result];
1264     }
1265 }
1266
1267 - (IBAction) previewScrubberChanged: (id) sender
1268 {
1269     if (aMovie)
1270     {
1271         [fMovieView pause:aMovie]; // Pause the movie
1272         QTTime time = [aMovie currentTime];
1273         [self setTime: time.timeScale * [fMovieScrubberSlider floatValue]];
1274         [self calculatePlaybackSMTPETimecodeForDisplay];
1275     }
1276 }
1277 #pragma mark *** Movie Notifications ***
1278
1279 - (void) installMovieCallbacks
1280 {
1281
1282
1283 /*Notification for any time the movie rate changes */
1284         [[NSNotificationCenter defaultCenter] addObserver:self
1285                                                  selector:@selector(movieRateDidChange:)
1286                                                      name:@"QTMovieRateDidChangeNotification"
1287                                                    object:aMovie];
1288         /*Notification for when the movie ends */
1289         [[NSNotificationCenter defaultCenter] addObserver:self
1290                                                  selector:@selector(movieDidEnd:)
1291                                                      name:@"QTMovieDidEndNotification"
1292                                                    object:aMovie];
1293 }
1294
1295 - (void)removeMovieCallbacks
1296 {
1297     if (aMovie)
1298     {
1299         /*Notification for any time the movie rate changes */
1300         [[NSNotificationCenter defaultCenter] removeObserver:self
1301                                                         name:@"QTMovieRateDidChangeNotification"
1302                                                       object:aMovie];
1303         /*Notification for when the movie ends */
1304         [[NSNotificationCenter defaultCenter] removeObserver:self
1305                                                         name:@"QTMovieDidEndNotification"
1306                                                       object:aMovie];
1307     }
1308 }
1309
1310 - (void)movieRateDidChange:(NSNotification *)notification
1311 {
1312     if (aMovie != nil)
1313     {
1314         /* For some stupid reason there is no "isPlaying" method for a QTMovie
1315          * object, given that, we detect the rate to determine whether the movie
1316          * is playing or not.
1317          */
1318         //[self adjustPreviewScrubberForCurrentMovieTime];
1319         if ([aMovie rate] != 0) // we are playing 
1320         {
1321             [fPlayPauseButton setTitle: @"||"];
1322         }
1323         else // we are paused or stopped
1324         {
1325             [fPlayPauseButton setTitle: @">"];
1326         }
1327     }
1328 }
1329 /* This notification is not currently used. However we should keep it "just in case" as
1330  * live preview playback is enhanced.
1331  */
1332 - (void)movieDidEnd:(NSNotification *)notification
1333 {
1334
1335     //[fHBController writeToActivityLog: "Movie DidEnd Notification Received"];
1336 }
1337
1338
1339 #pragma mark *** QTTime Utilities ***
1340
1341         // convert a time value (long) to a QTTime structure
1342 -(void)timeToQTTime:(long)timeValue resultTime:(QTTime *)aQTTime
1343 {
1344         NSNumber *timeScaleObj;
1345         long timeScaleValue;
1346
1347         timeScaleObj = [aMovie attributeForKey:QTMovieTimeScaleAttribute];
1348         timeScaleValue = [timeScaleObj longValue];
1349
1350         *aQTTime = QTMakeTime(timeValue, timeScaleValue);
1351 }
1352
1353         // set the movie's current time
1354 -(void)setTime:(int)timeValue
1355 {
1356         QTTime movieQTTime;
1357         NSValue *valueForQTTime;
1358         
1359         [self timeToQTTime:timeValue resultTime:&movieQTTime];
1360
1361         valueForQTTime = [NSValue valueWithQTTime:movieQTTime];
1362
1363         [aMovie setAttribute:valueForQTTime forKey:QTMovieCurrentTimeAttribute];
1364 }
1365
1366
1367 @end
1368
1369 @implementation PreviewController (Private)
1370
1371 //
1372 // -[PictureController(Private) optimalViewSizeForImageSize:]
1373 //
1374 // Given the size of the preview image to be shown, returns the best possible
1375 // size for the view.
1376 //
1377 - (NSSize)optimalViewSizeForImageSize: (NSSize)imageSize
1378 {
1379     // The min size is 480x360
1380     CGFloat minWidth = 480.0;
1381     CGFloat minHeight = 360.0;
1382
1383     NSSize screenSize = [[[self window] screen] visibleFrame].size;
1384     NSSize sheetSize = [[self window] frame].size;
1385     NSSize viewAreaSize = [fPictureViewArea frame].size;
1386     CGFloat paddingX = 0.00;
1387     CGFloat paddingY = 0.00;
1388     
1389     if (fTitle->width > screenSize.width || fTitle->height > screenSize.height)
1390     {
1391         if (scaleToScreen == YES)
1392         {
1393             paddingX = screenSize.width - imageSize.width;
1394             paddingY = screenSize.height - imageSize.height;
1395         }
1396         
1397         else
1398         {
1399             paddingX = sheetSize.width - viewAreaSize.width;
1400             paddingY = sheetSize.height - viewAreaSize.height;  
1401         }
1402
1403     }
1404     
1405     CGFloat maxWidth;
1406     CGFloat maxHeight;
1407     maxWidth =  screenSize.width - paddingX;
1408     maxHeight = screenSize.height - paddingY;
1409     
1410     NSSize resultSize = imageSize;
1411     CGFloat resultPar = resultSize.width / resultSize.height;
1412
1413     //note, a mbp 15" at 1440 x 900 is a 1.6 ar
1414     CGFloat screenAspect = screenSize.width / screenSize.height;
1415     // Note, a standard dvd will use 720 x 480 which is a 1.5
1416     CGFloat viewAreaAspect = viewAreaSize.width / viewAreaSize.height;
1417     
1418     if (scaleToScreen == YES)
1419     {
1420         
1421         if (screenAspect < viewAreaAspect)
1422         {
1423             resultSize.width = screenSize.width;
1424             resultSize.height = (screenSize.width / viewAreaAspect);
1425         }
1426         else
1427         {
1428             resultSize.height = screenSize.height;
1429             resultSize.width = resultSize.height * viewAreaAspect;
1430         }
1431         
1432     }
1433     else if ( resultSize.width > maxWidth || resultSize.height > maxHeight )
1434     {
1435         // Source is larger than screen in one or more dimensions
1436         if ( resultPar > screenAspect )
1437         {
1438             // Source aspect wider than screen aspect, snap to max width and vary height
1439             resultSize.width = maxWidth;
1440             resultSize.height = (maxWidth / resultPar);
1441         }
1442         else
1443         {
1444             // Source aspect narrower than screen aspect, snap to max height vary width
1445             resultSize.height = maxHeight;
1446             resultSize.width = (maxHeight * resultPar);
1447         }
1448     }
1449
1450     // If necessary, grow to minimum dimensions to ensure controls overlay is not obstructed
1451     if ( resultSize.width < minWidth )
1452     {
1453         resultSize.width = minWidth;
1454     }
1455     if ( resultSize.height < minHeight )
1456     {
1457         resultSize.height = minHeight;
1458     }
1459     
1460     return resultSize;
1461
1462     
1463 }
1464
1465 //
1466 // -[PictureController(Private) resizePanelForViewSize:animate:]
1467 //
1468 // Resizes the entire window to accomodate a view of a particular size.
1469 //
1470 - (void)resizeSheetForViewSize: (NSSize)viewSize
1471 {
1472     // Figure out the deltas for the new frame area
1473     NSSize currentSize = [fPictureViewArea frame].size;
1474     CGFloat deltaX = viewSize.width - currentSize.width;
1475     CGFloat deltaY = viewSize.height - currentSize.height;
1476     
1477     // Now resize the whole panel by those same deltas, but don't exceed the min
1478     NSRect frame = [[self window] frame];
1479     NSSize maxSize = [[[self window] screen] visibleFrame].size;
1480     /* if we are not Scale To Screen, put an 85% of visible screen on the window */
1481     if (scaleToScreen == NO )
1482     {
1483         maxSize.width = maxSize.width * 0.85;
1484         maxSize.height = maxSize.height * 0.85;
1485     }
1486     
1487     /* Set our min size to the storage size */
1488     NSSize minSize;
1489     minSize.width = fTitle->width;
1490     minSize.height = fTitle->height;
1491     
1492     frame.size.width += deltaX;
1493     frame.size.height += deltaY;
1494     if( frame.size.width < minSize.width )
1495     {
1496         frame.size.width = minSize.width;
1497     }
1498     
1499     if( frame.size.height < minSize.height )
1500     {
1501         frame.size.height = minSize.height;
1502     }
1503     /* compare frame to max size of screen */
1504     
1505     if( frame.size.width > maxSize.width )
1506     {
1507         frame.size.width = maxSize.width;
1508     }
1509     
1510     if( frame.size.height > maxSize.height )
1511     {
1512         frame.size.height = maxSize.height;
1513     }
1514     
1515     
1516     
1517
1518     
1519     // But now the sheet is off-center, so also shift the origin to center it and
1520     // keep the top aligned.
1521     if( frame.size.width != [[self window] frame].size.width )
1522         frame.origin.x -= (deltaX / 2.0);
1523     
1524         
1525         /* Since upon launch we can open up the preview window if it was open
1526          * the last time we quit (and at the size it was) we want to make
1527          * sure that upon resize we do not have the window off the screen
1528          * So check the origin against the screen origin and adjust if
1529          * necessary.
1530          */
1531         NSSize screenSize = [[[self window] screen] visibleFrame].size;
1532         NSPoint screenOrigin = [[[self window] screen] frame].origin;
1533         if (screenSize.height < frame.size.height)
1534         {
1535             frame.size.height = screenSize.height;
1536         }
1537         if (screenSize.width < frame.size.width)
1538         {
1539             frame.size.width = screenSize.width;
1540         }
1541         
1542         
1543         /* our origin is off the screen to the left*/
1544         if (frame.origin.x < screenOrigin.x)
1545         {
1546             /* so shift our origin to the right */
1547             frame.origin.x = screenOrigin.x;
1548         }
1549         else if ((frame.origin.x + frame.size.width) > (screenOrigin.x + screenSize.width))
1550         {
1551             /* the right side of the preview is off the screen, so shift to the left */
1552             frame.origin.x = (screenOrigin.x + screenSize.width) - frame.size.width;
1553         }
1554         
1555         [[self window] setFrame:frame display:YES animate:YES];
1556     
1557     
1558 }
1559
1560 //
1561 // -[PictureController(Private) setViewSize:]
1562 //
1563 // Changes the view's size and centers it vertically inside of its area.
1564 // Assumes resizeSheetForViewSize: has already been called.
1565 //
1566 - (void)setViewSize: (NSSize)viewSize
1567 {   
1568     
1569     /* special case for scaleToScreen */
1570     NSSize screenSize = [[[self window] screen] visibleFrame].size;
1571     NSSize areaSize = [fPictureViewArea frame].size;
1572     NSSize pictureSize = [fPictureView frame].size;
1573     CGFloat viewSizeAspect = viewSize.width / viewSize.height;
1574     
1575     if (viewSize.width > areaSize.width || viewSize.height > areaSize.height)
1576     {
1577         
1578         if (viewSizeAspect > 1.0) // we are wider than taller, so expand the width to fill the area and scale the height
1579         {
1580             viewSize.width = areaSize.width;
1581             viewSize.height = viewSize.width / viewSizeAspect;
1582         }
1583         else
1584         {
1585             viewSize.height = areaSize.height;
1586             viewSize.width = viewSize.height * viewSizeAspect;
1587         }
1588         
1589     }
1590     
1591     [fPictureView setFrameSize:viewSize];
1592     NSSize newAreaSize = [fPictureViewArea frame].size;
1593     
1594     
1595     // center it vertically and horizontally
1596     NSPoint origin = [fPictureViewArea frame].origin;
1597     origin.y += ([fPictureViewArea frame].size.height -
1598                  [fPictureView frame].size.height) / 2.0;
1599     
1600     origin.x += ([fPictureViewArea frame].size.width -
1601                  [fPictureView frame].size.width) / 2.0; 
1602
1603     origin.x = floor( origin.x );
1604     origin.y = floor( origin.y );
1605     
1606     [fPictureView setFrameOrigin:origin];
1607     
1608 }
1609
1610
1611 - (BOOL)viewNeedsToResizeToSize: (NSSize)newSize
1612 {
1613     NSSize viewSize = [fPictureViewArea frame].size;
1614     return (newSize.width != viewSize.width || newSize.height != viewSize.height);
1615 }
1616
1617 @end