standardpanel.cpp 13.7 KB
Newer Older
zorglub's avatar
zorglub committed
1
2
3
/*****************************************************************************
 * standardpanel.cpp : The "standard" playlist panel : just a treeview
 ****************************************************************************
4
 * Copyright © 2000-2010 VideoLAN
5
 * $Id$
zorglub's avatar
zorglub committed
6
7
 *
 * Authors: Clément Stenac <zorglub@videolan.org>
8
 *          Jean-Baptiste Kempf <jb@videolan.org>
zorglub's avatar
zorglub committed
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 *
 * 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
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 *****************************************************************************/
24

25
26
27
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
zorglub's avatar
zorglub committed
28

29
#include "components/playlist/standardpanel.hpp"
zorglub's avatar
zorglub committed
30

31
32
33
34
35
36
#include "components/playlist/playlist_model.hpp" /* PLModel */
#include "components/playlist/views.hpp"          /* 3 views */
#include "components/playlist/selector.hpp"       /* PLSelector */
#include "menus.hpp"                              /* Popup */
#include "input_manager.hpp"                      /* THEMIM */

37
38
39
#include "sorting.h"                              /* Columns order */

#include <vlc_services_discovery.h>               /* SD_CMD_SEARCH */
zorglub's avatar
zorglub committed
40

zorglub's avatar
zorglub committed
41
#include <QHeaderView>
zorglub's avatar
zorglub committed
42
#include <QModelIndexList>
zorglub's avatar
zorglub committed
43
#include <QMenu>
44
#include <QKeyEvent>
45
#include <QWheelEvent>
46
#include <QStackedLayout>
47
#include <QSignalMapper>
48
#include <QSettings>
49

zorglub's avatar
zorglub committed
50
#include <assert.h>
zorglub's avatar
zorglub committed
51

52
StandardPLPanel::StandardPLPanel( PlaylistWidget *_parent,
zorglub's avatar
zorglub committed
53
                                  intf_thread_t *_p_intf,
54
                                  playlist_item_t *p_root,
55
                                  PLSelector *_p_selector,
56
57
58
                                  PLModel *_p_model )
                : QWidget( _parent ), p_intf( _p_intf ),
                  p_selector( _p_selector ), model( _p_model )
zorglub's avatar
zorglub committed
59
{
60
61
    viewStack = new QStackedLayout( this );
    viewStack->setSpacing( 0 ); viewStack->setMargin( 0 );
62
    setMinimumWidth( 300 );
63

64
65
66
67
    iconView    = NULL;
    treeView    = NULL;
    listView    = NULL;
    picFlowView = NULL;
68

69
70
    currentRootIndexId  = -1;
    lastActivatedId     = -1;
zorglub's avatar
zorglub committed
71

72
73
    /* Saved Settings */
    getSettings()->beginGroup("Playlist");
74
    int i_savedViewMode = getSettings()->value( "view-mode", TREE_VIEW ).toInt();
75
    getSettings()->endGroup();
76
77
78
    /* Limit the saved value to a possible one inside [0, VIEW_COUNT[ */
    if(i_savedViewMode < 0 || i_savedViewMode >= VIEW_COUNT)
        i_savedViewMode = 0;
79

80
    showView( i_savedViewMode );
jpd's avatar
jpd committed
81

82
83
    DCONNECT( THEMIM, leafBecameParent( input_item_t *),
              this, browseInto( input_item_t * ) );
84
85
86

    CONNECT( model, currentChanged( const QModelIndex& ),
             this, handleExpansion( const QModelIndex& ) );
87
    CONNECT( model, rootChanged(), this, handleRootChange() );
88
89

    setRoot( p_root );
zorglub's avatar
zorglub committed
90
91
}

92
93
94
StandardPLPanel::~StandardPLPanel()
{
    getSettings()->beginGroup("Playlist");
95
96
    if( treeView )
        getSettings()->setValue( "headerStateV2", treeView->header()->saveState() );
jpd's avatar
jpd committed
97
98
99
100
101
102
    if( currentView == treeView )
        getSettings()->setValue( "view-mode", TREE_VIEW );
    else if( currentView == listView )
        getSettings()->setValue( "view-mode", LIST_VIEW );
    else if( currentView == iconView )
        getSettings()->setValue( "view-mode", ICON_VIEW );
103
104
    else if( currentView == picFlowView )
        getSettings()->setValue( "view-mode", PICTUREFLOW_VIEW );
105
106
107
    getSettings()->endGroup();
}

