OSDN Git Service

MacGui: internal code changes
[handbrake-jp/handbrake-jp-git.git] / macosx / ExpressController.m
1 #import "ExpressController.h"
2 #import "DriveDetector.h"
3
4 #define INSERT_STRING @"Insert a DVD"
5
6 @interface ExpressController (Private)
7
8 - (void) openUpdateDrives: (NSDictionary *) drives;
9 - (void) openBrowseDidEnd: (NSOpenPanel *) sheet returnCode: (int)
10     returnCode contextInfo: (void *) contextInfo;
11 - (void) openEnable: (BOOL) b;
12 - (void) openTimer: (NSTimer *) timer;
13
14 - (void) convertShow;
15 - (void) convertEnable: (BOOL) b;
16 - (void) convertTimer: (NSTimer *) timer;
17
18 @end
19
20 @implementation ExpressController
21
22 /***********************************************************************
23  * Application delegate methods
24  **********************************************************************/
25 - (void) awakeFromNib
26 {
27     NSEnumerator * enumerator;
28
29     /* Show the "Open DVD" interface */
30     fDriveDetector = [[DriveDetector alloc] initWithCallback: self
31         selector: @selector( openUpdateDrives: )];
32     [fDriveDetector run];
33     [self openEnable: YES];
34     [fWindow setContentSize: [fOpenView frame].size];
35     [fWindow setContentView: fOpenView];
36     [fWindow center];
37     [fWindow makeKeyAndOrderFront: nil];
38
39     /* NSTableView initializations */
40      NSButtonCell * buttonCell;
41      NSTableColumn * tableColumn;
42      enumerator = [[fConvertTableView tableColumns] objectEnumerator];
43      while( ( tableColumn = [enumerator nextObject] ) )
44      {
45          [tableColumn setEditable: NO];
46      }
47      tableColumn = [fConvertTableView tableColumnWithIdentifier: @"Check"];
48      buttonCell = [[[NSButtonCell alloc] initTextCell: @""] autorelease];
49     [buttonCell setEditable: YES];
50     [buttonCell setButtonType: NSSwitchButton];
51     [tableColumn setDataCell: buttonCell];
52
53     /* Preferences */
54     fConvertFolderString = [@"~/Movies" stringByExpandingTildeInPath];
55     [fConvertFolderString retain];
56 }
57
58 - (void) applicationWillFinishLaunching: (NSNotification *) n
59 {
60     fHandle = hb_init_express( HB_DEBUG_NONE, 0 );
61     fList   = hb_get_titles( fHandle );
62 }
63
64 - (void) applicationWillTerminate: (NSNotification *) n
65 {
66     hb_close( &fHandle );
67 }
68
69 /***********************************************************************
70  * Tableview datasource methods
71  **********************************************************************/
72 - (int) numberOfRowsInTableView: (NSTableView *) t
73 {
74     if( !fHandle )
75         return 0;
76
77     return hb_list_count( fList );
78 }
79
80 - (id) tableView:(NSTableView *) t objectValueForTableColumn:
81     (NSTableColumn *) col row: (int) row
82 {
83     if( [[col identifier] isEqualToString: @"Check"] )
84     {
85         return [fConvertCheckArray objectAtIndex: row];
86     }
87     else
88     {
89         hb_title_t * title = hb_list_item( fList, row );
90         if( [[col identifier] isEqualToString: @"Title"] )
91         {
92             return [@"Title " stringByAppendingFormat: @"%d",
93                     title->index];
94         }
95         else if( [[col identifier] isEqualToString: @"Duration"] )
96         {
97             if( title->hours > 0 )
98             {
99                 return [NSString stringWithFormat:
100                     @"%d hour%s %d min%s", title->hours,
101                     title->hours > 1 ? "s" : "", title->minutes,
102                     title->minutes > 1 ? "s": ""];
103             }
104             else if( title->minutes > 0 )
105             {
106                 return [NSString stringWithFormat:
107                     @"%d min%s %d sec%s", title->minutes,
108                     title->minutes > 1 ? "s" : "", title->seconds,
109                     title->seconds > 1 ? "s": ""];
110             }
111             else
112             {
113                 return [NSString stringWithFormat: @"%d seconds",
114                         title->seconds];
115             }
116         }
117     }
118     return nil;
119 }
120
121 - (void) tableView: (NSTableView *) t setObjectValue: (id) object
122     forTableColumn: (NSTableColumn *) col row: (int) row
123 {
124     if( [[col identifier] isEqualToString: @"Check"] )
125     {
126         [fConvertCheckArray replaceObjectAtIndex: row withObject: object];
127     }
128 }
129
130 /***********************************************************************
131  * User events methods
132  **********************************************************************/
133 - (void) openShow: (id) sender
134 {
135     NSRect frame  = [fWindow frame];
136     float  offset = [fConvertView frame].size.height -
137                     [fOpenView frame].size.height;
138
139     frame.origin.y    += offset;
140     frame.size.height -= offset;
141     [fWindow setContentView: fEmptyView];
142     [fWindow setFrame: frame display: YES animate: YES];
143     [fWindow setContentView: fOpenView];
144
145     [fDriveDetector run];
146 }
147
148 - (void) openMatrixChanged: (id) sender
149 {
150     [self openEnable: YES];
151     if( [fOpenMatrix selectedRow] )
152     {
153         [self openBrowse: self];
154     }
155 }
156
157 - (void) openBrowse: (id) sender
158 {
159     NSOpenPanel * panel = [NSOpenPanel openPanel];
160     [panel setAllowsMultipleSelection: NO];
161     [panel setCanChooseFiles: YES];
162     [panel setCanChooseDirectories: YES ];
163     [panel beginSheetForDirectory: nil file: nil types: nil
164         modalForWindow: fWindow modalDelegate: self
165         didEndSelector: @selector( openBrowseDidEnd:returnCode:contextInfo: )
166         contextInfo: nil];                                                      
167 }
168
169 - (void) openGo: (id) sender
170 {
171     [self openEnable: NO];
172     [fOpenIndicator setIndeterminate: YES];
173     [fOpenIndicator startAnimation: nil];
174     [fOpenProgressField setStringValue: @"Opening..."];
175     [fDriveDetector stop];
176
177     if( [fOpenMatrix selectedRow] )
178     {
179         hb_scan( fHandle, [fOpenFolderString UTF8String], 0 );
180     }
181     else
182     {
183         hb_scan( fHandle, [[fDrives objectForKey: [fOpenPopUp
184                  titleOfSelectedItem]] UTF8String], 0 );
185     }
186
187     NSTimer * timer = [NSTimer scheduledTimerWithTimeInterval: 2.0
188         target: self selector: @selector( openTimer: ) userInfo: nil
189         repeats: YES];
190 }
191
192 - (void) convertGo: (id) sender
193 {
194     int i, j;
195
196     for( i = 0; i < hb_list_count( fList ); i++ )
197     {
198         if( ![[fConvertCheckArray objectAtIndex: i] boolValue] )
199             continue;
200
201         hb_title_t * title = hb_list_item( fList, i );
202         hb_job_t   * job   = title->job;
203
204         int pixels = 307200;
205                 int aspect = title->aspect;
206                 if( [fConvertAspectPopUp indexOfSelectedItem] == 1)
207                 {
208             aspect = 4 * HB_ASPECT_BASE / 3;
209                 }
210
211                 int maxwidth = 640;
212                 job->vbitrate = 1000;
213                 if( [fConvertMaxWidthPopUp indexOfSelectedItem] == 1)
214                 {
215             maxwidth = 320;
216                         job->vbitrate = 500;
217                 }
218                 job->deinterlace = 1;
219                 
220                 do
221                 {
222                         hb_set_size( job, aspect, pixels );
223                         pixels -= 10;
224                 } while(job->width > maxwidth);
225                 
226         if( [fConvertFormatPopUp indexOfSelectedItem] == 0 )
227         {
228             /* iPod / H.264 */
229             job->mux      = HB_MUX_IPOD;
230             job->vcodec   = HB_VCODEC_X264;
231                         job->h264_level = 30;
232         }
233         else if( [fConvertFormatPopUp indexOfSelectedItem] == 1 )
234         {
235             /* iPod / MPEG-4 */
236             job->mux      = HB_MUX_MP4;
237             job->vcodec   = HB_VCODEC_FFMPEG;
238         }
239         else
240         {
241             /* PSP / MPEG-4 */
242             job->mux        = HB_MUX_PSP;
243             job->vrate      = 27000000;
244             job->vrate_base = 900900;   /* 29.97 fps */
245             job->vcodec     = HB_VCODEC_FFMPEG;
246             job->vbitrate   = 600;
247             pixels          = 76800;
248             job->arate      = 24000;
249             job->abitrate   = 96;
250             aspect          = 16 * HB_ASPECT_BASE / 9;
251
252                         if( [fConvertAspectPopUp indexOfSelectedItem] )
253                         {
254                                 aspect = -1;
255                         }
256
257                         hb_set_size( job, aspect, pixels );
258         }
259
260         job->vquality = -1.0;
261
262         const char * lang;
263
264         /* Audio selection */
265         hb_audio_t * audio;
266         lang = [[fConvertAudioPopUp titleOfSelectedItem] UTF8String];
267         job->audios[0] = -1;
268         for( j = 0; j < hb_list_count( title->list_audio ); j++ )
269         {
270             /* Choose the first track that matches the language */
271             audio = hb_list_item( title->list_audio, j );
272             if( !strcmp( lang, audio->lang_simple ) )
273             {
274                 job->audios[0] = j;
275                 break;
276             }
277         }
278         if( job->audios[0] == -1 )
279         {
280             /* If the language isn't available in this title, choose
281                the first track */
282             job->audios[0] = 0;
283         }
284         job->audios[1] = -1;
285
286         /* Subtitle selection */
287         hb_subtitle_t * subtitle;
288         lang = [[fConvertSubtitlePopUp titleOfSelectedItem] UTF8String];
289         job->subtitle = -1;
290         for( j = 0; j < hb_list_count( title->list_subtitle ); j++ )
291         {
292             /* Choose the first track that matches the language */
293             subtitle = hb_list_item( title->list_subtitle, j );
294             if( !strcmp( lang, subtitle->lang ) )
295             {
296                 job->subtitle = j;
297                 break;
298             }
299         }
300         
301         job->file = strdup( [[NSString stringWithFormat:                 
302                 @"%@/%s - Title %d.m4v", fConvertFolderString,      
303                 title->name, title->index] UTF8String] );
304         hb_add( fHandle, job );
305     }
306
307     hb_start( fHandle );
308
309     NSTimer * timer = [NSTimer scheduledTimerWithTimeInterval: 2.0
310         target: self selector: @selector( convertTimer: ) userInfo: nil
311         repeats: YES];
312
313     [self convertEnable: NO];
314 }
315
316 - (void) convertCancel: (id) sender
317 {
318     hb_stop( fHandle );
319     [self convertEnable: YES];
320 }
321
322 @end
323
324 /***********************************************************************
325  * Private methods
326  **********************************************************************/
327
328 @implementation ExpressController (Private)
329
330 - (void) openUpdateDrives: (NSDictionary *) drives
331 {
332     if( fDrives )
333     {
334         [fDrives release];
335     }
336     fDrives = [[NSDictionary alloc] initWithDictionary: drives];
337
338     NSString * device;
339     NSEnumerator * enumerator = [fDrives keyEnumerator];
340     [fOpenPopUp removeAllItems];
341     while( ( device = [enumerator nextObject] ) )
342     {
343         [fOpenPopUp addItemWithTitle: device];
344     }
345
346     if( ![fOpenPopUp numberOfItems] )
347     {
348         [fOpenPopUp addItemWithTitle: INSERT_STRING];
349     }
350     [fOpenPopUp selectItemAtIndex: 0];
351     if( [fOpenMatrix isEnabled] )
352     {
353         [self openEnable: YES];
354     }
355 }
356
357 - (void) openBrowseDidEnd: (NSOpenPanel *) sheet returnCode: (int)
358     returnCode contextInfo: (void *) contextInfo
359 {
360     if( returnCode != NSOKButton )
361         return;
362
363     if( fOpenFolderString )
364         [fOpenFolderString release];
365     fOpenFolderString = [[[sheet filenames] objectAtIndex: 0] retain];
366     [fOpenFolderField setStringValue: [fOpenFolderString lastPathComponent]];
367     [self openGo: self];
368 }
369
370 - (void) openEnable: (BOOL) b
371 {
372     [fOpenMatrix       setEnabled: b];
373     [fOpenPopUp        setEnabled: b];
374     [fOpenFolderField  setEnabled: b];
375     [fOpenBrowseButton setEnabled: b];
376     [fOpenGoButton     setEnabled: b];
377
378     if( b )
379     {
380         if( [fOpenMatrix selectedRow] )
381         {
382             [fOpenPopUp setEnabled: NO];
383         }
384         else
385         {
386             [fOpenFolderField  setEnabled: NO];
387             [fOpenBrowseButton setEnabled: NO];
388             if( [[fOpenPopUp titleOfSelectedItem]
389                     isEqualToString: INSERT_STRING] )
390             {
391                 [fOpenGoButton setEnabled: NO];
392             }
393         }
394     }
395 }
396
397 - (void) openTimer: (NSTimer *) timer
398 {
399     hb_state_t s;
400     hb_get_state( fHandle, &s );
401     switch( s.state )
402     {
403 #define p s.param.scanning
404         case HB_STATE_SCANNING:
405             [fOpenIndicator setIndeterminate: NO];
406             [fOpenIndicator setDoubleValue: 100.0 *
407                 ( (float) p.title_cur - 0.5 ) / p.title_count];
408             [fOpenProgressField setStringValue: [NSString
409                 stringWithFormat: @"Scanning title %d of %d...",
410                 p.title_cur, p.title_count]];
411             break;
412 #undef p
413
414         case HB_STATE_SCANDONE:
415             [timer invalidate];
416
417             [fOpenIndicator setIndeterminate: NO];
418             [fOpenIndicator setDoubleValue: 0.0];
419             [self openEnable: YES];
420
421             if( hb_list_count( fList ) )
422             {
423                 [self convertShow];
424             }
425             else
426             {
427                 [fDriveDetector run];
428             }
429             break;
430
431         default:
432             break;
433     }
434 }
435
436 - (void) convertShow
437 {
438     int i, j;
439
440     fConvertCheckArray = [[NSMutableArray alloc] initWithCapacity:
441         hb_list_count( fList )];
442     [fConvertAudioPopUp removeAllItems];
443     [fConvertSubtitlePopUp removeAllItems];
444     [fConvertSubtitlePopUp addItemWithTitle: @"None"];
445     for( i = 0; i < hb_list_count( fList ); i++ )
446     {
447         /* Default is to convert titles longer than 30 minutes. */
448         hb_title_t * title = hb_list_item( fList, i );
449         [fConvertCheckArray addObject: [NSNumber numberWithBool:
450             ( 60 * title->hours + title->minutes > 30 )]];
451
452         /* Update audio popup */
453         hb_audio_t * audio;
454         for( j = 0; j < hb_list_count( title->list_audio ); j++ )
455         {
456             audio = hb_list_item( title->list_audio, j );
457             [fConvertAudioPopUp addItemWithTitle:
458                 [NSString stringWithUTF8String: audio->lang_simple]];
459         }
460                 [fConvertAudioPopUp selectItemWithTitle: @"English"];
461
462         /* Update subtitle popup */
463         hb_subtitle_t * subtitle;
464         for( j = 0; j < hb_list_count( title->list_subtitle ); j++ )
465         {
466             subtitle = hb_list_item( title->list_subtitle, j );
467             [fConvertSubtitlePopUp addItemWithTitle:
468                 [NSString stringWithUTF8String: subtitle->lang]];
469         }
470     }
471     [fConvertTableView reloadData];
472
473     NSRect frame  = [fWindow frame];
474     float  offset = [fConvertView frame].size.height -
475                     [fOpenView frame].size.height;
476     frame.origin.y    -= offset;
477     frame.size.height += offset;
478     [fWindow setContentView: fEmptyView];
479     [fWindow setFrame: frame display: YES animate: YES];
480     [fWindow setContentView: fConvertView];
481
482     /* Folder popup */
483     NSMenuItem * item = [fConvertFolderPopUp itemAtIndex: 0];
484     [item setTitle: [fConvertFolderString lastPathComponent]];
485     NSImage * image32 = [[NSWorkspace sharedWorkspace] iconForFile:
486         fConvertFolderString];
487     NSImage * image16 = [[NSImage alloc] initWithSize:
488         NSMakeSize(16,16)];
489     [image16 lockFocus];
490     [[NSGraphicsContext currentContext]
491         setImageInterpolation: NSImageInterpolationHigh];
492     [image32 drawInRect: NSMakeRect(0,0,16,16)
493         fromRect: NSMakeRect(0,0,32,32) operation: NSCompositeCopy
494         fraction: 1.0];
495     [image16 unlockFocus];                                                      
496     [item setImage: image16];
497     [image16 release];
498
499     [self convertEnable: YES];
500 }
501
502 - (void) convertEnable: (BOOL) b
503 {
504     [fConvertTableView setEnabled: b];
505     [fConvertFolderPopUp setEnabled: b];
506     [fConvertFormatPopUp setEnabled: b];
507     [fConvertAspectPopUp setEnabled: b];
508     [fConvertMaxWidthPopUp setEnabled: b];
509     [fConvertAudioPopUp setEnabled: b];
510     [fConvertSubtitlePopUp setEnabled: b];
511     [fConvertOpenButton setEnabled: b];
512     if( b )
513     {
514         [fConvertGoButton setTitle: @"Convert"];
515         [fConvertGoButton setAction: @selector(convertGo:)];
516     }
517     else
518     {
519         [fConvertGoButton setTitle: @"Cancel"];
520         [fConvertGoButton setAction: @selector(convertCancel:)];
521     }
522 }
523
524 /***********************************************************************
525 * UpdateDockIcon
526 ***********************************************************************
527 * Shows a progression bar on the dock icon, filled according to
528 * 'progress' (0.0 <= progress <= 1.0).
529 * Called with progress < 0.0 or progress > 1.0, restores the original
530 * icon.
531 **********************************************************************/
532 - (void) UpdateDockIcon: (float) progress
533 {
534     NSImage * icon;
535     NSData * tiff;
536     NSBitmapImageRep * bmp;
537     uint32_t * pen;
538     uint32_t black = htonl( 0x000000FF );
539     uint32_t red   = htonl( 0xFF0000FF );
540     uint32_t white = htonl( 0xFFFFFFFF );
541     int row_start, row_end;
542     int i, j;
543         
544     /* Get application original icon */
545     icon = [NSImage imageNamed: @"NSApplicationIcon"];
546         
547     if( progress < 0.0 || progress > 1.0 )
548     {
549         [NSApp setApplicationIconImage: icon];
550         return;
551     }
552         
553     /* Get it in a raw bitmap form */
554     tiff = [icon TIFFRepresentationUsingCompression:
555                                            NSTIFFCompressionNone factor: 1.0];
556     bmp = [NSBitmapImageRep imageRepWithData: tiff];
557     
558     /* Draw the progression bar */
559     /* It's pretty simple (ugly?) now, but I'm no designer */
560         
561     row_start = 3 * (int) [bmp size].height / 4;
562     row_end   = 7 * (int) [bmp size].height / 8;
563         
564     for( i = row_start; i < row_start + 2; i++ )
565     {
566         pen = (uint32_t *) ( [bmp bitmapData] + i * [bmp bytesPerRow] );
567         for( j = 0; j < (int) [bmp size].width; j++ )
568         {
569             pen[j] = black;
570         }
571     }
572     for( i = row_start + 2; i < row_end - 2; i++ )
573     {
574         pen = (uint32_t *) ( [bmp bitmapData] + i * [bmp bytesPerRow] );
575         pen[0] = black;
576         pen[1] = black;
577         for( j = 2; j < (int) [bmp size].width - 2; j++ )
578         {
579             if( j < 2 + (int) ( ( [bmp size].width - 4.0 ) * progress ) )
580             {
581                 pen[j] = red;
582             }
583             else
584             {
585                 pen[j] = white;
586             }
587         }
588         pen[j]   = black;
589         pen[j+1] = black;
590     }
591     for( i = row_end - 2; i < row_end; i++ )
592     {
593         pen = (uint32_t *) ( [bmp bitmapData] + i * [bmp bytesPerRow] );
594         for( j = 0; j < (int) [bmp size].width; j++ )
595         {
596             pen[j] = black;
597         }
598     }
599         
600     /* Now update the dock icon */
601     tiff = [bmp TIFFRepresentationUsingCompression:
602                                           NSTIFFCompressionNone factor: 1.0];
603     icon = [[NSImage alloc] initWithData: tiff];
604     [NSApp setApplicationIconImage: icon];
605     [icon release];
606 }
607
608 - (void) convertTimer: (NSTimer *) timer
609 {
610     hb_state_t s;
611     hb_get_state( fHandle, &s );
612     switch( s.state )
613     {
614 #define p s.param.working
615         case HB_STATE_WORKING:
616         {
617             float progress_total = ( p.progress + p.job_cur - 1 ) / p.job_count;
618             NSMutableString * string = [NSMutableString
619                 stringWithFormat: @"Converting: %.1f %%",
620                 100.0 * progress_total];
621             if( p.seconds > -1 )
622             {
623                 [string appendFormat: @" (%.1f fps, ", p.rate_avg];
624                 if( p.hours > 0 )
625                 {
626                     [string appendFormat: @"%d hour%s %d min%s",
627                         p.hours, p.hours == 1 ? "" : "s",
628                         p.minutes, p.minutes == 1 ? "" : "s"];
629                 }
630                 else if( p.minutes > 0 )
631                 {
632                     [string appendFormat: @"%d min%s %d sec%s",
633                         p.minutes, p.minutes == 1 ? "" : "s",
634                         p.seconds, p.seconds == 1 ? "" : "s"];
635                 }
636                 else
637                 {
638                     [string appendFormat: @"%d second%s",
639                         p.seconds, p.seconds == 1 ? "" : "s"];
640                 }
641                 [string appendString: @" left)"];
642             }
643             [fConvertInfoString setStringValue: string];
644             [fConvertIndicator setIndeterminate: NO];
645             [fConvertIndicator setDoubleValue: 100.0 * progress_total];
646             [self UpdateDockIcon: progress_total];
647                         break;
648         }
649 #undef p
650
651 #define p s.param.muxing
652         case HB_STATE_MUXING:
653         {
654             NSMutableString * string = [NSMutableString
655                 stringWithFormat: @"Muxing..."];
656             [fConvertInfoString setStringValue: string];
657             [fConvertIndicator setIndeterminate: YES];
658             [fConvertIndicator startAnimation: nil];
659             [self UpdateDockIcon: 1.0];
660             break;
661         }
662 #undef p
663
664         case HB_STATE_WORKDONE:
665                 {
666                         [timer invalidate];
667             [fConvertIndicator setIndeterminate: NO];
668             [fConvertIndicator setDoubleValue: 0.0];
669             [self UpdateDockIcon: -1.0];
670             [self convertEnable: YES];
671                         
672 #define p s.param.workdone
673                         switch(p.error)
674                         {
675                                 case HB_ERROR_NONE:
676                                         [fConvertInfoString setStringValue: @"Done."];
677                                         break;
678                                 case HB_ERROR_CANCELED:
679                                         [fConvertInfoString setStringValue: @"Canceled."];
680                                         break;
681                                 case HB_ERROR_UNKNOWN:
682                                         [fConvertInfoString setStringValue: @"Unknown Error."];
683                                         break;
684                         }
685 #undef p
686
687                         hb_job_t * job;
688             while( ( job = hb_job( fHandle, 0 ) ) )
689             {
690                 hb_rem( fHandle, job );
691             }
692                         break;
693                 }
694         default:
695             break;
696     }
697 }
698
699 @end