OSDN Git Service

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