OSDN Git Service

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