standardpanel.cpp 12.4 KB
Newer Older
1 2 3
/*****************************************************************************
 * standardpanel.cpp : The "standard" playlist panel : just a treeview
 ****************************************************************************
4
 * Copyright © 2000-2010 VideoLAN
5
 * $Id$
6 7
 *
 * Authors: Clément Stenac <zorglub@videolan.org>
8
 *          Jean-Baptiste Kempf <jb@videolan.org>
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
28

29
#include "components/playlist/standardpanel.hpp"
Clément Stenac's avatar
Clément Stenac 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 */
40

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

Clément Stenac's avatar
Clément Stenac committed
50
#include <assert.h>
Clément Stenac's avatar
Clément Stenac committed
51

52
StandardPLPanel::StandardPLPanel( PlaylistWidget *_parent,
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 )
59
{
60 61
    viewStack = new QStackedLayout( this );
    viewStack->setSpacing( 0 ); viewStack->setMargin( 0 );
62
    setMinimumWidth( 300 );
63

64
    iconView = NULL;
65
    treeView = NULL;
Jakob Leben's avatar
Jakob Leben committed
66
    listView = NULL;
67

68 69
    currentRootIndexId  = -1;
    lastActivatedId     = -1;
Clément Stenac's avatar
Clément Stenac committed
70

71 72 73 74
    /* Saved Settings */
    getSettings()->beginGroup("Playlist");
    int i_viewMode = getSettings()->value( "view-mode", TREE_VIEW ).toInt();
    getSettings()->endGroup();
75

Jakob Leben's avatar
Jakob Leben committed
76 77
    showView( i_viewMode );

78 79
    DCONNECT( THEMIM, leafBecameParent( input_item_t *),
              this, browseInto( input_item_t * ) );
80 81 82

    CONNECT( model, currentChanged( const QModelIndex& ),
             this, handleExpansion( const QModelIndex& ) );
83
    CONNECT( model, rootChanged(), this, handleRootChange() );
84 85

    setRoot( p_root );
86 87
}

88 89 90
StandardPLPanel::~StandardPLPanel()
{
    getSettings()->beginGroup("Playlist");
91 92
    if( treeView )
        getSettings()->setValue( "headerStateV2", treeView->header()->saveState() );
Jakob Leben's avatar
Jakob Leben committed
93 94 95 96 97 98
    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 );
99 100 101
    getSettings()->endGroup();
}

102
/* Unused anymore, but might be useful, like in right-click menu */
103 104
void StandardPLPanel::gotoPlayingItem()
{
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
105
    currentView->scrollTo( model->currentIndex() );
106 107
}

108
void StandardPLPanel::handleExpansion( const QModelIndex& index )
Clément Stenac's avatar
Qt4:  
Clément Stenac committed
109
{
Ilkka Ollakka's avatar
Ilkka Ollakka committed
110
    assert( currentView );
111 112
    if( currentRootIndexId != -1 && currentRootIndexId != model->itemId( index.parent() ) )
        browseInto( index.parent() );
113
    currentView->scrollTo( index );
Clément Stenac's avatar
Qt4:  
Clément Stenac committed
114 115
}

