OSDN Git Service

Moves drive detection to a seperate class
[handbrake-jp/handbrake-jp-git.git] / macosx / Controller.mm
1 /* $Id: Controller.mm,v 1.79 2005/11/04 19:41:32 titer Exp $
2
3    This file is part of the HandBrake source code.
4    Homepage: <http://handbrake.m0k.org/>.
5    It may be used under the terms of the GNU General Public License. */
6
7 #include "Controller.h"
8
9 #define _(a) NSLocalizedString(a,NULL)
10
11 static int FormatSettings[3][4] =
12   { { HB_MUX_MP4 | HB_VCODEC_FFMPEG | HB_ACODEC_FAAC,
13       HB_MUX_MP4 | HB_VCODEC_X264   | HB_ACODEC_FAAC,
14       0,
15       0 },
16     { HB_MUX_AVI | HB_VCODEC_FFMPEG | HB_ACODEC_LAME,
17       HB_MUX_AVI | HB_VCODEC_FFMPEG | HB_ACODEC_AC3,
18       HB_MUX_AVI | HB_VCODEC_X264   | HB_ACODEC_LAME,
19       HB_MUX_AVI | HB_VCODEC_X264   | HB_ACODEC_AC3 },
20     { HB_MUX_OGM | HB_VCODEC_FFMPEG | HB_ACODEC_VORBIS,
21       HB_MUX_OGM | HB_VCODEC_FFMPEG | HB_ACODEC_LAME,
22       0,
23       0 } };
24
25 /*******************************
26  * HBController implementation *
27  *******************************/
28 @implementation HBController
29
30 - init
31 {
32     self    = [super init];
33     fHandle = NULL;
34     return self;
35 }
36
37 - (void) applicationDidFinishLaunching: (NSNotification *) notification
38 {
39     int    build;
40     char * version;
41
42     /* Init libhb */
43     fHandle = hb_init( HB_DEBUG_NONE, [[NSUserDefaults
44         standardUserDefaults] boolForKey:@"CheckForUpdates"] );
45
46     /* Init others controllers */
47     [fScanController    SetHandle: fHandle];
48     [fPictureController SetHandle: fHandle];
49     [fQueueController   SetHandle: fHandle];
50
51     /* Call UpdateUI every 2/10 sec */
52     [[NSRunLoop currentRunLoop] addTimer: [NSTimer
53         scheduledTimerWithTimeInterval: 0.2 target: self
54         selector: @selector( UpdateUI: ) userInfo: NULL repeats: FALSE]
55         forMode: NSModalPanelRunLoopMode];
56
57     if( ( build = hb_check_update( fHandle, &version ) ) > -1 )
58     {
59         /* Update available - tell the user */
60         NSBeginInformationalAlertSheet( _( @"Update is available" ),
61             _( @"Go get it!" ), _( @"Discard" ), NULL, fWindow, self,
62             @selector( UpdateAlertDone:returnCode:contextInfo: ),
63             NULL, NULL, [NSString stringWithFormat:
64             _( @"HandBrake %s (build %d) is now available for download." ),
65             version, build] );
66         return;
67     }
68
69     /* Show scan panel ASAP */
70     [self performSelectorOnMainThread: @selector(ShowScanPanel:)
71         withObject: NULL waitUntilDone: NO];
72 }
73
74 - (NSApplicationTerminateReply) applicationShouldTerminate:
75     (NSApplication *) app
76 {
77     if( [[fRipButton title] isEqualToString: _( @"Cancel" )] )
78     {
79         [self Cancel: NULL];
80         return NSTerminateCancel;
81     }
82     
83     /* Clean up */
84     hb_close( &fHandle );
85     return NSTerminateNow;
86 }
87
88 - (void) awakeFromNib
89 {
90     [fWindow center];
91
92     [self TranslateStrings];
93
94     /* Destination box */
95     [fDstFormatPopUp removeAllItems];
96     [fDstFormatPopUp addItemWithTitle: _( @"MP4 file" )];
97     [fDstFormatPopUp addItemWithTitle: _( @"AVI file" )];
98     [fDstFormatPopUp addItemWithTitle: _( @"OGM file" )];
99     [fDstFormatPopUp selectItemAtIndex: 0];
100
101     [self FormatPopUpChanged: NULL];
102
103     [fDstFile2Field setStringValue: [NSString stringWithFormat:
104         @"%@/Desktop/Movie.mp4", NSHomeDirectory()]];
105
106     /* Video encoder */
107     [fVidEncoderPopUp removeAllItems];
108     [fVidEncoderPopUp addItemWithTitle: @"FFmpeg"];
109     [fVidEncoderPopUp addItemWithTitle: @"XviD"];
110
111     /* Video quality */
112     [fVidTargetSizeField setIntValue: 700];
113     [fVidBitrateField    setIntValue: 1000];
114     [fVidQualityMatrix   selectCell: fVidBitrateCell];
115     [self VideoMatrixChanged: NULL];
116
117     /* Video framerate */
118     [fVidRatePopUp removeAllItems];
119     [fVidRatePopUp addItemWithTitle: _( @"Same as source" )];
120     for( int i = 0; i < hb_video_rates_count; i++ )
121     {
122         [fVidRatePopUp addItemWithTitle:
123             [NSString stringWithCString: hb_video_rates[i].string]];
124     }
125     [fVidRatePopUp selectItemAtIndex: 0];
126
127     /* Audio bitrate */
128     [fAudBitratePopUp removeAllItems];
129     for( int i = 0; i < hb_audio_bitrates_count; i++ )
130     {
131         [fAudBitratePopUp addItemWithTitle:
132             [NSString stringWithCString: hb_audio_bitrates[i].string]];
133     }
134     [fAudBitratePopUp selectItemAtIndex: hb_audio_bitrates_default];
135
136     /* Audio samplerate */
137     [fAudRatePopUp removeAllItems];
138     for( int i = 0; i < hb_audio_rates_count; i++ )
139     {
140         [fAudRatePopUp addItemWithTitle:
141             [NSString stringWithCString: hb_audio_rates[i].string]];
142     }
143     [fAudRatePopUp selectItemAtIndex: hb_audio_rates_default];
144
145     /* Bottom */
146     [fStatusField setStringValue: @""];
147
148     [self EnableUI: NO];
149     [fPauseButton setEnabled: NO];
150     [fRipButton setEnabled: NO];
151 }
152
153 - (void) TranslateStrings
154 {
155     [fSrcDVD1Field      setStringValue: _( @"DVD:" )];
156     [fSrcTitleField     setStringValue: _( @"Title:" )];
157     [fSrcChapterField   setStringValue: _( @"Chapters:" )];
158     [fSrcChapterToField setStringValue: _( @"to" )];
159     [fSrcDuration1Field setStringValue: _( @"Duration:" )];
160
161     [fDstFormatField    setStringValue: _( @"File format:" )];
162     [fDstCodecsField    setStringValue: _( @"Codecs:" )];
163     [fDstFile1Field     setStringValue: _( @"File:" )];
164     [fDstBrowseButton   setTitle:       _( @"Browse" )];
165
166     [fVidRateField      setStringValue: _( @"Framerate (fps):" )];
167     [fVidEncoderField   setStringValue: _( @"Encoder:" )];
168     [fVidQualityField   setStringValue: _( @"Quality:" )];
169 }
170
171 /***********************************************************************
172  * UpdateDockIcon
173  ***********************************************************************
174  * Shows a progression bar on the dock icon, filled according to
175  * 'progress' (0.0 <= progress <= 1.0).
176  * Called with progress < 0.0 or progress > 1.0, restores the original
177  * icon.
178  **********************************************************************/
179 - (void) UpdateDockIcon: (float) progress
180 {
181     NSImage * icon;
182     NSData * tiff;
183     NSBitmapImageRep * bmp;
184     uint32_t * pen;
185     uint32_t black = htonl( 0x000000FF );
186     uint32_t red   = htonl( 0xFF0000FF );
187     uint32_t white = htonl( 0xFFFFFFFF );
188     int row_start, row_end;
189     int i, j;
190
191     /* Get application original icon */
192     icon = [NSImage imageNamed: @"NSApplicationIcon"];
193
194     if( progress < 0.0 || progress > 1.0 )
195     {
196         [NSApp setApplicationIconImage: icon];
197         return;
198     }
199
200     /* Get it in a raw bitmap form */
201     tiff = [icon TIFFRepresentationUsingCompression:
202             NSTIFFCompressionNone factor: 1.0];
203     bmp = [NSBitmapImageRep imageRepWithData: tiff];
204     
205     /* Draw the progression bar */
206     /* It's pretty simple (ugly?) now, but I'm no designer */
207
208     row_start = 3 * (int) [bmp size].height / 4;
209     row_end   = 7 * (int) [bmp size].height / 8;
210
211     for( i = row_start; i < row_start + 2; i++ )
212     {
213         pen = (uint32_t *) ( [bmp bitmapData] + i * [bmp bytesPerRow] );
214         for( j = 0; j < (int) [bmp size].width; j++ )
215         {
216             pen[j] = black;
217         }
218     }
219     for( i = row_start + 2; i < row_end - 2; i++ )
220     {
221         pen = (uint32_t *) ( [bmp bitmapData] + i * [bmp bytesPerRow] );
222         pen[0] = black;
223         pen[1] = black;
224         for( j = 2; j < (int) [bmp size].width - 2; j++ )
225         {
226             if( j < 2 + (int) ( ( [bmp size].width - 4.0 ) * progress ) )
227             {
228                 pen[j] = red;
229             }
230             else
231             {
232                 pen[j] = white;
233             }
234         }
235         pen[j]   = black;
236         pen[j+1] = black;
237     }
238     for( i = row_end - 2; i < row_end; i++ )
239     {
240         pen = (uint32_t *) ( [bmp bitmapData] + i * [bmp bytesPerRow] );
241         for( j = 0; j < (int) [bmp size].width; j++ )
242         {
243             pen[j] = black;
244         }
245     }
246
247     /* Now update the dock icon */
248     tiff = [bmp TIFFRepresentationUsingCompression:
249             NSTIFFCompressionNone factor: 1.0];
250     icon = [[NSImage alloc] initWithData: tiff];
251     [NSApp setApplicationIconImage: icon];
252     [icon release];
253 }
254
255 - (void) UpdateUI: (NSTimer *) timer
256 {
257     hb_state_t s;
258     hb_get_state( fHandle, &s );
259
260     switch( s.state )
261     {
262         case HB_STATE_IDLE:
263             break;
264
265         case HB_STATE_SCANNING:
266             [fScanController UpdateUI: &s];
267             break;
268
269 #define p s.param.scandone
270         case HB_STATE_SCANDONE:
271         {
272             hb_list_t  * list;
273             hb_title_t * title;
274
275             [fScanController UpdateUI: &s];
276
277             list = hb_get_titles( fHandle );
278
279             if( !hb_list_count( list ) )
280             {
281                 break;
282             }
283
284             [fSrcTitlePopUp removeAllItems];
285             for( int i = 0; i < hb_list_count( list ); i++ )
286             {
287                 title = (hb_title_t *) hb_list_item( list, i );
288                 [fSrcDVD2Field setStringValue: [NSString
289                     stringWithUTF8String: title->dvd]];
290                 [fSrcTitlePopUp addItemWithTitle: [NSString
291                     stringWithFormat: @"%d - %02dh%02dm%02ds",
292                     title->index, title->hours, title->minutes,
293                     title->seconds]];
294             }
295
296             [self TitlePopUpChanged: NULL];
297             [self EnableUI: YES];
298             [fPauseButton setEnabled: NO];
299             [fRipButton   setEnabled: YES];
300             break;
301         }
302 #undef p
303
304 #define p s.param.working
305         case HB_STATE_WORKING:
306         {
307             float progress_total;
308             NSMutableString * string;
309
310             /* Update text field */
311             string = [NSMutableString stringWithFormat:
312                 _( @"Encoding: task %d of %d, %.2f %%" ),
313                 p.job_cur, p.job_count, 100.0 * p.progress];
314             if( p.seconds > -1 )
315             {
316                 [string appendFormat:
317                     _( @" (%.2f fps, avg %.2f fps, ETA %02dh%02dm%02ds)" ),
318                     p.rate_cur, p.rate_avg, p.hours, p.minutes, p.seconds];
319             }
320             [fStatusField setStringValue: string];
321
322             /* Update slider */
323             progress_total = ( p.progress + p.job_cur - 1 ) / p.job_count;
324             [fRipIndicator setDoubleValue: 100.0 * progress_total];
325
326             /* Update dock icon */
327             [self UpdateDockIcon: progress_total];
328
329             [fPauseButton setEnabled: YES];
330             [fPauseButton setTitle: _( @"Pause" )];
331             [fRipButton setEnabled: YES];
332             [fRipButton setTitle: _( @"Cancel" )];
333             break;
334         }
335 #undef p
336
337         case HB_STATE_PAUSED:
338             [fStatusField setStringValue: _( @"Paused" )];
339             [fPauseButton setEnabled: YES];
340             [fPauseButton setTitle: _( @"Resume" )];
341             [fRipButton setEnabled: YES];
342             [fRipButton setTitle: _( @"Cancel" )];
343             break;
344
345         case HB_STATE_WORKDONE:
346         {
347             [self EnableUI: YES];
348             [fStatusField setStringValue: _( @"Done." )];
349             [fRipIndicator setDoubleValue: 0.0];
350             [fRipButton setTitle: _( @"Rip" )];
351
352             /* Restore dock icon */
353             [self UpdateDockIcon: -1.0];
354
355             [fPauseButton setEnabled: NO];
356             [fPauseButton setTitle: _( @"Pause" )];
357             [fRipButton setEnabled: YES];
358             [fRipButton setTitle: _( @"Rip" )];
359
360             /* FIXME */
361             hb_job_t * job;
362             while( ( job = hb_job( fHandle, 0 ) ) )
363             {
364                 hb_rem( fHandle, job );
365             }
366             break;
367         }
368     }
369
370     /* FIXME: we should only do that when necessary */
371     if( [fQueueCheck state] == NSOnState )
372     {
373         int count = hb_count( fHandle );
374         if( count )
375         {
376             [fQueueCheck setTitle: [NSString stringWithFormat:
377                 @"Enable queue (%d task%s in queue)",
378                 count, ( count > 1 ) ? "s" : ""]];
379         }
380         else
381         {
382             [fQueueCheck setTitle: @"Enable queue (no task in queue)"];
383         }
384     }
385
386     [[NSRunLoop currentRunLoop] addTimer: [NSTimer
387         scheduledTimerWithTimeInterval: 0.2 target: self
388         selector: @selector( UpdateUI: ) userInfo: NULL repeats: FALSE]
389         forMode: NSModalPanelRunLoopMode];
390 }
391
392 - (void) EnableUI: (bool) b
393 {
394     NSControl * controls[] =
395       { fSrcDVD1Field, fSrcDVD2Field, fSrcTitleField, fSrcTitlePopUp,
396         fSrcChapterField, fSrcChapterStartPopUp, fSrcChapterToField,
397         fSrcChapterEndPopUp, fSrcDuration1Field, fSrcDuration2Field,
398         fDstFormatField, fDstFormatPopUp, fDstCodecsField,
399         fDstCodecsPopUp, fDstFile1Field, fDstFile2Field,
400         fDstBrowseButton, fVidRateField, fVidRatePopUp,
401         fVidEncoderField, fVidEncoderPopUp, fVidQualityField,
402         fVidQualityMatrix, fVidGrayscaleCheck, fSubField, fSubPopUp,
403         fAudLang1Field, fAudLang1PopUp, fAudLang2Field, fAudLang2PopUp,
404         fAudRateField, fAudRatePopUp, fAudBitrateField,
405         fAudBitratePopUp, fPictureButton };
406
407     for( unsigned i = 0;
408          i < sizeof( controls ) / sizeof( NSControl * ); i++ )
409     {
410         if( [[controls[i] className] isEqualToString: @"NSTextField"] )
411         {
412             NSTextField * tf = (NSTextField *) controls[i];
413             if( ![tf isBezeled] )
414             {
415                 [tf setTextColor: b ? [NSColor controlTextColor] :
416                     [NSColor disabledControlTextColor]];
417                 continue;
418             }
419         }
420         [controls[i] setEnabled: b];
421     }
422
423     [self VideoMatrixChanged: NULL];
424 }
425
426 - (IBAction) ShowScanPanel: (id) sender
427 {
428     [fScanController Show];
429 }
430
431 - (BOOL) windowShouldClose: (id) sender
432 {
433     /* Stop the application when the user closes the window */
434     [NSApp terminate: self];
435     return YES;
436 }
437
438 - (IBAction) VideoMatrixChanged: (id) sender;
439 {
440     bool target, bitrate, quality;
441
442     target = bitrate = quality = false;
443     if( [fVidQualityMatrix isEnabled] )
444     {
445         switch( [fVidQualityMatrix selectedRow] )
446         {
447             case 0:
448                 target = true;
449                 break;
450             case 1:
451                 bitrate = true;
452                 break;
453             case 2:
454                 quality = true;
455                 break;
456         }
457     }
458     [fVidTargetSizeField  setEnabled: target];
459     [fVidBitrateField     setEnabled: bitrate];
460     [fVidQualitySlider    setEnabled: quality];
461     [fVidTwoPassCheck     setEnabled: !quality &&
462         [fVidQualityMatrix isEnabled]];
463     if( quality )
464     {
465         [fVidTwoPassCheck setState: NSOffState];
466     }
467
468     [self QualitySliderChanged: sender];
469     [self CalculateBitrate:     sender];
470 }
471
472 - (IBAction) QualitySliderChanged: (id) sender
473 {
474     [fVidConstantCell setTitle: [NSString stringWithFormat:
475         _( @"Constant quality: %.0f %%" ), 100.0 *
476         [fVidQualitySlider floatValue]]];
477 }
478
479 - (IBAction) BrowseFile: (id) sender
480 {
481     /* Open a panel to let the user choose and update the text field */
482     NSSavePanel * panel = [NSSavePanel savePanel];
483
484     [panel beginSheetForDirectory: NULL file: NULL
485         modalForWindow: fWindow modalDelegate: self
486         didEndSelector: @selector( BrowseFileDone:returnCode:contextInfo: )
487         contextInfo: NULL];
488 }
489
490 - (void) BrowseFileDone: (NSSavePanel *) sheet
491     returnCode: (int) returnCode contextInfo: (void *) contextInfo
492 {
493     if( returnCode == NSOKButton )
494     {
495         [fDstFile2Field setStringValue: [sheet filename]];
496         [self FormatPopUpChanged: NULL];
497     }
498 }
499
500 - (IBAction) ShowPicturePanel: (id) sender
501 {
502     hb_list_t  * list  = hb_get_titles( fHandle );
503     hb_title_t * title = (hb_title_t *) hb_list_item( list,
504             [fSrcTitlePopUp indexOfSelectedItem] );
505
506     /* Resize the panel */
507     NSSize newSize;
508     newSize.width  = 246 + title->width;
509     newSize.height = 80 + title->height;
510     [fPicturePanel setContentSize: newSize];
511
512     [fPictureController SetTitle: title];
513
514     [NSApp beginSheet: fPicturePanel modalForWindow: fWindow
515         modalDelegate: NULL didEndSelector: NULL contextInfo: NULL];
516     [NSApp runModalForWindow: fPicturePanel];
517     [NSApp endSheet: fPicturePanel];
518     [fPicturePanel orderOut: self];
519 }
520
521 - (IBAction) ShowQueuePanel: (id) sender
522 {
523     /* Update the OutlineView */
524     [fQueueController Update: sender];
525
526     /* Show the panel */
527     [NSApp beginSheet: fQueuePanel modalForWindow: fWindow
528         modalDelegate: NULL didEndSelector: NULL contextInfo: NULL];
529     [NSApp runModalForWindow: fQueuePanel];
530     [NSApp endSheet: fQueuePanel];
531     [fQueuePanel orderOut: self];
532 }
533
534 - (void) PrepareJob
535 {
536     hb_list_t  * list  = hb_get_titles( fHandle );
537     hb_title_t * title = (hb_title_t *) hb_list_item( list,
538             [fSrcTitlePopUp indexOfSelectedItem] );
539     hb_job_t * job = title->job;
540
541     /* Chapter selection */
542     job->chapter_start = [fSrcChapterStartPopUp indexOfSelectedItem] + 1;
543     job->chapter_end   = [fSrcChapterEndPopUp   indexOfSelectedItem] + 1;
544
545     /* Format and codecs */
546     int format = [fDstFormatPopUp indexOfSelectedItem];
547     int codecs = [fDstCodecsPopUp indexOfSelectedItem];
548     job->mux    = FormatSettings[format][codecs] & HB_MUX_MASK;
549     job->vcodec = FormatSettings[format][codecs] & HB_VCODEC_MASK;
550     job->acodec = FormatSettings[format][codecs] & HB_ACODEC_MASK;
551
552     if( ( job->vcodec & HB_VCODEC_FFMPEG ) &&
553         [fVidEncoderPopUp indexOfSelectedItem] > 0 )
554     {
555         job->vcodec = HB_VCODEC_XVID;
556     }
557     if( job->vcodec & HB_VCODEC_X264 )
558     {
559         job->h264_13 = [fVidEncoderPopUp indexOfSelectedItem];
560     }
561
562     /* Video settings */
563     if( [fVidRatePopUp indexOfSelectedItem] > 0 )
564     {
565         job->vrate      = 27000000;
566         job->vrate_base = hb_video_rates[[fVidRatePopUp
567             indexOfSelectedItem]-1].rate;
568     }
569     else
570     {
571         job->vrate      = title->rate;
572         job->vrate_base = title->rate_base;
573     }
574
575     switch( [fVidQualityMatrix selectedRow] )
576     {
577         case 0:
578             /* Target size.
579                Bitrate should already have been calculated and displayed
580                in fVidBitrateField, so let's just use it */
581         case 1:
582             job->vquality = -1.0;
583             job->vbitrate = [fVidBitrateField intValue];
584             break;
585         case 2:
586             job->vquality = [fVidQualitySlider floatValue];
587             job->vbitrate = 0;
588             break;
589     }
590
591     job->grayscale = ( [fVidGrayscaleCheck state] == NSOnState );
592
593     /* Subtitle settings */
594     job->subtitle = [fSubPopUp indexOfSelectedItem] - 1;
595
596     /* Audio tracks */
597     job->audios[0] = [fAudLang1PopUp indexOfSelectedItem] - 1;
598     job->audios[1] = [fAudLang2PopUp indexOfSelectedItem] - 1;
599     job->audios[2] = -1;
600
601     /* Audio settings */
602     job->arate = hb_audio_rates[[fAudRatePopUp
603                      indexOfSelectedItem]].rate;
604     job->abitrate = hb_audio_bitrates[[fAudBitratePopUp
605                         indexOfSelectedItem]].rate;
606 }
607
608 - (IBAction) EnableQueue: (id) sender
609 {
610     bool e = ( [fQueueCheck state] == NSOnState );
611     [fQueueAddButton  setHidden: !e];
612     [fQueueShowButton setHidden: !e];
613     [fRipButton       setTitle: e ? @"Start" : @"Rip"];
614 }
615
616 - (IBAction) AddToQueue: (id) sender
617 {
618     hb_list_t  * list  = hb_get_titles( fHandle );
619     hb_title_t * title = (hb_title_t *) hb_list_item( list,
620             [fSrcTitlePopUp indexOfSelectedItem] );
621     hb_job_t * job = title->job;
622
623     [self PrepareJob];
624
625     /* Destination file */
626     job->file = strdup( [[fDstFile2Field stringValue] UTF8String] );
627
628     if( [fVidTwoPassCheck state] == NSOnState )
629     {
630         job->pass = 1;
631         hb_add( fHandle, job );
632         job->pass = 2;
633         hb_add( fHandle, job );
634     }
635     else
636     {
637         job->pass = 0;
638         hb_add( fHandle, job );
639     }
640 }
641
642 - (IBAction) Rip: (id) sender
643 {
644     /* Rip or Cancel ? */
645     if( [[fRipButton title] isEqualToString: _( @"Cancel" )] )
646     {
647         [self Cancel: sender];
648         return;
649     }
650
651     if( [fQueueCheck state] == NSOffState )
652     {
653         [self AddToQueue: sender];
654     }
655
656     if( [[NSFileManager defaultManager] fileExistsAtPath:
657             [fDstFile2Field stringValue]] )
658     {
659         NSBeginCriticalAlertSheet( _( @"File already exists" ),
660             _( @"Cancel" ), _( @"Overwrite" ), NULL, fWindow, self,
661             @selector( OverwriteAlertDone:returnCode:contextInfo: ),
662             NULL, NULL, [NSString stringWithFormat:
663             _( @"Do you want to overwrite %@?" ),
664             [fDstFile2Field stringValue]] );
665         return;
666     }
667
668     [self _Rip];
669 }
670
671 - (void) OverwriteAlertDone: (NSWindow *) sheet
672     returnCode: (int) returnCode contextInfo: (void *) contextInfo
673 {
674     if( returnCode == NSAlertAlternateReturn )
675     {
676         [self _Rip];
677     }
678 }
679
680 - (void) UpdateAlertDone: (NSWindow *) sheet
681     returnCode: (int) returnCode contextInfo: (void *) contextInfo
682 {
683     if( returnCode == NSAlertAlternateReturn )
684     {
685         /* Show scan panel */
686         [self performSelectorOnMainThread: @selector(ShowScanPanel:)
687             withObject: NULL waitUntilDone: NO];
688         return;
689     }
690
691     /* Go to HandBrake homepage and exit */
692     [self OpenHomepage: NULL];
693     [NSApp terminate: self];
694 }
695
696 - (void) _Rip
697 {
698     /* Let libhb do the job */
699     hb_start( fHandle );
700
701     /* Disable interface */
702     [self EnableUI: NO];
703     [fPauseButton setEnabled: NO];
704     [fRipButton   setEnabled: NO];
705 }
706
707 - (IBAction) Cancel: (id) sender
708 {
709     NSBeginCriticalAlertSheet( _( @"Cancel - Are you sure?" ),
710         _( @"Keep working" ), _( @"Cancel encoding" ), NULL, fWindow, self,
711         @selector( _Cancel:returnCode:contextInfo: ), NULL, NULL,
712         _( @"Encoding won't be recoverable." ) );
713 }
714
715 - (void) _Cancel: (NSWindow *) sheet
716     returnCode: (int) returnCode contextInfo: (void *) contextInfo
717 {
718     if( returnCode == NSAlertAlternateReturn )
719     {
720         hb_stop( fHandle );
721         [fPauseButton setEnabled: NO];
722         [fRipButton   setEnabled: NO];
723     }
724 }
725
726 - (IBAction) Pause: (id) sender
727 {
728     [fPauseButton setEnabled: NO];
729     [fRipButton   setEnabled: NO];
730
731     if( [[fPauseButton title] isEqualToString: _( @"Resume" )] )
732     {
733         hb_resume( fHandle );
734     }
735     else
736     {
737         hb_pause( fHandle );
738     }
739 }
740
741 - (IBAction) TitlePopUpChanged: (id) sender
742 {
743     hb_list_t  * list  = hb_get_titles( fHandle );
744     hb_title_t * title = (hb_title_t*)
745         hb_list_item( list, [fSrcTitlePopUp indexOfSelectedItem] );
746
747     /* Update chapter popups */
748     [fSrcChapterStartPopUp removeAllItems];
749     [fSrcChapterEndPopUp   removeAllItems];
750     for( int i = 0; i < hb_list_count( title->list_chapter ); i++ )
751     {
752         [fSrcChapterStartPopUp addItemWithTitle: [NSString
753             stringWithFormat: @"%d", i + 1]];
754         [fSrcChapterEndPopUp addItemWithTitle: [NSString
755             stringWithFormat: @"%d", i + 1]];
756     }
757     [fSrcChapterStartPopUp selectItemAtIndex: 0];
758     [fSrcChapterEndPopUp   selectItemAtIndex:
759         hb_list_count( title->list_chapter ) - 1];
760     [self ChapterPopUpChanged: NULL];
761
762     /* Update subtitle popups */
763     hb_subtitle_t * subtitle;
764     [fSubPopUp removeAllItems];
765     [fSubPopUp addItemWithTitle: @"None"];
766     for( int i = 0; i < hb_list_count( title->list_subtitle ); i++ )
767     {
768         subtitle = (hb_subtitle_t *) hb_list_item( title->list_subtitle, i );
769
770         /* We cannot use NSPopUpButton's addItemWithTitle because
771            it checks for duplicate entries */
772         [[fSubPopUp menu] addItemWithTitle: [NSString stringWithCString:
773             subtitle->lang] action: NULL keyEquivalent: @""];
774     }
775     [fSubPopUp selectItemAtIndex: 0];
776
777     /* Update lang popups */
778     hb_audio_t * audio;
779     [fAudLang1PopUp removeAllItems];
780     [fAudLang2PopUp removeAllItems];
781     [fAudLang1PopUp addItemWithTitle: _( @"None" )];
782     [fAudLang2PopUp addItemWithTitle: _( @"None" )];
783     for( int i = 0; i < hb_list_count( title->list_audio ); i++ )
784     {
785         audio = (hb_audio_t *) hb_list_item( title->list_audio, i );
786
787         [[fAudLang1PopUp menu] addItemWithTitle:
788             [NSString stringWithCString: audio->lang]
789             action: NULL keyEquivalent: @""];
790         [[fAudLang2PopUp menu] addItemWithTitle:
791             [NSString stringWithCString: audio->lang]
792             action: NULL keyEquivalent: @""];
793     }
794     [fAudLang1PopUp selectItemAtIndex: 1];
795     [fAudLang2PopUp selectItemAtIndex: 0];
796 }
797
798 - (IBAction) ChapterPopUpChanged: (id) sender
799 {
800     hb_list_t  * list  = hb_get_titles( fHandle );
801     hb_title_t * title = (hb_title_t *)
802         hb_list_item( list, [fSrcTitlePopUp indexOfSelectedItem] );
803
804     hb_chapter_t * chapter;
805     int64_t        duration = 0;
806     for( int i = [fSrcChapterStartPopUp indexOfSelectedItem];
807          i <= [fSrcChapterEndPopUp indexOfSelectedItem]; i++ )
808     {
809         chapter = (hb_chapter_t *) hb_list_item( title->list_chapter, i );
810         duration += chapter->duration;
811     }
812     
813     duration /= 90000; /* pts -> seconds */
814     [fSrcDuration2Field setStringValue: [NSString stringWithFormat:
815         @"%02lld:%02lld:%02lld", duration / 3600, ( duration / 60 ) % 60,
816         duration % 60]];
817
818     [self CalculateBitrate: sender];
819 }
820
821 - (IBAction) FormatPopUpChanged: (id) sender
822 {
823     NSString * string = [fDstFile2Field stringValue];
824     int format = [fDstFormatPopUp indexOfSelectedItem];
825     char * ext = NULL;
826
827     /* Update the codecs popup */
828     [fDstCodecsPopUp removeAllItems];
829     switch( format )
830     {
831         case 0:
832             ext = "mp4";
833             [fDstCodecsPopUp addItemWithTitle:
834                 _( @"MPEG-4 Video / AAC Audio" )];
835             [fDstCodecsPopUp addItemWithTitle:
836                 _( @"AVC/H.264 Video / AAC Audio" )];
837             break;
838         case 1: 
839             ext = "avi";
840             [fDstCodecsPopUp addItemWithTitle:
841                 _( @"MPEG-4 Video / MP3 Audio" )];
842             [fDstCodecsPopUp addItemWithTitle:
843                 _( @"MPEG-4 Video / AC-3 Audio" )];
844             [fDstCodecsPopUp addItemWithTitle:
845                 _( @"AVC/H.264 Video / MP3 Audio" )];
846             [fDstCodecsPopUp addItemWithTitle:
847                 _( @"AVC/H.264 Video / AC-3 Audio" )];
848             break;
849         case 2:
850             ext = "ogm";
851             [fDstCodecsPopUp addItemWithTitle:
852                 _( @"MPEG-4 Video / Vorbis Audio" )];
853             [fDstCodecsPopUp addItemWithTitle:
854                 _( @"MPEG-4 Video / MP3 Audio" )];
855             break;
856     }
857     [self CodecsPopUpChanged: NULL];
858
859     /* Add/replace to the correct extension */
860     if( [string characterAtIndex: [string length] - 4] == '.' )
861     {
862         [fDstFile2Field setStringValue: [NSString stringWithFormat:
863             @"%@.%s", [string substringToIndex: [string length] - 4],
864             ext]];
865     }
866     else
867     {
868         [fDstFile2Field setStringValue: [NSString stringWithFormat:
869             @"%@.%s", string, ext]];
870     }
871 }
872
873 - (IBAction) CodecsPopUpChanged: (id) sender
874 {
875     int format = [fDstFormatPopUp indexOfSelectedItem];
876     int codecs = [fDstCodecsPopUp indexOfSelectedItem];
877
878     /* Update the encoder popup */
879     if( ( FormatSettings[format][codecs] & HB_VCODEC_X264 ) )
880     {
881         /* MPEG-4 -> H.264 */
882         [fVidEncoderPopUp removeAllItems];
883         [fVidEncoderPopUp addItemWithTitle: @"x264 (Main profile)"];
884         [fVidEncoderPopUp addItemWithTitle: @"x264 (Baseline profile)"];
885     }
886     else if( ( FormatSettings[format][codecs] & HB_VCODEC_FFMPEG ) )
887     {
888         /* H.264 -> MPEG-4 */
889         [fVidEncoderPopUp removeAllItems];
890         [fVidEncoderPopUp addItemWithTitle: @"FFmpeg"];
891         [fVidEncoderPopUp addItemWithTitle: @"XviD"];
892         [fVidEncoderPopUp selectItemAtIndex: 0];
893     }
894
895     if( FormatSettings[format][codecs] & HB_ACODEC_AC3 )
896     {
897         /* AC-3 pass-through: disable samplerate and bitrate */
898         [fAudRatePopUp    setEnabled: NO];
899         [fAudBitratePopUp setEnabled: NO];
900     }
901     else
902     {
903         [fAudRatePopUp    setEnabled: YES];
904         [fAudBitratePopUp setEnabled: YES];
905     }
906
907     [self CalculateBitrate: sender];
908 }
909
910 - (IBAction) CalculateBitrate: (id) sender
911 {
912     if( !fHandle || [fVidQualityMatrix selectedRow] != 0 )
913     {
914         return;
915     }
916
917     hb_list_t  * list  = hb_get_titles( fHandle );
918     hb_title_t * title = (hb_title_t *) hb_list_item( list,
919             [fSrcTitlePopUp indexOfSelectedItem] );
920     hb_job_t * job = title->job;
921
922     [self PrepareJob];
923
924     [fVidBitrateField setIntValue: hb_calc_bitrate( job,
925             [fVidTargetSizeField intValue] )];
926 }
927
928 - (void) controlTextDidBeginEditing: (NSNotification *) notification
929 {
930     [self CalculateBitrate: NULL];
931 }
932
933 - (void) controlTextDidEndEditing: (NSNotification *) notification
934 {
935     [self CalculateBitrate: NULL];
936 }
937
938 - (void) controlTextDidChange: (NSNotification *) notification
939 {
940     [self CalculateBitrate: NULL];
941 }
942
943 - (IBAction) OpenHomepage: (id) sender
944 {
945     [[NSWorkspace sharedWorkspace] openURL: [NSURL
946         URLWithString:@"http://handbrake.m0k.org/"]];
947 }
948
949 - (IBAction) OpenForums: (id) sender
950 {
951     [[NSWorkspace sharedWorkspace] openURL: [NSURL
952         URLWithString:@"http://handbrake.m0k.org/forum/"]];
953 }
954
955 @end