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