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