* ALL: separation of the SPU engine from the VOUT.
[vlc.git] / modules / codec / cmml / intf.c
1 /*****************************************************************************
2  * intf.c: interface for CMML annotations/hyperlinks
3  *****************************************************************************
4  * Copyright (C) 2003-2004 Commonwealth Scientific and Industrial Research
5  *                         Organisation (CSIRO) Australia
6  * Copyright (C) 2004 VideoLAN
7  *
8  * $Id$
9  *
10  * Authors: Andre Pang <Andre.Pang@csiro.au>
11  *
12  * This program is free software; you can redistribute it and/or modify
13  * it under the terms of the GNU General Public License as published by
14  * the Free Software Foundation; either version 2 of the License, or
15  * (at your option) any later version.
16  * 
17  * This program is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  * GNU General Public License for more details.
21  *
22  * You should have received a copy of the GNU General Public License
23  * along with this program; if not, write to the Free Software
24  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
25  *****************************************************************************/
26
27 /*****************************************************************************
28  * Preamble
29  *****************************************************************************/
30 #include <stdlib.h>                                      /* malloc(), free() */
31 #include <string.h>
32
33 #include <vlc/vlc.h>
34
35 #ifdef HAVE_UNISTD_H
36 #    include <unistd.h>
37 #endif
38
39 #include <vlc/decoder.h>
40 #include <vlc/input.h>
41 #include <vlc/intf.h>
42 #include <vlc/vout.h>
43
44 #include <osd.h>
45
46 #include "vlc_keys.h"
47
48 #include "browser_open.h"
49 #include "history.h"
50 #include "xstrcat.h"
51 #include "xurl.h"
52
53 #undef  CMML_INTF_USE_TIMED_URIS
54
55 #undef  CMML_INTF_DEBUG
56 #undef  CMML_INTF_SUBPICTURE_DEBUG
57 #undef  CMML_INTF_HISTORY_DEBUG
58
59 /*****************************************************************************
60  * intf_sys_t: description and status of interface
61  *****************************************************************************/
62 struct intf_sys_t
63 {
64     decoder_t *         p_cmml_decoder;
65     input_thread_t *    p_input;
66
67     vlc_bool_t          b_key_pressed;
68 };
69
70 struct navigation_history_t
71 {
72     int i_history_size;
73     int i_last_item;
74 };
75
76 /*****************************************************************************
77  * Local prototypes.
78  *****************************************************************************/
79 static int   InitThread                 ( intf_thread_t * );
80 static int   MouseEvent                 ( vlc_object_t *, char const *,
81                                           vlc_value_t, vlc_value_t, void * );
82 static int   KeyEvent                   ( vlc_object_t *, char const *,
83                                           vlc_value_t, vlc_value_t, void * );
84
85 static void  FollowAnchor               ( intf_thread_t * );
86 static void  GoBack                     ( intf_thread_t * );
87 static void  GoForward                  ( intf_thread_t * );
88
89 static int   FollowAnchorCallback       ( vlc_object_t *, char const *,
90                                           vlc_value_t, vlc_value_t, void * );
91 static int   GoBackCallback             ( vlc_object_t *, char const *,
92                                           vlc_value_t, vlc_value_t, void * );
93 static int   GoForwardCallback          ( vlc_object_t *, char const *,
94                                           vlc_value_t, vlc_value_t, void * );
95
96 static char *GetTimedURLFromPlaylistItem( intf_thread_t *, playlist_item_t * );
97 static char *GetTimedURIFragmentForTime ( int );
98 static int   GetCurrentTimeInSeconds    ( input_thread_t * );
99 static int   DisplayAnchor              ( intf_thread_t *, vout_thread_t *,
100                                           char *, char * );
101 static int   DisplayPendingAnchor       ( intf_thread_t *, vout_thread_t * );
102 static history_t * GetHistory           ( playlist_t * );
103 static void  ReplacePlaylistItem        ( playlist_t *, char * );
104
105 /* Exported functions */
106 static void RunIntf        ( intf_thread_t *p_intf );
107
108 /*****************************************************************************
109  * OpenIntf: initialize CMML interface
110  *****************************************************************************/
111 int E_(OpenIntf) ( vlc_object_t *p_this )
112 {
113     intf_thread_t *p_intf = (intf_thread_t *)p_this;
114
115     p_intf->p_sys = malloc( sizeof( intf_sys_t ) );
116     if( p_intf->p_sys == NULL )
117     {
118         return( 1 );
119     };
120
121     p_intf->pf_run = RunIntf;
122
123     var_AddCallback( p_intf->p_vlc, "key-pressed", KeyEvent, p_intf );
124     /* we also need to add the callback for "mouse-clicked", but do that later
125      * when we've found a p_vout */
126
127     var_Create( p_intf->p_vlc, "browse-go-back", VLC_VAR_VOID );
128     var_AddCallback( p_intf->p_vlc, "browse-go-back",
129                      GoBackCallback, p_intf );
130     var_Create( p_intf->p_vlc, "browse-go-forward", VLC_VAR_VOID );
131     var_AddCallback( p_intf->p_vlc, "browse-go-forward",
132                      GoForwardCallback, p_intf );
133     var_Create( p_intf->p_vlc, "browse-follow-anchor", VLC_VAR_VOID );
134     var_AddCallback( p_intf->p_vlc, "browse-follow-anchor",
135                      FollowAnchorCallback, p_intf );
136
137     return( 0 );
138 }
139
140 /*****************************************************************************
141  * CloseIntf: destroy dummy interface
142  *****************************************************************************/
143 void E_(CloseIntf) ( vlc_object_t *p_this )
144 {
145     intf_thread_t * p_intf = (intf_thread_t *)p_this;
146     vout_thread_t * p_vout;
147
148 #ifdef CMML_INTF_DEBUG
149     msg_Dbg( p_intf, "freeing CMML interface" );
150 #endif
151
152     /* Erase the anchor text description from the video output if it exists */
153     p_vout = vlc_object_find( p_intf, VLC_OBJECT_VOUT, FIND_ANYWHERE );
154     if( p_vout )
155     {
156         spu_Control( p_vout->p_spu, SPU_CHANNEL_CLEAR, DEFAULT_CHAN );
157         vlc_object_release( p_vout );
158     }
159
160     var_DelCallback( p_intf->p_vlc, "key-pressed", KeyEvent, p_intf );
161
162     vlc_object_release( p_intf->p_sys->p_cmml_decoder );
163    
164     free( p_intf->p_sys );
165 }
166
167
168 /*****************************************************************************
169  * RunIntf: main loop
170  *****************************************************************************/
171 static void RunIntf( intf_thread_t *p_intf )
172 {
173     vout_thread_t * p_vout = NULL;
174
175     if( InitThread( p_intf ) < 0 )
176     {
177         msg_Err( p_intf, "can't initialize CMML interface" );
178         return;
179     }
180 #ifdef CMML_INTF_DEBUG
181     msg_Dbg( p_intf, "CMML intf initialized" );
182 #endif
183
184     /* if video output is dying, disassociate ourselves from it */
185     if( p_vout && p_vout->b_die )
186     {
187         var_DelCallback( p_vout, "mouse-clicked", MouseEvent, p_intf );
188         vlc_object_release( p_vout );
189         p_vout = NULL;
190     }
191
192     /* Main loop */
193     while( !p_intf->b_die )
194     {
195         
196         /* find a video output if we currently don't have one */
197         if( p_vout == NULL )
198         {
199             p_vout = vlc_object_find( p_intf->p_sys->p_input,
200                                       VLC_OBJECT_VOUT, FIND_CHILD );
201             if( p_vout )
202             {
203 #ifdef CMML_INTF_DEBUG
204                 msg_Dbg( p_intf, "found vout thread" );
205 #endif
206                 var_AddCallback( p_vout, "mouse-clicked", MouseEvent, p_intf );
207             }
208         }
209
210         vlc_mutex_lock( &p_intf->change_lock );
211
212         /*
213          * keyboard event
214          */
215         if( p_intf->p_sys->b_key_pressed )
216         {
217             vlc_value_t val;
218             int i, i_action = -1;
219             struct hotkey *p_hotkeys = p_intf->p_vlc->p_hotkeys;
220
221             /* Find action triggered by hotkey (if any) */
222             var_Get( p_intf->p_vlc, "key-pressed", &val );
223
224             /* Acknowledge that we've handled the b_key_pressed event */
225             p_intf->p_sys->b_key_pressed = VLC_FALSE;
226
227 #ifdef CMML_INTF_DEBUG
228             msg_Dbg( p_intf, "Got a keypress: %d", val.i_int );
229 #endif
230
231             for( i = 0; p_hotkeys[i].psz_action != NULL; i++ )
232             {
233                 if( p_hotkeys[i].i_key == val.i_int )
234                     i_action = p_hotkeys[i].i_action;
235             }
236
237             /* What did the user do? */
238             if( i_action != -1 )
239             {
240                 switch( i_action )
241                 {
242                     case ACTIONID_NAV_ACTIVATE:
243                         FollowAnchor( p_intf );
244                         break;
245                     case ACTIONID_HISTORY_BACK:
246                         GoBack( p_intf );
247                         break;
248                     case ACTIONID_HISTORY_FORWARD:
249                         GoForward( p_intf );
250                         break;
251                     default:
252                         break;
253                 }
254             }
255         }
256
257         vlc_mutex_unlock( &p_intf->change_lock );
258
259         (void) DisplayPendingAnchor( p_intf, p_vout );
260
261         /* Wait a bit */
262         msleep( INTF_IDLE_SLEEP );
263     }
264
265     /* if we're here, the video output is dying: release the vout object */
266
267     if( p_vout )
268     {
269         var_DelCallback( p_vout, "mouse-clicked", MouseEvent, p_intf );
270         vlc_object_release( p_vout );
271     }
272
273     vlc_object_release( p_intf->p_sys->p_input );
274 }
275
276 /*****************************************************************************
277  * DisplayPendingAnchor: get a pending anchor description/URL from the CMML
278  * decoder and display it on screen
279  *****************************************************************************/
280 static int DisplayPendingAnchor( intf_thread_t *p_intf, vout_thread_t *p_vout )
281 {
282     decoder_t *p_cmml_decoder;
283     char *psz_description = NULL;
284     char *psz_url = NULL;
285
286     intf_thread_t *p_primary_intf;
287     vlc_value_t val;
288
289     p_cmml_decoder = p_intf->p_sys->p_cmml_decoder;
290     if( var_Get( p_cmml_decoder, "psz-current-anchor-description", &val )
291             != VLC_SUCCESS )
292     {
293         return VLC_TRUE;
294     }
295
296     if( !val.p_address )
297         return VLC_TRUE;
298
299     psz_description = val.p_address;
300
301     if( var_Get( p_cmml_decoder, "psz-current-anchor-url", &val )
302             == VLC_SUCCESS )
303     {
304         psz_url = val.p_address;
305     }
306
307     if( p_vout != NULL )
308     {
309         /* don't display anchor if main interface can display it */
310         p_primary_intf = vlc_object_find( p_intf->p_vlc, VLC_OBJECT_INTF,
311                 FIND_CHILD );
312
313         if( p_primary_intf )
314         {
315             if( var_Get( p_primary_intf, "intf-displays-cmml-description", &val )
316                     == VLC_SUCCESS )
317             {
318                 if( val.b_bool == VLC_TRUE ) return VLC_TRUE;
319             }
320         }
321
322         /* display anchor as subtitle on-screen */
323         if( DisplayAnchor( p_intf, p_vout, psz_description, psz_url )
324                 != VLC_SUCCESS )
325         {
326             /* text render unsuccessful: do nothing */
327             return VLC_FALSE;
328         }
329
330         /* text render successful: clear description */
331         val.p_address = NULL;
332         if( var_Set( p_cmml_decoder, "psz-current-anchor-description", val )
333                 != VLC_SUCCESS )
334         {
335             msg_Dbg( p_intf,
336                      "reset of psz-current-anchor-description failed" );
337         }
338         free( psz_description );
339         psz_url = NULL;
340     }
341
342     return VLC_TRUE;
343 }
344
345
346 /*****************************************************************************
347  * InitThread:
348  *****************************************************************************/
349 static int InitThread( intf_thread_t * p_intf )
350 {
351     /* We might need some locking here */
352     if( !p_intf->b_die )
353     {
354         input_thread_t * p_input;
355         decoder_t *p_cmml_decoder;
356
357         p_cmml_decoder = vlc_object_find( p_intf, VLC_OBJECT_DECODER, FIND_PARENT );
358         p_input = vlc_object_find( p_intf, VLC_OBJECT_INPUT, FIND_PARENT );
359
360 #ifdef CMML_INTF_DEBUG
361         msg_Dbg( p_intf, "cmml decoder at %p, input thread at %p",
362                  p_cmml_decoder, p_input );
363 #endif
364
365         /* Maybe the input just died */
366         if( p_input == NULL )
367         {
368             return VLC_EGENERIC;
369         }
370
371         vlc_mutex_lock( &p_intf->change_lock );
372
373         p_intf->p_sys->p_input = p_input;
374         p_intf->p_sys->p_cmml_decoder = p_cmml_decoder;
375
376         p_intf->p_sys->b_key_pressed = VLC_FALSE;
377
378         vlc_mutex_unlock( &p_intf->change_lock );
379
380         return VLC_SUCCESS;
381     }
382     else
383     {
384         return VLC_EGENERIC;
385     }
386 }
387
388 /*****************************************************************************
389  * MouseEvent: callback for mouse events
390  *****************************************************************************/
391 static int MouseEvent( vlc_object_t *p_this, char const *psz_var,
392                        vlc_value_t oldval, vlc_value_t newval, void *p_data )
393 {
394     /* TODO: handle mouse clicks on the anchor text */
395
396     return VLC_SUCCESS;
397 }
398
399 /*****************************************************************************
400  * KeyEvent: callback for keyboard events
401  *****************************************************************************/
402 static int KeyEvent( vlc_object_t *p_this, char const *psz_var,
403                        vlc_value_t oldval, vlc_value_t newval, void *p_data )
404 {
405     intf_thread_t *p_intf = (intf_thread_t *)p_data;
406     vlc_mutex_lock( &p_intf->change_lock );
407
408     p_intf->p_sys->b_key_pressed = VLC_TRUE;
409     
410     vlc_mutex_unlock( &p_intf->change_lock );
411
412     return VLC_SUCCESS;
413 }
414
415 /*****************************************************************************
416  * FollowAnchor: follow the current anchor being displayed to the user
417  *****************************************************************************/
418 static void FollowAnchor ( intf_thread_t *p_intf )
419 {
420     intf_sys_t *p_sys;
421     decoder_t *p_cmml_decoder;
422     char *psz_url = NULL;
423     vlc_value_t val;
424
425     msg_Dbg( p_intf, "User followed anchor" );
426
427     p_sys = p_intf->p_sys;
428     p_cmml_decoder = p_sys->p_cmml_decoder;
429
430     if( var_Get( p_cmml_decoder, "psz-current-anchor-url", &val ) ==
431             VLC_SUCCESS )
432     {
433         if( val.p_address ) psz_url = val.p_address;
434     }
435
436 #ifdef CMML_INTF_DEBUG
437     msg_Dbg( p_intf, "Current URL is \"%s\"", psz_url );
438 #endif
439
440     if( psz_url )
441     {
442         playlist_t *p_playlist;
443         playlist_item_t *p_current_item;
444         char *psz_uri_to_load;
445         mtime_t i_seconds;
446         vlc_value_t time;
447
448         p_playlist = (playlist_t *) vlc_object_find( p_intf, 
449                 VLC_OBJECT_PLAYLIST, FIND_ANYWHERE );
450         if ( !p_playlist )
451         {
452             msg_Warn( p_intf, "can't find playlist" );
453             return;
454         }
455
456         /* Get new URL */
457         p_current_item = p_playlist->pp_items[p_playlist->i_index];
458 #ifdef CMML_INTF_DEBUG
459         msg_Dbg( p_intf, "Current playlist item URL is \"%s\"",
460                 p_current_item->input.psz_uri );
461 #endif
462
463         psz_uri_to_load = XURL_Concat( p_current_item->input.psz_uri,
464                                        psz_url );
465
466 #ifdef CMML_INTF_DEBUG
467         msg_Dbg( p_intf, "URL to load is \"%s\"", psz_uri_to_load );
468 #endif
469
470         if( var_Get( p_intf->p_sys->p_input, "time", &time ) )
471         {
472             msg_Dbg( p_intf, "couldn't get time from current clip" );
473             time.i_time = 0;
474         }
475         i_seconds = time.i_time / 1000000;
476 #ifdef CMML_INTF_DEBUG
477         msg_Dbg( p_intf, "Current time is \"%lld\"", i_seconds );
478 #endif
479
480         /* TODO: we need a (much) more robust way of detecting whether
481          * the file's a media file ... */
482         if( strstr( psz_uri_to_load, ".anx" ) != NULL )
483         {
484             history_t *p_history = NULL;
485             history_item_t *p_history_item = NULL;
486             char *psz_timed_url;
487             
488             p_history = GetHistory( p_playlist );
489
490             /* create history item */
491             psz_timed_url = GetTimedURLFromPlaylistItem( p_intf, p_current_item );
492             p_history_item = historyItem_New( psz_timed_url, psz_timed_url );
493             free( psz_timed_url );
494
495             if( !p_history_item )
496             {
497                 msg_Warn( p_intf, "could not initialise history item" );
498             }
499             else
500             {
501 #ifdef CMML_INTF_DEBUG
502                 msg_Dbg( p_intf, "history pre-index %d", p_history->i_index );
503 #endif
504                 history_PruneAndInsert( p_history, p_history_item );
505 #ifdef CMML_INTF_DEBUG
506                 msg_Dbg( p_intf, "new history item at %p, uri is \"%s\"",
507                          p_history_item, p_history_item->psz_uri );
508                 msg_Dbg( p_intf, "history index now %d", p_history->i_index );
509 #endif
510             }
511
512             /* free current-anchor-url */
513             free( psz_url );
514             val.p_address = NULL;
515             if( var_Set( p_cmml_decoder, "psz-current-anchor-url", val ) !=
516                     VLC_SUCCESS )
517             {
518                 msg_Dbg( p_intf, "couldn't reset psz-current-anchor-url" );
519             }
520
521             ReplacePlaylistItem( p_playlist, psz_uri_to_load );
522         }
523         else
524         {
525 #ifdef CMML_INTF_DEBUG
526             msg_Dbg( p_intf, "calling browser_Open with \"%s\"", psz_url );
527 #endif
528             (void) browser_Open( psz_url );
529             playlist_Command( p_playlist, PLAYLIST_PAUSE, 0 );
530         }
531
532         free( psz_uri_to_load );
533
534         vlc_object_release( p_playlist );
535     }
536 }
537
538 static
539 char *GetTimedURLFromPlaylistItem( intf_thread_t *p_intf,
540         playlist_item_t *p_current_item )
541 {
542 #ifdef CMML_INTF_USE_TIMED_URIS
543     char *psz_url = NULL;
544     char *psz_return_value = NULL;
545     char *psz_seconds = NULL;
546     int i_seconds;
547     
548     psz_url = XURL_GetWithoutFragment( p_current_item->input->psz_uri );
549
550     /* Get current time as a string */
551     if( XURL_IsFileURL( psz_url ) == VLC_TRUE )
552         psz_url = xstrcat( psz_url, "#" );
553     else
554         psz_url = xstrcat( psz_url, "?" );
555
556     /* jump back to 2 seconds before where we are now */
557     i_seconds = GetCurrentTimeInSeconds( p_intf->p_sys->p_input ) - 2;
558     psz_seconds = GetTimedURIFragmentForTime( i_seconds < 0 ? 0 : i_seconds );
559     if( psz_seconds )
560     {
561         psz_url = xstrcat( psz_url, psz_seconds );
562         free( psz_seconds );
563         psz_return_value = psz_url;
564     }
565
566     return psz_return_value;
567 #else
568     void *p;
569
570     /* Suppress warning messages about unused functions */
571     p = GetTimedURIFragmentForTime; /* unused */
572     p = GetCurrentTimeInSeconds;    /* unused */
573
574     return strdup( p_current_item->input.psz_uri );
575 #endif
576 }
577
578
579 /*
580  * Get the current time, rounded down to the nearest second
581  *
582  * http://www.ietf.org/internet-drafts/draft-pfeiffer-temporal-fragments-02.txt
583  */
584 static
585 int GetCurrentTimeInSeconds( input_thread_t *p_input )
586 {
587     vlc_value_t time;
588     mtime_t i_seconds;
589
590     var_Get( p_input, "time", &time );
591     i_seconds = time.i_time / 1000000;
592
593     return i_seconds;
594 }
595
596 static
597 char *GetTimedURIFragmentForTime( int seconds )
598 {
599     char *psz_time;
600
601     asprintf( &psz_time, "%d", seconds );
602
603     return psz_time;
604 }
605
606 static
607 int GoBackCallback( vlc_object_t *p_this, char const *psz_var,
608                     vlc_value_t oldval, vlc_value_t newval, void *p_data )
609 {
610     intf_thread_t *p_intf = (intf_thread_t *) p_data;
611     GoBack( p_intf );
612     return VLC_SUCCESS;
613 }
614
615 static
616 int GoForwardCallback( vlc_object_t *p_this, char const *psz_var,
617                        vlc_value_t oldval, vlc_value_t newval, void *p_data )
618 {
619     intf_thread_t *p_intf = (intf_thread_t *) p_data;
620     GoForward( p_intf );
621     return VLC_SUCCESS;
622 }
623
624 static
625 int FollowAnchorCallback( vlc_object_t *p_this, char const *psz_var,
626                           vlc_value_t oldval, vlc_value_t newval,
627                           void *p_data )
628 {
629     intf_thread_t *p_intf = (intf_thread_t *) p_data;
630     FollowAnchor( p_intf );
631     return VLC_SUCCESS;
632 }
633
634 static
635 void GoBack( intf_thread_t *p_intf )
636 {
637     vlc_value_t history;
638     history_t *p_history = NULL;
639     history_item_t *p_history_item = NULL;
640     history_item_t *p_new_history_item = NULL;
641     playlist_t *p_playlist = NULL;
642     char *psz_timed_url = NULL;
643     playlist_item_t *p_current_item;
644
645 #ifdef CMML_INTF_DEBUG
646     msg_Dbg( p_intf, "Going back in navigation history" );
647 #endif
648
649     /* Find the playlist */
650     p_playlist = (playlist_t *) vlc_object_find( p_intf, 
651             VLC_OBJECT_PLAYLIST, FIND_ANYWHERE );
652     if ( !p_playlist )
653     {
654         msg_Warn( p_intf, "can't find playlist" );
655         return;
656     }
657
658     /* Retrieve navigation history from playlist */
659     if( var_Get( p_playlist, "navigation-history", &history ) != VLC_SUCCESS ||
660         !history.p_address )
661     {
662         /* History doesn't exist yet: ignore user's request */
663         msg_Warn( p_intf, "can't go back: no history exists yet" );
664         vlc_object_release( p_playlist );
665         return;
666     }
667
668     p_history = history.p_address;
669 #ifdef CMML_INTF_DEBUG
670     msg_Dbg( p_intf, "back: nav history retrieved from %p", p_history );
671     msg_Dbg( p_intf, "nav history index:%d, p_xarray:%p", p_history->i_index,
672              p_history->p_xarray );
673 #endif
674
675     /* Check whether we can go back in the history */
676     if( history_CanGoBack( p_history ) == VLC_FALSE )
677     {
678         msg_Warn( p_intf, "can't go back: already at beginning of history" );
679         vlc_object_release( p_playlist );
680         return;
681     }
682
683     p_current_item = p_playlist->pp_items[p_playlist->i_index];
684
685     /* Save the currently-playing media in a new history item */
686     psz_timed_url = GetTimedURLFromPlaylistItem( p_intf, p_current_item );
687     p_new_history_item = historyItem_New( psz_timed_url, psz_timed_url );
688     free( psz_timed_url );
689
690     if( !p_new_history_item )
691     {
692 #ifdef CMML_INTF_DEBUG
693         msg_Dbg( p_intf, "back: could not initialise new history item" );
694 #endif
695         vlc_object_release( p_playlist );
696         return;
697     }
698
699     /* Go back in the history, saving the currently-playing item */
700     (void) history_GoBackSavingCurrentItem( p_history, p_new_history_item );
701     p_history_item = history_Item( p_history );
702
703 #ifdef CMML_INTF_DEBUG
704     msg_Dbg( p_intf, "retrieving item from h index %d", p_history->i_index );
705     msg_Dbg( p_intf, "got previous history item: %p", p_history_item );
706     msg_Dbg( p_intf, "prev history item URL: \"%s\"", p_history_item->psz_uri );
707 #endif
708
709     ReplacePlaylistItem( p_playlist, p_history_item->psz_uri );
710     vlc_object_release( p_playlist );
711 }
712
713 static
714 void GoForward( intf_thread_t *p_intf )
715 {
716     vlc_value_t history;
717     history_t *p_history = NULL;
718     history_item_t *p_history_item = NULL;
719     history_item_t *p_new_history_item = NULL;
720     playlist_t *p_playlist = NULL;
721     playlist_item_t *p_current_item;
722
723 #ifdef CMML_INTF_DEBUG
724     msg_Dbg( p_intf, "Going forward in navigation history" );
725 #endif
726
727     /* Find the playlist */
728     p_playlist = (playlist_t *) vlc_object_find( p_intf, 
729             VLC_OBJECT_PLAYLIST, FIND_ANYWHERE );
730     if ( !p_playlist )
731     {
732         msg_Warn( p_intf, "can't find playlist" );
733         return;
734     }
735
736     /* Retrieve navigation history from playlist */
737     if( var_Get( p_playlist, "navigation-history", &history ) != VLC_SUCCESS ||
738         !history.p_address )
739     {
740         /* History doesn't exist yet: ignore user's request */
741         msg_Warn( p_intf, "can't go back: no history exists yet" );
742         vlc_object_release( p_playlist );
743         return;
744     }
745
746     p_history = history.p_address;
747 #ifdef CMML_INTF_DEBUG
748     msg_Dbg( p_intf, "forward: nav history retrieved from %p", p_history );
749     msg_Dbg( p_intf, "nav history index:%d, p_xarray:%p", p_history->i_index,
750              p_history->p_xarray );
751 #endif
752
753     /* Check whether we can go forward in the history */
754     if( history_CanGoForward( p_history ) == VLC_FALSE )
755     {
756         msg_Warn( p_intf, "can't go forward: already at end of history" );
757         vlc_object_release( p_playlist );
758         return;
759     }
760
761     /* Save the currently-playing media in a new history item */
762     p_new_history_item = malloc( sizeof(history_item_t) );
763     if( !p_new_history_item )
764     {
765 #ifdef CMML_INTF_DEBUG
766         msg_Dbg( p_intf, "forward: could not initialise new history item" );
767 #endif
768         vlc_object_release( p_playlist );
769         return;
770     }
771     p_current_item = p_playlist->pp_items[p_playlist->i_index];
772     p_new_history_item->psz_uri = GetTimedURLFromPlaylistItem( p_intf, 
773             p_current_item );
774     p_new_history_item->psz_name = p_new_history_item->psz_uri;
775
776     /* Go forward in the history, saving the currently-playing item */
777     (void) history_GoForwardSavingCurrentItem( p_history, p_new_history_item );
778     p_history_item = history_Item( p_history );
779
780 #ifdef CMML_INTF_DEBUG
781     msg_Dbg( p_intf, "retrieving item from h index %d", p_history->i_index );
782     msg_Dbg( p_intf, "got next history item: %p", p_history_item );
783     msg_Dbg( p_intf, "next history item URL: \"%s\"", p_history_item->psz_uri );
784 #endif
785
786     ReplacePlaylistItem( p_playlist, p_history_item->psz_uri );
787     vlc_object_release( p_playlist );
788 }
789
790 static void ReplacePlaylistItem( playlist_t *p_playlist, char *psz_uri )
791 {
792     playlist_Stop( p_playlist );
793     (void) playlist_Add( p_playlist, psz_uri, psz_uri,
794                          PLAYLIST_REPLACE, p_playlist->i_index );
795     playlist_Goto( p_playlist, p_playlist->i_index );
796 }
797
798 /****************************************************************************
799  * DisplayAnchor: displays an anchor on the given video output
800  ****************************************************************************/
801 static int DisplayAnchor( intf_thread_t *p_intf,
802         vout_thread_t *p_vout,
803         char *psz_anchor_description,
804         char *psz_anchor_url )
805 {
806     int i_margin_h, i_margin_v;
807     mtime_t i_now;
808
809     i_margin_h = 0;
810     i_margin_v = 10;
811
812     i_now = mdate();
813
814     if( p_vout )
815     {
816         text_style_t *p_style = NULL;
817
818         text_style_t blue_with_underline = default_text_style;
819         blue_with_underline.b_underline = VLC_TRUE;
820         blue_with_underline.i_color = 0x22ff22;
821
822         if( psz_anchor_url )
823         {
824             /* Should display subtitle underlined and in blue,
825              * but it looks like VLC doesn't implement any
826              * text styles yet.  D'oh! */
827             p_style = &blue_with_underline;
828
829         }
830
831         /* TODO: p_subpicture doesn't have the proper i_x and i_y
832          * coordinates.  Need to look at the subpicture display system to
833          * work out why. */
834         if ( vout_ShowTextAbsolute( p_vout, DEFAULT_CHAN,
835                 psz_anchor_description, p_style, OSD_ALIGN_BOTTOM,
836                 i_margin_h, i_margin_v, i_now, 0 ) == VLC_SUCCESS )
837         {
838             /* Displayed successfully */
839 #ifdef CMML_INTF_SUBPICTURE_DEBUG
840             msg_Dbg( p_intf, "subpicture created at (%d, %d) (%d, %d)",
841                      p_subpicture->i_x, p_subpicture->i_y,
842                      p_subpicture->i_width, p_subpicture->i_height );
843 #endif
844         }
845         else
846         {
847             return VLC_EGENERIC;
848         }
849
850     }
851     else
852     {
853         msg_Dbg( p_intf, "DisplayAnchor couldn't find a video output" );
854         return VLC_EGENERIC;
855     }
856
857     return VLC_SUCCESS;
858 }
859
860 static history_t * GetHistory( playlist_t *p_playlist )
861 {
862     vlc_value_t val;
863     history_t *p_history = NULL;
864
865     if( var_Get( p_playlist, "navigation-history", &val ) != VLC_SUCCESS )
866     {
867         /* history doesn't exist yet: need to create it */
868         history_t *new_history = history_New();
869         val.p_address = new_history;
870         var_Create( p_playlist, "navigation-history",
871                 VLC_VAR_ADDRESS|VLC_VAR_DOINHERIT );
872         if( var_Set( p_playlist, "navigation-history", val ) != VLC_SUCCESS )
873         {
874             msg_Warn( p_playlist, "could not initialise history" );
875         }
876         else
877         {
878             p_history = new_history;
879 #ifdef CMML_INTF_HISTORY_DEBUG
880             msg_Dbg( p_playlist, "nav history created at %p", new_history );
881             msg_Dbg( p_playlist, "nav history index:%d, p_xarray:%p",
882                      p_history->i_index, p_history->p_xarray );
883 #endif
884         }
885     }
886     else
887     {
888         p_history = val.p_address;
889 #ifdef CMML_INTF_HISTORY_DEBUG
890         msg_Dbg( p_playlist, "nav history retrieved from %p", p_history );
891 #endif
892     }
893
894     return p_history;
895 }
896