]> git.sesse.net Git - vlc/blob - plugins/macosx/intf_macosx.m
* ./plugins/*: interface plugins set p_vlc->b_die to quit vlc, but check
[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.7 2002/07/15 20:09:31 sam 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_program setTitle: _NS("Program")];
227     [o_mi_title setTitle: _NS("Title")];
228     [o_mi_chapter setTitle: _NS("Chapter")];
229     [o_mi_language setTitle: _NS("Language")];
230     [o_mi_subtitle setTitle: _NS("Subtitles")];
231
232     [o_mu_window setTitle: _NS("Window")];
233     [o_mi_minimize setTitle: _NS("Minimize")];
234     [o_mi_bring_atf setTitle: _NS("Bring All to Front")];
235
236     /* dock menu */
237     [o_dmi_play setTitle: _NS("Play")];
238     [o_dmi_pause setTitle: _NS("Pause")];
239     [o_dmi_stop setTitle: _NS("Stop")];
240
241     [self manageMode];
242 }
243
244 - (void)applicationWillFinishLaunching:(NSNotification *)o_notification
245 {
246     intf_thread_t * p_intf = [NSApp getIntf];
247
248     [NSThread detachNewThreadSelector: @selector(manage)
249         toTarget: self withObject: nil];
250
251     [p_intf->p_sys->o_sendport setDelegate: self];
252     [[NSRunLoop currentRunLoop] 
253         addPort: p_intf->p_sys->o_sendport
254         forMode: NSDefaultRunLoopMode];
255 }
256
257 - (BOOL)application:(NSApplication *)o_app openFile:(NSString *)o_filename
258 {
259     [o_playlist appendArray:
260         [NSArray arrayWithObject: o_filename] atPos: -1];
261
262     return( TRUE );
263 }
264
265 - (void)manage
266 {
267     NSDate * o_sleep_date;
268     intf_thread_t * p_intf = [NSApp getIntf];
269     NSAutoreleasePool * o_pool = [[NSAutoreleasePool alloc] init];
270
271     while( !p_intf->b_die )
272     {
273         int i_start, i_stop;
274
275         vlc_mutex_lock( &p_intf->change_lock );
276
277         /* update the input */
278         if( p_intf->p_sys->p_input == NULL )
279         {
280             p_intf->p_sys->p_input = vlc_object_find( p_intf, VLC_OBJECT_INPUT,
281                                                               FIND_ANYWHERE );
282         }
283         else if( p_intf->p_sys->p_input->b_dead )
284         {
285             vlc_object_release( p_intf->p_sys->p_input );
286             p_intf->p_sys->p_input = NULL;
287         }
288
289         if( p_intf->p_sys->p_input )
290         {
291             input_thread_t *p_input = p_intf->p_sys->p_input;
292
293             vlc_mutex_lock( &p_input->stream.stream_lock );
294
295             if( !p_input->b_die )
296             {
297                 /* New input or stream map change */
298                 if( p_input->stream.b_changed )
299                 {
300                     [self manageMode];
301                     [self setupMenus];
302                     p_intf->p_sys->b_playing = 1;
303                 }
304
305                 if( p_intf->p_sys->i_part !=
306                     p_input->stream.p_selected_area->i_part )
307                 {
308                     p_intf->p_sys->b_chapter_update = 1;
309                     [self setupMenus];
310                 }
311             }
312
313             vlc_mutex_unlock( &p_input->stream.stream_lock );
314         }
315         else if( p_intf->p_sys->b_playing && !p_intf->b_die )
316         {
317             [self manageMode];
318             p_intf->p_sys->b_playing = 0;
319         }
320
321         /* update the log window */
322         vlc_mutex_lock( p_intf->p_sys->p_sub->p_lock );
323         i_stop = *p_intf->p_sys->p_sub->pi_stop;
324         vlc_mutex_unlock( p_intf->p_sys->p_sub->p_lock );
325
326         if( p_intf->p_sys->p_sub->i_start != i_stop )
327         {
328             NSColor *o_white = [NSColor whiteColor];
329             NSColor *o_red = [NSColor redColor];
330             NSColor *o_yellow = [NSColor yellowColor];
331             NSColor *o_gray = [NSColor grayColor];
332
333             unsigned int ui_length = [[o_messages string] length];
334
335             NSColor * pp_color[4] = { o_white, o_red, o_yellow, o_gray };
336             static const char * ppsz_type[4] = { ": ", " error: ", 
337                                                  " warning: ", " debug: " }; 
338         
339             [o_messages setEditable: YES];
340             [o_messages setSelectedRange: NSMakeRange( ui_length, 0 )];
341             [o_messages scrollRangeToVisible: NSMakeRange( ui_length, 0 )];
342
343             for( i_start = p_intf->p_sys->p_sub->i_start;
344                  i_start != i_stop;
345                  i_start = (i_start+1) % VLC_MSG_QSIZE )
346             {
347                 NSString *o_msg;
348                 NSDictionary *o_attr;
349                 NSAttributedString *o_msg_color;
350                 int i_type = p_intf->p_sys->p_sub->p_msg[i_start].i_type;
351
352                 o_attr = [NSDictionary dictionaryWithObject: o_gray
353                     forKey: NSForegroundColorAttributeName];
354                 o_msg = [NSString stringWithFormat: @"%s%s",
355                     p_intf->p_sys->p_sub->p_msg[i_start].psz_module, 
356                     ppsz_type[i_type]];
357                 o_msg_color = [[NSAttributedString alloc]
358                     initWithString: o_msg attributes: o_attr];
359                 [o_messages insertText: o_msg_color];
360
361                 o_attr = [NSDictionary dictionaryWithObject: pp_color[i_type]
362                     forKey: NSForegroundColorAttributeName];
363                 o_msg = [NSString stringWithCString:
364                     p_intf->p_sys->p_sub->p_msg[i_start].psz_msg];
365                 o_msg_color = [[NSAttributedString alloc]
366                     initWithString: o_msg attributes: o_attr];
367                 [o_messages insertText: o_msg_color];
368
369                 [o_messages insertText: @"\n"];
370             }
371
372             [o_messages setEditable: NO];
373
374             vlc_mutex_lock( p_intf->p_sys->p_sub->p_lock );
375             p_intf->p_sys->p_sub->i_start = i_start;
376             vlc_mutex_unlock( p_intf->p_sys->p_sub->p_lock );
377         }
378
379         vlc_mutex_unlock( &p_intf->change_lock );
380
381         o_sleep_date = [NSDate dateWithTimeIntervalSinceNow: 0.1];
382         [NSThread sleepUntilDate: o_sleep_date];
383     }
384
385     [self terminate];
386
387     [o_pool release];
388 }
389
390 - (void)terminate
391 {
392     NSEvent * pEvent;
393     vout_thread_t * p_vout;
394     playlist_t * p_playlist;
395     intf_thread_t * p_intf = [NSApp getIntf];
396
397     /* release input */
398     if( p_intf->p_sys->p_input )
399     {
400         vlc_object_release( p_intf->p_sys->p_input );
401         p_intf->p_sys->p_input = NULL;
402     }
403
404     /*
405      * Free playlists
406      */
407     msg_Dbg( p_intf, "removing all playlists" );
408     while( (p_playlist = vlc_object_find( p_intf->p_vlc, VLC_OBJECT_PLAYLIST,
409                                           FIND_CHILD )) )
410     {
411         vlc_object_detach_all( p_playlist );
412         vlc_object_release( p_playlist );
413         playlist_Destroy( p_playlist );
414     }
415
416     /*
417      * Free video outputs
418      */
419     msg_Dbg( p_intf, "removing all video outputs" );
420     while( (p_vout = vlc_object_find( p_intf->p_vlc, 
421                                       VLC_OBJECT_VOUT, FIND_CHILD )) )
422     {
423         vlc_object_detach_all( p_vout );
424         vlc_object_release( p_vout );
425         vout_DestroyThread( p_vout );
426     }
427
428     [NSApp stop: nil];
429
430     /* write cached user defaults to disk */
431     [[NSUserDefaults standardUserDefaults] synchronize];
432
433     /* send a dummy event to break out of the event loop */
434     pEvent = [NSEvent mouseEventWithType: NSLeftMouseDown
435                 location: NSMakePoint( 1, 1 ) modifierFlags: 0
436                 timestamp: 1 windowNumber: [[NSApp mainWindow] windowNumber]
437                 context: [NSGraphicsContext currentContext] eventNumber: 1
438                 clickCount: 1 pressure: 0.0];
439     [NSApp postEvent: pEvent atStart: YES];
440 }
441
442 - (void)manageMode
443 {
444     vlc_bool_t b_control = 0;
445     intf_thread_t * p_intf = [NSApp getIntf];
446
447     if( p_intf->p_sys->p_input )
448     {
449         /* control buttons for free pace streams */
450         b_control = p_intf->p_sys->p_input->stream.b_pace_control;
451
452         /* get ready for menu regeneration */
453         p_intf->p_sys->b_program_update = 1;
454         p_intf->p_sys->b_title_update = 1;
455         p_intf->p_sys->b_chapter_update = 1;
456         p_intf->p_sys->b_audio_update = 1;
457         p_intf->p_sys->b_spu_update = 1;
458         p_intf->p_sys->i_part = 0;
459
460         p_intf->p_sys->p_input->stream.b_changed = 0;
461         msg_Dbg( p_intf, "stream has changed, refreshing interface" );
462     }
463     else
464     {
465         /* unsensitize menus */
466         [o_mi_program setEnabled: FALSE];
467         [o_mi_title setEnabled: FALSE];
468         [o_mi_chapter setEnabled: FALSE];
469         [o_mi_language setEnabled: FALSE];
470         [o_mi_subtitle setEnabled: FALSE];
471     }
472 }
473
474 - (void)setupMenus
475 {
476     int i, i_nb_items;
477     NSMenuItem * o_item;
478     NSString * o_menu_title;
479     char psz_title[ 256 ];
480
481     es_descriptor_t * p_audio_es = NULL;
482     es_descriptor_t * p_spu_es = NULL;
483
484     intf_thread_t * p_intf = [NSApp getIntf];
485
486     p_intf->p_sys->b_chapter_update |= p_intf->p_sys->b_title_update;
487     p_intf->p_sys->b_audio_update |= p_intf->p_sys->b_title_update |
488                                      p_intf->p_sys->b_program_update;
489     p_intf->p_sys->b_spu_update |= p_intf->p_sys->b_title_update |
490                                    p_intf->p_sys->b_program_update;
491
492 #define p_input (p_intf->p_sys->p_input)
493
494     if( p_intf->p_sys->b_program_update )
495     {
496         NSMenu * o_program;
497         SEL pf_toggle_program;
498         pgrm_descriptor_t * p_pgrm;
499
500         if( p_input->stream.p_new_program )
501         {
502             p_pgrm = p_input->stream.p_new_program;
503         }
504         else
505         {
506             p_pgrm = p_input->stream.p_selected_program;
507         }
508
509         o_program = [o_mi_program submenu];
510         pf_toggle_program = @selector(toggleProgram:);
511
512         /* remove previous program items */
513         i_nb_items = [o_program numberOfItems];
514         for( i = 0; i < i_nb_items; i++ )
515         {
516             [o_program removeItemAtIndex: 0];
517         }
518
519         /* make (un)sensitive */
520         [o_mi_program setEnabled: 
521             p_input->stream.i_pgrm_number > 1];
522
523         /* add program items */
524         for( i = 0 ; i < p_input->stream.i_pgrm_number ; i++ )
525         {
526             snprintf( psz_title, sizeof(psz_title), "id %d",
527                 p_input->stream.pp_programs[i]->i_number );
528             psz_title[sizeof(psz_title) - 1] = '\0';
529
530             o_menu_title = [NSString stringWithCString: psz_title];
531
532             o_item = [o_program addItemWithTitle: o_menu_title
533                 action: pf_toggle_program keyEquivalent: @""];
534             [o_item setTag: p_input->stream.pp_programs[i]->i_number];
535             [o_item setTarget: o_controls];
536
537             if( p_pgrm == p_input->stream.pp_programs[i] )
538             {
539                 [o_item setState: NSOnState];
540             }
541         }
542
543         p_intf->p_sys->b_program_update = 0;
544     }
545
546     if( p_intf->p_sys->b_title_update )
547     {
548         NSMenu * o_title;
549         SEL pf_toggle_title;
550
551         o_title = [o_mi_title submenu];
552         pf_toggle_title = @selector(toggleTitle:);
553
554         /* remove previous title items */
555         i_nb_items = [o_title numberOfItems];
556         for( i = 0; i < i_nb_items; i++ )
557         {
558             [o_title removeItemAtIndex: 0];
559         }
560
561         /* make (un)sensitive */
562         [o_mi_title setEnabled: 
563             p_input->stream.i_area_nb > 1];
564
565         /* add title items */
566         for( i = 1 ; i < p_input->stream.i_area_nb ; i++ )
567         {
568             snprintf( psz_title, sizeof(psz_title), "Title %d (%d)", i,
569                 p_input->stream.pp_areas[i]->i_part_nb );
570             psz_title[sizeof(psz_title) - 1] = '\0';
571
572             o_menu_title = [NSString stringWithCString: psz_title];
573
574             o_item = [o_title addItemWithTitle: o_menu_title
575                 action: pf_toggle_title keyEquivalent: @""];
576             [o_item setTag: i];
577             [o_item setTarget: o_controls];
578
579             if( ( p_input->stream.pp_areas[i] ==
580                 p_input->stream.p_selected_area ) )
581             {
582                 [o_item setState: NSOnState];
583             }
584         }
585
586         p_intf->p_sys->b_title_update = 0;
587     }
588
589     if( p_intf->p_sys->b_chapter_update )
590     {
591         NSMenu * o_chapter;
592         SEL pf_toggle_chapter;
593
594         o_chapter = [o_mi_chapter submenu];
595         pf_toggle_chapter = @selector(toggleChapter:);
596
597         /* remove previous chapter items */
598         i_nb_items = [o_chapter numberOfItems];
599         for( i = 0; i < i_nb_items; i++ )
600         {
601             [o_chapter removeItemAtIndex: 0];
602         }
603
604         /* make (un)sensitive */
605         [o_mi_chapter setEnabled: 
606             p_input->stream.p_selected_area->i_part_nb > 1];
607
608         /* add chapter items */
609         for( i = 0 ; i < p_input->stream.p_selected_area->i_part_nb ; i++ )
610         {
611             snprintf( psz_title, sizeof(psz_title), "Chapter %d", i + 1 );
612             psz_title[sizeof(psz_title) - 1] = '\0';
613
614             o_menu_title = [NSString stringWithCString: psz_title];
615
616             o_item = [o_chapter addItemWithTitle: o_menu_title
617                 action: pf_toggle_chapter keyEquivalent: @""];
618             [o_item setTag: i + 1];
619             [o_item setTarget: o_controls];
620
621             if( ( p_input->stream.p_selected_area->i_part == i + 1 ) )
622             {
623                 [o_item setState: NSOnState];
624             }
625         }
626
627         p_intf->p_sys->i_part =
628                 p_input->stream.p_selected_area->i_part;
629
630         p_intf->p_sys->b_chapter_update = 0;
631     }
632
633     for( i = 0 ; i < p_input->stream.i_selected_es_number ; i++ )
634     {
635         if( p_input->stream.pp_selected_es[i]->i_cat == SPU_ES )
636         {
637             p_audio_es = p_input->stream.pp_selected_es[i];
638         }
639         else if( p_input->stream.pp_selected_es[i]->i_cat == SPU_ES )
640         {
641             p_spu_es = p_input->stream.pp_selected_es[i];
642         }
643     }
644
645     vlc_mutex_unlock( &p_input->stream.stream_lock );
646
647     if( p_intf->p_sys->b_audio_update )
648     {
649         [self setupLangMenu: o_mi_language es: p_audio_es
650             category: AUDIO_ES selector: @selector(toggleLanguage:)];
651
652         p_intf->p_sys->b_audio_update = 0;
653     }
654
655     if( p_intf->p_sys->b_spu_update )
656     {
657         [self setupLangMenu: o_mi_subtitle es: p_spu_es
658             category: SPU_ES selector: @selector(toggleLanguage:)];
659
660         p_intf->p_sys->b_spu_update = 0;
661     }
662
663     vlc_mutex_lock( &p_input->stream.stream_lock );
664
665 #undef p_input
666 }
667
668 - (void)setupLangMenu:(NSMenuItem *)o_mi
669                       es:(es_descriptor_t *)p_es
670                       category:(int)i_cat
671                       selector:(SEL)pf_callback
672 {
673     int i, i_nb_items;
674     NSMenu * o_menu = [o_mi submenu];
675     intf_thread_t * p_intf = [NSApp getIntf];
676
677     /* remove previous language items */
678     i_nb_items = [o_menu numberOfItems];
679     for( i = 0; i < i_nb_items; i++ )
680     {
681         [o_menu removeItemAtIndex: 0];
682     }
683
684     vlc_mutex_lock( &p_intf->p_sys->p_input->stream.stream_lock );
685
686 #define ES p_intf->p_sys->p_input->stream.pp_es[i]
687     for( i = 0 ; i < p_intf->p_sys->p_input->stream.i_es_number ; i++ )
688     {
689         if( ( ES->i_cat == i_cat ) &&
690             ( !ES->p_pgrm ||
691               ES->p_pgrm ==
692                  p_intf->p_sys->p_input->stream.p_selected_program ) )
693         {
694             NSMenuItem * o_lmi;
695             NSString * o_title;
696
697             if( *ES->psz_desc )
698             {
699                 o_title = [NSString stringWithCString: ES->psz_desc];
700             }
701             else
702             {
703                 char psz_title[ 256 ];
704
705                 snprintf( psz_title, sizeof(psz_title), "Language 0x%x",
706                           ES->i_id );
707                 psz_title[sizeof(psz_title) - 1] = '\0';
708
709                 o_title = [NSString stringWithCString: psz_title];
710             }
711
712             o_lmi = [o_menu addItemWithTitle: o_title
713                 action: pf_callback keyEquivalent: @""];
714             [o_lmi setRepresentedObject: 
715                 [NSValue valueWithPointer: ES]];
716             [o_lmi setTarget: o_controls];
717             [o_lmi setTag: i_cat];
718
719             if( /*p_es == ES*/ ES->p_decoder_fifo != NULL )
720             {
721                 [o_lmi setState: NSOnState];
722             }
723         }
724     }
725 #undef ES
726
727     vlc_mutex_unlock( &p_intf->p_sys->p_input->stream.stream_lock );
728
729     /* make (un)sensitive */
730     [o_mi setEnabled: 
731         [o_menu numberOfItems] ? TRUE : FALSE];
732 }
733
734 - (IBAction)clearRecentItems:(id)sender
735 {
736     [[NSDocumentController sharedDocumentController]
737                           clearRecentDocuments: nil];
738 }
739
740 - (void)openRecentItem:(id)sender
741 {
742     [self application: nil openFile: [sender title]]; 
743 }
744
745 @end
746
747 @implementation VLCMain (NSMenuValidation)
748
749 - (BOOL)validateMenuItem:(NSMenuItem *)o_mi
750 {
751     BOOL bEnabled = TRUE;
752
753     /* Recent Items Menu */
754
755     if( [[o_mi title] isEqualToString: _NS("Clear Menu")] )
756     {
757         NSMenu * o_menu = [o_mi_open_recent submenu];
758         int i_nb_items = [o_menu numberOfItems];
759         NSArray * o_docs = [[NSDocumentController sharedDocumentController]
760                                                        recentDocumentURLs];
761         UInt32 i_nb_docs = [o_docs count];
762
763         if( i_nb_items > 1 )
764         {
765             while( --i_nb_items )
766             {
767                 [o_menu removeItemAtIndex: 0];
768             }
769         }
770
771         if( i_nb_docs > 0 )
772         {
773             NSURL * o_url;
774             NSString * o_doc;
775
776             [o_menu insertItem: [NSMenuItem separatorItem] atIndex: 0];
777
778             while( TRUE )
779             {
780                 i_nb_docs--;
781
782                 o_url = [o_docs objectAtIndex: i_nb_docs];
783
784                 if( [o_url isFileURL] )
785                 {
786                     o_doc = [o_url path];
787                 }
788                 else
789                 {
790                     o_doc = [o_url absoluteString];
791                 }
792
793                 [o_menu insertItemWithTitle: o_doc
794                     action: @selector(openRecentItem:)
795                     keyEquivalent: @"" atIndex: 0]; 
796
797                 if( i_nb_docs == 0 )
798                 {
799                     break;
800                 }
801             } 
802         }
803         else
804         {
805             bEnabled = FALSE;
806         }
807     }
808
809     return( bEnabled );
810 }
811
812 @end
813
814 @implementation VLCMain (Internal)
815
816 - (void)handlePortMessage:(NSPortMessage *)o_msg
817 {
818     NSData * o_req;
819     struct vout_req_s * p_req;
820
821     o_req = [[o_msg components] lastObject];
822     p_req = *((struct vout_req_s **)[o_req bytes]);
823
824     [p_req->o_lock lock];
825
826     if( p_req->i_type == VOUT_REQ_CREATE_WINDOW )
827     {
828         VLCView * o_view;
829
830         p_req->p_vout->p_sys->o_window = [VLCWindow alloc];
831         [p_req->p_vout->p_sys->o_window setVout: p_req->p_vout];
832         [p_req->p_vout->p_sys->o_window setReleasedWhenClosed: YES];
833
834         if( p_req->p_vout->b_fullscreen )
835         {
836             [p_req->p_vout->p_sys->o_window 
837                 initWithContentRect: [[NSScreen mainScreen] frame] 
838                 styleMask: NSBorderlessWindowMask 
839                 backing: NSBackingStoreBuffered
840                 defer: NO screen: [NSScreen mainScreen]];
841
842             [p_req->p_vout->p_sys->o_window 
843                 setLevel: NSModalPanelWindowLevel];
844         }
845         else
846         {
847             unsigned int i_stylemask = NSTitledWindowMask |
848                                        NSMiniaturizableWindowMask |
849                                        NSResizableWindowMask;
850
851             [p_req->p_vout->p_sys->o_window 
852                 initWithContentRect: p_req->p_vout->p_sys->s_rect 
853                 styleMask: i_stylemask
854                 backing: NSBackingStoreBuffered
855                 defer: NO screen: [NSScreen mainScreen]];
856
857             if( !p_req->p_vout->p_sys->b_pos_saved )
858             {
859                 [p_req->p_vout->p_sys->o_window center];
860             }
861         }
862
863         o_view = [[VLCView alloc] init];
864         [o_view setVout: p_req->p_vout];
865         [o_view setMenu: o_mu_controls];
866         [p_req->p_vout->p_sys->o_window setContentView: o_view];
867         [o_view autorelease];
868
869         [o_view lockFocus];
870         p_req->p_vout->p_sys->p_qdport = [o_view qdPort];
871         [o_view unlockFocus];
872
873         [p_req->p_vout->p_sys->o_window setTitle: [NSString 
874             stringWithCString: VOUT_TITLE " (QuickTime)"]];
875         [p_req->p_vout->p_sys->o_window setAcceptsMouseMovedEvents: YES];
876         [p_req->p_vout->p_sys->o_window makeKeyAndOrderFront: nil];
877
878         p_req->i_result = 1;
879     }
880     else if( p_req->i_type == VOUT_REQ_DESTROY_WINDOW )
881     {
882         if( !p_req->p_vout->b_fullscreen )
883         {
884             NSRect s_rect;
885
886             s_rect = [[p_req->p_vout->p_sys->o_window contentView] frame];
887             p_req->p_vout->p_sys->s_rect.size = s_rect.size;
888
889             s_rect = [p_req->p_vout->p_sys->o_window frame];
890             p_req->p_vout->p_sys->s_rect.origin = s_rect.origin;
891
892             p_req->p_vout->p_sys->b_pos_saved = 1;
893         }
894
895         p_req->p_vout->p_sys->p_qdport = nil;
896         [p_req->p_vout->p_sys->o_window close];
897         p_req->p_vout->p_sys->o_window = nil;
898
899         p_req->i_result = 1;
900     }
901
902     [p_req->o_lock unlockWithCondition: 1];
903 }
904
905 @end
906