OSDN Git Service

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