]> git.sesse.net Git - vlc/blob - modules/gui/skins2/parser/interpreter.cpp
skins2: replace most config_Get with var_Inherit
[vlc] / modules / gui / skins2 / parser / interpreter.cpp
1 /*****************************************************************************
2  * interpreter.cpp
3  *****************************************************************************
4  * Copyright (C) 2003 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Cyril Deguet     <asmax@via.ecp.fr>
8  *          Olivier Teulière <ipkiss@via.ecp.fr>
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23  *****************************************************************************/
24
25 #include "interpreter.hpp"
26 #include "expr_evaluator.hpp"
27 #include "../commands/cmd_audio.hpp"
28 #include "../commands/cmd_muxer.hpp"
29 #include "../commands/cmd_playlist.hpp"
30 #include "../commands/cmd_playtree.hpp"
31 #include "../commands/cmd_dialogs.hpp"
32 #include "../commands/cmd_dummy.hpp"
33 #include "../commands/cmd_dvd.hpp"
34 #include "../commands/cmd_layout.hpp"
35 #include "../commands/cmd_quit.hpp"
36 #include "../commands/cmd_minimize.hpp"
37 #include "../commands/cmd_input.hpp"
38 #include "../commands/cmd_fullscreen.hpp"
39 #include "../commands/cmd_on_top.hpp"
40 #include "../commands/cmd_show_window.hpp"
41 #include "../commands/cmd_snapshot.hpp"
42 #include "../src/theme.hpp"
43 #include "../src/var_manager.hpp"
44 #include "../src/vlcproc.hpp"
45
46
47 Interpreter::Interpreter( intf_thread_t *pIntf ): SkinObject( pIntf )
48 {
49     /// Create the generic commands
50 #define REGISTER_CMD( name, cmd ) \
51     m_commandMap[name] = CmdGenericPtr( new cmd( getIntf() ) );
52 #define REGISTER_CMD1( name, cmd, a1 ) \
53     m_commandMap[name] = CmdGenericPtr( new cmd( getIntf(), a1 ) );
54
55     REGISTER_CMD( "none", CmdDummy )
56     REGISTER_CMD( "dialogs.changeSkin()", CmdDlgChangeSkin )
57     REGISTER_CMD( "dialogs.fileSimple()", CmdDlgFileSimple )
58     REGISTER_CMD( "dialogs.file()", CmdDlgFile )
59     REGISTER_CMD( "dialogs.directory()", CmdDlgDirectory )
60     REGISTER_CMD( "dialogs.disc()", CmdDlgDisc )
61     REGISTER_CMD( "dialogs.net()", CmdDlgNet )
62     REGISTER_CMD( "dialogs.playlist()", CmdDlgPlaylist )
63     REGISTER_CMD( "dialogs.messages()", CmdDlgMessages )
64     REGISTER_CMD( "dialogs.prefs()", CmdDlgPrefs )
65     REGISTER_CMD( "dialogs.fileInfo()", CmdDlgFileInfo )
66     REGISTER_CMD( "dialogs.streamingWizard()", CmdDlgStreamingWizard )
67
68     REGISTER_CMD( "dialogs.popup()", CmdDlgShowPopupMenu )
69     REGISTER_CMD( "dialogs.audioPopup()", CmdDlgShowAudioPopupMenu )
70     REGISTER_CMD( "dialogs.videoPopup()", CmdDlgShowVideoPopupMenu )
71     REGISTER_CMD( "dialogs.miscPopup()", CmdDlgShowMiscPopupMenu )
72
73     REGISTER_CMD( "dvd.nextTitle()", CmdDvdNextTitle )
74     REGISTER_CMD( "dvd.previousTitle()", CmdDvdPreviousTitle )
75     REGISTER_CMD( "dvd.nextChapter()", CmdDvdNextChapter )
76     REGISTER_CMD( "dvd.previousChapter()", CmdDvdPreviousChapter )
77     REGISTER_CMD( "dvd.rootMenu()", CmdDvdRootMenu )
78     REGISTER_CMD( "playlist.load()", CmdDlgPlaylistLoad )
79     REGISTER_CMD( "playlist.save()", CmdDlgPlaylistSave )
80     REGISTER_CMD( "playlist.add()", CmdDlgAdd )
81     REGISTER_CMD( "playlist.next()", CmdPlaylistNext )
82     REGISTER_CMD( "playlist.previous()", CmdPlaylistPrevious )
83     REGISTER_CMD1( "playlist.setRandom(true)", CmdPlaylistRandom, true )
84     REGISTER_CMD1( "playlist.setRandom(false)", CmdPlaylistRandom, false )
85     REGISTER_CMD1( "playlist.setLoop(true)", CmdPlaylistLoop, true )
86     REGISTER_CMD1( "playlist.setLoop(false)", CmdPlaylistLoop, false )
87     REGISTER_CMD1( "playlist.setRepeat(true)", CmdPlaylistRepeat, true )
88     REGISTER_CMD1( "playlist.setRepeat(false)", CmdPlaylistRepeat, false )
89     VarTree &rVarTree = VlcProc::instance( getIntf() )->getPlaytreeVar();
90     REGISTER_CMD1( "playlist.del()", CmdPlaytreeDel, rVarTree )
91     REGISTER_CMD1( "playtree.del()", CmdPlaytreeDel, rVarTree )
92     REGISTER_CMD( "playlist.sort()", CmdPlaytreeSort )
93     REGISTER_CMD( "playtree.sort()", CmdPlaytreeSort )
94     REGISTER_CMD( "vlc.fullscreen()", CmdFullscreen )
95     REGISTER_CMD( "vlc.play()", CmdPlay )
96     REGISTER_CMD( "vlc.pause()", CmdPause )
97     REGISTER_CMD( "vlc.stop()", CmdStop )
98     REGISTER_CMD( "vlc.faster()", CmdFaster )
99     REGISTER_CMD( "vlc.slower()", CmdSlower )
100     REGISTER_CMD( "vlc.mute()", CmdMute )
101     REGISTER_CMD( "vlc.volumeUp()", CmdVolumeUp )
102     REGISTER_CMD( "vlc.volumeDown()", CmdVolumeDown )
103     REGISTER_CMD( "vlc.minimize()", CmdMinimize )
104     REGISTER_CMD( "vlc.onTop()", CmdOnTop )
105     REGISTER_CMD( "vlc.snapshot()", CmdSnapshot )
106     REGISTER_CMD( "vlc.toggleRecord()", CmdToggleRecord )
107     REGISTER_CMD( "vlc.nextFrame()", CmdNextFrame )
108     REGISTER_CMD( "vlc.quit()", CmdQuit )
109     REGISTER_CMD1( "equalizer.enable()", CmdSetEqualizer, true )
110     REGISTER_CMD1( "equalizer.disable()", CmdSetEqualizer, false )
111
112     // Register the constant bool variables in the var manager
113     VarManager *pVarManager = VarManager::instance( getIntf() );
114     VarBool *pVarTrue = new VarBoolTrue( getIntf() );
115     pVarManager->registerVar( VariablePtr( pVarTrue ), "true" );
116     VarBool *pVarFalse = new VarBoolFalse( getIntf() );
117     pVarManager->registerVar( VariablePtr( pVarFalse ), "false" );
118
119 #undef  REGISTER_CMD
120 #undef  REGISTER_CMD1
121 }
122
123
124 Interpreter *Interpreter::instance( intf_thread_t *pIntf )
125 {
126     if( ! pIntf->p_sys->p_interpreter )
127     {
128         Interpreter *pInterpreter = new (std::nothrow) Interpreter( pIntf );
129         if( pInterpreter )
130             pIntf->p_sys->p_interpreter = pInterpreter;
131     }
132     return pIntf->p_sys->p_interpreter;
133 }
134
135
136 void Interpreter::destroy( intf_thread_t *pIntf )
137 {
138     delete pIntf->p_sys->p_interpreter;
139     pIntf->p_sys->p_interpreter = NULL;
140 }
141
142
143 CmdGeneric *Interpreter::parseAction( const string &rAction, Theme *pTheme )
144 {
145     // Try to find the command in the global command map
146     if( m_commandMap.find( rAction ) != m_commandMap.end() )
147     {
148         return m_commandMap[rAction].get();
149     }
150
151     CmdGeneric *pCommand = NULL;
152
153     if( rAction.find( ";" ) != string::npos )
154     {
155         // Several actions are defined...
156         list<CmdGeneric *> actionList;
157         string rightPart = rAction;
158         string::size_type pos = rightPart.find( ";" );
159         while( pos != string::npos )
160         {
161             string leftPart = rightPart.substr( 0, pos );
162             // Remove any whitespace at the end of the left part, and parse it
163             leftPart =
164                 leftPart.substr( 0, leftPart.find_last_not_of( " \t" ) + 1 );
165             actionList.push_back( parseAction( leftPart, pTheme ) );
166             // Now remove any whitespace at the beginning of the right part,
167             // and go on checking for further actions in it...
168             rightPart = rightPart.substr( pos, rightPart.size() );
169             if ( rightPart.find_first_not_of( " \t;" ) == string::npos )
170             {
171                 // The right part is completely buggy, it's time to leave the
172                 // loop...
173                 rightPart = "";
174                 break;
175             }
176
177             rightPart =
178                 rightPart.substr( rightPart.find_first_not_of( " \t;" ),
179                                   rightPart.size() );
180             pos = rightPart.find( ";" );
181         }
182         actionList.push_back( parseAction( rightPart, pTheme ) );
183
184         // The list is filled, we remove NULL pointers from it, just in case...
185         actionList.remove( NULL );
186
187         pCommand = new CmdMuxer( getIntf(), actionList );
188     }
189     else if( rAction.find( ".setLayout(" ) != string::npos )
190     {
191         int leftPos = rAction.find( ".setLayout(" );
192         string windowId = rAction.substr( 0, leftPos );
193         // 11 is the size of ".setLayout("
194         int rightPos = rAction.find( ")", windowId.size() + 11 );
195         string layoutId = rAction.substr( windowId.size() + 11,
196                                           rightPos - (windowId.size() + 11) );
197
198         TopWindow *pWin = pTheme->getWindowById( windowId );
199         GenericLayout *pLayout = pTheme->getLayoutById( layoutId );
200         if( !pWin )
201         {
202             msg_Err( getIntf(), "unknown window (%s)", windowId.c_str() );
203         }
204         else if( !pLayout )
205         {
206             msg_Err( getIntf(), "unknown layout (%s)", layoutId.c_str() );
207         }
208         // Check that the layout doesn't correspond to another window
209         else if( pWin != pLayout->getWindow() )
210         {
211             msg_Err( getIntf(), "layout %s is not associated to window %s",
212                      layoutId.c_str(), windowId.c_str() );
213         }
214         else
215         {
216             pCommand = new CmdLayout( getIntf(), *pWin, *pLayout );
217         }
218     }
219     else if( rAction.find( ".maximize()" ) != string::npos )
220     {
221         int leftPos = rAction.find( ".maximize()" );
222         string windowId = rAction.substr( 0, leftPos );
223
224         TopWindow *pWin = pTheme->getWindowById( windowId );
225         if( !pWin )
226         {
227             msg_Err( getIntf(), "unknown window (%s)", windowId.c_str() );
228         }
229         else
230         {
231             pCommand = new CmdMaximize( getIntf(),
232                                         pTheme->getWindowManager(),
233                                         *pWin );
234         }
235     }
236     else if( rAction.find( ".unmaximize()" ) != string::npos )
237     {
238         int leftPos = rAction.find( ".unmaximize()" );
239         string windowId = rAction.substr( 0, leftPos );
240
241         TopWindow *pWin = pTheme->getWindowById( windowId );
242         if( !pWin )
243         {
244             msg_Err( getIntf(), "unknown window (%s)", windowId.c_str() );
245         }
246         else
247         {
248             pCommand = new CmdUnmaximize( getIntf(),
249                                           pTheme->getWindowManager(),
250                                           *pWin );
251         }
252     }
253     else if( rAction.find( ".show()" ) != string::npos )
254     {
255         int leftPos = rAction.find( ".show()" );
256         string windowId = rAction.substr( 0, leftPos );
257
258         if( windowId == "playlist_window" &&
259             !var_InheritBool( getIntf(), "skinned-playlist") )
260         {
261             list<CmdGeneric *> list;
262             list.push_back( new CmdDlgPlaylist( getIntf() ) );
263             TopWindow *pWin = pTheme->getWindowById( windowId );
264             if( pWin )
265                 list.push_back( new CmdHideWindow( getIntf(),
266                                                    pTheme->getWindowManager(),
267                                                    *pWin ) );
268             pCommand = new CmdMuxer( getIntf(), list );
269         }
270         else
271         {
272             TopWindow *pWin = pTheme->getWindowById( windowId );
273             if( pWin )
274             {
275                 pCommand = new CmdShowWindow( getIntf(),
276                                               pTheme->getWindowManager(),
277                                               *pWin );
278             }
279             else
280             {
281                 // It was maybe the id of a popup
282                 Popup *pPopup = pTheme->getPopupById( windowId );
283                 if( pPopup )
284                 {
285                     pCommand = new CmdShowPopup( getIntf(), *pPopup );
286                 }
287                 else
288                 {
289                     msg_Err( getIntf(), "unknown window or popup (%s)",
290                              windowId.c_str() );
291                 }
292             }
293         }
294     }
295     else if( rAction.find( ".hide()" ) != string::npos )
296     {
297         int leftPos = rAction.find( ".hide()" );
298         string windowId = rAction.substr( 0, leftPos );
299         if( windowId == "playlist_window" &&
300            !var_InheritBool( getIntf(), "skinned-playlist") )
301         {
302             list<CmdGeneric *> list;
303             list.push_back( new CmdDlgPlaylist( getIntf() ) );
304             TopWindow *pWin = pTheme->getWindowById( windowId );
305             if( pWin )
306                 list.push_back( new CmdHideWindow( getIntf(),
307                                                    pTheme->getWindowManager(),
308                                                    *pWin ) );
309             pCommand = new CmdMuxer( getIntf(), list );
310         }
311         else
312         {
313             TopWindow *pWin = pTheme->getWindowById( windowId );
314             if( pWin )
315             {
316                 pCommand = new CmdHideWindow( getIntf(),
317                                               pTheme->getWindowManager(),
318                                               *pWin );
319             }
320             else
321             {
322                 msg_Err( getIntf(), "unknown window (%s)", windowId.c_str() );
323             }
324         }
325     }
326
327     if( pCommand )
328     {
329         // Add the command in the pool
330         pTheme->m_commands.push_back( CmdGenericPtr( pCommand ) );
331     }
332     else
333     {
334         msg_Warn( getIntf(), "unknown action: %s", rAction.c_str() );
335     }
336
337     return pCommand;
338 }
339
340
341 VarBool *Interpreter::getVarBool( const string &rName, Theme *pTheme )
342 {
343     VarManager *pVarManager = VarManager::instance( getIntf() );
344
345     // Convert the expression into Reverse Polish Notation
346     ExprEvaluator evaluator( getIntf() );
347     evaluator.parse( rName );
348
349     list<VarBool*> varStack;
350
351     // Get the first token from the RPN stack
352     string token = evaluator.getToken();
353     while( !token.empty() )
354     {
355         if( token == "and" )
356         {
357             // Get the 2 last variables on the stack
358             if( varStack.empty() )
359             {
360                 msg_Err( getIntf(), "invalid boolean expression: %s",
361                          rName.c_str());
362                 return NULL;
363             }
364             VarBool *pVar1 = varStack.back();
365             varStack.pop_back();
366             if( varStack.empty() )
367             {
368                 msg_Err( getIntf(), "invalid boolean expression: %s",
369                          rName.c_str());
370                 return NULL;
371             }
372             VarBool *pVar2 = varStack.back();
373             varStack.pop_back();
374
375             // Create a composite boolean variable
376             VarBool *pNewVar = new VarBoolAndBool( getIntf(), *pVar1, *pVar2 );
377             varStack.push_back( pNewVar );
378             // Register this variable in the manager
379             pVarManager->registerVar( VariablePtr( pNewVar ) );
380         }
381         else if( token == "or" )
382         {
383             // Get the 2 last variables on the stack
384             if( varStack.empty() )
385             {
386                 msg_Err( getIntf(), "invalid boolean expression: %s",
387                          rName.c_str());
388                 return NULL;
389             }
390             VarBool *pVar1 = varStack.back();
391             varStack.pop_back();
392             if( varStack.empty() )
393             {
394                 msg_Err( getIntf(), "invalid boolean expression: %s",
395                          rName.c_str());
396                 return NULL;
397             }
398             VarBool *pVar2 = varStack.back();
399             varStack.pop_back();
400
401             // Create a composite boolean variable
402             VarBool *pNewVar = new VarBoolOrBool( getIntf(), *pVar1, *pVar2 );
403             varStack.push_back( pNewVar );
404             // Register this variable in the manager
405             pVarManager->registerVar( VariablePtr( pNewVar ) );
406         }
407         else if( token == "not" )
408         {
409             // Get the last variable on the stack
410             if( varStack.empty() )
411             {
412                 msg_Err( getIntf(), "invalid boolean expression: %s",
413                          rName.c_str());
414                 return NULL;
415             }
416             VarBool *pVar = varStack.back();
417             varStack.pop_back();
418
419             // Create a composite boolean variable
420             VarBool *pNewVar = new VarNotBool( getIntf(), *pVar );
421             varStack.push_back( pNewVar );
422             // Register this variable in the manager
423             pVarManager->registerVar( VariablePtr( pNewVar ) );
424         }
425         else
426         {
427             // Try first to get the variable from the variable manager
428             // Indeed, if the skin designer is stupid enough to call a layout
429             // "dvd", we want "dvd.isActive" to resolve as the built-in action
430             // and not as the "layoutId.isActive" one.
431             VarBool *pVar = (VarBool*)pVarManager->getVar( token, "bool" );
432             if( pVar )
433             {
434                 varStack.push_back( pVar );
435             }
436             else if( token.find( ".isVisible" ) != string::npos )
437             {
438                 int leftPos = token.find( ".isVisible" );
439                 string windowId = token.substr( 0, leftPos );
440                 TopWindow *pWin = pTheme->getWindowById( windowId );
441                 if( pWin )
442                 {
443                     // Push the visibility variable onto the stack
444                     varStack.push_back( &pWin->getVisibleVar() );
445                 }
446                 else
447                 {
448                     msg_Err( getIntf(), "unknown window (%s)",
449                              windowId.c_str() );
450                     return NULL;
451                 }
452             }
453             else if( token.find( ".isMaximized" ) != string::npos )
454             {
455                 int leftPos = token.find( ".isMaximized" );
456                 string windowId = token.substr( 0, leftPos );
457                 TopWindow *pWin = pTheme->getWindowById( windowId );
458                 if( pWin )
459                 {
460                     // Push the "maximized" variable onto the stack
461                     varStack.push_back( &pWin->getMaximizedVar() );
462                 }
463                 else
464                 {
465                     msg_Err( getIntf(), "unknown window (%s)",
466                              windowId.c_str() );
467                     return NULL;
468                 }
469             }
470             else if( token.find( ".isActive" ) != string::npos )
471             {
472                 int leftPos = token.find( ".isActive" );
473                 string layoutId = token.substr( 0, leftPos );
474                 GenericLayout *pLayout = pTheme->getLayoutById( layoutId );
475                 if( pLayout )
476                 {
477                     // Push the isActive variable onto the stack
478                     varStack.push_back( &pLayout->getActiveVar() );
479                 }
480                 else
481                 {
482                     msg_Err( getIntf(), "unknown layout (%s)",
483                              layoutId.c_str() );
484                     return NULL;
485                 }
486             }
487             else
488             {
489                 msg_Err( getIntf(), "cannot resolve boolean variable: %s",
490                          token.c_str());
491                 return NULL;
492             }
493         }
494         // Get the first token from the RPN stack
495         token = evaluator.getToken();
496     }
497
498     // The stack should contain a single variable
499     if( varStack.size() != 1 )
500     {
501         msg_Err( getIntf(), "invalid boolean expression: %s", rName.c_str() );
502         return NULL;
503     }
504     return varStack.back();
505 }
506
507
508 VarPercent *Interpreter::getVarPercent( const string &rName, Theme *pTheme )
509 {
510     VarManager *pVarManager = VarManager::instance( getIntf() );
511     return static_cast<VarPercent*>(pVarManager->getVar( rName, "percent" ));
512 }
513
514
515 VarList *Interpreter::getVarList( const string &rName, Theme *pTheme )
516 {
517     VarManager *pVarManager = VarManager::instance( getIntf() );
518     return static_cast<VarList*>(pVarManager->getVar( rName, "list" ));
519 }
520
521
522 VarTree *Interpreter::getVarTree( const string &rName, Theme *pTheme )
523 {
524     VarManager *pVarManager = VarManager::instance( getIntf() );
525     return static_cast<VarTree*>(pVarManager->getVar( rName, "tree" ));
526 }
527
528
529 string Interpreter::getConstant( const string &rValue )
530 {
531     // Check if the value is a registered constant; if not, keep as is.
532     string val = VarManager::instance( getIntf() )->getConst( rValue );
533     return val.empty() ? rValue : val;
534 }
535