OSDN Git Service

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