116 117
void StandardPLPanel::handleRootChange()
{
118
    browseInto();
119 120
}

Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
121 122 123 124
void StandardPLPanel::popupPlView( const QPoint &point )
{
    QModelIndex index = currentView->indexAt( point );
    QPoint globalPoint = currentView->viewport()->mapToGlobal( point );
125 126 127 128
    QItemSelectionModel *selection = currentView->selectionModel();
    QModelIndexList list = selection->selectedIndexes();

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

132
void StandardPLPanel::popupSelectColumn( QPoint pos )
133
{
134
    QMenu menu;
135
    assert( treeView );
136

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

151
void StandardPLPanel::toggleColumnShown( int i )
152
{
153
    treeView->setColumnHidden( i, !treeView->isColumnHidden( i ) );
154
}
155

156
/* Search in the playlist */
157
void StandardPLPanel::search( const QString& searchText )
Clément Stenac's avatar
Clément Stenac committed
158
{
159 160 161 162 163 164 165 166 167 168
    int type;
    QString name;
    p_selector->getCurrentSelectedItem( &type, &name );
    if( type != SD_TYPE )
    {
        bool flat = currentView == iconView || currentView == listView;
        model->search( searchText,
                       flat ? currentView->rootIndex() : QModelIndex(),
                       !flat );
    }
169 170
}

171
void StandardPLPanel::searchDelayed( const QString& searchText )
172 173 174 175 176 177
{
    int type;
    QString name;
    p_selector->getCurrentSelectedItem( &type, &name );

    if( type == SD_TYPE )
178
    {
179
        if( !name.isEmpty() && !searchText.isEmpty() )
Rémi Duraffort's avatar
Rémi Duraffort committed
180
            playlist_ServicesDiscoveryControl( THEPL, qtu( name ), SD_CMD_SEARCH, qtu( searchText ) );
181
    }
Clément Stenac's avatar
Clément Stenac committed
182 183
}

184 185
/* Set the root of the new Playlist */
/* This activated by the selector selection */
186 187
void StandardPLPanel::setRoot( playlist_item_t *p_item )
{
Clément Stenac's avatar
Clément Stenac committed
188
    model->rebuild( p_item );
189 190
}

191 192 193 194
void StandardPLPanel::browseInto( const QModelIndex &index )
{
    if( currentView == iconView || currentView == listView )
    {
Ilkka Ollakka's avatar
Ilkka Ollakka committed
195
        currentRootIndexId = model->itemId( index );
196 197 198
        currentView->setRootIndex( index );
    }

199
    emit viewChanged( index );
200 201 202 203 204 205 206 207 208
}

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

209
void StandardPLPanel::wheelEvent( QWheelEvent *e )
Clément Stenac's avatar
Clément Stenac committed
210
{
211 212 213 214 215 216 217
    // 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)
Clément Stenac's avatar
Clément Stenac committed
218
    {
219 220
        QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
        if( keyEvent->key() == Qt::Key_Delete ||
221
            keyEvent->key() == Qt::Key_Backspace )
222 223 224 225
        {
            deleteSelection();
            return true;
        }
Clément Stenac's avatar
Clément Stenac committed
226
    }
227
    return false;
Clément Stenac's avatar
Clément Stenac committed
228 229 230 231
}

void StandardPLPanel::deleteSelection()
{
232
    QItemSelectionModel *selection = currentView->selectionModel();
Clément Stenac's avatar
Clément Stenac committed
233 234 235 236
    QModelIndexList list = selection->selectedIndexes();
    model->doDelete( list );
}

237 238 239 240 241 242
void StandardPLPanel::createIconView()
{
    iconView = new PlIconView( model, this );
    iconView->setContextMenuPolicy( Qt::CustomContextMenu );
    CONNECT( iconView, customContextMenuRequested( const QPoint & ),
             this, popupPlView( const QPoint & ) );
243 244
    CONNECT( iconView, activated( const QModelIndex & ),
             this, activate( const QModelIndex & ) );
245
    iconView->installEventFilter( this );
246
    viewStack->addWidget( iconView );
247 248
}

Jakob Leben's avatar
Jakob Leben committed
249 250 251 252 253 254 255 256
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 & ) );
257
    listView->installEventFilter( this );
258
    viewStack->addWidget( listView );
Jakob Leben's avatar
Jakob Leben committed
259 260
}

261 262 263
void StandardPLPanel::createTreeView()
{
    /* Create and configure the QTreeView */
264
    treeView = new PlTreeView;
265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282

    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 );

283 284 285
    /* setModel after setSortingEnabled(true), or the model will sort immediately! */
    treeView->setModel( model );

Jakob Leben's avatar
Jakob Leben committed
286 287
    getSettings()->beginGroup("Playlist");