108
/* Unused anymore, but might be useful, like in right-click menu */
109
110
void StandardPLPanel::gotoPlayingItem()
{
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
111
    currentView->scrollTo( model->currentIndex() );
112
113
}

114
void StandardPLPanel::handleExpansion( const QModelIndex& index )
zorglub's avatar
Qt4:    
zorglub committed
115
{
Ilkka Ollakka's avatar
Ilkka Ollakka committed
116
    assert( currentView );
117
118
    if( currentRootIndexId != -1 && currentRootIndexId != model->itemId( index.parent() ) )
        browseInto( index.parent() );
119
    currentView->scrollTo( index );
zorglub's avatar
Qt4:    
zorglub committed
120
121
}

122
123
void StandardPLPanel::handleRootChange()
{
124
    browseInto();
zorglub's avatar
zorglub committed
125
126
}

Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
127
128
129
130
void StandardPLPanel::popupPlView( const QPoint &point )
{
    QModelIndex index = currentView->indexAt( point );
    QPoint globalPoint = currentView->viewport()->mapToGlobal( point );
131
132
133
134
    QItemSelectionModel *selection = currentView->selectionModel();
    QModelIndexList list = selection->selectedIndexes();

    if( !model->popup( index, globalPoint, list ) )
Erwan Tulou's avatar
Erwan Tulou committed
135
        QVLCMenu::PopupMenu( p_intf, true );
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
136
137
}

138
void StandardPLPanel::popupSelectColumn( QPoint pos )
139
{
140
    QMenu menu;
141
    assert( treeView );
142

143
144
    /* We do not offer the option to hide index 0 column, or
    * QTreeView will behave weird */
145
    int i, j;
146
    for( i = 1 << 1, j = 1; i < COLUMN_END; i <<= 1, j++ )
147
    {
148
        QAction* option = menu.addAction( qfu( psz_column_title( i ) ) );
149
        option->setCheckable( true );
150
        option->setChecked( !treeView->isColumnHidden( j ) );
151
152
        selectColumnsSigMapper->setMapping( option, j );
        CONNECT( option, triggered(), selectColumnsSigMapper, map() );
153
    }
154
    menu.exec( QCursor::pos() );
155
156
}

157
void StandardPLPanel::toggleColumnShown( int i )
158
{
159
    treeView->setColumnHidden( i, !treeView->isColumnHidden( i ) );
160
}
161

162
/* Search in the playlist */
ivoire's avatar
ivoire committed
163
void StandardPLPanel::search( const QString& searchText )
zorglub's avatar
zorglub committed
164
{
165
166
167
168
169
    int type;
    QString name;
    p_selector->getCurrentSelectedItem( &type, &name );
    if( type != SD_TYPE )
    {
170
        bool flat = currentView == iconView || currentView == listView || currentView == picFlowView;
171
172
173
174
        model->search( searchText,
                       flat ? currentView->rootIndex() : QModelIndex(),
                       !flat );
    }
175
176
}

177
void StandardPLPanel::searchDelayed( const QString& searchText )
178
179
180
181
182
183
{
    int type;
    QString name;
    p_selector->getCurrentSelectedItem( &type, &name );

    if( type == SD_TYPE )
184
    {
185
        if( !name.isEmpty() && !searchText.isEmpty() )
ivoire's avatar
ivoire committed
186
            playlist_ServicesDiscoveryControl( THEPL, qtu( name ), SD_CMD_SEARCH, qtu( searchText ) );
187
    }
zorglub's avatar
zorglub committed
188
189
}

190
191
/* Set the root of the new Playlist */
/* This activated by the selector selection */
192
193
void StandardPLPanel::setRoot( playlist_item_t *p_item )
{
zorglub's avatar
zorglub committed
194
    model->rebuild( p_item );
zorglub's avatar
zorglub committed
195
196
}

197
198
void StandardPLPanel::browseInto( const QModelIndex &index )
{
199
    if( currentView == iconView || currentView == listView || currentView == picFlowView )
200
    {
Ilkka Ollakka's avatar
Ilkka Ollakka committed
201
        currentRootIndexId = model->itemId( index );
202
203
204
        currentView->setRootIndex( index );
    }

205
    emit viewChanged( index );
206
207
208
209
210
211
212
213
214
}

void StandardPLPanel::browseInto( )
{
    browseInto( currentRootIndexId != -1 && currentView != treeView ?
                model->index( currentRootIndexId, 0 ) :
                QModelIndex() );
}

