]> git.sesse.net Git - vlc/blob - modules/gui/macosx/intf.m
MacOS X port:
[vlc] / modules / gui / macosx / intf.m
1 /*****************************************************************************
2  * intf.m: MacOS X interface plugin
3  *****************************************************************************
4  * Copyright (C) 2002 VideoLAN
5  * $Id: intf.m,v 1.17 2003/01/04 04:11:08 jlj Exp $
6  *
7  * Authors: Jon Lech Johansen <jon-vl@nanocrew.net>
8  *          Christophe Massiot <massiot@via.ecp.fr>
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  * 
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
23  *****************************************************************************/
24
25 /*****************************************************************************
26  * Preamble
27  *****************************************************************************/
28 #include <stdlib.h>                                      /* malloc(), free() */
29 #include <sys/param.h>                                    /* for MAXPATHLEN */
30 #include <string.h>
31
32 #include <QuickTime/QuickTime.h>
33
34 #include "intf.h"
35 #include "vout.h"
36 #include "prefs.h"
37 #include "playlist.h"
38
39 /*****************************************************************************
40  * Local prototypes.
41  *****************************************************************************/
42 static void Run       ( intf_thread_t *p_intf );
43
44 /*****************************************************************************
45  * OpenIntf: initialize interface
46  *****************************************************************************/
47 int E_(OpenIntf) ( vlc_object_t *p_this )
48 {   
49     intf_thread_t *p_intf = (intf_thread_t*) p_this;
50
51     p_intf->p_sys = malloc( sizeof( intf_sys_t ) );
52     if( p_intf->p_sys == NULL )
53     {
54         return( 1 );
55     }
56
57     memset( p_intf->p_sys, 0, sizeof( *p_intf->p_sys ) );
58
59     p_intf->p_sys->o_pool = [[NSAutoreleasePool alloc] init];
60     p_intf->p_sys->o_sendport = [[NSPort port] retain];
61
62     p_intf->p_sys->p_sub = msg_Subscribe( p_intf );
63
64     p_intf->pf_run = Run;
65
66     [[VLCApplication sharedApplication] autorelease];
67     [NSApp initIntlSupport];
68     [NSApp setIntf: p_intf];
69
70     [NSBundle loadNibNamed: @"MainMenu" owner: NSApp];
71
72     return( 0 );
73 }
74
75 /*****************************************************************************
76  * CloseIntf: destroy interface
77  *****************************************************************************/
78 void E_(CloseIntf) ( vlc_object_t *p_this )
79 {
80     intf_thread_t *p_intf = (intf_thread_t*) p_this;
81
82     msg_Unsubscribe( p_intf, p_intf->p_sys->p_sub );
83
84     [p_intf->p_sys->o_sendport release];
85     [p_intf->p_sys->o_pool release];
86
87     free( p_intf->p_sys );
88 }
89
90 /*****************************************************************************
91  * Run: main loop
92  *****************************************************************************/
93 static void Run( intf_thread_t *p_intf )
94 {
95     [NSApp run];
96 }
97
98 /*****************************************************************************
99  * VLCApplication implementation 
100  *****************************************************************************/
101 @implementation VLCApplication
102
103 - (id)init
104 {
105     /* default encoding: ISO-8859-1 */
106     i_encoding = NSISOLatin1StringEncoding;
107
108     return( [super init] );
109 }
110
111 - (void)initIntlSupport
112 {
113     char *psz_lang = getenv( "LANG" );
114
115     if( psz_lang == NULL )
116     {
117         return;
118     }
119
120     if( strncmp( psz_lang, "pl", 2 ) == 0 )
121     {
122         i_encoding = NSISOLatin2StringEncoding;
123     }
124     else if( strncmp( psz_lang, "ja", 2 ) == 0 ) 
125     {
126         i_encoding = NSJapaneseEUCStringEncoding;
127     }
128     else if( strncmp( psz_lang, "ru", 2 ) == 0 )
129     {
130 #define CFSENC2NSSENC(e) CFStringConvertEncodingToNSStringEncoding(e)
131         i_encoding = CFSENC2NSSENC( kCFStringEncodingKOI8_R ); 
132 #undef CFSENC2NSSENC
133     }
134 }
135
136 - (NSString *)localizedString:(char *)psz
137 {
138     UInt32 uiLength = (UInt32)strlen( psz );
139     NSData * o_data = [NSData dataWithBytes: psz length: uiLength];
140     NSString *o_str = [[NSString alloc] initWithData: o_data
141                                         encoding: i_encoding];
142     return( [o_str autorelease] );
143 }
144
145 - (void)setIntf:(intf_thread_t *)_p_intf
146 {
147     p_intf = _p_intf;
148 }
149
150 - (intf_thread_t *)getIntf
151 {
152     return( p_intf );
153 }
154
155 - (void)terminate:(id)sender
156 {
157     [self getIntf]->p_vlc->b_die = VLC_TRUE;
158 }
159
160 @end
161
162 /*****************************************************************************
163  * VLCMain implementation 
164  *****************************************************************************/
165 @implementation VLCMain
166
167 - (id)init
168 {
169     self = [super init];
170
171     if( self != nil )
172     {
173         o_prefs = nil;
174     }
175
176     return( self ); 
177 }
178
179 - (void)awakeFromNib
180 {
181     NSString * pTitle = [NSString
182         stringWithCString: VOUT_TITLE " (Cocoa)"];
183
184     [o_window setTitle: pTitle];
185
186     /* button controls */
187     [o_btn_playlist setToolTip: _NS("Playlist")];
188     [o_btn_prev setToolTip: _NS("Previous")];
189     [o_btn_slower setToolTip: _NS("Slower")];
190     [o_btn_play setToolTip: _NS("Play")];
191     [o_btn_pause setToolTip: _NS("Pause")];
192     [o_btn_stop setToolTip: _NS("Stop")];
193     [o_btn_faster setToolTip: _NS("Faster")];
194     [o_btn_next setToolTip: _NS("Next")];
195     [o_btn_prefs setToolTip: _NS("Preferences")];
196
197     /* messages panel */ 
198     [o_msgs_panel setTitle: _NS("Messages")];
199     [o_msgs_btn_ok setTitle: _NS("Close")];
200
201     /* main menu */
202     [o_mi_about setTitle: _NS("About vlc")];
203     [o_mi_prefs setTitle: _NS("Preferences")];
204     [o_mi_hide setTitle: _NS("Hide vlc")];
205     [o_mi_hide_others setTitle: _NS("Hide Others")];
206     [o_mi_show_all setTitle: _NS("Show All")];
207     [o_mi_quit setTitle: _NS("Quit vlc")];
208
209     [o_mu_file setTitle: _NS("File")];
210     [o_mi_open_generic setTitle: _NS("Open...")];
211     [o_mi_open_file setTitle: _NS("Open File...")];
212     [o_mi_open_disc setTitle: _NS("Open Disc...")];
213     [o_mi_open_net setTitle: _NS("Open Network...")];
214     [o_mi_open_recent setTitle: _NS("Open Recent")];
215     [o_mi_open_recent_cm setTitle: _NS("Clear Menu")];
216
217     [o_mu_edit setTitle: _NS("Edit")];
218     [o_mi_cut setTitle: _NS("Cut")];
219     [o_mi_copy setTitle: _NS("Copy")];
220     [o_mi_paste setTitle: _NS("Paste")];
221     [o_mi_clear setTitle: _NS("Clear")];
222     [o_mi_select_all setTitle: _NS("Select All")];
223
224     [o_mu_view setTitle: _NS("View")];
225     [o_mi_playlist setTitle: _NS("Playlist")];
226     [o_mi_messages setTitle: _NS("Messages")];
227
228     [o_mu_controls setTitle: _NS("Controls")];
229     [o_mi_play setTitle: _NS("Play")];
230     [o_mi_pause setTitle: _NS("Pause")];
231     [o_mi_stop setTitle: _NS("Stop")];
232     [o_mi_faster setTitle: _NS("Faster")];
233     [o_mi_slower setTitle: _NS("Slower")];
234     [o_mi_previous setTitle: _NS("Previous")];
235     [o_mi_next setTitle: _NS("Next")];
236     [o_mi_loop setTitle: _NS("Loop")];
237     [o_mi_vol_up setTitle: _NS("Volume Up")];
238     [o_mi_vol_down setTitle: _NS("Volume Down")];
239     [o_mi_mute setTitle: _NS("Mute")];
240     [o_mi_channels setTitle: _NS("Channels")];
241     [o_mi_device setTitle: _NS("Device")];
242     [o_mi_fullscreen setTitle: _NS("Fullscreen")];
243     [o_mi_screen setTitle: _NS("Screen")];
244     [o_mi_deinterlace setTitle: _NS("Deinterlace")];
245     [o_mi_program setTitle: _NS("Program")];
246     [o_mi_title setTitle: _NS("Title")];
247     [o_mi_chapter setTitle: _NS("Chapter")];
248     [o_mi_language setTitle: _NS("Language")];
249     [o_mi_subtitle setTitle: _NS("Subtitles")];
250
251     [o_mu_window setTitle: _NS("Window")];
252     [o_mi_minimize setTitle: _NS("Minimize")];
253     [o_mi_bring_atf setTitle: _NS("Bring All to Front")];
254
255     /* dock menu */
256     [o_dmi_play setTitle: _NS("Play")];
257     [o_dmi_pause setTitle: _NS("Pause")];
258     [o_dmi_stop setTitle: _NS("Stop")];
259
260     [self manageMode];
261 }
262
263 - (void)applicationWillFinishLaunching:(NSNotification *)o_notification
264 {
265     intf_thread_t * p_intf = [NSApp getIntf];
266
267     f_slider_old = f_slider = 0.0;
268     o_slider_lock = [[NSLock alloc] init];
269
270     [NSThread detachNewThreadSelector: @selector(manage)
271         toTarget: self withObject: nil];
272
273     [p_intf->p_sys->o_sendport setDelegate: self];
274     [[NSRunLoop currentRunLoop] 
275         addPort: p_intf->p_sys->o_sendport
276         forMode: NSDefaultRunLoopMode];
277 }
278
279 - (BOOL)application:(NSApplication *)o_app openFile:(NSString *)o_filename
280 {
281     [o_playlist appendArray:
282         [NSArray arrayWithObject: o_filename] atPos: -1];
283
284     return( TRUE );
285 }
286
287 - (void)manage
288 {
289     NSDate * o_sleep_date;
290     intf_thread_t * p_intf = [NSApp getIntf];
291     NSAutoreleasePool * o_pool = [[NSAutoreleasePool alloc] init];
292
293     while( !p_intf->b_die )
294     {
295         int i_start, i_stop;
296
297         vlc_mutex_lock( &p_intf->change_lock );
298
299         /* update the input */
300         if( p_intf->p_sys->p_input == NULL )
301         {
302             p_intf->p_sys->p_input = vlc_object_find( p_intf, VLC_OBJECT_INPUT,
303                                                               FIND_ANYWHERE );
304         }
305         else if( p_intf->p_sys->p_input->b_dead )
306         {
307             vlc_object_release( p_intf->p_sys->p_input );
308             p_intf->p_sys->p_input = NULL;
309         }
310
311         if( p_intf->p_sys->p_input != NULL )
312         {
313             vlc_bool_t b_need_menus = 0;
314             input_thread_t * p_input = p_intf->p_sys->p_input;
315             aout_instance_t * p_aout = vlc_object_find( p_intf, VLC_OBJECT_AOUT,
316                                                         FIND_ANYWHERE );
317             vout_thread_t * p_vout = vlc_object_find( p_intf, VLC_OBJECT_VOUT,
318                                                       FIND_ANYWHERE );
319
320             /* Disable screen saver. */
321             UpdateSystemActivity( UsrActivity );
322
323             vlc_mutex_lock( &p_input->stream.stream_lock );
324
325             if( !p_input->b_die )
326             {
327                 [self displayTime];
328
329                 /* New input or stream map change */
330                 if( p_input->stream.b_changed )
331                 {
332                     [self manageMode];
333                     b_need_menus = 1;
334                     p_intf->p_sys->b_playing = 1;
335                 }
336
337                 if( p_intf->p_sys->i_part !=
338                     p_input->stream.p_selected_area->i_part )
339                 {
340                     p_intf->p_sys->b_chapter_update = 1;
341                     b_need_menus = 1;
342                 }
343             }
344
345             if ( p_aout != NULL )
346             {
347                 vlc_value_t val;
348                 if ( var_Get( (vlc_object_t *)p_aout, "intf-change", &val )
349                       >= 0 && val.b_bool )
350                 {
351                     p_intf->p_sys->b_aout_update = 1;
352                     b_need_menus = 1;
353                 }
354                 vlc_object_release( (vlc_object_t *)p_aout );
355             }
356
357             if ( p_vout != NULL )
358             {
359                 vlc_value_t val;
360                 if ( var_Get( (vlc_object_t *)p_vout, "intf-change", &val )
361                       >= 0 && val.b_bool )
362                 {
363                     p_intf->p_sys->b_vout_update = 1;
364                     b_need_menus = 1;
365                 }
366                 vlc_object_release( (vlc_object_t *)p_vout );
367             }
368
369             if ( b_need_menus )
370                 [self setupMenus];
371
372             vlc_mutex_unlock( &p_input->stream.stream_lock );
373         }
374         else if( p_intf->p_sys->b_playing && !p_intf->b_die )
375         {
376             [self displayTime];
377             [self manageMode];
378             p_intf->p_sys->b_playing = 0;
379         }
380
381         /* update the log window */
382         vlc_mutex_lock( p_intf->p_sys->p_sub->p_lock );
383         i_stop = *p_intf->p_sys->p_sub->pi_stop;
384         vlc_mutex_unlock( p_intf->p_sys->p_sub->p_lock );
385
386         if( p_intf->p_sys->p_sub->i_start != i_stop )
387         {
388             NSColor *o_white = [NSColor whiteColor];
389             NSColor *o_red = [NSColor redColor];
390             NSColor *o_yellow = [NSColor yellowColor];
391             NSColor *o_gray = [NSColor grayColor];
392
393             unsigned int ui_length = [[o_messages string] length];
394
395             NSColor * pp_color[4] = { o_white, o_red, o_yellow, o_gray };
396             static const char * ppsz_type[4] = { ": ", " error: ", 
397                                                  " warning: ", " debug: " }; 
398         
399             [o_messages setEditable: YES];
400             [o_messages setSelectedRange: NSMakeRange( ui_length, 0 )];
401             [o_messages scrollRangeToVisible: NSMakeRange( ui_length, 0 )];
402
403             for( i_start = p_intf->p_sys->p_sub->i_start;
404                  i_start != i_stop;
405                  i_start = (i_start+1) % VLC_MSG_QSIZE )
406             {
407                 NSString *o_msg;
408                 NSDictionary *o_attr;
409                 NSAttributedString *o_msg_color;
410                 int i_type = p_intf->p_sys->p_sub->p_msg[i_start].i_type;
411
412                 o_attr = [NSDictionary dictionaryWithObject: o_gray
413                     forKey: NSForegroundColorAttributeName];
414                 o_msg = [NSString stringWithFormat: @"%s%s",
415                     p_intf->p_sys->p_sub->p_msg[i_start].psz_module, 
416                     ppsz_type[i_type]];
417                 o_msg_color = [[NSAttributedString alloc]
418                     initWithString: o_msg attributes: o_attr];
419                 [o_messages insertText: o_msg_color];
420
421                 o_attr = [NSDictionary dictionaryWithObject: pp_color[i_type]
422                     forKey: NSForegroundColorAttributeName];
423                 o_msg = [NSString stringWithCString:
424                     p_intf->p_sys->p_sub->p_msg[i_start].psz_msg];
425                 o_msg_color = [[NSAttributedString alloc]
426                     initWithString: o_msg attributes: o_attr];
427                 [o_messages insertText: o_msg_color];
428
429                 [o_messages insertText: @"\n"];
430             }
431
432             [o_messages setEditable: NO];
433
434             vlc_mutex_lock( p_intf->p_sys->p_sub->p_lock );
435             p_intf->p_sys->p_sub->i_start = i_start;
436             vlc_mutex_unlock( p_intf->p_sys->p_sub->p_lock );
437         }
438
439         vlc_mutex_unlock( &p_intf->change_lock );
440
441         o_sleep_date = [NSDate dateWithTimeIntervalSinceNow: 0.1];
442         [NSThread sleepUntilDate: o_sleep_date];
443     }
444
445     [self terminate];
446
447     [o_pool release];
448 }
449
450 - (void)terminate
451 {
452     NSEvent * pEvent;
453     vout_thread_t * p_vout;
454     playlist_t * p_playlist;
455     intf_thread_t * p_intf = [NSApp getIntf];
456
457     /* release input */
458     if( p_intf->p_sys->p_input )
459     {
460         vlc_object_release( p_intf->p_sys->p_input );
461         p_intf->p_sys->p_input = NULL;
462     }
463
464     /*
465      * Free playlists
466      */
467     msg_Dbg( p_intf, "removing all playlists" );
468     while( (p_playlist = vlc_object_find( p_intf->p_vlc, VLC_OBJECT_PLAYLIST,
469                                           FIND_CHILD )) )
470     {
471         vlc_object_detach( p_playlist );
472         vlc_object_release( p_playlist );
473         playlist_Destroy( p_playlist );
474     }
475
476     /*
477      * Free video outputs
478      */
479     msg_Dbg( p_intf, "removing all video outputs" );
480     while( (p_vout = vlc_object_find( p_intf->p_vlc, 
481                                       VLC_OBJECT_VOUT, FIND_CHILD )) )
482     {
483         vlc_object_detach( p_vout );
484         vlc_object_release( p_vout );
485         vout_Destroy( p_vout );
486     }
487
488     [o_slider_lock release];
489
490     if( o_prefs != nil )
491     {
492         [o_prefs release];
493         o_prefs = nil;
494     }
495
496     [NSApp stop: nil];
497
498     /* write cached user defaults to disk */
499     [[NSUserDefaults standardUserDefaults] synchronize];
500
501     /* send a dummy event to break out of the event loop */
502     pEvent = [NSEvent mouseEventWithType: NSLeftMouseDown
503                 location: NSMakePoint( 1, 1 ) modifierFlags: 0
504                 timestamp: 1 windowNumber: [[NSApp mainWindow] windowNumber]
505                 context: [NSGraphicsContext currentContext] eventNumber: 1
506                 clickCount: 1 pressure: 0.0];
507     [NSApp postEvent: pEvent atStart: YES];
508 }
509
510 - (void)manageMode
511 {
512     vlc_bool_t b_input;
513     vlc_bool_t b_plmul = 0;
514     vlc_bool_t b_control = 0;
515     playlist_t * p_playlist = NULL;
516     intf_thread_t * p_intf = [NSApp getIntf];
517
518     if( ( b_input = ( p_intf->p_sys->p_input != NULL ) ) )
519     {
520         /* control buttons for free pace streams */
521         b_control = p_intf->p_sys->p_input->stream.b_pace_control;
522
523         /* get ready for menu regeneration */
524         p_intf->p_sys->b_program_update = 1;
525         p_intf->p_sys->b_title_update = 1;
526         p_intf->p_sys->b_chapter_update = 1;
527         p_intf->p_sys->b_audio_update = 1;
528         p_intf->p_sys->b_spu_update = 1;
529         p_intf->p_sys->i_part = 0;
530
531         p_intf->p_sys->p_input->stream.b_changed = 0;
532         msg_Dbg( p_intf, "stream has changed, refreshing interface" );
533     }
534     else
535     {
536         /* unsensitize menus */
537         [o_mi_program setEnabled: FALSE];
538         [o_mi_title setEnabled: FALSE];
539         [o_mi_chapter setEnabled: FALSE];
540         [o_mi_language setEnabled: FALSE];
541         [o_mi_subtitle setEnabled: FALSE];
542         [o_mi_channels setEnabled: FALSE];
543         [o_mi_device setEnabled: FALSE];
544         [o_mi_screen setEnabled: FALSE];
545     }
546
547     p_playlist = vlc_object_find( p_intf, VLC_OBJECT_PLAYLIST, 
548                                               FIND_ANYWHERE ); 
549     if( p_playlist != NULL )
550     {
551         vlc_mutex_lock( &p_playlist->object_lock );
552         b_plmul = p_playlist->i_size > 1;
553         vlc_mutex_unlock( &p_playlist->object_lock );
554         vlc_object_release( p_playlist );
555     }
556
557     /* set control items */
558     [o_btn_stop setEnabled: b_input];
559     [o_btn_pause setEnabled: b_control];
560     [o_btn_faster setEnabled: b_control];
561     [o_btn_slower setEnabled: b_control];
562     [o_btn_prev setEnabled: b_plmul];
563     [o_btn_next setEnabled: b_plmul];
564
565     if ( (p_intf->p_sys->b_loop = config_GetInt( p_intf, "loop" )) )
566     {
567         [o_mi_loop setState: NSOnState];
568     }
569     else
570     {
571         [o_mi_loop setState: NSOffState];
572     }
573 }
574
575 - (void)setupMenus
576 {
577     unsigned int i, i_nb_items;
578     NSMenuItem * o_item;
579     NSString * o_menu_title;
580     char psz_title[ 256 ];
581
582     es_descriptor_t * p_audio_es = NULL;
583     es_descriptor_t * p_spu_es = NULL;
584
585     intf_thread_t * p_intf = [NSApp getIntf];
586
587     p_intf->p_sys->b_chapter_update |= p_intf->p_sys->b_title_update;
588     p_intf->p_sys->b_audio_update |= p_intf->p_sys->b_title_update |
589                                      p_intf->p_sys->b_program_update;
590     p_intf->p_sys->b_spu_update |= p_intf->p_sys->b_title_update |
591                                    p_intf->p_sys->b_program_update;
592
593 #define p_input (p_intf->p_sys->p_input)
594
595     if( p_intf->p_sys->b_program_update )
596     {
597         NSMenu * o_program;
598         SEL pf_toggle_program;
599         pgrm_descriptor_t * p_pgrm;
600
601         if( p_input->stream.p_new_program )
602         {
603             p_pgrm = p_input->stream.p_new_program;
604         }
605         else
606         {
607             p_pgrm = p_input->stream.p_selected_program;
608         }
609
610         o_program = [o_mi_program submenu];
611         pf_toggle_program = @selector(toggleProgram:);
612
613         /* remove previous program items */
614         i_nb_items = [o_program numberOfItems];
615         for( i = 0; i < i_nb_items; i++ )
616         {
617             [o_program removeItemAtIndex: 0];
618         }
619
620         /* make (un)sensitive */
621         [o_mi_program setEnabled: 
622             p_input->stream.i_pgrm_number > 1];
623
624         /* add program items */
625         for( i = 0 ; i < p_input->stream.i_pgrm_number ; i++ )
626         {
627             snprintf( psz_title, sizeof(psz_title), "id %d",
628                 p_input->stream.pp_programs[i]->i_number );
629             psz_title[sizeof(psz_title) - 1] = '\0';
630
631             o_menu_title = [NSString stringWithCString: psz_title];
632
633             o_item = [o_program addItemWithTitle: o_menu_title
634                 action: pf_toggle_program keyEquivalent: @""];
635             [o_item setTag: p_input->stream.pp_programs[i]->i_number];
636             [o_item setTarget: o_controls];
637
638             if( p_pgrm == p_input->stream.pp_programs[i] )
639             {
640                 [o_item setState: NSOnState];
641             }
642         }
643
644         p_intf->p_sys->b_program_update = 0;
645     }
646
647     if( p_intf->p_sys->b_title_update )
648     {
649         NSMenu * o_title;
650         SEL pf_toggle_title;
651
652         o_title = [o_mi_title submenu];
653         pf_toggle_title = @selector(toggleTitle:);
654
655         /* remove previous title items */
656         i_nb_items = [o_title numberOfItems];
657         for( i = 0; i < i_nb_items; i++ )
658         {
659             [o_title removeItemAtIndex: 0];
660         }
661
662         /* make (un)sensitive */
663         [o_mi_title setEnabled: 
664             p_input->stream.i_area_nb > 1];
665
666         /* add title items */
667         for( i = 1 ; i < p_input->stream.i_area_nb ; i++ )
668         {
669             snprintf( psz_title, sizeof(psz_title), "Title %d (%d)", i,
670                 p_input->stream.pp_areas[i]->i_part_nb );
671             psz_title[sizeof(psz_title) - 1] = '\0';
672
673             o_menu_title = [NSString stringWithCString: psz_title];
674
675             o_item = [o_title addItemWithTitle: o_menu_title
676                 action: pf_toggle_title keyEquivalent: @""];
677             [o_item setTag: i];
678             [o_item setTarget: o_controls];
679
680             if( ( p_input->stream.pp_areas[i] ==
681                 p_input->stream.p_selected_area ) )
682             {
683                 [o_item setState: NSOnState];
684             }
685         }
686
687         p_intf->p_sys->b_title_update = 0;
688     }
689
690     if( p_intf->p_sys->b_chapter_update )
691     {
692         NSMenu * o_chapter;
693         SEL pf_toggle_chapter;
694
695         o_chapter = [o_mi_chapter submenu];
696         pf_toggle_chapter = @selector(toggleChapter:);
697
698         /* remove previous chapter items */
699         i_nb_items = [o_chapter numberOfItems];
700         for( i = 0; i < i_nb_items; i++ )
701         {
702             [o_chapter removeItemAtIndex: 0];
703         }
704
705         /* make (un)sensitive */
706         [o_mi_chapter setEnabled: 
707             p_input->stream.p_selected_area->i_part_nb > 1];
708
709         /* add chapter items */
710         for( i = 0 ; i < p_input->stream.p_selected_area->i_part_nb ; i++ )
711         {
712             snprintf( psz_title, sizeof(psz_title), "Chapter %d", i + 1 );
713             psz_title[sizeof(psz_title) - 1] = '\0';
714
715             o_menu_title = [NSString stringWithCString: psz_title];
716
717             o_item = [o_chapter addItemWithTitle: o_menu_title
718                 action: pf_toggle_chapter keyEquivalent: @""];
719             [o_item setTag: i + 1];
720             [o_item setTarget: o_controls];
721
722             if( ( p_input->stream.p_selected_area->i_part == i + 1 ) )
723             {
724                 [o_item setState: NSOnState];
725             }
726         }
727
728         p_intf->p_sys->i_part =
729                 p_input->stream.p_selected_area->i_part;
730
731         p_intf->p_sys->b_chapter_update = 0;
732     }
733
734     for( i = 0 ; i < p_input->stream.i_selected_es_number ; i++ )
735     {
736         if( p_input->stream.pp_selected_es[i]->i_cat == SPU_ES )
737         {
738             p_audio_es = p_input->stream.pp_selected_es[i];
739         }
740         else if( p_input->stream.pp_selected_es[i]->i_cat == SPU_ES )
741         {
742             p_spu_es = p_input->stream.pp_selected_es[i];
743         }
744     }
745
746     vlc_mutex_unlock( &p_input->stream.stream_lock );
747
748     if( p_intf->p_sys->b_audio_update )
749     {
750         [self setupLangMenu: o_mi_language es: p_audio_es
751             category: AUDIO_ES selector: @selector(toggleLanguage:)];
752
753         p_intf->p_sys->b_audio_update = 0;
754     }
755
756     if( p_intf->p_sys->b_spu_update )
757     {
758         [self setupLangMenu: o_mi_subtitle es: p_spu_es
759             category: SPU_ES selector: @selector(toggleLanguage:)];
760
761         p_intf->p_sys->b_spu_update = 0;
762     }
763
764     if ( p_intf->p_sys->b_aout_update )
765     {
766         aout_instance_t * p_aout = vlc_object_find( p_intf, VLC_OBJECT_AOUT,
767                                                     FIND_ANYWHERE );
768
769         if ( p_aout != NULL )
770         {
771             vlc_value_t val;
772             val.b_bool = 0;
773
774             var_Set( (vlc_object_t *)p_aout, "intf-change", val );
775
776             [self setupVarMenu: o_mi_channels target: (vlc_object_t *)p_aout
777                 var: "audio-channels" selector: @selector(toggleVar:)];
778
779             [self setupVarMenu: o_mi_device target: (vlc_object_t *)p_aout
780                 var: "audio-device" selector: @selector(toggleVar:)];
781
782             vlc_object_release( (vlc_object_t *)p_aout );
783         }
784
785         p_intf->p_sys->b_aout_update = 0;
786     }
787
788     if ( p_intf->p_sys->b_vout_update )
789     {
790         vout_thread_t * p_vout = vlc_object_find( p_intf, VLC_OBJECT_VOUT,
791                                                   FIND_ANYWHERE );
792
793         if ( p_vout != NULL )
794         {
795             vlc_value_t val;
796             val.b_bool = 0;
797
798             var_Set( (vlc_object_t *)p_vout, "intf-change", val );
799
800             [self setupVarMenu: o_mi_screen target: (vlc_object_t *)p_vout
801                 var: "video-device" selector: @selector(toggleVar:)];
802
803             vlc_object_release( (vlc_object_t *)p_vout );
804         }
805
806         p_intf->p_sys->b_vout_update = 0;
807     }
808
809     vlc_mutex_lock( &p_input->stream.stream_lock );
810
811 #undef p_input
812 }
813
814 - (void)setupLangMenu:(NSMenuItem *)o_mi
815                       es:(es_descriptor_t *)p_es
816                       category:(int)i_cat
817                       selector:(SEL)pf_callback
818 {
819     unsigned int i, i_nb_items;
820     NSMenu * o_menu = [o_mi submenu];
821     intf_thread_t * p_intf = [NSApp getIntf];
822
823     /* remove previous language items */
824     i_nb_items = [o_menu numberOfItems];
825     for( i = 0; i < i_nb_items; i++ )
826     {
827         [o_menu removeItemAtIndex: 0];
828     }
829
830     /* make sensitive : we can't change it after we build the menu, and
831      * before, we don't yet how many items we will have. So make it
832      * always sensitive. --Meuuh */
833     [o_mi setEnabled: TRUE];
834
835     vlc_mutex_lock( &p_intf->p_sys->p_input->stream.stream_lock );
836
837 #define ES p_intf->p_sys->p_input->stream.pp_es[i]
838     for( i = 0 ; i < p_intf->p_sys->p_input->stream.i_es_number ; i++ )
839     {
840         if( ( ES->i_cat == i_cat ) &&
841             ( !ES->p_pgrm ||
842               ES->p_pgrm ==
843                  p_intf->p_sys->p_input->stream.p_selected_program ) )
844         {
845             NSMenuItem * o_lmi;
846             NSString * o_title;
847
848             if( *ES->psz_desc )
849             {
850                 o_title = [NSString stringWithCString: ES->psz_desc];
851             }
852             else
853             {
854                 char psz_title[ 256 ];
855
856                 snprintf( psz_title, sizeof(psz_title), "Language 0x%x",
857                           ES->i_id );
858                 psz_title[sizeof(psz_title) - 1] = '\0';
859
860                 o_title = [NSString stringWithCString: psz_title];
861             }
862
863             o_lmi = [o_menu addItemWithTitle: o_title
864                 action: pf_callback keyEquivalent: @""];
865             [o_lmi setRepresentedObject: 
866                 [NSValue valueWithPointer: ES]];
867             [o_lmi setTarget: o_controls];
868             [o_lmi setTag: i_cat];
869
870             if( /*p_es == ES*/ ES->p_decoder_fifo != NULL )
871             {
872                 [o_lmi setState: NSOnState];
873             }
874         }
875     }
876 #undef ES
877
878     vlc_mutex_unlock( &p_intf->p_sys->p_input->stream.stream_lock );
879 }
880
881 - (void)setupVarMenu:(NSMenuItem *)o_mi
882                      target:(vlc_object_t *)p_object
883                      var:(const char *)psz_variable
884                      selector:(SEL)pf_callback
885 {
886     int i, i_nb_items;
887     NSMenu * o_menu = [o_mi submenu];
888     vlc_value_t val;
889     char * psz_value;
890
891     /* remove previous items */
892     i_nb_items = [o_menu numberOfItems];
893     for( i = 0; i < i_nb_items; i++ )
894     {
895         [o_menu removeItemAtIndex: 0];
896     }
897
898     if ( var_Get( p_object, psz_variable, &val ) < 0 )
899     {
900         return;
901     }
902     psz_value = val.psz_string;
903
904     if ( var_Change( p_object, psz_variable,
905                      VLC_VAR_GETLIST, &val ) < 0 )
906     {
907         free( psz_value );
908         return;
909     }
910
911     /* make (un)sensitive */
912     [o_mi setEnabled: (val.p_list->i_count > 0)];
913
914     for ( i = 0; i < val.p_list->i_count; i++ )
915     {
916         NSMenuItem * o_lmi;
917         NSString * o_title;
918
919         o_title = [NSString stringWithCString: val.p_list->p_values[i].psz_string];
920         o_lmi = [o_menu addItemWithTitle: o_title
921                  action: pf_callback keyEquivalent: @""];
922         /* FIXME: this isn't 64-bit clean ! */
923         [o_lmi setTag: (int)psz_variable];
924         [o_lmi setRepresentedObject:
925             [NSValue valueWithPointer: p_object]];
926         [o_lmi setTarget: o_controls];
927
928         if ( !strcmp( psz_value, val.p_list->p_values[i].psz_string ) )
929             [o_lmi setState: NSOnState];
930     }
931
932     var_Change( p_object, psz_variable, VLC_VAR_FREELIST,
933                 &val );
934
935     free( psz_value );
936 }
937
938 - (IBAction)clearRecentItems:(id)sender
939 {
940     [[NSDocumentController sharedDocumentController]
941                           clearRecentDocuments: nil];
942 }
943
944 - (void)openRecentItem:(id)sender
945 {
946     [self application: nil openFile: [sender title]]; 
947 }
948
949 - (IBAction)viewPreferences:(id)sender
950 {
951     if( o_prefs == nil )
952     {
953         o_prefs = [[VLCPrefs alloc] init];
954     }
955
956     [o_prefs createPrefPanel: @"main"];
957 }
958
959 - (IBAction)timesliderUpdate:(id)sender
960 {
961     switch( [[NSApp currentEvent] type] )
962     {
963         case NSLeftMouseDown:
964             [o_slider_lock lock];
965             break;
966
967         case NSLeftMouseUp:
968             f_slider = [sender floatValue];
969             [o_slider_lock unlock];
970             break;
971
972         default:
973             break;
974     }
975 }
976
977 - (void)displayTime
978 {
979     intf_thread_t * p_intf = [NSApp getIntf];
980     input_thread_t * p_input = p_intf->p_sys->p_input;
981
982     if( p_input == NULL )
983     {
984         [o_timeslider setEnabled: FALSE];
985         [o_timeslider setFloatValue: 0.0];
986         [o_timefield setStringValue: @"0:00:00"]; 
987
988         return;
989     }
990
991 #define p_area p_input->stream.p_selected_area
992
993     if( p_input->stream.b_changed )
994     {
995         [o_timeslider setEnabled: p_input->stream.b_seekable];
996     }
997     else if( p_intf->p_sys->b_playing )
998     {
999         NSString * o_time;
1000         char psz_time[ OFFSETTOTIME_MAX_SIZE ];
1001
1002         input_OffsetToTime( p_input, psz_time, p_area->i_tell );
1003
1004         o_time = [NSString stringWithCString: psz_time];
1005         [o_timefield setStringValue: o_time];
1006
1007         if( p_input->stream.b_seekable )
1008         {
1009             if( f_slider == f_slider_old )
1010             {
1011                 float f_updated = ( 100. * p_area->i_tell ) /
1012                                            p_area->i_size;
1013
1014                 if( f_slider != f_updated && [o_slider_lock tryLock] )
1015                 {
1016                     [o_timeslider setFloatValue: f_updated];
1017                     [o_slider_lock unlock]; 
1018                 }
1019             }
1020             else
1021             {
1022                 off_t i_seek = ( f_slider * p_area->i_size ) / 100;
1023
1024                 /* release the lock to be able to seek */
1025                 vlc_mutex_unlock( &p_input->stream.stream_lock );
1026                 input_Seek( p_input, i_seek, INPUT_SEEK_SET );
1027                 vlc_mutex_lock( &p_input->stream.stream_lock ); 
1028
1029                 /* Update the old value */
1030                 f_slider_old = f_slider;
1031             }
1032         }
1033     }
1034 #undef p_area
1035 }
1036
1037 @end
1038
1039 @implementation VLCMain (NSMenuValidation)
1040
1041 - (BOOL)validateMenuItem:(NSMenuItem *)o_mi
1042 {
1043     BOOL bEnabled = TRUE;
1044
1045     /* Recent Items Menu */
1046
1047     if( [[o_mi title] isEqualToString: _NS("Clear Menu")] )
1048     {
1049         NSMenu * o_menu = [o_mi_open_recent submenu];
1050         int i_nb_items = [o_menu numberOfItems];
1051         NSArray * o_docs = [[NSDocumentController sharedDocumentController]
1052                                                        recentDocumentURLs];
1053         UInt32 i_nb_docs = [o_docs count];
1054
1055         if( i_nb_items > 1 )
1056         {
1057             while( --i_nb_items )
1058             {
1059                 [o_menu removeItemAtIndex: 0];
1060             }
1061         }
1062
1063         if( i_nb_docs > 0 )
1064         {
1065             NSURL * o_url;
1066             NSString * o_doc;
1067
1068             [o_menu insertItem: [NSMenuItem separatorItem] atIndex: 0];
1069
1070             while( TRUE )
1071             {
1072                 i_nb_docs--;
1073
1074                 o_url = [o_docs objectAtIndex: i_nb_docs];
1075
1076                 if( [o_url isFileURL] )
1077                 {
1078                     o_doc = [o_url path];
1079                 }
1080                 else
1081                 {
1082                     o_doc = [o_url absoluteString];
1083                 }
1084
1085                 [o_menu insertItemWithTitle: o_doc
1086                     action: @selector(openRecentItem:)
1087                     keyEquivalent: @"" atIndex: 0]; 
1088
1089                 if( i_nb_docs == 0 )
1090                 {
1091                     break;
1092                 }
1093             } 
1094         }
1095         else
1096         {
1097             bEnabled = FALSE;
1098         }
1099     }
1100
1101     return( bEnabled );
1102 }
1103
1104 @end
1105
1106 @implementation VLCMain (Internal)
1107
1108 - (void)handlePortMessage:(NSPortMessage *)o_msg
1109 {
1110     NSData * o_data;
1111     NSValue * o_value;
1112     NSInvocation * o_inv;
1113     vout_thread_t * p_vout;
1114  
1115     o_data = [[o_msg components] lastObject];
1116     o_inv = *((NSInvocation **)[o_data bytes]); 
1117     [o_inv getArgument: &o_value atIndex: 2];
1118     p_vout = (vout_thread_t *)[o_value pointerValue];
1119
1120     [p_vout->p_sys->o_lock lock];
1121     [o_inv invoke];
1122     [p_vout->p_sys->o_lock unlockWithCondition: 1];
1123 }
1124
1125 @end