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