OSDN Git Service

c6421d09c2085ea04d42e66687e9ca7d8b5ae62f
[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 @interface PreviewController (Private)
18
19 - (NSSize)optimalViewSizeForImageSize: (NSSize)imageSize;
20 - (void)resizeSheetForViewSize: (NSSize)viewSize;
21 - (void)setViewSize: (NSSize)viewSize;
22 - (BOOL)viewNeedsToResizeToSize: (NSSize)newSize;
23
24 @end
25
26 @implementation PreviewController
27
28 - (id)init
29 {
30         if (self = [super initWithWindowNibName:@"PicturePreview"])
31         {
32         // NSWindowController likes to lazily load its window. However since
33         // this controller tries to set all sorts of outlets before the window
34         // is displayed, we need it to load immediately. The correct way to do
35         // this, according to the documentation, is simply to invoke the window
36         // getter once.
37         //
38         // If/when we switch a lot of this stuff to bindings, this can probably
39         // go away.
40         [self window];
41         
42                 fPicturePreviews = [[NSMutableDictionary dictionaryWithCapacity: HB_NUM_HBLIB_PICTURES] retain];
43         /* Init libhb with check for updates libhb style set to "0" so its ignored and lets sparkle take care of it */
44         int loggingLevel = [[[NSUserDefaults standardUserDefaults] objectForKey:@"LoggingLevel"] intValue];
45         fPreviewLibhb = hb_init(loggingLevel, 0);
46         
47         }
48         return self;
49 }
50
51
52
53 //------------------------------------------------------------------------------------
54 // Displays and brings the picture window to the front
55 //------------------------------------------------------------------------------------
56 - (IBAction) showPreviewWindow: (id)sender
57 {
58     [self showWindow:sender];
59     [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"PreviewWindowIsOpen"];
60     
61     /* lets set the preview window to accept mouse moved events */
62     [fPreviewWindow setAcceptsMouseMovedEvents:YES];
63     hudTimerSeconds = 0;
64     [self pictureSliderChanged:nil];
65     [self startReceivingLibhbNotifications];
66 }
67
68 - (void)setHBController: (HBController *)controller
69 {
70     fHBController = controller;
71 }
72
73 - (void)awakeFromNib
74 {
75     [fPreviewWindow setDelegate:self];
76     if( ![[self window] setFrameUsingName:@"Preview"] )
77         [[self window] center];
78     [self setWindowFrameAutosaveName:@"Preview"];
79     [[self window] setExcludedFromWindowsMenu:YES];
80     
81     /* lets set the preview window to accept mouse moved events */
82     [fPreviewWindow setAcceptsMouseMovedEvents:YES];
83     //[self pictureSliderChanged:nil];
84     [self startReceivingLibhbNotifications];
85     
86     hudTimerSeconds = 0;
87     /* we set the progress indicator to not use threaded animation
88      * as it causes a conflict with the qtmovieview's controllerbar
89     */
90     [fMovieCreationProgressIndicator setUsesThreadedAnimation:NO];
91     
92     /* Setup our layers for core animation */
93     [fPictureViewArea setWantsLayer:YES];
94     [fPictureView setWantsLayer:YES];
95
96     [fCancelPreviewMovieButton setWantsLayer:YES];
97     [fMovieCreationProgressIndicator setWantsLayer:YES];
98
99     [fPictureControlBox setWantsLayer:YES];
100     [fEncodingControlBox setWantsLayer:YES];
101         [fMovieView setWantsLayer:YES];
102         [fMovieView setHidden:YES];
103     [fMovieView setDelegate:self];
104
105     /* Since the xib has everything off center for easy acess
106      * we align our views and windows here we an align to anything
107      * since it will actually change later upon source load, but
108      * for convenience we will use the fPictureViewArea
109      */
110      
111      /* Align the still preview image view to the picture box */
112      [fPictureView setFrameSize:[fPictureViewArea frame].size];
113      [fMovieView setFrameSize:[fPictureViewArea frame].size];
114      //[fPreviewWindow setFrameSize:[fPictureViewArea frame].size];
115     
116     
117 }
118 - (BOOL)acceptsMouseMovedEvents
119 {
120     return YES;
121 }
122
123 - (void)windowWillClose:(NSNotification *)aNotification
124 {
125     /* Upon Closing the picture window, we make sure we clean up any
126      * preview movie that might be playing
127      */
128     hb_stop( fPreviewLibhb );
129     isEncoding = NO;
130     // Show the picture view
131     [fPictureView setHidden:NO];
132     [fMovieView pause:nil];
133     [fMovieView setHidden:YES];
134         [fMovieView setMovie:nil];
135
136     hudTimerSeconds = 0;
137     [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"PreviewWindowIsOpen"];
138 }
139
140 - (BOOL)windowShouldClose:(id)fPictureWindow
141 {
142      return YES;
143 }
144
145 - (void) dealloc
146 {
147     hb_stop(fPreviewLibhb);
148     if (fPreviewMoviePath)
149     {
150         [[NSFileManager defaultManager] removeFileAtPath:fPreviewMoviePath handler:nil];
151         [fPreviewMoviePath release];
152     }    
153     
154     [fLibhbTimer invalidate];
155     [fLibhbTimer release];
156     
157     [fHudTimer invalidate];
158     [fHudTimer release];
159     
160     [fPicturePreviews release];
161     [fFullScreenWindow release];
162
163     [super dealloc];
164 }
165
166 - (void) SetHandle: (hb_handle_t *) handle
167 {
168     fHandle = handle;
169     
170
171     
172     /* we set the preview length popup in seconds */
173     [fPreviewMovieLengthPopUp removeAllItems];
174     [fPreviewMovieLengthPopUp addItemWithTitle: @"5"];
175     [fPreviewMovieLengthPopUp addItemWithTitle: @"10"];
176     [fPreviewMovieLengthPopUp addItemWithTitle: @"15"];
177     [fPreviewMovieLengthPopUp addItemWithTitle: @"20"];
178     [fPreviewMovieLengthPopUp addItemWithTitle: @"25"];
179     [fPreviewMovieLengthPopUp addItemWithTitle: @"30"];
180     [fPreviewMovieLengthPopUp addItemWithTitle: @"35"];
181     [fPreviewMovieLengthPopUp addItemWithTitle: @"40"];
182     [fPreviewMovieLengthPopUp addItemWithTitle: @"45"];
183     [fPreviewMovieLengthPopUp addItemWithTitle: @"50"];
184     [fPreviewMovieLengthPopUp addItemWithTitle: @"55"];
185     [fPreviewMovieLengthPopUp addItemWithTitle: @"60"];
186     
187     /* adjust the preview slider length */
188     /* We use our advance pref to determine how many previews we scanned */
189     int hb_num_previews = [[[NSUserDefaults standardUserDefaults] objectForKey:@"PreviewsNumber"] intValue];
190     [fPictureSlider setMaxValue: hb_num_previews - 1.0];
191     [fPictureSlider setNumberOfTickMarks: hb_num_previews];
192     
193     if ([[NSUserDefaults standardUserDefaults] objectForKey:@"PreviewLength"])
194     {
195         [fPreviewMovieLengthPopUp selectItemWithTitle:[[NSUserDefaults standardUserDefaults] objectForKey:@"PreviewLength"]];
196     }
197     else
198     {
199         /* currently hard set default to 10 seconds */
200         [fPreviewMovieLengthPopUp selectItemAtIndex: 1];
201     }
202 }
203
204 - (void) SetTitle: (hb_title_t *) title
205 {
206     hb_job_t * job = title->job;
207     
208     fTitle = title;
209     fPicture = 0;
210     MaxOutputWidth = title->width - job->crop[2] - job->crop[3];
211     MaxOutputHeight = title->height - job->crop[0] - job->crop[1];
212     [self SettingsChanged: nil];
213     
214     /* set the top of the hud controller boxes centered vertically with the origin of our window */
215     NSPoint hudControlBoxOrigin = [fPictureControlBox frame].origin;
216     hudControlBoxOrigin.y = ([[self window] frame].size.height / 2) - [fPictureControlBox frame].size.height;
217     [fPictureControlBox setFrameOrigin:hudControlBoxOrigin];
218     [fEncodingControlBox setFrameOrigin:hudControlBoxOrigin];
219
220 }
221
222
223
224 // Adjusts the window to draw the current picture (fPicture) adjusting its size as
225 // necessary to display as much of the picture as possible.
226 - (void) displayPreview
227 {
228     hb_job_t * job = fTitle->job;
229     /* lets make sure that the still picture view is not hidden and that 
230      * the movie preview is 
231      */
232     [fMovieView pause:nil];
233     [fMovieView setHidden:YES];
234         [fMovieView setMovie:nil];
235     [fMovieCreationProgressIndicator stopAnimation: nil];
236     [fMovieCreationProgressIndicator setHidden: YES];
237     
238     [fPictureView setHidden:NO];
239     
240     NSImage *fPreviewImage = [self imageForPicture: fPicture];
241     NSSize imageScaledSize = [fPreviewImage size];
242     [fPictureView setImage: fPreviewImage];
243     
244     NSSize displaySize = NSMakeSize( ( CGFloat )fTitle->width, ( CGFloat )fTitle->height );
245     NSString *sizeInfoString;
246     /* Set the picture size display fields below the Preview Picture*/
247     if( fTitle->job->anamorphic.mode == 1 ) // Original PAR Implementation
248     {
249         output_width = fTitle->width-fTitle->job->crop[2]-fTitle->job->crop[3];
250         output_height = fTitle->height-fTitle->job->crop[0]-fTitle->job->crop[1];
251         display_width = output_width * fTitle->job->anamorphic.par_width / fTitle->job->anamorphic.par_height;
252         sizeInfoString = [NSString stringWithFormat:
253                           @"Source: %dx%d, Output: %dx%d, Anamorphic: %dx%d Strict",
254                           fTitle->width, fTitle->height, output_width, output_height, display_width, output_height];
255         
256         displaySize.width = display_width;
257         displaySize.height = fTitle->height;
258         imageScaledSize.width = display_width;
259         imageScaledSize.height = output_height;   
260     }
261     else if (fTitle->job->anamorphic.mode == 2) // Loose Anamorphic
262     {
263         hb_set_anamorphic_size(job, &output_width, &output_height, &output_par_width, &output_par_height);
264         display_width = output_width * output_par_width / output_par_height;
265         sizeInfoString = [NSString stringWithFormat:
266                           @"Source: %dx%d, Output: %dx%d, Anamorphic: %dx%d Loose",
267                           fTitle->width, fTitle->height, output_width, output_height, display_width, output_height];
268         
269         displaySize.width = display_width;
270         displaySize.height = fTitle->height;
271         imageScaledSize.width = display_width;
272         imageScaledSize.height = output_height;
273     }
274     else if (fTitle->job->anamorphic.mode == 3) // Custom Anamorphic
275     {
276         hb_set_anamorphic_size(job, &output_width, &output_height, &output_par_width, &output_par_height);
277         display_width = output_width * output_par_width / output_par_height;
278         sizeInfoString = [NSString stringWithFormat:
279                           @"Source: %dx%d, Output: %dx%d, Anamorphic: %dx%d Custom",
280                           fTitle->width, fTitle->height, output_width, output_height, fTitle->job->anamorphic.dar_width, fTitle->job->anamorphic.dar_height];
281         
282         displaySize.width = fTitle->job->anamorphic.dar_width + fTitle->job->crop[2] + fTitle->job->crop[3];
283         displaySize.height = fTitle->job->anamorphic.dar_height + fTitle->job->crop[0] + fTitle->job->crop[1];
284         imageScaledSize.width = (int)fTitle->job->anamorphic.dar_width;
285         imageScaledSize.height = (int)fTitle->job->height;   
286     } 
287     else // No Anamorphic
288     {
289         sizeInfoString = [NSString stringWithFormat:
290                           @"Source: %dx%d, Output: %dx%d", fTitle->width, fTitle->height,
291                           fTitle->job->width, fTitle->job->height];
292         /* original
293         displaySize.width = fTitle->width;
294         displaySize.height = fTitle->height;
295         imageScaledSize.width = fTitle->job->width;
296         imageScaledSize.height = fTitle->job->height;
297         */
298         /* Test ... */
299         displaySize.width = fTitle->width;
300         displaySize.height = fTitle->height;
301         imageScaledSize.width = fTitle->job->width + fTitle->job->crop[2] + fTitle->job->crop[3];
302         imageScaledSize.height = fTitle->job->height + fTitle->job->crop[0] - fTitle->job->crop[1];
303     }
304     
305     NSSize viewSize = [self optimalViewSizeForImageSize:displaySize];
306     
307         /* we also need to take into account scaling to full screen to activate switching the view size */
308     if( [self viewNeedsToResizeToSize:viewSize])
309     {
310         if (fTitle->job->anamorphic.mode != 2 || (fTitle->job->anamorphic.mode == 2 && fTitle->width == fTitle->job->width))
311         {
312             [self resizeSheetForViewSize:viewSize];
313             [self setViewSize:viewSize];
314             
315         }
316     }   
317     
318     viewSize.width = viewSize.width - (viewSize.width - imageScaledSize.width);
319     viewSize.height = viewSize.height - (viewSize.height - imageScaledSize.height);
320     [self setViewSize:viewSize];
321     
322     /* special case for scaleToScreen */
323     if (scaleToScreen == YES)
324     {
325         [fPreviewImage setSize: viewSize];
326         [fPictureView setImage: fPreviewImage];
327     }
328     
329     NSString *scaleString;
330     CGFloat scale = ( ( CGFloat )[fPictureView frame].size.width) / ( ( CGFloat )imageScaledSize.width);
331     if (scale * 100.0 != 100)
332     {
333         //CGFloat scale = ( ( CGFloat )[fPictureView frame].size.width) / ( ( CGFloat )imageScaledSize.width);        
334         scaleString = [NSString stringWithFormat:
335                        NSLocalizedString( @" (%.0f%% actual size)",
336                                          @"String shown when a preview is scaled" ), scale * 100.0];
337     }
338     else
339     {
340         scaleString = @"(Actual size)";
341     }
342     /* Set the info fields in the hud controller */
343     [fInfoField setStringValue: [NSString stringWithFormat:
344                                  @"%@", sizeInfoString]];
345     
346     [fscaleInfoField setStringValue: [NSString stringWithFormat:
347                                       @"%@", scaleString]];
348     /* Set the info field in the window title bar */
349     [[self window] setTitle:[NSString stringWithFormat: @"Preview - %@ %@",sizeInfoString, scaleString]];
350 }
351
352 - (IBAction) previewDurationPopUpChanged: (id) sender
353 {
354     
355     [[NSUserDefaults standardUserDefaults] setObject:[fPreviewMovieLengthPopUp titleOfSelectedItem] forKey:@"PreviewLength"];
356     
357 }    
358     
359 - (IBAction) SettingsChanged: (id) sender
360 {
361          // Purge the existing picture previews so they get recreated the next time
362         // they are needed.
363         [self purgeImageCache];
364         [self pictureSliderChanged:nil];
365 }
366
367 - (IBAction) pictureSliderChanged: (id) sender
368 {
369     // Show the picture view
370     [fPictureView setHidden:NO];
371     [fMovieView pause:nil];
372     [fMovieView setHidden:YES];
373         [fMovieView setMovie:nil];
374     [fEncodingControlBox setHidden: YES];
375     
376     int newPicture = [fPictureSlider intValue];
377     if (newPicture != fPicture)
378     {
379         fPicture = newPicture;
380     }
381     [self displayPreview];
382     
383 }
384
385 - (IBAction)showPreviewPanel: (id)sender forTitle: (hb_title_t *)title
386 {
387     if ([fPreviewWindow isVisible])
388     {
389         [fPreviewWindow close];
390     }
391     else
392     {
393         [self showWindow:sender];
394         [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"PreviewWindowIsOpen"];
395         [fPreviewWindow setAcceptsMouseMovedEvents:YES];
396         scaleToScreen = NO;
397         [self pictureSliderChanged:nil];
398     }
399     
400 }
401
402 - (NSString*) pictureSizeInfoString
403 {
404     return [fInfoField stringValue];
405 }
406
407 - (IBAction)showPictureSettings:(id)sender
408 {
409     [fHBController showPicturePanel:self];
410 }
411
412 #pragma mark Hud Control Overlay
413 - (void) mouseMoved:(NSEvent *)theEvent
414 {
415     [super mouseMoved:theEvent];
416     NSPoint mouseLoc = [theEvent locationInWindow];
417     
418     /* Test for mouse location to show/hide hud controls */
419     if( isEncoding == NO ) {
420         if( NSPointInRect( mouseLoc, [fPictureControlBox frame] ) )
421         {
422             [[fPictureControlBox animator] setHidden: NO];
423             [self stopHudTimer];
424         }
425                 else if( NSPointInRect( mouseLoc, [fPictureViewArea frame] ) )
426         {
427             [[fPictureControlBox animator] setHidden: NO];
428             [self startHudTimer];
429         }
430         else
431             [[fPictureControlBox animator] setHidden: YES];
432         }
433 }
434
435 - (void) startHudTimer
436 {
437         if( fHudTimer ) {
438                 [fHudTimer invalidate];
439                 [fHudTimer release];
440         }
441     fHudTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(hudTimerFired:) userInfo:nil repeats:YES];
442     [fHudTimer retain];
443 }
444
445 - (void) stopHudTimer
446 {
447     if( fHudTimer )
448     {
449         [fHudTimer invalidate];
450         [fHudTimer release];
451         fHudTimer = nil;
452         hudTimerSeconds = 0;
453     }
454 }
455
456 - (void) hudTimerFired: (NSTimer*)theTimer
457 {
458     hudTimerSeconds++;
459     if( hudTimerSeconds >= 10 ) {
460         [[fPictureControlBox animator] setHidden: YES];
461         [self stopHudTimer];
462     }
463 }
464
465
466
467 - (IBAction)toggleScaleToScreen:(id)sender
468 {
469     if (scaleToScreen == YES)
470     {
471         scaleToScreen = NO;
472         /* make sure we are set to a still preview */
473         [self pictureSliderChanged:nil];
474         [fScaleToScreenToggleButton setTitle:@"Scale To Screen"];
475     }
476     else
477     {
478         scaleToScreen = YES;
479         /* make sure we are set to a still preview */
480         [self pictureSliderChanged:nil];
481         [fScaleToScreenToggleButton setTitle:@"Actual Scale"];
482     }
483     
484 }
485
486
487
488 // Title-less windows normally don't receive key presses, override this
489 - (BOOL)canBecomeKeyWindow
490 {
491     return YES;
492 }
493
494 // Title-less windows normally can't become main which means that another
495 // non-fullscreen window will have the "active" titlebar in expose. Bad, fix it.
496 - (BOOL)canBecomeMainWindow
497 {
498     return YES;
499 }
500
501
502 - (IBAction)goWindowedScreen:(id)sender
503 {
504     
505     /* Get the screen info to release the display but don't actually do
506      * it until the windowed screen is setup.
507      */
508     scaleToScreen = NO;
509     [self pictureSliderChanged:nil];
510     [fScaleToScreenToggleButton setTitle:@"<->"];
511         
512     NSScreen* mainScreen = [NSScreen mainScreen]; 
513     NSDictionary* screenInfo = [mainScreen deviceDescription]; 
514     NSNumber* screenID = [screenInfo objectForKey:@"NSScreenNumber"];
515     CGDirectDisplayID displayID = (CGDirectDisplayID)[screenID longValue]; 
516     
517     [fFullScreenWindow dealloc];
518     [fFullScreenWindow release];
519     
520     
521     [fPreviewWindow setContentView:fPictureViewArea]; 
522     [fPictureViewArea setNeedsDisplay:YES];
523     [self setWindow:fPreviewWindow];
524     
525     // Show the window. 
526     [fPreviewWindow makeKeyAndOrderFront:self];
527     
528     /* Set the window back to regular level */
529     [fPreviewWindow setLevel:NSNormalWindowLevel];
530     
531     /* Set the isFullScreen flag back to NO */
532     //isFullScreen = NO;
533     scaleToScreen = NO;
534     /* make sure we are set to a still preview */
535     [self pictureSliderChanged:nil];
536     [self showPreviewWindow:nil];
537     
538     /* Change the name of fFullScreenToggleButton appropriately */
539     //[fFullScreenToggleButton setTitle: @"Full Screen"];
540     // [fScaleToScreenToggleButton setHidden:YES];
541     /* set the picture settings pallete back to normal level */
542     [fHBController picturePanelWindowed];
543     
544     /* Release the display now that the we are back in windowed mode */
545     CGDisplayRelease(displayID);
546     
547     [fPreviewWindow setAcceptsMouseMovedEvents:YES];
548     //[fFullScreenWindow setAcceptsMouseMovedEvents:NO];
549     
550     hudTimerSeconds = 0;
551     [self startHudTimer];
552     
553 }
554
555
556 #pragma mark Still Preview Image Processing
557
558
559 // This function converts an image created by libhb (specified via pictureIndex) into
560 // an NSImage suitable for the GUI code to use. If removeBorders is YES,
561 // makeImageForPicture crops the image generated by libhb stripping off the gray
562 // border around the content. This is the low-level method that generates the image.
563 // -imageForPicture calls this function whenever it can't find an image in its cache.
564 + (NSImage *) makeImageForPicture: (int)pictureIndex
565                 libhb:(hb_handle_t*)handle
566                 title:(hb_title_t*)title
567 {
568     static uint8_t * buffer;
569     static int bufferSize;
570
571     // Make sure we have a big enough buffer to receive the image from libhb. libhb
572     int dstWidth = title->job->width;
573     int dstHeight = title->job->height;
574         
575     int newSize;
576     newSize = dstWidth * dstHeight * 4;
577     if( bufferSize < newSize )
578     {
579         bufferSize = newSize;
580         buffer     = (uint8_t *) realloc( buffer, bufferSize );
581     }
582
583     hb_get_preview( handle, title, pictureIndex, buffer );
584
585     // Create an NSBitmapImageRep and copy the libhb image into it, converting it from
586     // libhb's format to one suitable for NSImage. Along the way, we'll strip off the
587     // border around libhb's image.
588         
589     // The image data returned by hb_get_preview is 4 bytes per pixel, BGRA format.
590     // Alpha is ignored.
591         
592     NSBitmapFormat bitmapFormat = (NSBitmapFormat)NSAlphaFirstBitmapFormat;
593     NSBitmapImageRep * imgrep = [[[NSBitmapImageRep alloc]
594             initWithBitmapDataPlanes:nil
595             pixelsWide:dstWidth
596             pixelsHigh:dstHeight
597             bitsPerSample:8
598             samplesPerPixel:3   // ignore alpha
599             hasAlpha:NO
600             isPlanar:NO
601             colorSpaceName:NSCalibratedRGBColorSpace
602             bitmapFormat:bitmapFormat
603             bytesPerRow:dstWidth * 4
604             bitsPerPixel:32] autorelease];
605
606     UInt32 * src = (UInt32 *)buffer;
607     UInt32 * dst = (UInt32 *)[imgrep bitmapData];
608     int r, c;
609     for (r = 0; r < dstHeight; r++)
610     {
611         for (c = 0; c < dstWidth; c++)
612 #if TARGET_RT_LITTLE_ENDIAN
613             *dst++ = Endian32_Swap(*src++);
614 #else
615             *dst++ = *src++;
616 #endif
617     }
618
619     NSImage * img = [[[NSImage alloc] initWithSize: NSMakeSize(dstWidth, dstHeight)] autorelease];
620     [img addRepresentation:imgrep];
621
622     return img;
623 }
624
625 // Returns the preview image for the specified index, retrieving it from its internal
626 // cache or by calling makeImageForPicture if it is not cached. Generally, you should
627 // use imageForPicture so that images are cached. Calling makeImageForPicture will
628 // always generate a new copy of the image.
629 - (NSImage *) imageForPicture: (int) pictureIndex
630 {
631     // The preview for the specified index may not currently exist, so this method
632     // generates it if necessary.
633     NSString * key = [NSString stringWithFormat:@"%d", pictureIndex];
634     NSImage * theImage = [fPicturePreviews objectForKey:key];
635     if (!theImage)
636     {
637         theImage = [PreviewController makeImageForPicture:pictureIndex libhb:fHandle title:fTitle];
638         [fPicturePreviews setObject:theImage forKey:key];
639     }
640     return theImage;
641 }
642
643 // Purges all images from the cache. The next call to imageForPicture will cause a new
644 // image to be generated.
645 - (void) purgeImageCache
646 {
647     [fPicturePreviews removeAllObjects];
648 }
649
650  
651
652 #pragma mark Movie Preview
653 - (IBAction) createMoviePreview: (id) sender
654 {
655     
656     
657     /* Lets make sure the still picture previews are showing in case
658      * there is currently a movie showing */
659     [self pictureSliderChanged:nil];
660     
661     /* Rip or Cancel ? */
662     hb_state_t s;
663     hb_get_state2( fPreviewLibhb, &s );
664     
665     if(sender == fCancelPreviewMovieButton && (s.state == HB_STATE_WORKING || s.state == HB_STATE_PAUSED))
666         {
667         hb_stop( fPreviewLibhb );
668         [fPictureView setHidden:NO];
669         [fMovieView pause:nil];
670         [fMovieView setHidden:YES];
671                 [fMovieView setMovie:nil];
672         [fPictureSlider setHidden:NO];
673         isEncoding = NO;
674         
675         return;
676     }
677     
678     
679     /* we use controller.mm's prepareJobForPreview to go ahead and set all of our settings
680      * however, we want to use a temporary destination field of course
681      * so that we do not put our temp preview in the users chosen
682      * directory */
683     
684     hb_job_t * job = fTitle->job;
685     
686     /* We run our current setting through prepeareJob in Controller.mm
687      * just as if it were a regular encode */
688     
689     [fHBController prepareJobForPreview];
690     
691     /* Destination file. We set this to our preview directory
692      * changing the extension appropriately.*/
693     if (fTitle->job->mux == HB_MUX_MP4) // MP4 file
694     {
695         /* we use .m4v for our mp4 files so that ac3 and chapters in mp4 will play properly */
696         fPreviewMoviePath = @"~/Library/Application Support/HandBrake/Previews/preview_temp.m4v";
697     }
698     else if (fTitle->job->mux == HB_MUX_MKV) // MKV file
699     {
700         fPreviewMoviePath = @"~/Library/Application Support/HandBrake/Previews/preview_temp.mkv";
701     }
702     else if (fTitle->job->mux == HB_MUX_AVI) // AVI file
703     {
704         fPreviewMoviePath = @"~/Library/Application Support/HandBrake/Previews/preview_temp.avi";
705     }
706     else if (fTitle->job->mux == HB_MUX_OGM) // OGM file
707     {
708         fPreviewMoviePath = @"~/Library/Application Support/HandBrake/Previews/preview_temp.ogm";
709     }
710     
711     fPreviewMoviePath = [[fPreviewMoviePath stringByExpandingTildeInPath]retain];
712     
713     /* See if there is an existing preview file, if so, delete it */
714     if( ![[NSFileManager defaultManager] fileExistsAtPath:fPreviewMoviePath] )
715     {
716         [[NSFileManager defaultManager] removeFileAtPath:fPreviewMoviePath
717                                                  handler:nil];
718     }
719     
720     /* We now direct our preview encode to fPreviewMoviePath */
721     fTitle->job->file = [fPreviewMoviePath UTF8String];
722     
723     /* We use our advance pref to determine how many previews to scan */
724     int hb_num_previews = [[[NSUserDefaults standardUserDefaults] objectForKey:@"PreviewsNumber"] intValue];
725     job->start_at_preview = fPicture + 1;
726     job->seek_points = hb_num_previews;
727     
728     /* we use the preview duration popup to get the specified
729      * number of seconds for the preview encode.
730      */
731     
732     job->pts_to_stop = [[fPreviewMovieLengthPopUp titleOfSelectedItem] intValue] * 90000LL;
733     
734     /* lets go ahead and send it off to libhb
735      * Note: unlike a full encode, we only send 1 pass regardless if the final encode calls for 2 passes.
736      * this should suffice for a fairly accurate short preview and cuts our preview generation time in half.
737      * However we also need to take into account the indepth scan for subtitles.
738      */
739     /*
740      * If scanning we need to do some extra setup of the job.
741      */
742     if( job->indepth_scan == 1 )
743     {
744         char *x264opts_tmp;
745         
746         /*
747          * When subtitle scan is enabled do a fast pre-scan job
748          * which will determine which subtitles to enable, if any.
749          */
750         job->pass = -1;
751         x264opts_tmp = job->x264opts;
752         
753         job->x264opts = NULL;
754         job->indepth_scan = 1;  
755         /*
756          * Add the pre-scan job
757          */
758         hb_add( fPreviewLibhb, job );
759         job->x264opts = x264opts_tmp;
760     }                  
761     /* Go ahead and perform the actual encoding preview scan */
762     job->indepth_scan = 0;
763     job->pass = 0;
764     hb_add( fPreviewLibhb, job );
765     
766     [fEncodingControlBox setHidden: NO];
767     [fPictureControlBox setHidden: YES];
768     
769     [fMovieCreationProgressIndicator setHidden: NO];
770     [fPreviewMovieStatusField setHidden: NO];
771     
772     isEncoding = YES;
773
774     /* Let fPreviewLibhb do the job */
775     hb_start( fPreviewLibhb );
776         
777 }
778
779 - (void) startReceivingLibhbNotifications
780 {
781     if (!fLibhbTimer)
782     {
783         fLibhbTimer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(libhbTimerFired:) userInfo:nil repeats:YES];
784         [fLibhbTimer retain];
785     }
786 }
787
788 - (void) stopReceivingLibhbNotifications
789 {
790     if (fLibhbTimer)
791     {
792         [fLibhbTimer invalidate];
793         [fLibhbTimer release];
794         fLibhbTimer = nil;
795     }
796 }
797 - (void) libhbTimerFired: (NSTimer*)theTimer
798 {
799     hb_state_t s;
800     hb_get_state( fPreviewLibhb, &s );
801     [self libhbStateChanged: s];
802     
803 }
804
805 - (void) libhbStateChanged: (hb_state_t)state
806 {
807     switch( state.state )
808     {
809         case HB_STATE_IDLE:
810         case HB_STATE_SCANNING:
811         case HB_STATE_SCANDONE:
812             break;
813             
814         case HB_STATE_WORKING:
815         {
816 #define p state.param.working
817             
818             NSMutableString * string;
819                         /* Update text field */
820                         string = [NSMutableString stringWithFormat: NSLocalizedString( @"Encoding preview:  %.2f %%", @"" ), 100.0 * p.progress];
821             
822                         if( p.seconds > -1 )
823             {
824                 [string appendFormat:
825                  NSLocalizedString( @" (%.2f fps, avg %.2f fps, ETA %02dh%02dm%02ds)", @"" ),
826                  p.rate_cur, p.rate_avg, p.hours, p.minutes, p.seconds];
827             }
828             [fPreviewMovieStatusField setStringValue: string];
829             
830             [fMovieCreationProgressIndicator setIndeterminate: NO];
831             /* Update slider */
832                         [fMovieCreationProgressIndicator setDoubleValue: 100.0 * p.progress];
833             
834             [fCreatePreviewMovieButton setTitle: @"Cancel Preview"];
835             
836             break;
837             
838         }
839 #undef p
840             
841 #define p state.param.muxing            
842         case HB_STATE_MUXING:
843         {
844             // Update fMovieCreationProgressIndicator
845             [fMovieCreationProgressIndicator setIndeterminate: YES];
846             [fMovieCreationProgressIndicator startAnimation: nil];
847             [fPreviewMovieStatusField setStringValue: [NSString stringWithFormat:
848                                          NSLocalizedString( @"Muxing Preview ...", @"" )]];
849             break;
850         }
851 #undef p                        
852         case HB_STATE_PAUSED:
853             [fMovieCreationProgressIndicator stopAnimation: nil];
854             break;
855                         
856         case HB_STATE_WORKDONE:
857         {
858             // Delete all remaining jobs since libhb doesn't do this on its own.
859             hb_job_t * job;
860             while( ( job = hb_job(fPreviewLibhb, 0) ) )
861                 hb_rem( fHandle, job );
862             
863             [fPreviewMovieStatusField setStringValue: @""];
864             [fPreviewMovieStatusField setHidden: YES];
865             
866             [fMovieCreationProgressIndicator stopAnimation: nil];
867             [fMovieCreationProgressIndicator setHidden: YES];
868             [fEncodingControlBox setHidden: YES];
869             isEncoding = NO;
870             /* we make sure the picture slider and preview match */
871             [self pictureSliderChanged:nil];
872
873             // Show the movie view
874             [self showMoviePreview:fPreviewMoviePath];
875             [fCreatePreviewMovieButton setTitle: @"Live Preview"];
876
877             break;
878         }
879     }
880 }
881
882 - (IBAction) showMoviePreview: (NSString *) path
883 {
884     /* Since the gray background for the still images is part of
885      * fPictureView, lets leave the picture view visible and postion
886      * the fMovieView over the image portion of fPictureView so
887      * we retain the gray cropping border  we have already established
888      * with the still previews
889      */
890     
891     /* Load the new movie into fMovieView */
892     if (path) 
893     {
894                 QTMovie * aMovie;
895                 NSError  *outError;
896                 NSURL *movieUrl = [NSURL fileURLWithPath:path];
897                 NSDictionary *movieAttributes = [NSDictionary dictionaryWithObjectsAndKeys:
898                                                                                  movieUrl, QTMovieURLAttribute,
899                                                                                  [NSNumber numberWithBool:NO], QTMovieAskUnresolvedDataRefsAttribute,
900                                                                                  [NSNumber numberWithBool:YES], @"QTMovieOpenForPlaybackAttribute",
901                                                                                  [NSNumber numberWithBool:NO], @"QTMovieOpenAsyncRequiredAttribute",                                                            
902                                                                                  [NSNumber numberWithBool:NO], @"QTMovieOpenAsyncOKAttribute",
903                                                                                  QTMovieApertureModeClean, QTMovieApertureModeAttribute,
904                                                                                  nil];
905         
906         aMovie = [[[QTMovie alloc] initWithAttributes:movieAttributes error:&outError] autorelease];
907         
908                 if (!aMovie) 
909         {
910                         NSLog(@"Unable to open movie");
911                 }
912         else 
913         {
914             NSRect movieBounds;
915             /* we get some size information from the preview movie */
916             NSSize movieSize= [[aMovie attributeForKey:QTMovieNaturalSizeAttribute] sizeValue];
917             movieBounds = [fMovieView movieBounds];
918             movieBounds.size.height = movieSize.height;
919             
920             if ([fMovieView isControllerVisible]) 
921             {
922                 CGFloat controllerBarHeight = [fMovieView controllerBarHeight];
923                 if ( controllerBarHeight != 0 ) //Check if QTKit return a real value or not.
924                     movieBounds.size.height += controllerBarHeight;
925                 else
926                     movieBounds.size.height += 15;
927             }
928             
929             movieBounds.size.width = movieSize.width;
930             
931             /* We need to find out if the preview movie needs to be scaled down so
932              * that it doesn't overflow our available viewing container (just like for image
933              * in -displayPreview) for HD sources, etc. [fPictureViewArea frame].size.height*/
934             if( (movieBounds.size.height) > [fPictureViewArea frame].size.height || scaleToScreen == YES )
935             {
936                 /* The preview movie would be larger than the available viewing area
937                  * in the preview movie, so we go ahead and scale it down to the same size
938                  * as the still preview  or we readjust our window to allow for the added height if need be
939                  */
940                 NSSize displaySize = NSMakeSize( ( CGFloat ) movieBounds.size.width, ( CGFloat ) movieBounds.size.height );
941                 NSSize viewSize = [self optimalViewSizeForImageSize:displaySize];
942                 if( [self viewNeedsToResizeToSize:viewSize] ) 
943                 {
944                     [self resizeSheetForViewSize:viewSize];
945                     [self setViewSize:viewSize];
946                 }
947                 [fMovieView setFrameSize:viewSize];
948             }
949             else
950             {
951                 /* Since the preview movie is smaller than the available viewing area
952                  * we can go ahead and use the preview movies native size */
953                 [fMovieView setFrameSize:movieBounds.size];
954             }
955             
956             //lets reposition the movie if need be
957             
958             NSPoint origin = [fPictureViewArea frame].origin;
959             origin.x += trunc( ( [fPictureViewArea frame].size.width -
960                                 [fMovieView frame].size.width ) / 2.0 );
961             /* We need to detect whether or not we are currently less than the available height.*/
962             if( movieBounds.size.height < [fPictureView frame].size.height )
963             {
964                 /* If we are, we are adding 15 to the height to allow for the controller bar so
965                  * we need to subtract half of that for the origin.y to get the controller bar
966                  * below the movie to it lines up vertically with where our still preview was
967                  */
968                 origin.y += trunc( ( ( [fPictureViewArea frame].size.height -
969                                       [fMovieView frame].size.height ) / 2.0 ) - 7.5 );
970             }
971             else
972             {
973                 /* if we are >= to the height of the picture view area, the controller bar
974                  * gets taken care of with picture resizing, so we do not want to offset the height
975                  */
976                 origin.y += trunc( ( [fPictureViewArea frame].size.height -
977                                     [fMovieView frame].size.height ) / 2.0 );
978             }
979             [fMovieView setFrameOrigin:origin];
980             [fMovieView setMovie:aMovie];
981             [fMovieView setHidden:NO];
982             // to actually play the movie
983             [fMovieView play:aMovie];
984         }
985     }
986     isEncoding = NO;
987 }
988
989 @end
990
991 @implementation PreviewController (Private)
992
993 //
994 // -[PictureController(Private) optimalViewSizeForImageSize:]
995 //
996 // Given the size of the preview image to be shown, returns the best possible
997 // size for the view.
998 //
999 - (NSSize)optimalViewSizeForImageSize: (NSSize)imageSize
1000 {
1001     // The min size is 320x240
1002     CGFloat minWidth = 480.0;
1003     CGFloat minHeight = 360.0;
1004
1005     NSSize screenSize = [[NSScreen mainScreen] visibleFrame].size;
1006     NSSize sheetSize = [[self window] frame].size;
1007     NSSize viewAreaSize = [fPictureViewArea frame].size;
1008     CGFloat paddingX = sheetSize.width - viewAreaSize.width;
1009     CGFloat paddingY = sheetSize.height - viewAreaSize.height;
1010     CGFloat maxWidth;
1011     CGFloat maxHeight;
1012     
1013     maxWidth =  screenSize.width - paddingX;
1014     maxHeight = screenSize.height - paddingY;
1015     
1016     NSSize resultSize = imageSize;
1017     CGFloat resultPar = resultSize.width / resultSize.height;
1018
1019     //note, a mbp 15" at 1440 x 900 is a 1.6 ar
1020     CGFloat screenAspect = screenSize.width / screenSize.height;
1021     // Note, a standard dvd will use 720 x 480 which is a 1.5
1022     CGFloat viewAreaAspect = viewAreaSize.width / viewAreaSize.height;
1023
1024     if (scaleToScreen == YES)
1025     {
1026         
1027         if (screenAspect < viewAreaAspect)
1028         {
1029             resultSize.width = screenSize.width;
1030             resultSize.height = (screenSize.width / viewAreaAspect);
1031         }
1032         else
1033         {
1034             resultSize.height = screenSize.height;
1035             resultSize.width = resultSize.height * viewAreaAspect;
1036         }
1037         
1038     }
1039     else if ( resultSize.width > maxWidth || resultSize.height > maxHeight )
1040     {
1041         // Source is larger than screen in one or more dimensions
1042         if ( resultPar > screenAspect )
1043         {
1044             // Source aspect wider than screen aspect, snap to max width and vary height
1045             resultSize.width = maxWidth;
1046             resultSize.height = (maxWidth / resultPar);
1047         }
1048         else
1049         {
1050             // Source aspect narrower than screen aspect, snap to max height vary width
1051             resultSize.height = maxHeight;
1052             resultSize.width = (maxHeight * resultPar);
1053         }
1054     }
1055
1056     // If necessary, grow to minimum dimensions to ensure controls overlay is not obstructed
1057     if ( resultSize.width < minWidth )
1058     {
1059         resultSize.width = minWidth;
1060     }
1061     if ( resultSize.height < minHeight )
1062     {
1063         resultSize.height = minHeight;
1064     }
1065
1066       return resultSize;
1067
1068     
1069 }
1070
1071 //
1072 // -[PictureController(Private) resizePanelForViewSize:animate:]
1073 //
1074 // Resizes the entire window to accomodate a view of a particular size.
1075 //
1076 - (void)resizeSheetForViewSize: (NSSize)viewSize
1077 {
1078     // Figure out the deltas for the new frame area
1079     NSSize currentSize = [fPictureViewArea frame].size;
1080     CGFloat deltaX = viewSize.width - currentSize.width;
1081     CGFloat deltaY = viewSize.height - currentSize.height;
1082     
1083     // Now resize the whole panel by those same deltas, but don't exceed the min
1084     NSRect frame = [[self window] frame];
1085     NSSize maxSize = [[[self window] screen] visibleFrame].size;
1086     NSSize minSize = [[self window] minSize];
1087     
1088     frame.size.width += deltaX;
1089     frame.size.height += deltaY;
1090     if( frame.size.width < minSize.width )
1091     {
1092         frame.size.width = minSize.width;
1093     }
1094     
1095     if( frame.size.height < minSize.height )
1096     {
1097         frame.size.height = minSize.height;
1098     }
1099     /* compare frame to max size of screen */
1100     if( frame.size.width > maxSize.width )
1101     {
1102         frame.size.width = maxSize.width;
1103     }
1104     
1105     if( frame.size.height > maxSize.height )
1106     {
1107         frame.size.height = maxSize.height;
1108     }
1109     
1110     
1111
1112     
1113     // But now the sheet is off-center, so also shift the origin to center it and
1114     // keep the top aligned.
1115     if( frame.size.width != [[self window] frame].size.width )
1116         frame.origin.x -= (deltaX / 2.0);
1117     
1118         
1119         /* Since upon launch we can open up the preview window if it was open
1120          * the last time we quit (and at the size it was) we want to make
1121          * sure that upon resize we do not have the window off the screen
1122          * So check the origin against the screen origin and adjust if
1123          * necessary.
1124          */
1125         NSSize screenSize = [[[self window] screen] visibleFrame].size;
1126         NSPoint screenOrigin = [[[self window] screen] frame].origin;
1127         if (screenSize.height < frame.size.height)
1128         {
1129             frame.size.height = screenSize.height;
1130         }
1131         if (screenSize.width < frame.size.width)
1132         {
1133             frame.size.width = screenSize.width;
1134         }
1135         
1136         
1137         /* our origin is off the screen to the left*/
1138         if (frame.origin.x < screenOrigin.x)
1139         {
1140             /* so shift our origin to the right */
1141             frame.origin.x = screenOrigin.x;
1142         }
1143         else if ((frame.origin.x + frame.size.width) > (screenOrigin.x + screenSize.width))
1144         {
1145             /* the right side of the preview is off the screen, so shift to the left */
1146             frame.origin.x = (screenOrigin.x + screenSize.width) - frame.size.width;
1147         }
1148         
1149         [[self window] setFrame:frame display:YES animate:YES];
1150     
1151     
1152 }
1153
1154 //
1155 // -[PictureController(Private) setViewSize:]
1156 //
1157 // Changes the view's size and centers it vertically inside of its area.
1158 // Assumes resizeSheetForViewSize: has already been called.
1159 //
1160 - (void)setViewSize: (NSSize)viewSize
1161 {   
1162     /* special case for scaleToScreen */
1163     NSSize screenSize = [[[self window] screen] visibleFrame].size;
1164     NSSize areaSize = [fPictureViewArea frame].size;
1165     if (scaleToScreen == YES || viewSize.width > areaSize.width || viewSize.height > areaSize.height)
1166     {
1167         /* for scaleToScreen, we expand the fPictureView to fit the entire screen */
1168         CGFloat viewSizeAspect = viewSize.width / viewSize.height;
1169         if (viewSizeAspect > 1.0) // we are wider than taller, so expand the width to fill the area and scale the height
1170         {
1171             viewSize.width = areaSize.width;
1172             viewSize.height = viewSize.width / viewSizeAspect;
1173         }
1174         else
1175         {
1176             viewSize.height = areaSize.height;
1177             viewSize.width = viewSize.height * viewSizeAspect;
1178         }
1179         
1180     }
1181     
1182     
1183     [fPictureView setFrameSize:viewSize];
1184
1185     // center it vertically and horizontally
1186     NSPoint origin = [fPictureViewArea frame].origin;
1187     origin.y += ([fPictureViewArea frame].size.height -
1188                  [fPictureView frame].size.height) / 2.0;
1189     
1190     origin.x += ([fPictureViewArea frame].size.width -
1191                  [fPictureView frame].size.width) / 2.0; 
1192
1193     origin.x = floor( origin.x );
1194     origin.y = floor( origin.y );
1195     
1196     [fPictureView setFrameOrigin:origin];
1197     
1198 }
1199
1200
1201 - (BOOL)viewNeedsToResizeToSize: (NSSize)newSize
1202 {
1203     NSSize viewSize = [fPictureViewArea frame].size;
1204     return (newSize.width != viewSize.width || newSize.height != viewSize.height);
1205 }
1206
1207 @end