215
void StandardPLPanel::wheelEvent( QWheelEvent *e )
zorglub's avatar
zorglub committed
216
{
217
218
219
220
221
222
223
    // Accept this event in order to prevent unwanted volume up/down changes
    e->accept();
}

bool StandardPLPanel::eventFilter ( QObject * watched, QEvent * event )
{
    if (event->type() == QEvent::KeyPress)
zorglub's avatar
zorglub committed
224
    {
225
226
        QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
        if( keyEvent->key() == Qt::Key_Delete ||
227
            keyEvent->key() == Qt::Key_Backspace )
228
229
230
231
        {
            deleteSelection();
            return true;
        }
zorglub's avatar
zorglub committed
232
    }
233
    return false;
zorglub's avatar
zorglub committed
234
235
236
237
}

void StandardPLPanel::deleteSelection()
{
238
    QItemSelectionModel *selection = currentView->selectionModel();
zorglub's avatar
zorglub committed
239
240
241
242
    QModelIndexList list = selection->selectedIndexes();
    model->doDelete( list );
}

243
244
245
246
247
248
void StandardPLPanel::createIconView()
{
    iconView = new PlIconView( model, this );
    iconView->setContextMenuPolicy( Qt::CustomContextMenu );
    CONNECT( iconView, customContextMenuRequested( const QPoint & ),
             this, popupPlView( const QPoint & ) );
249
250
    CONNECT( iconView, activated( const QModelIndex & ),
             this, activate( const QModelIndex & ) );
251
    iconView->installEventFilter( this );
252
    viewStack->addWidget( iconView );
253
254
}

jpd's avatar
jpd committed
255
256
257
258
259
260
261
262
void StandardPLPanel::createListView()
{
    listView = new PlListView( model, this );
    listView->setContextMenuPolicy( Qt::CustomContextMenu );
    CONNECT( listView, customContextMenuRequested( const QPoint & ),
             this, popupPlView( const QPoint & ) );
    CONNECT( listView, activated( const QModelIndex & ),
             this, activate( const QModelIndex & ) );
263
    listView->installEventFilter( this );
264
    viewStack->addWidget( listView );
jpd's avatar
jpd committed
265
266
}

267
268
269
270
271
272
273
274
275
276
277
278
void StandardPLPanel::createCoverView()
{
    picFlowView = new PicFlowView( model, this );
    picFlowView->setContextMenuPolicy( Qt::CustomContextMenu );
    CONNECT( picFlowView, customContextMenuRequested( const QPoint & ),
             this, popupPlView( const QPoint & ) );
    CONNECT( picFlowView, activated( const QModelIndex & ),
             this, activate( const QModelIndex & ) );
    viewStack->addWidget( picFlowView );
    picFlowView->installEventFilter( this );
}

279
280
281
void StandardPLPanel::createTreeView()
{
    /* Create and configure the QTreeView */
282
    treeView = new PlTreeView;
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300

    treeView->setIconSize( QSize( 20, 20 ) );
    treeView->setAlternatingRowColors( true );
    treeView->setAnimated( true );
    treeView->setUniformRowHeights( true );
    treeView->setSortingEnabled( true );
    treeView->header()->setSortIndicator( -1 , Qt::AscendingOrder );
    treeView->header()->setSortIndicatorShown( true );
    treeView->header()->setClickable( true );
    treeView->header()->setContextMenuPolicy( Qt::CustomContextMenu );

    treeView->setSelectionBehavior( QAbstractItemView::SelectRows );
    treeView->setSelectionMode( QAbstractItemView::ExtendedSelection );
    treeView->setDragEnabled( true );
    treeView->setAcceptDrops( true );
    treeView->setDropIndicatorShown( true );
    treeView->setContextMenuPolicy( Qt::CustomContextMenu );

301
302
303
    /* setModel after setSortingEnabled(true), or the model will sort immediately! */
    treeView->setModel( model );

jpd's avatar
jpd committed
304
305
    getSettings()->beginGroup("Playlist");

306
307
308
309
310
311
312
313
314
    if( getSettings()->contains( "headerStateV2" ) )
    {
        treeView->header()->restoreState(
                getSettings()->value( "headerStateV2" ).toByteArray() );
    }
    else
    {
        for( int m = 1, c = 0; m != COLUMN_END; m <<= 1, c++ )
        {
315
            treeView->setColumnHidden( c, !( m & COLUMN_DEFAULT ) );
316
317
318
319
320
            if( m == COLUMN_TITLE ) treeView->header()->resizeSection( c, 200 );
            else if( m == COLUMN_DURATION ) treeView->header()->resizeSection( c, 80 );
        }
    }

jpd's avatar
jpd committed
321
322
    getSettings()->endGroup();

323
324
    /* Connections for the TreeView */
    CONNECT( treeView, activated( const QModelIndex& ),
325
             this, activate( const QModelIndex& ) );
326
327
328
329
    CONNECT( treeView->header(), customContextMenuRequested( const QPoint & ),
             this, popupSelectColumn( QPoint ) );
    CONNECT( treeView, customContextMenuRequested( const QPoint & ),
             this, popupPlView( const QPoint & ) );
330
    treeView->installEventFilter( this );
331
332
333
334
335
336

    /* SignalMapper for columns */
    selectColumnsSigMapper = new QSignalMapper( this );
    CONNECT( selectColumnsSigMapper, mapped( int ),
             this, toggleColumnShown( int ) );

337
    viewStack->addWidget( treeView );
338
339
}

