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