New recently played menu.
[vlc.git] / modules / gui / qt4 / dialogs_provider.cpp
1 /*****************************************************************************
2  * dialogs_provider.cpp : Dialog Provider
3  *****************************************************************************
4  * Copyright (C) 2006-2008 the VideoLAN team
5  * $Id$
6  *
7  * Authors: ClĂ©ment Stenac <zorglub@videolan.org>
8  *          Jean-Baptiste Kempf <jb@videolan.org>
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23  *****************************************************************************/
24 #ifdef HAVE_CONFIG_H
25 # include "config.h"
26 #endif
27
28 #include <QEvent>
29 #include <QApplication>
30 #include <QSignalMapper>
31 #include <QFileDialog>
32
33 #include <vlc_common.h>
34 #include "qt4.hpp"
35 #include "dialogs_provider.hpp"
36 #include "main_interface.hpp"
37 #include "menus.hpp"
38 #include <vlc_intf_strings.h>
39 #include "input_manager.hpp"
40 #include "recents.hpp"
41
42 /* The dialogs */
43 #include "dialogs/playlist.hpp"
44 #include "dialogs/bookmarks.hpp"
45 #include "dialogs/preferences.hpp"
46 #include "dialogs/mediainfo.hpp"
47 #include "dialogs/messages.hpp"
48 #include "dialogs/extended.hpp"
49 #include "dialogs/vlm.hpp"
50 #include "dialogs/sout.hpp"
51 #include "dialogs/open.hpp"
52 #include "dialogs/help.hpp"
53 #include "dialogs/gototime.hpp"
54 #include "dialogs/podcast_configuration.hpp"
55
56 DialogsProvider* DialogsProvider::instance = NULL;
57
58 DialogsProvider::DialogsProvider( intf_thread_t *_p_intf ) :
59                                   QObject( NULL ), p_intf( _p_intf )
60 {
61     b_isDying = false;
62
63     menusMapper = new QSignalMapper();
64     CONNECT( menusMapper, mapped(QObject *), this, menuAction( QObject *) );
65
66     menusUpdateMapper = new QSignalMapper();
67     CONNECT( menusUpdateMapper, mapped(QObject *),
68              this, menuUpdateAction( QObject *) );
69
70     SDMapper = new QSignalMapper();
71     CONNECT( SDMapper, mapped (QString), this, SDMenuAction( QString ) );
72 }
73
74 DialogsProvider::~DialogsProvider()
75 {
76     msg_Dbg( p_intf, "Destroying the Dialog Provider" );
77     PlaylistDialog::killInstance();
78     MediaInfoDialog::killInstance();
79     MessagesDialog::killInstance();
80     ExtendedDialog::killInstance();
81     BookmarksDialog::killInstance();
82     HelpDialog::killInstance();
83 #ifdef UPDATE_CHECK
84     UpdateDialog::killInstance();
85 #endif
86
87     delete menusMapper;
88     delete menusUpdateMapper;
89     delete SDMapper;
90 }
91
92 void DialogsProvider::quit()
93 {
94     /* Stop the playlist */
95     playlist_Stop( THEPL );
96     b_isDying = true;
97     vlc_object_kill( p_intf->p_libvlc );
98     QApplication::closeAllWindows();
99     QApplication::quit();
100 }
101
102 void DialogsProvider::customEvent( QEvent *event )
103 {
104     if( event->type() == DialogEvent_Type )
105     {
106         DialogEvent *de = static_cast<DialogEvent*>(event);
107         switch( de->i_dialog )
108         {
109         case INTF_DIALOG_FILE_SIMPLE:
110         case INTF_DIALOG_FILE:
111             openDialog(); break;
112         case INTF_DIALOG_FILE_GENERIC:
113             openFileGenericDialog( de->p_arg ); break;
114         case INTF_DIALOG_DISC:
115             openDiscDialog(); break;
116         case INTF_DIALOG_NET:
117             openNetDialog(); break;
118         case INTF_DIALOG_SAT:
119         case INTF_DIALOG_CAPTURE:
120             openCaptureDialog(); break;
121         case INTF_DIALOG_DIRECTORY:
122             PLAppendDir(); break;
123         case INTF_DIALOG_PLAYLIST:
124             playlistDialog(); break;
125         case INTF_DIALOG_MESSAGES:
126             messagesDialog(); break;
127         case INTF_DIALOG_FILEINFO:
128            mediaInfoDialog(); break;
129         case INTF_DIALOG_PREFS:
130            prefsDialog(); break;
131         case INTF_DIALOG_BOOKMARKS:
132            bookmarksDialog(); break;
133         case INTF_DIALOG_EXTENDED:
134            extendedDialog(); break;
135 #ifdef ENABLE_VLM
136         case INTF_DIALOG_VLM:
137            vlmDialog(); break;
138 #endif
139         case INTF_DIALOG_INTERACTION:
140            doInteraction( de->p_arg ); break;
141         case INTF_DIALOG_POPUPMENU:
142            QVLCMenu::PopupMenu( p_intf, (de->i_arg != 0) ); break;
143         case INTF_DIALOG_AUDIOPOPUPMENU:
144            QVLCMenu::AudioPopupMenu( p_intf ); break;
145         case INTF_DIALOG_VIDEOPOPUPMENU:
146            QVLCMenu::VideoPopupMenu( p_intf ); break;
147         case INTF_DIALOG_MISCPOPUPMENU:
148            QVLCMenu::MiscPopupMenu( p_intf ); break;
149         case INTF_DIALOG_WIZARD:
150         case INTF_DIALOG_STREAMWIZARD:
151             openThenStreamingDialogs(); break;
152 #ifdef UPDATE_CHECK
153         case INTF_DIALOG_UPDATEVLC:
154             updateDialog(); break;
155 #endif
156         case INTF_DIALOG_EXIT:
157             quit(); break;
158         default:
159            msg_Warn( p_intf, "unimplemented dialog" );
160         }
161     }
162 }
163
164 /****************************************************************************
165  * Individual simple dialogs
166  ****************************************************************************/
167 void DialogsProvider::playlistDialog()
168 {
169     PlaylistDialog::getInstance( p_intf )->toggleVisible();
170 }
171
172 void DialogsProvider::prefsDialog()
173 {
174     PrefsDialog::getInstance( p_intf )->toggleVisible();
175 }
176
177 void DialogsProvider::extendedDialog()
178 {
179     ExtendedDialog::getInstance( p_intf )->toggleVisible();
180 }
181
182 void DialogsProvider::messagesDialog()
183 {
184     MessagesDialog::getInstance( p_intf )->toggleVisible();
185 }
186
187 void DialogsProvider::gotoTimeDialog()
188 {
189     GotoTimeDialog::getInstance( p_intf )->toggleVisible();
190 }
191
192 #ifdef ENABLE_VLM
193 void DialogsProvider::vlmDialog()
194 {
195     VLMDialog::getInstance( p_intf )->toggleVisible();
196 }
197 #endif
198
199 void DialogsProvider::helpDialog()
200 {
201     HelpDialog::getInstance( p_intf )->toggleVisible();
202 }
203
204 #ifdef UPDATE_CHECK
205 void DialogsProvider::updateDialog()
206 {
207     UpdateDialog::getInstance( p_intf )->toggleVisible();
208 }
209 #endif
210
211 void DialogsProvider::aboutDialog()
212 {
213     AboutDialog::getInstance( p_intf )->toggleVisible();
214 }
215
216 void DialogsProvider::mediaInfoDialog()
217 {
218     MediaInfoDialog::getInstance( p_intf )->toggleVisible();
219 }
220
221 void DialogsProvider::mediaCodecDialog()
222 {
223     MediaInfoDialog::getInstance( p_intf )->showTab( 2 );
224 }
225
226 void DialogsProvider::bookmarksDialog()
227 {
228     BookmarksDialog::getInstance( p_intf )->toggleVisible();
229 }
230
231 void DialogsProvider::podcastConfigureDialog()
232 {
233     PodcastConfigDialog::getInstance( p_intf )->toggleVisible();
234 }
235
236
237 /****************************************************************************
238  * All the open/add stuff
239  * Open Dialog first - Simple Open then
240  ****************************************************************************/
241
242 void DialogsProvider::openDialog( int i_tab )
243 {
244     OpenDialog::getInstance( p_intf->p_sys->p_mi , p_intf )->showTab( i_tab );
245 }
246 void DialogsProvider::openDialog()
247 {
248     openDialog( OPEN_FILE_TAB );
249 }
250 void DialogsProvider::openFileGenericDialog( intf_dialog_args_t *p_arg )
251 {
252     if( p_arg == NULL )
253     {
254         msg_Warn( p_intf, "openFileGenericDialog() called with NULL arg" );
255         return;
256     }
257
258     /* Replace the extensions to a Qt format */
259     int i = 0;
260     QString extensions = qfu( p_arg->psz_extensions );
261     while ( ( i = extensions.indexOf( "|", i ) ) != -1 )
262     {
263         if( ( extensions.count( "|" ) % 2 ) == 0 )
264             extensions.replace( i, 1, ");;" );
265         else
266             extensions.replace( i, 1, "(" );
267     }
268     extensions.replace(QString(";*"), QString(" *"));
269     extensions.append( ")" );
270
271     /* Save */
272     if( p_arg->b_save )
273     {
274         QString file = QFileDialog::getSaveFileName( NULL, p_arg->psz_title,
275                             qfu( p_intf->p_sys->psz_filepath ), extensions );
276         if( !file.isEmpty() )
277         {
278             p_arg->i_results = 1;
279             p_arg->psz_results = (char **)malloc( p_arg->i_results * sizeof( char * ) );
280             p_arg->psz_results[0] = strdup( qtu( toNativeSepNoSlash( file ) ) );
281         }
282         else
283             p_arg->i_results = 0;
284     }
285     else /* non-save mode */
286     {
287         QStringList files = QFileDialog::getOpenFileNames( NULL,
288                 p_arg->psz_title, qfu( p_intf->p_sys->psz_filepath ),
289                 extensions );
290         p_arg->i_results = files.count();
291         p_arg->psz_results = (char **)malloc( p_arg->i_results * sizeof( char * ) );
292         i = 0;
293         foreach( QString file, files )
294             p_arg->psz_results[i++] = strdup( qtu( toNativeSepNoSlash( file ) ) );
295     }
296
297     /* Callback */
298     if( p_arg->pf_callback )
299         p_arg->pf_callback( p_arg );
300
301     /* Clean afterwards */
302     if( p_arg->psz_results )
303     {
304         for( i = 0; i < p_arg->i_results; i++ )
305             free( p_arg->psz_results[i] );
306         free( p_arg->psz_results );
307     }
308     free( p_arg->psz_title );
309     free( p_arg->psz_extensions );
310     free( p_arg );
311 }
312
313 void DialogsProvider::openFileDialog()
314 {
315     openDialog( OPEN_FILE_TAB );
316 }
317 void DialogsProvider::openDiscDialog()
318 {
319     openDialog( OPEN_DISC_TAB );
320 }
321 void DialogsProvider::openNetDialog()
322 {
323     openDialog( OPEN_NETWORK_TAB );
324 }
325 void DialogsProvider::openCaptureDialog()
326 {
327     openDialog( OPEN_CAPTURE_TAB );
328 }
329
330 /* Same as the open one, but force the enqueue */
331 void DialogsProvider::PLAppendDialog()
332 {
333     OpenDialog::getInstance( p_intf->p_sys->p_mi, p_intf, false, OPEN_AND_ENQUEUE)
334                             ->showTab( OPEN_FILE_TAB );
335 }
336
337 void DialogsProvider::MLAppendDialog()
338 {
339     OpenDialog::getInstance( p_intf->p_sys->p_mi, p_intf, false,
340                             OPEN_AND_ENQUEUE, false, false )
341                                     ->showTab( OPEN_FILE_TAB );
342 }
343
344 /**
345  * Simple open
346  ***/
347 QStringList DialogsProvider::showSimpleOpen( QString help,
348                                              int filters,
349                                              QString path )
350 {
351     QString fileTypes = "";
352     if( filters & EXT_FILTER_MEDIA ) {
353         ADD_FILTER_MEDIA( fileTypes );
354     }
355     if( filters & EXT_FILTER_VIDEO ) {
356         ADD_FILTER_VIDEO( fileTypes );
357     }
358     if( filters & EXT_FILTER_AUDIO ) {
359         ADD_FILTER_AUDIO( fileTypes );
360     }
361     if( filters & EXT_FILTER_PLAYLIST ) {
362         ADD_FILTER_PLAYLIST( fileTypes );
363     }
364     if( filters & EXT_FILTER_SUBTITLE ) {
365         ADD_FILTER_SUBTITLE( fileTypes );
366     }
367     ADD_FILTER_ALL( fileTypes );
368     fileTypes.replace(QString(";*"), QString(" *"));
369
370     return QFileDialog::getOpenFileNames( NULL,
371         help.isEmpty() ? qfu(I_OP_SEL_FILES ) : help,
372         path.isEmpty() ? qfu( p_intf->p_sys->psz_filepath ) : path,
373         fileTypes );
374 }
375
376 /**
377  * Open a file,
378  * pl helps you to choose from playlist or media library,
379  * go to start or enqueue
380  **/
381 void DialogsProvider::addFromSimple( bool pl, bool go)
382 {
383     QStringList files = DialogsProvider::showSimpleOpen();
384     int i = 0;
385     foreach( QString file, files )
386     {
387         playlist_Add( THEPL, qtu( toNativeSeparators( file ) ), NULL,
388                       go ? ( PLAYLIST_APPEND | ( i ? 0 : PLAYLIST_GO ) |
389                                                ( i ? PLAYLIST_PREPARSE : 0 ) )
390                          : ( PLAYLIST_APPEND | PLAYLIST_PREPARSE ),
391                       PLAYLIST_END,
392                       pl ? true : false, false );
393         RecentsMRL::getInstance( p_intf )->addRecent(
394                 toNativeSeparators( file ) );
395         i++;
396     }
397 }
398
399 void DialogsProvider::simpleOpenDialog()
400 {
401     addFromSimple( true, true ); /* Playlist and Go */
402 }
403
404 void DialogsProvider::simplePLAppendDialog()
405 {
406     addFromSimple( true, false );
407 }
408
409 void DialogsProvider::simpleMLAppendDialog()
410 {
411     addFromSimple( false, false );
412 }
413
414 /* Directory */
415 /**
416  * Open a directory,
417  * pl helps you to choose from playlist or media library,
418  * go to start or enqueue
419  **/
420 static void openDirectory( intf_thread_t *p_intf, bool pl, bool go )
421 {
422     QString dir = QFileDialog::getExistingDirectory( NULL, qtr("Open Directory") );
423
424     if (!dir.isEmpty() )
425     {
426         QString mrl = dir.endsWith( "VIDEO_TS", Qt::CaseInsensitive )
427             ? "dvd://" : "directory://" + toNativeSeparators( dir );
428         msg_Dbg( p_intf, "Directory opening: %s", qtu( dir ) );
429         input_item_t *p_input = input_item_NewExt( THEPL, qtu( mrl ),
430                               NULL, 0, NULL, -1 );
431
432         /* FIXME: playlist_AddInput() can fail */
433         playlist_AddInput( THEPL, p_input,
434                        go ? ( PLAYLIST_APPEND | PLAYLIST_GO ) : PLAYLIST_APPEND,
435                        PLAYLIST_END, pl, pl_Unlocked );
436         RecentsMRL::getInstance( p_intf )->addRecent( mrl );
437         if( !go )
438             input_Read( THEPL, p_input, true );
439         vlc_gc_decref( p_input );
440     }
441 }
442
443 void DialogsProvider::PLOpenDir()
444 {
445     openDirectory( p_intf, true, true );
446 }
447
448 void DialogsProvider::PLAppendDir()
449 {
450     openDirectory( p_intf, true, false );
451 }
452
453 void DialogsProvider::MLAppendDir()
454 {
455     openDirectory( p_intf, false , false );
456 }
457
458 /****************
459  * Playlist     *
460  ****************/
461 void DialogsProvider::openAPlaylist()
462 {
463     QStringList files = showSimpleOpen( qtr( "Open playlist..." ),
464                                         EXT_FILTER_PLAYLIST );
465     foreach( QString file, files )
466     {
467         playlist_Import( THEPL, qtu( toNativeSeparators( file ) ) );
468     }
469 }
470
471 void DialogsProvider::saveAPlaylist()
472 {
473     QFileDialog *qfd = new QFileDialog( NULL,
474                                    qtr( "Save playlist as..." ),
475                                    qfu( p_intf->p_sys->psz_filepath ),
476                                    qtr( "XSPF playlist (*.xspf);; " ) +
477                                    qtr( "M3U playlist (*.m3u);; Any (*.*) " ) );
478     qfd->setFileMode( QFileDialog::AnyFile );
479     qfd->setAcceptMode( QFileDialog::AcceptSave );
480     qfd->setConfirmOverwrite( true );
481
482     if( qfd->exec() == QDialog::Accepted )
483     {
484         if( qfd->selectedFiles().count() > 0 )
485         {
486             static const char psz_xspf[] = "export-xspf",
487                               psz_m3u[] = "export-m3u";
488             const char *psz_module;
489
490             QString file = qfd->selectedFiles().first();
491             QString filter = qfd->selectedFilter();
492
493             if( file.contains( ".xsp" ) ||
494                 ( filter.contains( ".xspf" ) && !file.contains( ".m3u" ) ) )
495             {
496                 psz_module = psz_xspf;
497                 if( !file.contains( ".xsp" ) )
498                     file.append( ".xspf" );
499             }
500             else
501             {
502                 psz_module = psz_m3u;
503                 if( !file.contains( ".m3u" ) )
504                     file.append( ".m3u" );
505             }
506
507             playlist_Export( THEPL, qtu( toNativeSeparators( file ) ),
508                         THEPL->p_local_category, psz_module);
509         }
510     }
511     delete qfd;
512 }
513
514
515 /****************************************************************************
516  * Sout emulation
517  ****************************************************************************/
518
519 void DialogsProvider::streamingDialog( QWidget *parent, QString mrl,
520                                        bool b_transcode_only )
521 {
522     SoutDialog *s = SoutDialog::getInstance( parent, p_intf, b_transcode_only );
523
524     if( s->exec() == QDialog::Accepted )
525     {
526         msg_Dbg( p_intf, "Sout mrl %s", qta( s->getMrl() ) );
527         /* Just do it */
528         int i_len = strlen( qtu( s->getMrl() ) ) + 10;
529         char *psz_option = (char*)malloc( i_len );
530         snprintf( psz_option, i_len - 1, "%s", qtu( s->getMrl() ) );
531
532         playlist_AddExt( THEPL, qtu( mrl ), "Streaming",
533                          PLAYLIST_APPEND | PLAYLIST_GO, PLAYLIST_END,
534                         -1, &psz_option, 1, true, pl_Unlocked );
535         RecentsMRL::getInstance( p_intf )->addRecent( mrl );
536     }
537 }
538
539 void DialogsProvider::openThenStreamingDialogs()
540 {
541     OpenDialog::getInstance( p_intf->p_sys->p_mi, p_intf, false, OPEN_AND_STREAM )
542                                 ->showTab( OPEN_FILE_TAB );
543 }
544
545 void DialogsProvider::openThenTranscodingDialogs()
546 {
547     OpenDialog::getInstance( p_intf->p_sys->p_mi , p_intf, false, OPEN_AND_SAVE )
548                                 ->showTab( OPEN_FILE_TAB );
549 }
550
551 /****************************************************************************
552  * Menus / Interaction
553  ****************************************************************************/
554
555 void DialogsProvider::menuAction( QObject *data )
556 {
557     QVLCMenu::DoAction( p_intf, data );
558 }
559
560 void DialogsProvider::menuUpdateAction( QObject *data )
561 {
562     MenuFunc * f = qobject_cast<MenuFunc *>(data);
563     f->doFunc( p_intf );
564 }
565
566 void DialogsProvider::SDMenuAction( QString data )
567 {
568     char *psz_sd = strdup( qtu( data ) );
569     if( !playlist_IsServicesDiscoveryLoaded( THEPL, psz_sd ) )
570         playlist_ServicesDiscoveryAdd( THEPL, psz_sd );
571     else
572         playlist_ServicesDiscoveryRemove( THEPL, psz_sd );
573     free( psz_sd );
574 }
575
576 void DialogsProvider::doInteraction( intf_dialog_args_t *p_arg )
577 {
578     InteractionDialog *qdialog;
579     interaction_dialog_t *p_dialog = p_arg->p_dialog;
580     switch( p_dialog->i_action )
581     {
582     case INTERACT_NEW:
583         qdialog = new InteractionDialog( p_intf, p_dialog );
584         p_dialog->p_private = (void*)qdialog;
585         if( !(p_dialog->i_status == ANSWERED_DIALOG) )
586             qdialog->show();
587         break;
588     case INTERACT_UPDATE:
589         qdialog = (InteractionDialog*)(p_dialog->p_private);
590         if( qdialog )
591             qdialog->update();
592         else
593         {
594             /* The INTERACT_NEW message was forgotten
595                so we must create the dialog and update it*/
596             qdialog = new InteractionDialog( p_intf, p_dialog );
597             p_dialog->p_private = (void*)qdialog;
598             if( !(p_dialog->i_status == ANSWERED_DIALOG) )
599                 qdialog->show();
600             if( qdialog )
601                 qdialog->update();
602         }
603         break;
604     case INTERACT_HIDE:
605         msg_Dbg( p_intf, "Hide the Interaction Dialog" );
606         qdialog = (InteractionDialog*)(p_dialog->p_private);
607         if( qdialog )
608             qdialog->hide();
609         p_dialog->i_status = HIDDEN_DIALOG;
610         break;
611     case INTERACT_DESTROY:
612         msg_Dbg( p_intf, "Destroy the Interaction Dialog" );
613         qdialog = (InteractionDialog*)(p_dialog->p_private);
614         if( !p_dialog->i_flags & DIALOG_NONBLOCKING_ERROR )
615             delete qdialog;
616         p_dialog->i_status = DESTROYED_DIALOG;
617         break;
618     }
619 }
620
621 void DialogsProvider::loadSubtitlesFile()
622 {
623     input_thread_t *p_input = THEMIM->getInput();
624     if( !p_input )
625         return;
626     input_item_t *p_item = input_GetItem( p_input );
627     if( !p_item )
628         return;
629     char *path = input_item_GetURI( p_item );
630     if( !path )
631         path = strdup( "" );
632     char *sep = strrchr( path, DIR_SEP_CHAR );
633     if( sep )
634         *sep = '\0';
635     QStringList qsl = showSimpleOpen( qtr( "Open subtitles..." ),
636                                       EXT_FILTER_SUBTITLE,
637                                       path );
638     free( path );
639     QString qsFile;
640     foreach( qsFile, qsl )
641     {
642         if( !input_AddSubtitles( p_input, qtu( toNativeSeparators( qsFile ) ),
643                     true ) )
644             msg_Warn( p_intf, "unable to load subtitles from '%s'",
645                       qtu( qsFile ) );
646     }
647 }
648
649 /**
650  * Play the MRL contained in the Recently played menu.
651  **/
652 void DialogsProvider::playMRL( const QString &mrl )
653 {
654     input_item_t *p_input = input_item_New( p_intf, 
655             qtu( mrl ), NULL );
656     playlist_AddInput( THEPL, p_input, PLAYLIST_GO,
657             PLAYLIST_END, true, pl_Unlocked );
658     vlc_gc_decref( p_input );
659
660     RecentsMRL::getInstance( p_intf )->addRecent( mrl );
661 }