OSDN Git Service

MacGui: Picture Filters separate hud inspector initial implementation
[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     
323     if ([fPreviewWindow isVisible])
324     {
325         
326         [fPreviewWindow close];
327         
328     }
329     else
330     {
331         [self showWindow:sender];
332         [fPreviewWindow setAcceptsMouseMovedEvents:YES];
333         isFullScreen = NO;
334         scaleToScreen = NO;
335         hudTimerSeconds = 0;
336         [self startHudTimer];
337     }
338     
339 }
340
341 - (IBAction)showPictureSettings:(id)sender
342 {
343     [fHBController showPicturePanel:self];
344 }
345
346 #pragma mark Hud Control Overlay
347 - (void) mouseMoved:(NSEvent *)theEvent
348 {
349     [super mouseMoved:theEvent];
350     
351     if (isEncoding == NO)
352     {    
353         if (hudTimerSeconds == 0)
354         {
355             hudTimerSeconds ++;
356             [self startHudTimer];
357         }
358         
359         if (hudTimerSeconds > 20)
360         {
361             
362             
363             [self stopHudTimer];
364             [self showHideHudControls];
365         }
366         
367     }
368 }
369
370 - (void) startHudTimer
371 {
372     if (!fHudTimer)
373     {
374         fHudTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(hudTimerFired:) userInfo:nil repeats:YES];
375         [fHudTimer retain];
376     }
377 }
378
379 - (void) stopHudTimer
380 {
381     if (fHudTimer)
382     {
383         [fHudTimer invalidate];
384         [fHudTimer release];
385         fHudTimer = nil;
386         hudTimerSeconds = 0;
387     }
388 }
389
390 - (void) hudTimerFired: (NSTimer*)theTimer
391 {
392     hudTimerSeconds ++;
393     [self showHideHudControls];
394
395 }
396
397 - (void) showHideHudControls
398 {
399     /* Test for mouse location to show/hide hud controls */
400     NSPoint    mouseLoc;
401     NSRect     targetFrame;
402     NSRect     controlBoxFrame;
403     targetFrame = [fPictureViewArea frame];
404     controlBoxFrame = [fPictureControlBox frame];
405     
406     if (isFullScreen)
407     {
408         mouseLoc = [fFullScreenWindow mouseLocationOutsideOfEventStream];
409         [fScaleToScreenToggleButton setHidden:NO];
410     }
411     else
412     {
413         mouseLoc = [fPreviewWindow mouseLocationOutsideOfEventStream];
414         [fScaleToScreenToggleButton setHidden:YES];
415     }
416     
417     /* if the pointer is inside the picture view areas but not
418      * in the controlbox, check the hudTimerSeconds to see if
419      * its in the allowable time span
420      */
421     if ( hudTimerSeconds > 0 && hudTimerSeconds < 20)
422     {
423         
424         if (isEncoding == NO)
425         {
426             if (NSPointInRect (mouseLoc, controlBoxFrame))
427             {
428                 /* Mouse is over the preview area so show hud controls so just
429                  * reset the timer to keep the control box visible
430                 */
431                 [fPictureControlBox setHidden: NO];
432                 hudTimerSeconds = 1;
433                 return;
434             }
435             /* Re-verify we are within the target frame */
436             if (NSPointInRect (mouseLoc, targetFrame))
437             {
438                 /* Mouse is over the preview area so show hud controls */
439                 [[fPictureControlBox animator] setHidden: NO];
440                 /* increment our timer by one */
441                 hudTimerSeconds ++;
442             }
443             else
444             {
445                 [[fPictureControlBox animator] setHidden: YES];
446                 [self stopHudTimer];
447             }
448         }
449         
450     }
451     else
452     {
453         [[fPictureControlBox animator] setHidden: YES];
454     }
455     
456 }
457
458
459 #pragma mark Fullscreen Mode
460
461 - (IBAction)toggleScreenMode:(id)sender
462 {
463     if (!isFullScreen)
464     {
465         [self goFullScreen:nil];
466     }
467     else
468     {
469         [self goWindowedScreen:nil];
470     }
471 }
472
473 - (IBAction)toggleScaleToScreen:(id)sender
474 {
475     if (scaleToScreen == YES)
476     {
477         scaleToScreen = NO;
478         /* make sure we are set to a still preview */
479         [self pictureSliderChanged:nil];
480         [fScaleToScreenToggleButton setTitle:@"<->"];
481     }
482     else
483     {
484         scaleToScreen = YES;
485         /* make sure we are set to a still preview */
486         [self pictureSliderChanged:nil];
487         [fScaleToScreenToggleButton setTitle:@">-<"];
488     }
489     
490     /* Actually perform the scaling */
491     /*
492     NSSize displaySize = NSMakeSize( ( CGFloat )fTitle->width, ( CGFloat )fTitle->height );
493     NSSize viewSize = [self optimalViewSizeForImageSize:displaySize];
494     [self resizeSheetForViewSize:viewSize];
495     [self setViewSize:viewSize];
496     */
497 }
498
499 - (BOOL)fullScreen
500 {
501     return isFullScreen;
502 }
503
504 - (IBAction)goFullScreen:(id)sender 
505
506     // Get the screen information. 
507     NSScreen* mainScreen = [NSScreen mainScreen];
508     NSDictionary* screenInfo = [mainScreen deviceDescription]; 
509     NSNumber* screenID = [screenInfo objectForKey:@"NSScreenNumber"]; 
510     // Capture the screen. 
511     CGDirectDisplayID displayID = (CGDirectDisplayID)[screenID longValue]; 
512     CGDisplayErr err = CGDisplayCapture(displayID); 
513     
514     if (err == CGDisplayNoErr) 
515     { 
516         
517         /* make sure we are set to a still preview and not scaled to screen */
518         scaleToScreen = NO;
519         [self pictureSliderChanged:nil];
520         
521         // Create the full-screen window. 
522         //NSRect winRect = [mainScreen frame];
523         //fPictureViewArea
524         NSRect winRect = [fPictureViewArea frame];
525           
526         fFullScreenWindow = [[NSWindow alloc] initWithContentRect:winRect 
527                                                         styleMask:NSBorderlessWindowMask 
528                                                           backing:NSBackingStoreBuffered 
529                                                             defer:NO 
530                                                            screen:[NSScreen mainScreen]]; 
531         
532         // Establish the window attributes. 
533         [fFullScreenWindow setReleasedWhenClosed:NO]; 
534         [fFullScreenWindow setDisplaysWhenScreenProfileChanges:YES]; 
535         [fFullScreenWindow setDelegate:self]; 
536         
537         /* insert a view into the new window */
538         [fFullScreenWindow setContentView:fPictureViewArea]; 
539         [fPictureViewArea setNeedsDisplay:YES];
540         
541         // Center the window 
542         
543         /* Better to center the window using the screen's frame
544          * and the windows origin. Note that we should take into
545          * account the auto sizing and alignment that occurs in 
546          * setViewSize each time the preview changes.
547          */
548         
549         NSSize screenSize = [[NSScreen mainScreen] frame].size;
550         NSSize windowSize = [fFullScreenWindow frame].size;
551         NSPoint windowOrigin = [fFullScreenWindow frame].origin;
552         
553         /* Adjust our origin y (vertical) based on the screen height */
554         windowOrigin.y = (screenSize.height - windowSize.height) / 2.0;
555         windowOrigin.x = (screenSize.width - windowSize.width) / 2.0;
556         
557         [fFullScreenWindow setFrameOrigin:windowOrigin];
558         
559         
560         
561         /* lets kill the timer for now */
562         [self stopReceivingLibhbNotifications];
563         
564         /* We need to retain the fPreviewWindow */
565         [fPreviewWindow retain];
566         
567         [self setWindow:fFullScreenWindow];
568         
569         // The window has to be above the level of the shield window.
570         int32_t shieldLevel = CGShieldingWindowLevel(); 
571         
572         [fFullScreenWindow setLevel:shieldLevel]; 
573         
574         // Show the window. 
575         [fFullScreenWindow makeKeyAndOrderFront:self];
576         
577         /* Change the name of fFullScreenToggleButton appropriately */
578         [fFullScreenToggleButton setTitle: @"Windowed"];
579         
580         /* Lets fire the timer back up for the hud controls, etc. */
581         [self startReceivingLibhbNotifications];
582         
583         isFullScreen = YES;
584         [fScaleToScreenToggleButton setHidden:NO];
585         
586         /* make sure we are set to a still preview */
587         [self pictureSliderChanged:nil];
588         
589         //[fPreviewWindow setAcceptsMouseMovedEvents:NO];
590         [fFullScreenWindow setAcceptsMouseMovedEvents:YES];
591         
592         
593         hudTimerSeconds = 0;
594         [self startHudTimer];
595     } 
596
597
598 - (IBAction)goWindowedScreen:(id)sender
599 {
600     
601     /* Get the screen info to release the display but don't actually do
602      * it until the windowed screen is setup.
603      */
604     scaleToScreen = NO;
605     [self pictureSliderChanged:nil];
606     [fScaleToScreenToggleButton setTitle:@"<->"];
607         
608     NSScreen* mainScreen = [NSScreen mainScreen]; 
609     NSDictionary* screenInfo = [mainScreen deviceDescription]; 
610     NSNumber* screenID = [screenInfo objectForKey:@"NSScreenNumber"];
611     CGDirectDisplayID displayID = (CGDirectDisplayID)[screenID longValue]; 
612     
613     [fFullScreenWindow dealloc];
614     [fFullScreenWindow release];
615     
616     
617     [fPreviewWindow setContentView:fPictureViewArea]; 
618     [fPictureViewArea setNeedsDisplay:YES];
619     [self setWindow:fPreviewWindow];
620     
621     // Show the window. 
622     [fPreviewWindow makeKeyAndOrderFront:self];
623     
624     /* Set the window back to regular level */
625     [fPreviewWindow setLevel:NSNormalWindowLevel];
626     
627     /* Set the isFullScreen flag back to NO */
628     isFullScreen = NO;
629     scaleToScreen = NO;
630     /* make sure we are set to a still preview */
631     [self pictureSliderChanged:nil];
632     [self showPreviewWindow:nil];
633     
634     /* Change the name of fFullScreenToggleButton appropriately */
635     [fFullScreenToggleButton setTitle: @"Full Screen"];
636     // [fScaleToScreenToggleButton setHidden:YES];
637     /* set the picture settings pallete back to normal level */
638     [fHBController picturePanelWindowed];
639     
640     /* Release the display now that the we are back in windowed mode */
641     CGDisplayRelease(displayID);
642     
643     [fPreviewWindow setAcceptsMouseMovedEvents:YES];
644     //[fFullScreenWindow setAcceptsMouseMovedEvents:NO];
645     
646     hudTimerSeconds = 0;
647     [self startHudTimer];
648     
649 }
650
651
652 #pragma mark Still Preview Image Processing
653
654
655 // This function converts an image created by libhb (specified via pictureIndex) into
656 // an NSImage suitable for the GUI code to use. If removeBorders is YES,
657 // makeImageForPicture crops the image generated by libhb stripping off the gray
658 // border around the content. This is the low-level method that generates the image.
659 // -imageForPicture calls this function whenever it can't find an image in its cache.
660 + (NSImage *) makeImageForPicture: (int)pictureIndex
661                 libhb:(hb_handle_t*)handle
662                 title:(hb_title_t*)title
663                 removeBorders:(BOOL)removeBorders
664 {
665     if (removeBorders)
666     {
667         //     |<---------- title->width ----------->|
668         //     |   |<---- title->job->width ---->|   |
669         //     |   |                             |   |
670         //     .......................................
671         //     ....+-----------------------------+....
672         //     ....|                             |....<-- gray border
673         //     ....|                             |....
674         //     ....|                             |....
675         //     ....|                             |<------- image
676         //     ....|                             |....
677         //     ....|                             |....
678         //     ....|                             |....
679         //     ....|                             |....
680         //     ....|                             |....
681         //     ....+-----------------------------+....
682         //     .......................................
683
684         static uint8_t * buffer;
685         static int bufferSize;
686
687         // Make sure we have a big enough buffer to receive the image from libhb. libhb
688         // creates images with a one-pixel border around the original content. Hence we
689         // add 2 pixels horizontally and vertically to the buffer size.
690         int srcWidth = title->width + 2;
691         int srcHeight= title->height + 2;
692         int newSize;
693         newSize = srcWidth * srcHeight * 4;
694         if( bufferSize < newSize )
695         {
696             bufferSize = newSize;
697             buffer     = (uint8_t *) realloc( buffer, bufferSize );
698         }
699
700         hb_get_preview( handle, title, pictureIndex, buffer );
701
702         // Create an NSBitmapImageRep and copy the libhb image into it, converting it from
703         // libhb's format to one suitable for NSImage. Along the way, we'll strip off the
704         // border around libhb's image.
705         
706         // The image data returned by hb_get_preview is 4 bytes per pixel, BGRA format.
707         // Alpha is ignored.
708         
709         int dstWidth = title->job->width;
710         int dstHeight = title->job->height;
711         NSBitmapFormat bitmapFormat = (NSBitmapFormat)NSAlphaFirstBitmapFormat;
712         NSBitmapImageRep * imgrep = [[[NSBitmapImageRep alloc]
713                 initWithBitmapDataPlanes:nil
714                 pixelsWide:dstWidth
715                 pixelsHigh:dstHeight
716                 bitsPerSample:8
717                 samplesPerPixel:3   // ignore alpha
718                 hasAlpha:NO
719                 isPlanar:NO
720                 colorSpaceName:NSCalibratedRGBColorSpace
721                 bitmapFormat:bitmapFormat
722                 bytesPerRow:dstWidth * 4
723                 bitsPerPixel:32] autorelease];
724
725         int borderTop = (srcHeight - dstHeight) / 2;
726         int borderLeft = (srcWidth - dstWidth) / 2;
727         
728         UInt32 * src = (UInt32 *)buffer;
729         UInt32 * dst = (UInt32 *)[imgrep bitmapData];
730         src += borderTop * srcWidth;    // skip top rows in src to get to first row of dst
731         src += borderLeft;              // skip left pixels in src to get to first pixel of dst
732         for (int r = 0; r < dstHeight; r++)
733         {
734             for (int c = 0; c < dstWidth; c++)
735 #if TARGET_RT_LITTLE_ENDIAN
736                 *dst++ = Endian32_Swap(*src++);
737 #else
738                 *dst++ = *src++;
739 #endif
740             src += (srcWidth - dstWidth);   // skip to next row in src
741         }
742
743         NSImage * img = [[[NSImage alloc] initWithSize: NSMakeSize(dstWidth, dstHeight)] autorelease];
744         [img addRepresentation:imgrep];
745
746         return img;
747     }
748     else
749     {
750         // Make sure we have big enough buffer
751         static uint8_t * buffer;
752         static int bufferSize;
753
754         int newSize;
755         newSize = ( title->width + 2 ) * (title->height + 2 ) * 4;
756         if( bufferSize < newSize )
757         {
758             bufferSize = newSize;
759             buffer     = (uint8_t *) realloc( buffer, bufferSize );
760         }
761
762         hb_get_preview( handle, title, pictureIndex, buffer );
763
764         // The image data returned by hb_get_preview is 4 bytes per pixel, BGRA format.
765         // We'll copy that into an NSImage swapping it to ARGB in the process. Alpha is
766         // ignored.
767         int width = title->width + 2;      // hblib adds a one-pixel border to the image
768         int height = title->height + 2;
769         int numPixels = width * height;
770         NSBitmapFormat bitmapFormat = (NSBitmapFormat)NSAlphaFirstBitmapFormat;
771         NSBitmapImageRep * imgrep = [[[NSBitmapImageRep alloc]
772                 initWithBitmapDataPlanes:nil
773                 pixelsWide:width
774                 pixelsHigh:height
775                 bitsPerSample:8
776                 samplesPerPixel:3   // ignore alpha
777                 hasAlpha:NO
778                 isPlanar:NO
779                 colorSpaceName:NSCalibratedRGBColorSpace
780                 bitmapFormat:bitmapFormat
781                 bytesPerRow:width * 4
782                 bitsPerPixel:32] autorelease];
783
784         UInt32 * src = (UInt32 *)buffer;
785         UInt32 * dst = (UInt32 *)[imgrep bitmapData];
786         for (int i = 0; i < numPixels; i++)
787 #if TARGET_RT_LITTLE_ENDIAN
788             *dst++ = Endian32_Swap(*src++);
789 #else
790             *dst++ = *src++;
791 #endif
792
793         NSImage * img = [[[NSImage alloc] initWithSize: NSMakeSize(width, height)] autorelease];
794         [img addRepresentation:imgrep];
795
796         return img;
797     }
798 }
799
800 // Returns the preview image for the specified index, retrieving it from its internal
801 // cache or by calling makeImageForPicture if it is not cached. Generally, you should
802 // use imageForPicture so that images are cached. Calling makeImageForPicture will
803 // always generate a new copy of the image.
804 - (NSImage *) imageForPicture: (int) pictureIndex
805 {
806     // The preview for the specified index may not currently exist, so this method
807     // generates it if necessary.
808     NSString * key = [NSString stringWithFormat:@"%d", pictureIndex];
809     NSImage * theImage = [fPicturePreviews objectForKey:key];
810     if (!theImage)
811     {
812         theImage = [PreviewController makeImageForPicture:pictureIndex libhb:fHandle title:fTitle removeBorders: NO];
813         [fPicturePreviews setObject:theImage forKey:key];
814     }
815     return theImage;
816 }
817
818 // Purges all images from the cache. The next call to imageForPicture will cause a new
819 // image to be generated.
820 - (void) purgeImageCache
821 {
822     [fPicturePreviews removeAllObjects];
823 }
824
825  
826
827 #pragma mark Movie Preview
828 - (IBAction) createMoviePreview: (id) sender
829 {
830     
831     
832     /* Lets make sure the still picture previews are showing in case
833      * there is currently a movie showing */
834     [self pictureSliderChanged:nil];
835     
836     /* Rip or Cancel ? */
837     hb_state_t s;
838     hb_get_state2( fPreviewLibhb, &s );
839     
840     if(sender == fCancelPreviewMovieButton && (s.state == HB_STATE_WORKING || s.state == HB_STATE_PAUSED))
841         {
842         
843         play_movie = NO;
844         hb_stop( fPreviewLibhb );
845         [fPictureView setHidden:NO];
846         [fMovieView pause:nil];
847         [fMovieView setHidden:YES];
848         [fPictureSlider setHidden:NO];
849         isEncoding = NO;
850         
851         return;
852     }
853     
854     
855     /* we use controller.mm's prepareJobForPreview to go ahead and set all of our settings
856      * however, we want to use a temporary destination field of course
857      * so that we do not put our temp preview in the users chosen
858      * directory */
859     
860     hb_job_t * job = fTitle->job;
861     
862     /* We run our current setting through prepeareJob in Controller.mm
863      * just as if it were a regular encode */
864     
865     [fHBController prepareJobForPreview];
866     
867     /* Destination file. We set this to our preview directory
868      * changing the extension appropriately.*/
869     if (fTitle->job->mux == HB_MUX_MP4) // MP4 file
870     {
871         /* we use .m4v for our mp4 files so that ac3 and chapters in mp4 will play properly */
872         fPreviewMoviePath = @"~/Library/Application Support/HandBrake/Previews/preview_temp.m4v";
873     }
874     else if (fTitle->job->mux == HB_MUX_MKV) // MKV file
875     {
876         fPreviewMoviePath = @"~/Library/Application Support/HandBrake/Previews/preview_temp.mkv";
877     }
878     else if (fTitle->job->mux == HB_MUX_AVI) // AVI file
879     {
880         fPreviewMoviePath = @"~/Library/Application Support/HandBrake/Previews/preview_temp.avi";
881     }
882     else if (fTitle->job->mux == HB_MUX_OGM) // OGM file
883     {
884         fPreviewMoviePath = @"~/Library/Application Support/HandBrake/Previews/preview_temp.ogm";
885     }
886     
887     fPreviewMoviePath = [[fPreviewMoviePath stringByExpandingTildeInPath]retain];
888     
889     /* See if there is an existing preview file, if so, delete it */
890     if( ![[NSFileManager defaultManager] fileExistsAtPath:fPreviewMoviePath] )
891     {
892         [[NSFileManager defaultManager] removeFileAtPath:fPreviewMoviePath
893                                                  handler:nil];
894     }
895     
896     /* We now direct our preview encode to fPreviewMoviePath */
897     fTitle->job->file = [fPreviewMoviePath UTF8String];
898     
899     /* We use our advance pref to determine how many previews to scan */
900     int hb_num_previews = [[[NSUserDefaults standardUserDefaults] objectForKey:@"PreviewsNumber"] intValue];
901     job->start_at_preview = fPicture + 1;
902     job->seek_points = hb_num_previews;
903     
904     /* we use the preview duration popup to get the specified
905      * number of seconds for the preview encode.
906      */
907     
908     job->pts_to_stop = [[fPreviewMovieLengthPopUp titleOfSelectedItem] intValue] * 90000LL;
909     
910     /* lets go ahead and send it off to libhb
911      * Note: unlike a full encode, we only send 1 pass regardless if the final encode calls for 2 passes.
912      * this should suffice for a fairly accurate short preview and cuts our preview generation time in half.
913      */
914     hb_add( fPreviewLibhb, job );
915     
916     [fEncodingControlBox setHidden: NO];
917     [fPictureControlBox setHidden: YES];
918     
919     [fMovieCreationProgressIndicator setHidden: NO];
920     [fPreviewMovieStatusField setHidden: NO];
921     
922     isEncoding = YES;
923     
924     play_movie = YES;
925     
926     /* Let fPreviewLibhb do the job */
927     hb_start( fPreviewLibhb );
928         
929 }
930
931 - (void) startReceivingLibhbNotifications
932 {
933     if (!fLibhbTimer)
934     {
935         fLibhbTimer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(libhbTimerFired:) userInfo:nil repeats:YES];
936         [fLibhbTimer retain];
937     }
938 }
939
940 - (void) stopReceivingLibhbNotifications
941 {
942     if (fLibhbTimer)
943     {
944         [fLibhbTimer invalidate];
945         [fLibhbTimer release];
946         fLibhbTimer = nil;
947     }
948 }
949 - (void) libhbTimerFired: (NSTimer*)theTimer
950 {
951     hb_state_t s;
952     hb_get_state( fPreviewLibhb, &s );
953     [self libhbStateChanged: s];
954     
955 }
956
957 - (void) libhbStateChanged: (hb_state_t &)state
958 {
959     switch( state.state )
960     {
961         case HB_STATE_IDLE:
962         case HB_STATE_SCANNING:
963         case HB_STATE_SCANDONE:
964             break;
965             
966         case HB_STATE_WORKING:
967         {
968 #define p state.param.working
969             
970             NSMutableString * string;
971                         /* Update text field */
972                         string = [NSMutableString stringWithFormat: NSLocalizedString( @"Encoding preview:  %.2f %%", @"" ), 100.0 * p.progress];
973             
974                         if( p.seconds > -1 )
975             {
976                 [string appendFormat:
977                  NSLocalizedString( @" (%.2f fps, avg %.2f fps, ETA %02dh%02dm%02ds)", @"" ),
978                  p.rate_cur, p.rate_avg, p.hours, p.minutes, p.seconds];
979             }
980             [fPreviewMovieStatusField setStringValue: string];
981             
982             [fMovieCreationProgressIndicator setIndeterminate: NO];
983             /* Update slider */
984                         [fMovieCreationProgressIndicator setDoubleValue: 100.0 * p.progress];
985             
986             [fCreatePreviewMovieButton setTitle: @"Cancel Preview"];
987             
988             break;
989             
990         }
991 #undef p
992             
993 #define p state.param.muxing            
994         case HB_STATE_MUXING:
995         {
996             // Update fMovieCreationProgressIndicator
997             [fMovieCreationProgressIndicator setIndeterminate: YES];
998             [fMovieCreationProgressIndicator startAnimation: nil];
999             [fPreviewMovieStatusField setStringValue: [NSString stringWithFormat:
1000                                          NSLocalizedString( @"Muxing Preview ...", @"" )]];
1001             break;
1002         }
1003 #undef p                        
1004         case HB_STATE_PAUSED:
1005             [fMovieCreationProgressIndicator stopAnimation: nil];
1006             break;
1007                         
1008         case HB_STATE_WORKDONE:
1009         {
1010             // Delete all remaining jobs since libhb doesn't do this on its own.
1011             hb_job_t * job;
1012             while( ( job = hb_job(fPreviewLibhb, 0) ) )
1013                 hb_rem( fHandle, job );
1014             
1015             [fPreviewMovieStatusField setStringValue: @""];
1016             [fPreviewMovieStatusField setHidden: YES];
1017             
1018             [fMovieCreationProgressIndicator stopAnimation: nil];
1019             [fMovieCreationProgressIndicator setHidden: YES];
1020             [fEncodingControlBox setHidden: YES];
1021             isEncoding = NO;
1022             /* we make sure the picture slider and preview match */
1023             [self pictureSliderChanged:nil];
1024             
1025             
1026             // Show the movie view
1027             if (play_movie)
1028             {
1029             [self showMoviePreview:fPreviewMoviePath];
1030             }
1031             
1032             [fCreatePreviewMovieButton setTitle: @"Live Preview"];
1033             
1034             
1035             break;
1036         }
1037     }
1038         
1039 }
1040
1041 - (IBAction) showMoviePreview: (NSString *) path
1042 {
1043     /* Since the gray background for the still images is part of
1044      * fPictureView, lets leave the picture view visible and postion
1045      * the fMovieView over the image portion of fPictureView so
1046      * we retain the gray cropping border  we have already established
1047      * with the still previews
1048      */
1049     [fMovieView setHidden:NO];
1050     
1051     /* Load the new movie into fMovieView */
1052     QTMovie * aMovie;
1053     NSRect movieBounds;
1054     if (path)
1055     {
1056         [fMovieView setControllerVisible: YES];
1057         /* let's make sure there is no movie currently set */
1058         [fMovieView setMovie:nil];
1059         
1060         aMovie = [QTMovie movieWithFile:path error:nil];
1061         
1062         /* we get some size information from the preview movie */
1063         Rect movieBox;
1064         GetMovieBox ([aMovie quickTimeMovie], &movieBox);
1065         movieBounds = [fMovieView movieBounds];
1066         movieBounds.size.height = movieBox.bottom - movieBox.top;
1067         
1068         if ([fMovieView isControllerVisible])
1069             movieBounds.size.height += [fMovieView controllerBarHeight];
1070         /* since for whatever the reason I cannot seem to get the [fMovieView controllerBarHeight]
1071          * For now just use 15 for additional height as it seems to line up well
1072          */
1073         movieBounds.size.height += 15;
1074         
1075         movieBounds.size.width = movieBox.right - movieBox.left;
1076         
1077         /* We need to find out if the preview movie needs to be scaled down so
1078          * that it doesn't overflow our available viewing container (just like for image
1079          * in -displayPreview) for HD sources, etc. [fPictureViewArea frame].size.height*/
1080         if( ((int)movieBounds.size.height) > [fPictureView frame].size.height || scaleToScreen == YES)
1081         {
1082             /* The preview movie would be larger than the available viewing area
1083              * in the preview movie, so we go ahead and scale it down to the same size
1084              * as the still preview  or we readjust our window to allow for the added height if need be
1085              */
1086             NSSize displaySize = NSMakeSize( (float)movieBounds.size.width, (float)movieBounds.size.height );
1087             NSSize viewSize = [self optimalViewSizeForImageSize:displaySize];
1088             if( [self viewNeedsToResizeToSize:viewSize] )
1089             {
1090                 
1091                 [self resizeSheetForViewSize:viewSize];
1092                 [self setViewSize:viewSize];
1093                 
1094             }
1095             
1096             [fMovieView setPreservesAspectRatio:YES];
1097             [fMovieView setFrameSize:viewSize];
1098         }
1099         else
1100         {
1101             /* Since the preview movie is smaller than the available viewing area
1102              * we can go ahead and use the preview movies native size */
1103             [fMovieView setFrameSize:movieBounds.size];
1104
1105         }
1106         
1107
1108         
1109
1110         // lets reposition the movie if need be
1111         
1112         NSPoint origin = [fPictureViewArea frame].origin;
1113         origin.x += trunc(([fPictureViewArea frame].size.width -
1114                            [fMovieView frame].size.width) / 2.0);
1115         /* We need to detect whether or not we are currently less than the available height.*/
1116         if (movieBounds.size.height < [fPictureView frame].size.height)
1117         {
1118         /* If we are, we are adding 15 to the height to allow for the controller bar so
1119          * we need to subtract half of that for the origin.y to get the controller bar
1120          * below the movie to it lines up vertically with where our still preview was
1121          */
1122         origin.y += trunc((([fPictureViewArea frame].size.height -
1123                             [fMovieView frame].size.height) / 2.0) - 7.5);
1124         }
1125         else
1126         {
1127         /* if we are >= to the height of the picture view area, the controller bar
1128          * gets taken care of with picture resizing, so we do not want to offset the height
1129          */
1130         origin.y += trunc(([fPictureViewArea frame].size.height -
1131                             [fMovieView frame].size.height) / 2.0);
1132         }
1133         [fMovieView setFrameOrigin:origin]; 
1134         
1135         [fMovieView setMovie:aMovie];
1136         /// to actually play the movie
1137         [fMovieView play:aMovie];
1138     }
1139     else
1140     {
1141         aMovie = nil;
1142     }       
1143     isEncoding = NO;
1144 }
1145
1146
1147 @end
1148
1149 @implementation PreviewController (Private)
1150
1151 //
1152 // -[PictureController(Private) optimalViewSizeForImageSize:]
1153 //
1154 // Given the size of the preview image to be shown, returns the best possible
1155 // size for the view.
1156 //
1157 - (NSSize)optimalViewSizeForImageSize: (NSSize)imageSize
1158 {
1159     // The min size is 320x240
1160     CGFloat minWidth = 480.0;
1161     CGFloat minHeight = 360.0;
1162
1163     NSSize screenSize = [[NSScreen mainScreen] frame].size;
1164     NSSize sheetSize = [[self window] frame].size;
1165     NSSize viewAreaSize = [fPictureViewArea frame].size;
1166     CGFloat paddingX = sheetSize.width - viewAreaSize.width;
1167     CGFloat paddingY = sheetSize.height - viewAreaSize.height;
1168     CGFloat maxWidth;
1169     CGFloat maxHeight;
1170     
1171     if (isFullScreen)
1172     {
1173         /* We are in full screen mode so lets use the full screen if we need to */
1174         maxWidth =  screenSize.width - paddingX;
1175         maxHeight = screenSize.height - paddingY;
1176     }
1177     else
1178     {
1179         // The max size of the view is when the sheet is taking up 85% of the screen.
1180         maxWidth = (0.85 * screenSize.width) - paddingX;
1181         maxHeight = (0.85 * screenSize.height) - paddingY;
1182     }
1183     
1184     NSSize resultSize = imageSize;
1185     
1186     // Its better to have a view that's too small than a view that's too big, so
1187     // apply the maximum constraints last.
1188     if( resultSize.width < minWidth )
1189     {
1190         resultSize.height *= (minWidth / resultSize.width);
1191         resultSize.width = minWidth;
1192     }
1193     if( resultSize.height < minHeight )
1194     {
1195         resultSize.width *= (minHeight / resultSize.height);
1196         resultSize.height = minHeight;
1197     }
1198     if( resultSize.width > maxWidth )
1199     {
1200         resultSize.height *= (maxWidth / resultSize.width);
1201         resultSize.width = maxWidth;
1202     }
1203     if( resultSize.height > maxHeight )
1204     {
1205         resultSize.width *= (maxHeight / resultSize.height);
1206         resultSize.height = maxHeight;
1207     }
1208     
1209     if (scaleToScreen == YES)
1210     {
1211         //CGFloat scaleToScreenWidth;
1212         //CGFloat scaleToScreenHeight;
1213         CGFloat screenAspect;
1214         CGFloat viewAreaAspect; 
1215         //note, a mbp 15" at 1440 x 900 is a 1.6 ar
1216         screenAspect = screenSize.width / screenSize.height;
1217         
1218         // Note, a standard dvd will use 720 x 480 which is a 1.5
1219         viewAreaAspect = viewAreaSize.width / viewAreaSize.height;
1220         
1221         if (screenAspect < viewAreaAspect)
1222         {
1223             resultSize.width = screenSize.width;
1224             resultSize.height = (screenSize.width / viewAreaAspect);
1225         }
1226         else
1227         {
1228             resultSize.height = screenSize.height;
1229             resultSize.width = resultSize.height * viewAreaAspect;
1230         }
1231         
1232     }
1233
1234       return resultSize;
1235
1236     
1237 }
1238
1239 //
1240 // -[PictureController(Private) resizePanelForViewSize:animate:]
1241 //
1242 // Resizes the entire window to accomodate a view of a particular size.
1243 //
1244 - (void)resizeSheetForViewSize: (NSSize)viewSize
1245 {
1246     // Figure out the deltas for the new frame area
1247     NSSize currentSize = [fPictureViewArea frame].size;
1248     CGFloat deltaX = viewSize.width - currentSize.width;
1249     CGFloat deltaY = viewSize.height - currentSize.height;
1250     
1251     // Now resize the whole panel by those same deltas, but don't exceed the min
1252     NSRect frame = [[self window] frame];
1253     NSSize maxSize = [[self window] maxSize];
1254     NSSize minSize = [[self window] minSize];
1255     frame.size.width += deltaX;
1256     frame.size.height += deltaY;
1257     if( frame.size.width < minSize.width )
1258     {
1259         frame.size.width = minSize.width;
1260     }
1261     
1262     if( frame.size.height < minSize.height )
1263     {
1264         frame.size.height = minSize.height;
1265     }
1266     
1267     
1268     // But now the sheet is off-center, so also shift the origin to center it and
1269     // keep the top aligned.
1270     if( frame.size.width != [[self window] frame].size.width )
1271         frame.origin.x -= (deltaX / 2.0);
1272     
1273     if (isFullScreen)
1274     {
1275         if( frame.size.height != [[self window] frame].size.height )
1276         {
1277             frame.origin.y -= (deltaY / 2.0);
1278         }
1279         else
1280         {
1281             if( frame.size.height != [[self window] frame].size.height )
1282                 frame.origin.y -= deltaY;
1283         }
1284         
1285         [[self window] setFrame:frame display:YES animate:NO];
1286     }
1287     else
1288     {
1289         [[self window] setFrame:frame display:YES animate:YES];
1290     }
1291     
1292 }
1293
1294 //
1295 // -[PictureController(Private) setViewSize:]
1296 //
1297 // Changes the view's size and centers it vertically inside of its area.
1298 // Assumes resizeSheetForViewSize: has already been called.
1299 //
1300 - (void)setViewSize: (NSSize)viewSize
1301 {
1302     [fPictureView setFrameSize:viewSize];
1303     
1304     // center it vertically
1305     NSPoint origin = [fPictureViewArea frame].origin;
1306     origin.y += ([fPictureViewArea frame].size.height -
1307                  [fPictureView frame].size.height) / 2.0;
1308     [fPictureView setFrameOrigin:origin];
1309     
1310     NSPoint controlboxorigin = [fPictureView frame].origin;
1311     
1312     /* for now, put the origin.y 100 above the bottom of the fPictureView */
1313     controlboxorigin.y += 100;
1314     
1315     controlboxorigin.x += ([fPictureViewArea frame].size.width -
1316                  [fPictureControlBox frame].size.width) / 2.0;
1317     /* requires that thefPictureControlBox and the fEncodingControlBox
1318      * are the same width to line up.
1319      */
1320     [fPictureControlBox setFrameOrigin:controlboxorigin];
1321     [fEncodingControlBox setFrameOrigin:controlboxorigin];
1322     
1323 }
1324
1325
1326 - (BOOL)viewNeedsToResizeToSize: (NSSize)newSize
1327 {
1328     NSSize viewSize = [fPictureView frame].size;
1329     return (newSize.width != viewSize.width || newSize.height != viewSize.height);
1330 }
1331
1332 @end