OSDN Git Service

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