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