OSDN Git Service

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