OSDN Git Service

HandBrake 0.5
[handbrake-jp/handbrake-jp-git.git] / macosx / Controller.mm
1 /* $Id: Controller.mm,v 1.7 2003/11/07 21:22:17 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 <paths.h>
8 #include <IOKit/IOKitLib.h>
9 #include <IOKit/IOBSD.h>
10 #include <IOKit/storage/IOMedia.h>
11 #include <IOKit/storage/IODVDMedia.h>
12
13 #include "Controller.h"
14
15 @implementation HBController
16
17 - (void) applicationDidFinishLaunching: (NSNotification *) notification
18 {
19     /* Init libhb */
20     fHandle = HBInit( 1, 0 );
21     [fPictureGLView SetHandle: fHandle];
22
23     /* Update the GUI every 1/10 sec */
24     fDie = false;
25     [NSTimer scheduledTimerWithTimeInterval: 0.1
26         target: self selector: @selector( UpdateIntf: )
27         userInfo: nil repeats: YES];
28
29     /* Detect drives mounted after the app is started */
30     [[[NSWorkspace sharedWorkspace] notificationCenter]
31         addObserver: self selector: @selector( DetectDrives: )
32         name: NSWorkspaceDidMountNotification object: nil];
33 }
34
35 - (NSApplicationTerminateReply) applicationShouldTerminate:
36     (NSApplication *) app
37 {
38     /* Clean up */
39     fDie = true;
40     HBClose( &fHandle );
41
42     return NSTerminateNow;
43 }
44
45 - (void) awakeFromNib
46 {
47     [fDVDPopUp removeAllItems];
48     [fScanProgress setStyle: NSProgressIndicatorSpinningStyle];
49     [fScanProgress setDisplayedWhenStopped: NO];
50     [fVideoCodecPopUp removeAllItems];
51     [fVideoCodecPopUp addItemWithTitle: @"MPEG-4 (Ffmpeg)"];
52     [fVideoCodecPopUp addItemWithTitle: @"MPEG-4 (XviD)"];
53     [fVideoCodecPopUp selectItemWithTitle: @"MPEG-4 (Ffmpeg)"];
54     [fAudioBitratePopUp removeAllItems];
55     [fAudioBitratePopUp addItemWithTitle: @"32"];
56     [fAudioBitratePopUp addItemWithTitle: @"64"];
57     [fAudioBitratePopUp addItemWithTitle: @"96"];
58     [fAudioBitratePopUp addItemWithTitle: @"128"];
59     [fAudioBitratePopUp addItemWithTitle: @"160"];
60     [fAudioBitratePopUp addItemWithTitle: @"192"];
61     [fAudioBitratePopUp addItemWithTitle: @"224"];
62     [fAudioBitratePopUp addItemWithTitle: @"256"];
63     [fAudioBitratePopUp addItemWithTitle: @"288"];
64     [fAudioBitratePopUp addItemWithTitle: @"320"];
65     [fAudioBitratePopUp selectItemWithTitle: @"128"];
66
67     char string[1024]; memset( string, 0, 1024 );
68     snprintf( string, 1024, "%s/Desktop/Movie.avi", getenv( "HOME" ) );
69     [fFileField setStringValue: [NSString stringWithCString: string]];
70
71     /* Show the scan view */
72     [fWindow setContentSize: [fScanView frame].size];
73     [fWindow setContentView: fScanView];
74     [fWindow center];
75
76     /* Detect DVD drives */
77     [self DetectDrives: nil];
78     [self ScanMatrixChanged: self];
79 }
80
81 - (BOOL) windowShouldClose: (id) sender
82 {
83     /* Stop the application when the user closes the window */
84     [NSApp terminate: self];
85     return YES;
86 }
87
88 - (IBAction) BrowseDVD: (id) sender
89 {
90     /* Open a panel to let the user choose and update the text field */
91     NSOpenPanel * panel = [NSOpenPanel openPanel];
92
93     [panel setAllowsMultipleSelection: NO];
94     [panel setCanChooseFiles: NO];
95     [panel setCanChooseDirectories: YES ];
96
97     [panel beginSheetForDirectory: nil file: nil types: nil
98         modalForWindow: fWindow modalDelegate: self
99         didEndSelector: @selector( BrowseDVDDone:returnCode:contextInfo: )
100         contextInfo: nil];
101 }
102
103 - (void) BrowseDVDDone: (NSOpenPanel *) sheet
104     returnCode: (int) returnCode contextInfo: (void *) contextInfo
105 {
106     if( returnCode == NSOKButton )
107     {
108         [fDVDFolderField setStringValue:
109             [[sheet filenames] objectAtIndex: 0]];
110     }
111 }
112
113 - (IBAction) VideoMatrixChanged: (id) sender;
114 {
115     if( ![fVideoMatrix selectedRow] )
116     {
117         [fCustomBitrateField setEnabled: YES];
118         [fTargetSizeField setEnabled: NO];
119     }
120     else
121     {
122         [fCustomBitrateField setEnabled: NO];
123         [fTargetSizeField setEnabled: YES];
124         [fTargetSizeField UpdateBitrate];
125     }
126 }
127
128 - (IBAction) BrowseFile: (id) sender
129 {
130     /* Open a panel to let the user choose and update the text field */
131     NSSavePanel * panel = [NSSavePanel savePanel];
132
133     [panel beginSheetForDirectory: nil file: nil
134         modalForWindow: fWindow modalDelegate: self
135         didEndSelector: @selector( BrowseFileDone:returnCode:contextInfo: )
136         contextInfo: nil];
137 }
138
139 - (void) BrowseFileDone: (NSSavePanel *) sheet
140     returnCode: (int) returnCode contextInfo: (void *) contextInfo
141 {
142     if( returnCode == NSOKButton )
143     {
144         [fFileField setStringValue: [sheet filename]];
145     }
146 }
147
148 - (IBAction) Scan: (id) sender
149 {
150     /* Ask libhb to start scanning the specified volume */
151     
152     if( ![fScanMatrix selectedRow] )
153     {
154         /* DVD drive */
155         HBScanDevice( fHandle,
156                       (char*) [[fDVDPopUp titleOfSelectedItem] cString],
157                       0 );
158     }
159     else
160     {
161         /* DVD folder */
162         HBScanDevice( fHandle,
163                       (char*) [[fDVDFolderField stringValue] cString],
164                       0 );
165     }
166 }
167
168 - (IBAction) ShowPicturePanel: (id) sender
169 {
170     HBTitle * title = (HBTitle*)
171         HBListItemAt( fTitleList, [fTitlePopUp indexOfSelectedItem] );
172     
173     [fPictureGLView SetTitle: title];
174
175     fPicture = 0;
176     [fPictureGLView ShowPicture: fPicture animate: HB_ANIMATE_NONE];
177
178     [fWidthStepper  setValueWraps: NO];
179     [fWidthStepper  setIncrement: 16];
180     [fWidthStepper  setMinValue: 16];
181     [fWidthStepper  setMaxValue: title->outWidthMax];
182     [fWidthStepper  setIntValue: title->outWidth];
183     [fWidthField    setIntValue: title->outWidth];
184     [fDeinterlaceCheck setState:
185         title->deinterlace ? NSOnState : NSOffState];
186     [fTopStepper    setValueWraps: NO];
187     [fTopStepper    setIncrement: 2];
188     [fTopStepper    setMinValue: 0];
189     [fTopStepper    setMaxValue: title->inHeight / 4];
190     [fTopStepper    setIntValue: title->topCrop];
191     [fTopField      setIntValue: title->topCrop];
192     [fBottomStepper setValueWraps: NO];
193     [fBottomStepper setIncrement: 2];
194     [fBottomStepper setMinValue: 0];
195     [fBottomStepper setMaxValue: title->inHeight / 4];
196     [fBottomStepper setIntValue: title->bottomCrop];
197     [fBottomField   setIntValue: title->bottomCrop];
198     [fLeftStepper   setValueWraps: NO];
199     [fLeftStepper   setIncrement: 2];
200     [fLeftStepper   setMinValue: 0];
201     [fLeftStepper   setMaxValue: title->inWidth / 4];
202     [fLeftStepper   setIntValue: title->leftCrop];
203     [fLeftField     setIntValue: title->leftCrop];
204     [fRightStepper  setValueWraps: NO];
205     [fRightStepper  setIncrement: 2];
206     [fRightStepper  setMinValue: 0];
207     [fRightStepper  setMaxValue: title->inWidth / 4];
208     [fRightStepper  setIntValue: title->rightCrop];
209     [fRightField    setIntValue: title->rightCrop];
210
211     [fPreviousButton setEnabled: NO];
212     [fNextButton     setEnabled: YES];
213
214     char string[1024]; memset( string, 0, 1024 );
215     sprintf( string, "Final size: %dx%d",
216              title->outWidth, title->outHeight );
217     [fInfoField setStringValue: [NSString stringWithCString: string]];
218
219     /* Resize the panel */
220     NSSize newSize;
221     /* XXX */
222     newSize.width  = 762 /*fPicturePanelSize.width*/ +
223         title->outWidthMax - 720;
224     newSize.height = 740 /*fPicturePanelSize.height*/ +
225         title->outHeightMax - 576;
226     [fPicturePanel setContentSize: newSize];
227
228     [NSApp beginSheet: fPicturePanel modalForWindow: fWindow
229         modalDelegate: nil didEndSelector: nil contextInfo: nil];
230     [NSApp runModalForWindow: fPicturePanel];
231     [NSApp endSheet: fPicturePanel];
232     [fPicturePanel orderOut: self];
233 }
234
235 - (IBAction) ClosePanel: (id) sender
236 {
237     [NSApp stopModal];
238 }
239
240 - (IBAction) Rip: (id) sender
241 {
242     /* Rip or Cancel ? */
243     if( [[fRipButton title] compare: @"Cancel" ] == NSOrderedSame )
244     {
245         [self Cancel: self];
246         return;
247     }
248     
249     if( [fCustomBitrateField intValue] < 256 )
250     {
251         NSBeginCriticalAlertSheet( @"Invalid video bitrate", @"Ooops",
252             nil, nil, fWindow, self, nil, nil, nil,
253             @"Video bitrate is too low !" );
254         return;
255     }
256     if( [fCustomBitrateField intValue] > 8192 )
257     {
258         NSBeginCriticalAlertSheet( @"Invalid video bitrate", @"Ooops",
259             nil, nil, fWindow, self, nil, nil, nil,
260             @"Video bitrate is too high !" );
261         return;
262     }
263     if( [fLanguagePopUp indexOfSelectedItem] ==
264             [fSecondaryLanguagePopUp indexOfSelectedItem] )
265     {
266         NSBeginCriticalAlertSheet( @"Invalid secondary language",
267             @"Ooops", nil, nil, fWindow, self, nil, nil, nil,
268             @"Do you _really_ want to encode the same audio track twice?" );
269         return;
270     }
271
272     FILE * file;
273     if( ( file = fopen( [[fFileField stringValue] cString], "r" ) ) )
274     {
275         fclose( file );
276         NSBeginCriticalAlertSheet( @"File already exists",
277             @"Nooo!", @"Yes, go ahead", nil, fWindow, self,
278             @selector( OverwriteAlertDone:returnCode:contextInfo: ),
279             nil, nil,
280             [NSString stringWithFormat: @"Do you want to overwrite %s?",
281                 [[fFileField stringValue] cString]] );
282         return;
283     }
284
285     [self _Rip];
286 }
287
288 - (void) OverwriteAlertDone: (NSWindow *) sheet
289     returnCode: (int) returnCode contextInfo: (void *) contextInfo
290 {
291     if( returnCode == NSAlertAlternateReturn )
292     {
293         [self _Rip];
294     }
295 }
296
297 - (void) _Rip
298 {
299     /* Get the specified title & audio track(s) */
300     HBTitle * title = (HBTitle*)
301         HBListItemAt( fTitleList, [fTitlePopUp indexOfSelectedItem] );
302     HBAudio * audio1 = (HBAudio*)
303         HBListItemAt( title->audioList,
304                       [fLanguagePopUp indexOfSelectedItem] );
305     HBAudio * audio2 = (HBAudio*)
306         HBListItemAt( title->audioList,
307                       [fSecondaryLanguagePopUp indexOfSelectedItem] );
308
309     /* Use user settings */
310     title->file    = strdup( [[fFileField stringValue] cString] );
311     title->bitrate = [fCustomBitrateField intValue];
312     title->twoPass = ( [fTwoPassCheck state] == NSOnState );
313     title->codec   = ( [[fVideoCodecPopUp titleOfSelectedItem] compare:
314                          @"MPEG-4 (Ffmpeg)"] == NSOrderedSame ) ?
315                          HB_CODEC_FFMPEG : HB_CODEC_XVID;
316     audio1->outBitrate = [[fAudioBitratePopUp titleOfSelectedItem]
317                               intValue];
318     if( audio2 )
319     {
320         audio2->outBitrate =
321             [[fAudioBitratePopUp titleOfSelectedItem] intValue];
322     }
323
324     /* Let libhb do the job */
325     HBStartRip( fHandle, title, audio1, audio2 );
326 }
327
328 - (IBAction) Cancel: (id) sender
329 {
330     HBStopRip( fHandle );
331 }
332
333 - (IBAction) Suspend: (id) sender
334 {
335     if( [[fSuspendButton title] compare: @"Resume" ] == NSOrderedSame )
336     {
337         [self Resume: self];
338         return;
339     }
340
341     HBPauseRip( fHandle );
342 }
343
344 - (IBAction) Resume: (id) sender
345 {
346     HBResumeRip( fHandle );
347 }
348
349 - (IBAction) PreviousPicture: (id) sender
350 {
351     fPicture--;
352     if( [fOpenGLCheck state] == NSOnState )
353     {
354         [fPictureGLView ShowPicture: fPicture
355             animate: HB_ANIMATE_LEFT];
356     }
357     else
358     {
359         [fPictureGLView ShowPicture: fPicture
360             animate: HB_ANIMATE_NONE];
361     }
362
363     [fPreviousButton setEnabled: ( fPicture > 0 )];
364     [fNextButton     setEnabled: YES];
365 }
366
367 - (IBAction) NextPicture: (id) sender
368 {
369     fPicture++;
370     if( [fOpenGLCheck state] == NSOnState )
371     {
372         [fPictureGLView ShowPicture: fPicture
373             animate: HB_ANIMATE_RIGHT];
374     }
375     else
376     {
377         [fPictureGLView ShowPicture: fPicture
378             animate: HB_ANIMATE_NONE];
379     }
380
381     [fPreviousButton setEnabled: YES];
382     [fNextButton     setEnabled: ( fPicture < 9 )];
383 }
384
385 - (IBAction) UpdatePicture: (id) sender
386 {
387     HBTitle * title = (HBTitle*)
388         HBListItemAt( fTitleList, [fTitlePopUp indexOfSelectedItem] );
389     title->outWidth    = [fWidthStepper intValue];
390     title->deinterlace = ( [fDeinterlaceCheck state] == NSOnState );
391     title->topCrop     = [fTopStepper intValue];
392     title->bottomCrop  = [fBottomStepper intValue];
393     title->leftCrop    = [fLeftStepper intValue];
394     title->rightCrop   = [fRightStepper intValue];
395
396     [fPictureGLView ShowPicture: fPicture animate: HB_ANIMATE_NONE];
397
398     [fWidthStepper  setIntValue: title->outWidth];
399     [fTopStepper    setIntValue: title->topCrop];
400     [fBottomStepper setIntValue: title->bottomCrop];
401     [fLeftStepper   setIntValue: title->leftCrop];
402     [fRightStepper  setIntValue: title->rightCrop];
403     [fWidthField    setIntValue: [fWidthStepper intValue]];
404     [fTopField      setIntValue: [fTopStepper intValue]];
405     [fBottomField   setIntValue: [fBottomStepper intValue]];
406     [fLeftField     setIntValue: [fLeftStepper intValue]];
407     [fRightField    setIntValue: [fRightStepper intValue]];
408
409     char string[1024]; memset( string, 0, 1024 );
410     sprintf( string, "Final size: %dx%d",
411              title->outWidth, title->outHeight );
412     [fInfoField setStringValue: [NSString stringWithCString: string]];
413 }
414
415 - (void) UpdateIntf: (NSTimer *) timer
416 {
417     if( fDie )
418     {
419         [timer invalidate];
420         return;
421     }
422
423     int      modeChanged;
424     HBStatus status;
425
426     modeChanged = HBGetStatus( fHandle, &status );
427
428     switch( status.mode )
429     {
430         case HB_MODE_NEED_DEVICE:
431             break;
432
433         case HB_MODE_SCANNING:
434         {
435             if( modeChanged )
436             {
437                 [fScanMatrix setEnabled: NO];
438                 [fDVDPopUp setEnabled: NO];
439                 [fDVDFolderField setEnabled: NO];
440                 [fScanBrowseButton setEnabled: NO];
441                 [fScanProgress startAnimation: self];
442                 [fScanButton setEnabled: NO];
443             }
444
445             char string[1024]; memset( string, 0, 1024 );
446             if( status.scannedTitle )
447             {
448                 sprintf( string, "Scanning title %d...",
449                          status.scannedTitle );
450             }
451             else
452             {
453                 sprintf( string, "Opening device..." );
454             }
455             [fScanStatusField setStringValue:
456                 [NSString stringWithCString: string]];
457
458             break;
459         }
460
461         case HB_MODE_INVALID_DEVICE:
462         {
463             if( !modeChanged )
464                 break;
465             
466             [fScanMatrix setEnabled: YES];
467             [self ScanMatrixChanged: self];
468             [fScanProgress stopAnimation: self];
469             [fScanButton setEnabled: YES];
470
471             [fScanStatusField setStringValue:
472                 @"Invalid volume, try again" ];
473             break;
474         }
475
476         case HB_MODE_READY_TO_RIP:
477         {
478             if( !modeChanged )
479                 break;
480             
481             fTitleList = status.titleList;
482             
483             /* Show a temporary empty view while the window
484                resizing animation */
485             [fWindow setContentView: fTempView ];
486
487             /* Actually resize it */
488             NSRect newFrame;
489             newFrame = [NSWindow contentRectForFrameRect: [fWindow frame]
490                          styleMask: [fWindow styleMask]];
491             newFrame.origin.y    += newFrame.size.height -
492                                         [fRipView frame].size.height;
493             newFrame.size.height  = [fRipView frame].size.height;
494             newFrame.size.width   = [fRipView frame].size.width;
495             newFrame = [NSWindow frameRectForContentRect: newFrame
496                          styleMask: [fWindow styleMask]];
497             [fWindow setFrame: newFrame display: YES animate: YES];
498
499             /* Show the new GUI */
500             [fWindow setContentView: fRipView ];
501             [fSuspendButton setEnabled: NO];
502             
503             [fTitlePopUp removeAllItems];
504             HBTitle * title;
505             for( int i = 0; i < HBListCountItems( fTitleList ); i++ )
506             {
507                 title = (HBTitle*) HBListItemAt( fTitleList, i );
508                 char string[1024]; memset( string, 0, 1024 );
509                 sprintf( string, "%d - %02dh%02dm%02ds",
510                          title->index, title->length / 3600,
511                          ( title->length % 3600 ) / 60,
512                          title->length % 60 );
513                 [[fTitlePopUp menu] addItemWithTitle:
514                     [NSString stringWithCString: string]
515                     action: nil keyEquivalent: @""];
516             }
517             [self TitlePopUpChanged: self];
518             
519             break;
520         }
521
522         case HB_MODE_ENCODING:
523         {
524             if( modeChanged )
525             {
526                 [fTitlePopUp             setEnabled: NO];
527                 [fVideoCodecPopUp        setEnabled: NO];
528                 [fVideoMatrix            setEnabled: NO];
529                 [fCustomBitrateField     setEnabled: NO];
530                 [fTargetSizeField        setEnabled: NO];
531                 [fTwoPassCheck           setEnabled: NO];
532                 [fCropButton             setEnabled: NO];
533                 [fLanguagePopUp          setEnabled: NO];
534                 [fSecondaryLanguagePopUp setEnabled: NO];
535                 [fAudioCodecPopUp        setEnabled: NO];
536                 [fAudioBitratePopUp      setEnabled: NO];
537                 [fFileFormatPopUp        setEnabled: NO];
538                 [fFileBrowseButton       setEnabled: NO];
539                 [fSuspendButton          setEnabled: YES];
540                 [fSuspendButton          setTitle: @"Suspend"];
541                 [fRipButton              setTitle: @"Cancel"];
542             }
543         
544             if( !status.position )
545             {
546                 [fRipStatusField setStringValue: @"Starting..."];
547                 [fRipProgress setIndeterminate: YES];
548                 [fRipProgress startAnimation: self];;
549             }
550             else
551             {
552                 char string[1024];
553                 memset( string, 0, 1024 );
554                 sprintf( string, "Encoding: %.2f %% (pass %d of %d)",
555                          100 * status.position, status.pass,
556                          status.passCount );
557                 [fRipStatusField setStringValue:
558                     [NSString stringWithCString: string]];
559                 memset( string, 0, 1024 );
560                 sprintf( string, "Speed: %.2f fps (avg %.2f fps, "
561                          "%02dh%02dm%02ds remaining)",
562                          status.frameRate, status.avFrameRate,
563                          status.remainingTime / 3600,
564                          ( status.remainingTime / 60 ) % 60,
565                          status.remainingTime % 60 );
566                 [fRipInfoField setStringValue:
567                     [NSString stringWithCString: string]];
568
569                 [fRipProgress setIndeterminate: NO];
570                 [fRipProgress setDoubleValue: 100 * status.position];
571             }
572             
573             break;
574         }
575
576         case HB_MODE_PAUSED:
577         {
578             if( !modeChanged )
579                 break;
580             
581             char string[1024]; memset( string, 0, 1024 );
582             sprintf( string, "Encoding: %.2f %% (PAUSED)",
583                      100 * status.position ) ;
584             [fRipStatusField setStringValue:
585                 [NSString stringWithCString: string]];
586             [fRipInfoField setStringValue: @""];
587             
588             [fRipProgress setDoubleValue: 100 * status.position];
589
590             [fSuspendButton setTitle: @"Resume"];
591             break;
592         }
593
594         case HB_MODE_STOPPING:
595             if( !modeChanged )
596                 break;
597
598             [fRipStatusField setStringValue: @"Stopping..."];
599             [fRipInfoField setStringValue: @""];
600             [fRipProgress setIndeterminate: YES];
601             [fRipProgress startAnimation: self];;
602             break;
603
604         case HB_MODE_DONE:
605         case HB_MODE_CANCELED:
606         case HB_MODE_ERROR:
607             if( !modeChanged )
608                 break;
609
610             /* Warn the finder to update itself */
611             [[NSWorkspace sharedWorkspace] noteFileSystemChanged:
612                 [fFileField stringValue]];
613             
614             [fRipProgress setIndeterminate: NO];
615             [fRipInfoField setStringValue: @""];
616
617             if( status.mode == HB_MODE_DONE )
618             {
619                 [fRipProgress setDoubleValue: 100];
620                 [fRipStatusField setStringValue: @"Done." ];
621                 NSBeep();
622                 [NSApp requestUserAttention: NSInformationalRequest];
623                 [NSApp beginSheet: fDonePanel
624                     modalForWindow: fWindow modalDelegate: nil
625                     didEndSelector: nil contextInfo: nil];
626                 [NSApp runModalForWindow: fDonePanel];
627                 [NSApp endSheet: fDonePanel];
628                 [fDonePanel orderOut: self];
629             }
630             else if( status.mode == HB_MODE_CANCELED )
631             {
632                 [fRipProgress setDoubleValue: 0];
633                 [fRipStatusField setStringValue: @"Canceled." ];
634             }
635             else
636             {
637                 [fRipProgress setDoubleValue: 0];
638                 switch( status.error )
639                 {
640                     case HB_ERROR_A52_SYNC:
641                         [fRipStatusField setStringValue:
642                         @"An error occured (corrupted AC3 data)." ];
643                         break;
644                     case HB_ERROR_AVI_WRITE:
645                         [fRipStatusField setStringValue:
646                         @"An error occured (could not write to file)." ];
647                         break;
648                     case HB_ERROR_DVD_OPEN:
649                         [fRipStatusField setStringValue:
650                         @"An error occured (could not open device)." ];
651                         break;
652                     case HB_ERROR_DVD_READ:
653                         [fRipStatusField setStringValue:
654                         @"An error occured (DVD read failed)." ];
655                         break;
656                     case HB_ERROR_MP3_INIT:
657                         [fRipStatusField setStringValue:
658                         @"An error occured (could not init MP3 encoder)." ];
659                         break;
660                     case HB_ERROR_MP3_ENCODE:
661                         [fRipStatusField setStringValue:
662                         @"An error occured (MP3 encoder failed)." ];
663                         break;
664                     case HB_ERROR_MPEG4_INIT:
665                         [fRipStatusField setStringValue:
666                         @"An error occured (could not init MPEG4 encoder)." ];
667                         break;
668                 }
669             }
670
671             [fTitlePopUp             setEnabled: YES];
672             [fVideoCodecPopUp        setEnabled: YES];
673             [fVideoMatrix            setEnabled: YES];
674             [fTwoPassCheck           setEnabled: YES];
675             [fCropButton             setEnabled: YES];
676             [fLanguagePopUp          setEnabled: YES];
677             [fSecondaryLanguagePopUp setEnabled: YES];
678             [fAudioCodecPopUp        setEnabled: YES];
679             [fAudioBitratePopUp      setEnabled: YES];
680             [fFileFormatPopUp        setEnabled: YES];
681             [fFileBrowseButton       setEnabled: YES];
682             [fSuspendButton          setEnabled: NO];
683             [fSuspendButton          setTitle: @"Suspend"];
684             [fRipButton              setTitle: @"Rip"];
685
686             [self VideoMatrixChanged: self];
687             [self VideoCodecPopUpChanged: self];
688
689             break;
690
691         default:
692             break;
693     }
694 }
695
696 - (void) DetectDrives: (NSNotification *) notification
697 {
698     /* Scan DVD drives (stolen from VLC) */
699     io_object_t next_media;
700     mach_port_t master_port;
701     kern_return_t kern_result;
702     io_iterator_t media_iterator;
703     CFMutableDictionaryRef classes_to_match;
704
705     kern_result = IOMasterPort( MACH_PORT_NULL, &master_port );
706     if( kern_result != KERN_SUCCESS )
707     {
708         return;
709     }
710
711     classes_to_match = IOServiceMatching( kIODVDMediaClass );
712     if( classes_to_match == NULL )
713     {
714         return;
715     }
716
717     CFDictionarySetValue( classes_to_match, CFSTR( kIOMediaEjectableKey ),
718                           kCFBooleanTrue );
719
720     kern_result =
721         IOServiceGetMatchingServices( master_port, classes_to_match,
722                                       &media_iterator );
723     if( kern_result != KERN_SUCCESS )
724     {
725         return;
726     }
727
728     NSMutableArray * drivesList;
729     drivesList = [NSMutableArray arrayWithCapacity: 1];
730
731     next_media = IOIteratorNext( media_iterator );
732     if( next_media != NULL )
733     {
734         char psz_buf[0x32];
735         size_t dev_path_length;
736         CFTypeRef str_bsd_path;
737         do
738         {
739             str_bsd_path =
740                 IORegistryEntryCreateCFProperty( next_media,
741                                                  CFSTR( kIOBSDNameKey ),
742                                                  kCFAllocatorDefault,
743                                                  0 );
744             if( str_bsd_path == NULL )
745             {
746                 IOObjectRelease( next_media );
747                 continue;
748             }
749
750             snprintf( psz_buf, sizeof(psz_buf), "%s%c", _PATH_DEV, 'r' );
751             dev_path_length = strlen( psz_buf );
752
753             if( CFStringGetCString( (CFStringRef) str_bsd_path,
754                                     (char*)&psz_buf + dev_path_length,
755                                     sizeof(psz_buf) - dev_path_length,
756                                     kCFStringEncodingASCII ) )
757             {
758                 [drivesList addObject:
759                     [NSString stringWithCString: psz_buf]];
760             }
761
762             CFRelease( str_bsd_path );
763
764             IOObjectRelease( next_media );
765
766         } while( ( next_media = IOIteratorNext( media_iterator ) ) != NULL );
767     }
768
769     IOObjectRelease( media_iterator );
770
771     [fDVDPopUp removeAllItems];
772     for( unsigned i = 0; i < [drivesList count]; i++ )
773     {
774         [[fDVDPopUp menu] addItemWithTitle:
775             [drivesList objectAtIndex: i] action: nil
776             keyEquivalent: @""];
777     }
778     [self ScanMatrixChanged: self];
779 }
780
781 - (IBAction) ScanMatrixChanged: (id) sender
782 {
783     if( ![fScanMatrix selectedRow] )
784     {
785         [fDVDPopUp setEnabled: YES];
786         [fDVDFolderField setEnabled: NO];
787         [fScanBrowseButton setEnabled: NO];
788         [fScanButton setEnabled: ( [fDVDPopUp selectedItem] != nil )];
789     }
790     else
791     {
792         [fDVDPopUp setEnabled: NO];
793         [fDVDFolderField setEnabled: YES];
794         [fScanBrowseButton setEnabled: YES];
795         [fScanButton setEnabled: YES];
796     }
797 }
798
799 - (IBAction) TitlePopUpChanged: (id) sender
800 {
801     HBTitle * title = (HBTitle*)
802         HBListItemAt( fTitleList, [fTitlePopUp indexOfSelectedItem] );
803
804     [fLanguagePopUp removeAllItems];
805     [fSecondaryLanguagePopUp removeAllItems];
806     
807     HBAudio * audio;
808     for( int i = 0; i < HBListCountItems( title->audioList ); i++ )
809     {
810         audio = (HBAudio*) HBListItemAt( title->audioList, i );
811
812         /* We cannot use NSPopUpButton's addItemWithTitle because
813            it checks for duplicate entries */
814         [[fLanguagePopUp menu] addItemWithTitle:
815             [NSString stringWithCString: audio->language]
816             action: nil keyEquivalent: @""];
817         [[fSecondaryLanguagePopUp menu] addItemWithTitle:
818             [NSString stringWithCString: audio->language]
819             action: nil keyEquivalent: @""];
820     }
821     [fSecondaryLanguagePopUp addItemWithTitle: @"None"];
822     [fSecondaryLanguagePopUp selectItemWithTitle: @"None"];
823     [fSecondaryLanguagePopUp setEnabled:
824         ( HBListCountItems( title->audioList ) > 1 )];
825
826     [fTargetSizeField SetHBTitle: title];
827     if( [fVideoMatrix selectedRow] )
828     {
829         [fTargetSizeField UpdateBitrate];
830     }
831 }
832
833 - (IBAction) VideoCodecPopUpChanged: (id) sender
834 {
835     if( [[fVideoCodecPopUp titleOfSelectedItem]
836             compare: @"MPEG-4 (Ffmpeg)"] == NSOrderedSame )
837     {
838         [fTwoPassCheck setEnabled: YES];
839     }
840     else
841     {
842         [fTwoPassCheck setState: NSOffState];
843         [fTwoPassCheck setEnabled: NO];
844     }
845 }
846
847 - (IBAction) AudioPopUpChanged: (id) sender
848 {
849     if( [fVideoMatrix selectedRow] )
850     {
851         [fTargetSizeField UpdateBitrate];
852     }
853 }
854
855 @end