340
void StandardPLPanel::showView( int i_view )
341
{
342
343
344
345
346
347
348
349
350
351
    switch( i_view )
    {
    case TREE_VIEW:
    {
        if( treeView == NULL )
            createTreeView();
        currentView = treeView;
        break;
    }
    case ICON_VIEW:
352
    {
353
        if( iconView == NULL )
354
            createIconView();
355
        currentView = iconView;
jpd's avatar
jpd committed
356
357
358
359
360
361
362
        break;
    }
    case LIST_VIEW:
    {
        if( listView == NULL )
            createListView();
        currentView = listView;
363
        break;
364
    }
365
366
367
368
369
370
371
    case PICTUREFLOW_VIEW:
    {
        if( picFlowView == NULL )
            createCoverView();
        currentView = picFlowView;
        break;
    }
372
    default: return;
373
    }
374

375
    viewStack->setCurrentWidget( currentView );
376
    browseInto();
377
    gotoPlayingItem();
378
}
379

380
381
382
383
384
385
386
387
const int StandardPLPanel::getViewNumber()
{
    if( currentView == treeView )
        return TREE_VIEW;
    else if( currentView == iconView )
        return ICON_VIEW;
    else if( currentView == listView )
        return LIST_VIEW;
388
389
    else
        return PICTUREFLOW_VIEW;
390
391
}

392
393
394
395
396
void StandardPLPanel::cycleViews()
{
    if( currentView == iconView )
        showView( TREE_VIEW );
    else if( currentView == treeView )
jpd's avatar
jpd committed
397
398
        showView( LIST_VIEW );
    else if( currentView == listView )
399
400
        showView( PICTUREFLOW_VIEW  );
    else if( currentView == picFlowView )
401
402
403
404
405
        showView( ICON_VIEW );
    else
        assert( 0 );
}

406
407
void StandardPLPanel::activate( const QModelIndex &index )
{
408
    /* If we are not a leaf node */
409
    if( !index.data( PLModel::IsLeafNodeRole ).toBool() )
410
    {
411
412
        if( currentView != treeView )
            browseInto( index );
413
414
415
    }
    else
    {
jpd's avatar
jpd committed
416
417
418
        playlist_Lock( THEPL );
        playlist_item_t *p_item = playlist_ItemGetById( THEPL, model->itemId( index ) );
        p_item->i_flags |= PLAYLIST_SUBITEM_STOP_FLAG;
419
        lastActivatedId = p_item->p_input->i_id;
jpd's avatar
jpd committed
420
        playlist_Unlock( THEPL );
421
422
423
        model->activateItem( index );
    }
}
424

425
void StandardPLPanel::browseInto( input_item_t *p_input )
426
{
427
    if( p_input->i_id != lastActivatedId ) return;
428
429
430

    playlist_Lock( THEPL );

431
    playlist_item_t *p_item = playlist_ItemGetByInput( THEPL, p_input );
432
433
434
435
436
    if( !p_item )
    {
        playlist_Unlock( THEPL );
        return;
    }
437

jpd's avatar
jpd committed
438
    QModelIndex index = model->index( p_item->i_id, 0 );
439
440
    playlist_Unlock( THEPL );

441
    if( currentView == treeView )
jpd's avatar
jpd committed
442
        treeView->setExpanded( index, true );
443
444
    else
        browseInto( index );
jpd's avatar
jpd committed
445

446
    lastActivatedId = -1;
447
}