OSDN Git Service

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