OSDN Git Service

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