288 289 290 291 292 293 294 295 296
    if( getSettings()->contains( "headerStateV2" ) )
    {
        treeView->header()->restoreState(
                getSettings()->value( "headerStateV2" ).toByteArray() );
    }
    else
    {
        for( int m = 1, c = 0; m != COLUMN_END; m <<= 1, c++ )
        {
297
            treeView->setColumnHidden( c, !( m & COLUMN_DEFAULT ) );
298 299 300 301 302
            if( m == COLUMN_TITLE ) treeView->header()->resizeSection( c, 200 );
            else if( m == COLUMN_DURATION ) treeView->header()->resizeSection( c, 80 );
        }
    }

Jakob Leben's avatar
Jakob Leben committed
303 304
    getSettings()->endGroup();

305 306
    /* Connections for the TreeView */
    CONNECT( treeView, activated( const QModelIndex& ),
307
             this, activate( const QModelIndex& ) );
308 309 310 311
    CONNECT( treeView->header(), customContextMenuRequested( const QPoint & ),
             this, popupSelectColumn( QPoint ) );
    CONNECT( treeView, customContextMenuRequested( const QPoint & ),
             this, popupPlView( const QPoint & ) );
312
    treeView->installEventFilter( this );
313 314 315 316 317 318

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

319
    viewStack->addWidget( treeView );
320 321
}

322
void StandardPLPanel::showView( int i_view )
323
{
324 325 326 327 328 329 330 331 332 333
    switch( i_view )
    {
    case TREE_VIEW:
    {
        if( treeView == NULL )
            createTreeView();
        currentView = treeView;
        break;
    }
    case ICON_VIEW:
334
    {
335
        if( iconView == NULL )
336
            createIconView();
337
        currentView = iconView;
Jakob Leben's avatar
Jakob Leben committed
338 339 340 341 342 343 344
        break;
    }
    case LIST_VIEW:
    {
        if( listView == NULL )
            createListView();
        currentView = listView;
345
        break;
346
    }
347
    default: return;
348
    }
349

350
    viewStack->setCurrentWidget( currentView );
351
    //viewActions[i_view]->setChecked( true );
352
    browseInto();
353
    gotoPlayingItem();
354
}
355

356 357 358 359 360
void StandardPLPanel::cycleViews()
{
    if( currentView == iconView )
        showView( TREE_VIEW );
    else if( currentView == treeView )
Jakob Leben's avatar
Jakob Leben committed
361 362
        showView( LIST_VIEW );
    else if( currentView == listView )
363 364 365 366 367
        showView( ICON_VIEW );
    else
        assert( 0 );
}

368 369
void StandardPLPanel::activate( const QModelIndex &index )
{
370
    if( !index.data( PLModel::IsLeafNodeRole ).toBool() )
371
    {
372 373
        if( currentView != treeView )
            browseInto( index );
374 375 376
    }
    else
    {
377 378 379
        playlist_Lock( THEPL );
        playlist_item_t *p_item = playlist_ItemGetById( THEPL, model->itemId( index ) );
        p_item->i_flags |= PLAYLIST_SUBITEM_STOP_FLAG;
380
        lastActivatedId = p_item->p_input->i_id;
381
        playlist_Unlock( THEPL );
382 383 384
        model->activateItem( index );
    }
}
385

386
void StandardPLPanel::browseInto( input_item_t *p_input )
387
{
388
    if( p_input->i_id != lastActivatedId ) return;
389 390 391

    playlist_Lock( THEPL );

392
    playlist_item_t *p_item = playlist_ItemGetByInput( THEPL, p_input );
393 394 395 396 397
    if( !p_item )
    {
        playlist_Unlock( THEPL );
        return;
    }
398

399
    QModelIndex index = model->index( p_item->i_id, 0 );
400 401 402

    playlist_Unlock( THEPL );

403
    if( currentView == treeView )
404
        treeView->setExpanded( index, true );
405 406
    else
        browseInto( index );
407

408
    lastActivatedId = -1;
409
}