OSDN Git Service

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