interpreter.cpp 18.4 KB
Newer Older
1
2
3
/*****************************************************************************
 * interpreter.cpp
 *****************************************************************************
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
11
12
13
14
15
16
17
18
19
20
21
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
dionoea's avatar
dionoea committed
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
32
#include "../commands/cmd_dialogs.hpp"
#include "../commands/cmd_dummy.hpp"
33
#include "../commands/cmd_dvd.hpp"
34
35
#include "../commands/cmd_layout.hpp"
#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"
Cyril Deguet's avatar
Cyril Deguet committed
43
#include "../src/var_manager.hpp"
44
45
46
47
48
#include "../src/vlcproc.hpp"


Interpreter::Interpreter( intf_thread_t *pIntf ): SkinObject( pIntf )
{
Cyril Deguet's avatar
Cyril Deguet committed
49
50
51
52
53
54
55
56
    /// Create the generic commands
#define REGISTER_CMD( name, cmd ) \
    m_commandMap[name] = CmdGenericPtr( new cmd( getIntf() ) );

    REGISTER_CMD( "none", CmdDummy )
    REGISTER_CMD( "dialogs.changeSkin()", CmdDlgChangeSkin )
    REGISTER_CMD( "dialogs.fileSimple()", CmdDlgFileSimple )
    REGISTER_CMD( "dialogs.file()", CmdDlgFile )
57
    REGISTER_CMD( "dialogs.directory()", CmdDlgDirectory )
Cyril Deguet's avatar
Cyril Deguet committed
58
59
    REGISTER_CMD( "dialogs.disc()", CmdDlgDisc )
    REGISTER_CMD( "dialogs.net()", CmdDlgNet )
zorglub's avatar
zorglub committed
60
    REGISTER_CMD( "dialogs.playlist()", CmdDlgPlaylist )
Cyril Deguet's avatar
Cyril Deguet committed
61
62
63
    REGISTER_CMD( "dialogs.messages()", CmdDlgMessages )
    REGISTER_CMD( "dialogs.prefs()", CmdDlgPrefs )
    REGISTER_CMD( "dialogs.fileInfo()", CmdDlgFileInfo )
64
    REGISTER_CMD( "dialogs.streamingWizard()", CmdDlgStreamingWizard )
65

66
    REGISTER_CMD( "dialogs.popup()", CmdDlgShowPopupMenu )
67
68
69
70
    REGISTER_CMD( "dialogs.audioPopup()", CmdDlgShowAudioPopupMenu )
    REGISTER_CMD( "dialogs.videoPopup()", CmdDlgShowVideoPopupMenu )
    REGISTER_CMD( "dialogs.miscPopup()", CmdDlgShowMiscPopupMenu )

71
72
73
74
75
    REGISTER_CMD( "dvd.nextTitle()", CmdDvdNextTitle )
    REGISTER_CMD( "dvd.previousTitle()", CmdDvdPreviousTitle )
    REGISTER_CMD( "dvd.nextChapter()", CmdDvdNextChapter )
    REGISTER_CMD( "dvd.previousChapter()", CmdDvdPreviousChapter )
    REGISTER_CMD( "dvd.rootMenu()", CmdDvdRootMenu )
76
77
    REGISTER_CMD( "playlist.load()", CmdDlgPlaylistLoad )
    REGISTER_CMD( "playlist.save()", CmdDlgPlaylistSave )
Cyril Deguet's avatar
Cyril Deguet committed
78
79
80
    REGISTER_CMD( "playlist.add()", CmdDlgAdd )
    REGISTER_CMD( "playlist.next()", CmdPlaylistNext )
    REGISTER_CMD( "playlist.previous()", CmdPlaylistPrevious )
81
82
83
84
    m_commandMap["playlist.setRandom(true)"] =
        CmdGenericPtr( new CmdPlaylistRandom( getIntf(), true ) );
    m_commandMap["playlist.setRandom(false)"] =
        CmdGenericPtr( new CmdPlaylistRandom( getIntf(), false ) );
85
86
87
88
    m_commandMap["playlist.setLoop(true)"] =
        CmdGenericPtr( new CmdPlaylistLoop( getIntf(), true ) );
    m_commandMap["playlist.setLoop(false)"] =
        CmdGenericPtr( new CmdPlaylistLoop( getIntf(), false ) );
89
90
91
92
    m_commandMap["playlist.setRepeat(true)"] =
        CmdGenericPtr( new CmdPlaylistRepeat( getIntf(), true ) );
    m_commandMap["playlist.setRepeat(false)"] =
        CmdGenericPtr( new CmdPlaylistRepeat( getIntf(), false ) );
93
    VarTree &rVarTree = VlcProc::instance( getIntf() )->getPlaytreeVar();
94
    m_commandMap["playlist.del()"] =
95
        CmdGenericPtr( new CmdPlaytreeDel( getIntf(), rVarTree ) );
96
97
    m_commandMap["playtree.del()"] =
        CmdGenericPtr( new CmdPlaytreeDel( getIntf(), rVarTree ) );
98
    REGISTER_CMD( "playlist.sort()", CmdPlaytreeSort )
99
    REGISTER_CMD( "playtree.sort()", CmdPlaytreeSort )
Cyril Deguet's avatar
Cyril Deguet committed
100
    REGISTER_CMD( "vlc.fullscreen()", CmdFullscreen )
101
102
    REGISTER_CMD( "vlc.play()", CmdPlay )
    REGISTER_CMD( "vlc.pause()", CmdPause )
ipkiss's avatar
ipkiss committed
103
    REGISTER_CMD( "vlc.stop()", CmdStop )
Cyril Deguet's avatar
Cyril Deguet committed
104
105
    REGISTER_CMD( "vlc.faster()", CmdFaster )
    REGISTER_CMD( "vlc.slower()", CmdSlower )
ipkiss's avatar
ipkiss committed
106
    REGISTER_CMD( "vlc.mute()", CmdMute )
107
108
    REGISTER_CMD( "vlc.volumeUp()", CmdVolumeUp )
    REGISTER_CMD( "vlc.volumeDown()", CmdVolumeDown )
109
    REGISTER_CMD( "vlc.minimize()", CmdMinimize )
110
    REGISTER_CMD( "vlc.onTop()", CmdOnTop )
111
    REGISTER_CMD( "vlc.snapshot()", CmdSnapshot )
ipkiss's avatar
ipkiss committed
112
    REGISTER_CMD( "vlc.quit()", CmdQuit )
113
114
115
116
    m_commandMap["equalizer.enable()"] =
        CmdGenericPtr( new CmdSetEqualizer( getIntf(), true ) );
    m_commandMap["equalizer.disable()"] =
        CmdGenericPtr( new CmdSetEqualizer( getIntf(), false ) );
117
118
119
120
121
122
123

    // Register the constant bool variables in the var manager
    VarManager *pVarManager = VarManager::instance( getIntf() );
    VarBool *pVarTrue = new VarBoolTrue( getIntf() );
    pVarManager->registerVar( VariablePtr( pVarTrue ), "true" );
    VarBool *pVarFalse = new VarBoolFalse( getIntf() );
    pVarManager->registerVar( VariablePtr( pVarFalse ), "false" );
124
125
126
}


Cyril Deguet's avatar
Cyril Deguet committed
127
Interpreter *Interpreter::instance( intf_thread_t *pIntf )
128
{
Cyril Deguet's avatar
Cyril Deguet committed
129
    if( ! pIntf->p_sys->p_interpreter )
130
    {
Cyril Deguet's avatar
Cyril Deguet committed
131
132
133
134
135
136
        Interpreter *pInterpreter;
        pInterpreter = new Interpreter( pIntf );
        if( pInterpreter )
        {
            pIntf->p_sys->p_interpreter = pInterpreter;
        }
137
    }
Cyril Deguet's avatar
Cyril Deguet committed
138
139
140
141
142
143
144
    return pIntf->p_sys->p_interpreter;
}


void Interpreter::destroy( intf_thread_t *pIntf )
{
    if( pIntf->p_sys->p_interpreter )
145
    {
Cyril Deguet's avatar
Cyril Deguet committed
146
147
        delete pIntf->p_sys->p_interpreter;
        pIntf->p_sys->p_interpreter = NULL;
148
    }
Cyril Deguet's avatar
Cyril Deguet committed
149
150
151
152
153
154
155
}


CmdGeneric *Interpreter::parseAction( const string &rAction, Theme *pTheme )
{
    // Try to find the command in the global command map
    if( m_commandMap.find( rAction ) != m_commandMap.end() )
156
    {
Cyril Deguet's avatar
Cyril Deguet committed
157
        return m_commandMap[rAction].get();
158
    }
Cyril Deguet's avatar
Cyril Deguet committed
159
160
161

    CmdGeneric *pCommand = NULL;

162
163
164
165
166
167
168
169
    if( rAction.find( ";" ) != string::npos )
    {
        // Several actions are defined...
        list<CmdGeneric *> actionList;
        string rightPart = rAction;
        string::size_type pos = rightPart.find( ";" );
        while( pos != string::npos )
        {
170
            string leftPart = rightPart.substr( 0, pos );
171
172
173
174
175
176
177
            // Remove any whitespace at the end of the left part, and parse it
            leftPart =
                leftPart.substr( 0, leftPart.find_last_not_of( " \t" ) + 1 );
            actionList.push_back( parseAction( leftPart, pTheme ) );
            // Now remove any whitespace at the beginning of the right part,
            // and go on checking for further actions in it...
            rightPart = rightPart.substr( pos, rightPart.size() );
178
179
180
181
182
183
184
185
            if ( rightPart.find_first_not_of( " \t;" ) == string::npos )
            {
                // The right part is completely buggy, it's time to leave the
                // loop...
                rightPart = "";
                break;
            }

186
187
188
189
190
191
192
193
194
195
196
197
198
            rightPart =
                rightPart.substr( rightPart.find_first_not_of( " \t;" ),
                                  rightPart.size() );
            pos = rightPart.find( ";" );
        }
        actionList.push_back( parseAction( rightPart, pTheme ) );

        // The list is filled, we remove NULL pointers from it, just in case...
        actionList.remove( NULL );

        pCommand = new CmdMuxer( getIntf(), actionList );
    }
    else if( rAction.find( ".setLayout(" ) != string::npos )
199
200
201
202
203
204
205
    {
        int leftPos = rAction.find( ".setLayout(" );
        string windowId = rAction.substr( 0, leftPos );
        // 11 is the size of ".setLayout("
        int rightPos = rAction.find( ")", windowId.size() + 11 );
        string layoutId = rAction.substr( windowId.size() + 11,
                                          rightPos - (windowId.size() + 11) );
206
207
208
209
210

        TopWindow *pWin = pTheme->getWindowById( windowId );
        GenericLayout *pLayout = pTheme->getLayoutById( layoutId );
        if( !pWin )
        {
zorglub's avatar
zorglub committed
211
            msg_Err( getIntf(), "unknown window (%s)", windowId.c_str() );
212
213
214
        }
        else if( !pLayout )
        {
zorglub's avatar
zorglub committed
215
            msg_Err( getIntf(), "unknown layout (%s)", layoutId.c_str() );
216
217
218
219
        }
        // Check that the layout doesn't correspond to another window
        else if( pWin != pLayout->getWindow() )
        {
zorglub's avatar
zorglub committed
220
            msg_Err( getIntf(), "layout %s is not associated to window %s",
221
222
223
224
225
226
                     layoutId.c_str(), windowId.c_str() );
        }
        else
        {
            pCommand = new CmdLayout( getIntf(), *pWin, *pLayout );
        }
227
    }
228
229
230
231
    else if( rAction.find( ".show()" ) != string::npos )
    {
        int leftPos = rAction.find( ".show()" );
        string windowId = rAction.substr( 0, leftPos );
232
233
234

        if( windowId == "playlist_window" &&
            !config_GetInt( getIntf(), "skinned-playlist") )
235
        {
zorglub's avatar
zorglub committed
236
237
238
239
240
241
242
243
            list<CmdGeneric *> list;
            list.push_back( new CmdDlgPlaylist( getIntf() ) );
            TopWindow *pWin = pTheme->getWindowById( windowId );
            if( pWin )
                list.push_back( new CmdHideWindow( getIntf(),
                                                   pTheme->getWindowManager(),
                                                   *pWin ) );
            pCommand = new CmdMuxer( getIntf(), list );
244
245
246
        }
        else
        {
247
248
            TopWindow *pWin = pTheme->getWindowById( windowId );
            if( pWin )
249
            {
250
251
252
                pCommand = new CmdShowWindow( getIntf(),
                                              pTheme->getWindowManager(),
                                              *pWin );
253
254
255
            }
            else
            {
256
257
258
259
260
261
262
263
264
265
266
                // It was maybe the id of a popup
                Popup *pPopup = pTheme->getPopupById( windowId );
                if( pPopup )
                {
                    pCommand = new CmdShowPopup( getIntf(), *pPopup );
                }
                else
                {
                    msg_Err( getIntf(), "unknown window or popup (%s)",
                             windowId.c_str() );
                }
267
            }
268
269
270
271
272
273
        }
    }
    else if( rAction.find( ".hide()" ) != string::npos )
    {
        int leftPos = rAction.find( ".hide()" );
        string windowId = rAction.substr( 0, leftPos );
274
275
        if( windowId == "playlist_window" &&
           ! config_GetInt( getIntf(), "skinned-playlist") )
276
        {
zorglub's avatar
zorglub committed
277
278
279
280
281
282
283
284
            list<CmdGeneric *> list;
            list.push_back( new CmdDlgPlaylist( getIntf() ) );
            TopWindow *pWin = pTheme->getWindowById( windowId );
            if( pWin )
                list.push_back( new CmdHideWindow( getIntf(),
                                                   pTheme->getWindowManager(),
                                                   *pWin ) );
            pCommand = new CmdMuxer( getIntf(), list );
285
286
287
        }
        else
        {
288
289
290
291
292
293
294
295
296
297
298
            TopWindow *pWin = pTheme->getWindowById( windowId );
            if( pWin )
            {
                pCommand = new CmdHideWindow( getIntf(),
                                              pTheme->getWindowManager(),
                                              *pWin );
            }
            else
            {
                msg_Err( getIntf(), "unknown window (%s)", windowId.c_str() );
            }
299
300
        }
    }
301
302
303
304
305
306

    if( pCommand )
    {
        // Add the command in the pool
        pTheme->m_commands.push_back( CmdGenericPtr( pCommand ) );
    }
307
308
    else
    {
zorglub's avatar
zorglub committed
309
        msg_Warn( getIntf(), "unknown action: %s", rAction.c_str() );
310
    }
311
312
313
314
315
316
317

    return pCommand;
}


VarBool *Interpreter::getVarBool( const string &rName, Theme *pTheme )
{
Cyril Deguet's avatar
Cyril Deguet committed
318
    VarManager *pVarManager = VarManager::instance( getIntf() );
319

320
321
322
    // Convert the expression into Reverse Polish Notation
    ExprEvaluator evaluator( getIntf() );
    evaluator.parse( rName );
323
324
325
326

    list<VarBool*> varStack;

    // Get the first token from the RPN stack
327
    string token = evaluator.getToken();
328
    while( !token.empty() )
329
    {
330
        if( token == "and" )
331
        {
332
333
334
            // Get the 2 last variables on the stack
            if( varStack.empty() )
            {
zorglub's avatar
zorglub committed
335
                msg_Err( getIntf(), "invalid boolean expression: %s",
336
337
338
339
340
341
342
                         rName.c_str());
                return NULL;
            }
            VarBool *pVar1 = varStack.back();
            varStack.pop_back();
            if( varStack.empty() )
            {
zorglub's avatar
zorglub committed
343
                msg_Err( getIntf(), "invalid boolean expression: %s",
344
345
346
347
348
349
350
                         rName.c_str());
                return NULL;
            }
            VarBool *pVar2 = varStack.back();
            varStack.pop_back();

            // Create a composite boolean variable
351
            VarBool *pNewVar = new VarBoolAndBool( getIntf(), *pVar1, *pVar2 );
352
            varStack.push_back( pNewVar );
353
            // Register this variable in the manager
354
            pVarManager->registerVar( VariablePtr( pNewVar ) );
355
        }
356
357
358
359
360
        else if( token == "or" )
        {
            // Get the 2 last variables on the stack
            if( varStack.empty() )
            {
zorglub's avatar
zorglub committed
361
                msg_Err( getIntf(), "invalid boolean expression: %s",
362
363
364
365
366
367
368
                         rName.c_str());
                return NULL;
            }
            VarBool *pVar1 = varStack.back();
            varStack.pop_back();
            if( varStack.empty() )
            {
zorglub's avatar
zorglub committed
369
                msg_Err( getIntf(), "invalid boolean expression: %s",
370
371
372
373
374
375
376
377
378
379
380
381
                         rName.c_str());
                return NULL;
            }
            VarBool *pVar2 = varStack.back();
            varStack.pop_back();

            // Create a composite boolean variable
            VarBool *pNewVar = new VarBoolOrBool( getIntf(), *pVar1, *pVar2 );
            varStack.push_back( pNewVar );
            // Register this variable in the manager
            pVarManager->registerVar( VariablePtr( pNewVar ) );
        }
382
        else if( token == "not" )
383
        {
384
385
386
            // Get the last variable on the stack
            if( varStack.empty() )
            {
zorglub's avatar
zorglub committed
387
                msg_Err( getIntf(), "invalid boolean expression: %s",
388
389
390
391
392
393
394
                         rName.c_str());
                return NULL;
            }
            VarBool *pVar = varStack.back();
            varStack.pop_back();

            // Create a composite boolean variable
395
            VarBool *pNewVar = new VarNotBool( getIntf(), *pVar );
396
            varStack.push_back( pNewVar );
397
            // Register this variable in the manager
398
            pVarManager->registerVar( VariablePtr( pNewVar ) );
399
        }
400
        else
401
        {
402
403
404
405
406
407
            // Try first to get the variable from the variable manager
            // Indeed, if the skin designer is stupid enough to call a layout
            // "dvd", we want "dvd.isActive" to resolve as the built-in action
            // and not as the "layoutId.isActive" one.
            VarBool *pVar = (VarBool*)pVarManager->getVar( token, "bool" );
            if( pVar )
408
            {
409
                varStack.push_back( pVar );
410
            }
411
            else if( token.find( ".isVisible" ) != string::npos )
412
            {
413
414
415
416
417
418
419
420
421
422
423
424
425
                int leftPos = token.find( ".isVisible" );
                string windowId = token.substr( 0, leftPos );
                TopWindow *pWin = pTheme->getWindowById( windowId );
                if( pWin )
                {
                    // Push the visibility variable onto the stack
                    varStack.push_back( &pWin->getVisibleVar() );
                }
                else
                {
                    msg_Err( getIntf(), "unknown window (%s)", windowId.c_str() );
                    return NULL;
                }
426
            }
427
            else if( token.find( ".isActive" ) != string::npos )
428
            {
429
430
431
432
433
434
435
436
437
438
439
440
441
                int leftPos = token.find( ".isActive" );
                string layoutId = token.substr( 0, leftPos );
                GenericLayout *pLayout = pTheme->getLayoutById( layoutId );
                if( pLayout )
                {
                    // Push the isActive variable onto the stack
                    varStack.push_back( &pLayout->getActiveVar() );
                }
                else
                {
                    msg_Err( getIntf(), "unknown layout (%s)", layoutId.c_str() );
                    return NULL;
                }
442
443
            }
            else
444
            {
zorglub's avatar
zorglub committed
445
                msg_Err( getIntf(), "cannot resolve boolean variable: %s",
446
447
448
                         token.c_str());
                return NULL;
            }
449
        }
450
        // Get the first token from the RPN stack
451
        token = evaluator.getToken();
452
    }
453
454
455

    // The stack should contain a single variable
    if( varStack.size() != 1 )
Cyril Deguet's avatar
Cyril Deguet committed
456
    {
zorglub's avatar
zorglub committed
457
        msg_Err( getIntf(), "invalid boolean expression: %s", rName.c_str() );
Cyril Deguet's avatar
Cyril Deguet committed
458
459
        return NULL;
    }
460
    return varStack.back();
461
462
463
464
465
}


VarPercent *Interpreter::getVarPercent( const string &rName, Theme *pTheme )
{
Cyril Deguet's avatar
Cyril Deguet committed
466
467
468
    // Try to get the variable from the variable manager
    VarManager *pVarManager = VarManager::instance( getIntf() );
    VarPercent *pVar = (VarPercent*)pVarManager->getVar( rName, "percent" );
469
470
471
472
473
474
    return pVar;
}


VarList *Interpreter::getVarList( const string &rName, Theme *pTheme )
{
Cyril Deguet's avatar
Cyril Deguet committed
475
476
477
    // Try to get the variable from the variable manager
    VarManager *pVarManager = VarManager::instance( getIntf() );
    VarList *pVar = (VarList*)pVarManager->getVar( rName, "list" );
478
479
480
    return pVar;
}

481

482
483
484
485
486
487
488
VarTree *Interpreter::getVarTree( const string &rName, Theme *pTheme )
{
    // Try to get the variable from the variable manager
    VarManager *pVarManager = VarManager::instance( getIntf() );
    VarTree *pVar = (VarTree*)pVarManager->getVar( rName, "tree" );
    return pVar;
}
489
490
491
492
493
494
495
496
497
498
499
500
501
502


string Interpreter::getConstant( const string &rValue )
{
    // Check if the value is a registered constant
    string val = VarManager::instance( getIntf() )->getConst( rValue );
    if( val.empty() )
    {
        // if not, keep the value as is
        val = rValue;
    }
    return val;
}