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