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