standardpanel.cpp 15.1 KB
Newer Older
1 2 3
/*****************************************************************************
 * standardpanel.cpp : The "standard" playlist panel : just a treeview
 ****************************************************************************
4
 * Copyright (C) 2000-2009 VideoLAN
5
 * $Id$
6 7
 *
 * Authors: Clément Stenac <zorglub@videolan.org>
8
 *          JB 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 "dialogs_provider.hpp"
30

31
#include "components/playlist/playlist_model.hpp"
32
#include "components/playlist/standardpanel.hpp"
33
#include "components/playlist/icon_view.hpp"
Clément Stenac's avatar
Clément Stenac committed
34 35
#include "util/customwidgets.hpp"

36 37
#include <vlc_intf_strings.h>

Clément Stenac's avatar
Clément Stenac committed
38
#include <QPushButton>
39
#include <QHeaderView>
Clément Stenac's avatar
Clément Stenac committed
40 41
#include <QKeyEvent>
#include <QModelIndexList>
Clément Stenac's avatar
Clément Stenac committed
42
#include <QLabel>
43
#include <QMenu>
44
#include <QSignalMapper>
45
#include <QWheelEvent>
46 47
#include <QToolButton>
#include <QFontMetrics>
48

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

Rafaël Carré's avatar
Rafaël Carré committed
51 52
#include "sorting.h"

53
StandardPLPanel::StandardPLPanel( PlaylistWidget *_parent,
54
                                  intf_thread_t *_p_intf,
Clément Stenac's avatar
Clément Stenac committed
55
                                  playlist_t *p_playlist,
Clément Stenac's avatar
Clément Stenac committed
56
                                  playlist_item_t *p_root ):
57
                                  QWidget( _parent ), p_intf( _p_intf )
58
{
59
    layout = new QGridLayout( this );
60
    layout->setSpacing( 0 ); layout->setMargin( 0 );
61
    setMinimumWidth( 300 );
62

63
    model = new PLModel( p_playlist, p_intf, p_root, this );
64 65
    CONNECT( model, currentChanged( const QModelIndex& ),
             this, handleExpansion( const QModelIndex& ) );
66

67
    iconView = NULL;
68
    treeView = NULL;
69

70
    currentRootId = -1;
Clément Stenac's avatar
Clément Stenac committed
71

72
    /* Title label */
73
    /*title = new QLabel;
74 75 76 77
    QFont titleFont;
    titleFont.setPointSize( titleFont.pointSize() + 6 );
    titleFont.setFamily( "Verdana" );
    title->setFont( titleFont );
78 79 80 81
    layout->addWidget( title, 0, 0 );*/

    locationBar = new LocationBar( model );
    layout->addWidget( locationBar, 0, 0 );
82 83

    /* A Spacer and the search possibilities */
84
    layout->setColumnStretch( 1, 10 );
85 86

    SearchLineEdit *search = new SearchLineEdit( this );
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
87
    search->setMaximumWidth( 300 );
88
    layout->addWidget( search, 0, 4 );
89 90
    CONNECT( search, textChanged( const QString& ),
             this, search( const QString& ) );
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
91
    layout->setColumnStretch( 4, 2 );
92

93 94
    /* Add item to the playlist button */
    addButton = new QPushButton;
95
    addButton->setIcon( QIcon( ":/buttons/playlist/playlist_add" ) );
96
    addButton->setMaximumWidth( 30 );
97
    BUTTONACT( addButton, popupAdd() );
98
    layout->addWidget( addButton, 0, 3 );
99

100
    /* Button to switch views */
101
    QToolButton *viewButton = new QToolButton( this );
102
    viewButton->setIcon( style()->standardIcon( QStyle::SP_FileDialogContentsView ) );
103
    layout->addWidget( viewButton, 0, 2 );
104 105 106 107 108 109 110

    /* View selection menu */
    viewSelectionMapper = new QSignalMapper;
    CONNECT( viewSelectionMapper, mapped( int ), this, showView( int ) );

    QActionGroup *actionGroup = new QActionGroup( this );

111 112 113 114
    treeViewAction = actionGroup->addAction( "Detailed view" );
    treeViewAction->setCheckable( true );
    viewSelectionMapper->setMapping( treeViewAction, TREE_VIEW );
    CONNECT( treeViewAction, triggered(), viewSelectionMapper, map() );
115

116 117 118 119
    iconViewAction = actionGroup->addAction( "Icon view" );
    iconViewAction->setCheckable( true );
    viewSelectionMapper->setMapping( iconViewAction, ICON_VIEW );
    CONNECT( iconViewAction, triggered(), viewSelectionMapper, map() );
120

121
    BUTTONACT( viewButton, cycleViews() );
122 123 124 125
    QMenu *viewMenu = new QMenu( this );
    viewMenu->addActions( actionGroup->actions() );

    viewButton->setMenu( viewMenu );
126 127 128 129 130

    /* Saved Settings */
    getSettings()->beginGroup("Playlist");

    int i_viewMode = getSettings()->value( "view-mode", TREE_VIEW ).toInt();
131
    showView( i_viewMode );
132 133

    getSettings()->endGroup();
134 135 136 137

    last_activated_id = -1;
    CONNECT( THEMIM, inputChanged( input_thread_t * ),
             this, handleInputChange( input_thread_t * ) );
138 139
}

140 141 142
StandardPLPanel::~StandardPLPanel()
{
    getSettings()->beginGroup("Playlist");
143 144
    if( treeView )
        getSettings()->setValue( "headerStateV2", treeView->header()->saveState() );
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
145
    getSettings()->setValue( "view-mode", ( currentView == iconView ) ? ICON_VIEW : TREE_VIEW );
146 147 148
    getSettings()->endGroup();
}

149
/* Unused anymore, but might be useful, like in right-click menu */
150 151
void StandardPLPanel::gotoPlayingItem()
{
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
152
    currentView->scrollTo( model->currentIndex() );
153 154
}

155
void StandardPLPanel::handleExpansion( const QModelIndex& index )
Clément Stenac's avatar
Qt4:  
Clément Stenac committed
156
{
Ilkka Ollakka's avatar
Ilkka Ollakka committed
157
    assert( currentView );
158
    currentView->scrollTo( index );
Clément Stenac's avatar
Qt4:  
Clément Stenac committed
159 160
}

161
/* PopupAdd Menu for the Add Menu */
162
void StandardPLPanel::popupAdd()
163
{
164
    QMenu popup;
165 166 167
    if( currentRootId == THEPL->p_local_category->i_id ||
        currentRootId == THEPL->p_local_onelevel->i_id )
    {
168
        popup.addAction( qtr(I_PL_ADDF), THEDP, SLOT( simplePLAppendDialog()) );
169
        popup.addAction( qtr(I_PL_ADDDIR), THEDP, SLOT( PLAppendDir()) );
170
        popup.addAction( qtr(I_OP_ADVOP), THEDP, SLOT( PLAppendDialog()) );
171
    }
172 173 174 175
    else if( ( THEPL->p_ml_category &&
                currentRootId == THEPL->p_ml_category->i_id ) ||
             ( THEPL->p_ml_onelevel &&
                currentRootId == THEPL->p_ml_onelevel->i_id ) )
176
    {
177
        popup.addAction( qtr(I_PL_ADDF), THEDP, SLOT( simpleMLAppendDialog()) );
178
        popup.addAction( qtr(I_PL_ADDDIR), THEDP, SLOT( MLAppendDir() ) );
179
        popup.addAction( qtr(I_OP_ADVOP), THEDP, SLOT( MLAppendDialog() ) );
180
    }
181

182 183
    popup.exec( QCursor::pos() - addButton->mapFromGlobal( QCursor::pos() )
                        + QPoint( 0, addButton->height() ) );
184 185
}

Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
186 187 188 189 190 191 192 193 194
void StandardPLPanel::popupPlView( const QPoint &point )
{
    QModelIndex index = currentView->indexAt( point );
    QPoint globalPoint = currentView->viewport()->mapToGlobal( point );
    QItemSelectionModel *selection = currentView->selectionModel();
    QModelIndexList list = selection->selectedIndexes();
    model->popup( index, globalPoint, list );
}

195
void StandardPLPanel::popupSelectColumn( QPoint pos )
196
{
197
    QMenu menu;
198
    assert( treeView );
199

200 201
    /* We do not offer the option to hide index 0 column, or
    * QTreeView will behave weird */
202
    int i, j;
203
    for( i = 1 << 1, j = 1; i < COLUMN_END; i <<= 1, j++ )
204
    {
205 206 207
        QAction* option = menu.addAction(
            qfu( psz_column_title( i ) ) );
        option->setCheckable( true );
208
        option->setChecked( !treeView->isColumnHidden( j ) );
209 210
        selectColumnsSigMapper->setMapping( option, j );
        CONNECT( option, triggered(), selectColumnsSigMapper, map() );
211
    }
212
    menu.exec( QCursor::pos() );
213 214
}

215
void StandardPLPanel::toggleColumnShown( int i )
216
{
217
    treeView->setColumnHidden( i, !treeView->isColumnHidden( i ) );
218
}
219

220
/* Search in the playlist */
221
void StandardPLPanel::search( const QString& searchText )
Clément Stenac's avatar
Clément Stenac committed
222 223 224 225
{
    model->search( searchText );
}

226 227
/* Set the root of the new Playlist */
/* This activated by the selector selection */
228 229 230
void StandardPLPanel::setRoot( playlist_item_t *p_item )
{
    QPL_LOCK;
231
    assert( p_item );
232

233 234
    playlist_item_t *p_pref_item = playlist_GetPreferredNode( THEPL, p_item );
    if( p_pref_item ) p_item = p_pref_item;
235 236 237 238 239

    /* needed for popupAdd() */
    currentRootId = p_item->i_id;

    /* cosmetics, ..still need playlist locking.. */
240
    /*char *psz_title = input_item_GetName( p_item->p_input );
241
    title->setText( qfu(psz_title) );
242
    free( psz_title );*/
243

244 245
    QPL_UNLOCK;

246
    /* do THE job */
Clément Stenac's avatar
Clément Stenac committed
247
    model->rebuild( p_item );
248

249 250
    locationBar->setIndex( QModelIndex() );

251 252 253 254 255 256 257 258 259 260 261 262 263 264 265
    /* enable/disable adding */
    if( p_item == THEPL->p_local_category ||
        p_item == THEPL->p_local_onelevel )
    {
        addButton->setEnabled( true );
        addButton->setToolTip( qtr(I_PL_ADDPL) );
    }
    else if( ( THEPL->p_ml_category && p_item == THEPL->p_ml_category) ||
              ( THEPL->p_ml_onelevel && p_item == THEPL->p_ml_onelevel ) )
    {
        addButton->setEnabled( true );
        addButton->setToolTip( qtr(I_PL_ADDML) );
    }
    else
        addButton->setEnabled( false );
266 267
}

Clément Stenac's avatar
Clément Stenac committed
268 269 270 271 272
void StandardPLPanel::removeItem( int i_id )
{
    model->removeItem( i_id );
}

273
/* Delete and Suppr key remove the selection
274
   FilterKey function and code function */
Clément Stenac's avatar
Clément Stenac committed
275 276 277 278 279 280 281 282 283 284 285 286 287
void StandardPLPanel::keyPressEvent( QKeyEvent *e )
{
    switch( e->key() )
    {
    case Qt::Key_Back:
    case Qt::Key_Delete:
        deleteSelection();
        break;
    }
}

void StandardPLPanel::deleteSelection()
{
288
    QItemSelectionModel *selection = currentView->selectionModel();
Clément Stenac's avatar
Clément Stenac committed
289 290 291 292
    QModelIndexList list = selection->selectedIndexes();
    model->doDelete( list );
}

293 294 295 296 297 298
void StandardPLPanel::createIconView()
{
    iconView = new PlIconView( model, this );
    iconView->setContextMenuPolicy( Qt::CustomContextMenu );
    CONNECT( iconView, customContextMenuRequested( const QPoint & ),
             this, popupPlView( const QPoint & ) );
299 300
    CONNECT( iconView, activated( const QModelIndex & ),
             this, activate( const QModelIndex & ) );
301 302
    CONNECT( locationBar, invoked( const QModelIndex & ),
             iconView, setRootIndex( const QModelIndex & ) );
303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328

    layout->addWidget( iconView, 1, 0, 1, -1 );
}

void StandardPLPanel::createTreeView()
{
    /* Create and configure the QTreeView */
    treeView = new QTreeView;

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

329 330 331
    /* setModel after setSortingEnabled(true), or the model will sort immediately! */
    treeView->setModel( model );

332 333 334 335 336 337 338 339 340
    if( getSettings()->contains( "headerStateV2" ) )
    {
        treeView->header()->restoreState(
                getSettings()->value( "headerStateV2" ).toByteArray() );
    }
    else
    {
        for( int m = 1, c = 0; m != COLUMN_END; m <<= 1, c++ )
        {
341
            treeView->setColumnHidden( c, !( m & COLUMN_DEFAULT ) );
342 343 344 345 346 347 348
            if( m == COLUMN_TITLE ) treeView->header()->resizeSection( c, 200 );
            else if( m == COLUMN_DURATION ) treeView->header()->resizeSection( c, 80 );
        }
    }

    /* Connections for the TreeView */
    CONNECT( treeView, activated( const QModelIndex& ),
349
             this, activate( const QModelIndex& ) );
350 351 352 353 354 355 356 357 358 359 360 361 362 363
    CONNECT( treeView->header(), customContextMenuRequested( const QPoint & ),
             this, popupSelectColumn( QPoint ) );
    CONNECT( treeView, customContextMenuRequested( const QPoint & ),
             this, popupPlView( const QPoint & ) );

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

    /* Finish the layout */
    layout->addWidget( treeView, 1, 0, 1, -1 );
}

364
void StandardPLPanel::showView( int i_view )
365
{
366 367 368 369 370 371 372 373 374 375
    switch( i_view )
    {
    case TREE_VIEW:
    {
        if( treeView == NULL )
            createTreeView();
        locationBar->setIndex( treeView->rootIndex() );
        if( iconView ) iconView->hide();
        treeView->show();
        currentView = treeView;
376
        treeViewAction->setChecked( true );
377 378 379
        break;
    }
    case ICON_VIEW:
380
    {
381
        if( iconView == NULL )
382 383
            createIconView();

Jakob Leben's avatar
Jakob Leben committed
384
        locationBar->setIndex( iconView->rootIndex() );
385
        if( treeView ) treeView->hide();
386
        iconView->show();
387
        currentView = iconView;
388
        iconViewAction->setChecked( true );
389
        break;
390
    }
391
    default:;
392
    }
393
}
394

395 396 397 398 399 400 401 402 403 404
void StandardPLPanel::cycleViews()
{
    if( currentView == iconView )
        showView( TREE_VIEW );
    else if( currentView == treeView )
        showView( ICON_VIEW );
    else
        assert( 0 );
}

405 406 407 408 409
void StandardPLPanel::wheelEvent( QWheelEvent *e )
{
    // Accept this event in order to prevent unwanted volume up/down changes
    e->accept();
}
410 411 412

void StandardPLPanel::activate( const QModelIndex &index )
{
413 414
    last_activated_id = model->itemId( index );
    if( model->hasChildren( index ) )
415
    {
416 417
        if( currentView == iconView ) {
            iconView->setRootIndex( index );
418 419
            //title->setText( index.data().toString() );
            locationBar->setIndex( index );
420
        }
421 422 423 424 425 426
    }
    else
    {
        model->activateItem( index );
    }
}
427 428 429

void StandardPLPanel::handleInputChange( input_thread_t *p_input_thread )
{
Jakob Leben's avatar
Jakob Leben committed
430 431
    if( currentView != iconView ) return;

432 433 434 435 436 437 438 439 440 441 442 443
    input_item_t *p_input_item = input_GetItem( p_input_thread );
    if( !p_input_item ) return;

    playlist_Lock( THEPL );

    playlist_item_t *p_item = playlist_ItemGetByInput( THEPL, p_input_item );

    if( p_item  && p_item->p_parent &&
        p_item->p_parent->i_id == last_activated_id )
    {
        QModelIndex index = model->index( p_item->p_parent->i_id, 0 );
        iconView->setRootIndex( index );
444 445
        //title->setText( index.data().toString() );
        locationBar->setIndex( index );
446 447 448 449 450
        last_activated_id = p_item->i_id;
    }

    playlist_Unlock( THEPL );
}
451 452 453 454 455 456 457 458 459 460 461 462 463 464 465

LocationBar::LocationBar( PLModel *m )
{
  model = m;
  mapper = new QSignalMapper;
  CONNECT( mapper, mapped( int ), this, invoke( int ) );
}

void LocationBar::setIndex( const QModelIndex &index )
{
  clear();
  QAction *prev = NULL;
  QModelIndex i = index;
  QFont font;
  QFontMetrics metrics( font );
466
  font.setBold( true );
467 468 469
  while( true )
  {
      PLItem *item = model->getItem( i );
470 471

      QToolButton *btn = new QToolButton;
Rafaël Carré's avatar
Rafaël Carré committed
472 473 474
      char *fb_name = input_item_GetTitleFbName( item->inputItem() );
      QString text = qfu(fb_name);
      free(fb_name);
475 476
      text = QString("/ ") + metrics.elidedText( text, Qt::ElideRight, 150 );
      btn->setText( text );
477
      btn->setFont( font );
478 479 480 481 482
      prev = insertWidget( prev, btn );

      mapper->setMapping( btn, item->id() );
      CONNECT( btn, clicked( ), mapper, map( ) );

483 484
      font = QFont();

485 486 487 488 489 490 491 492
      if( i.isValid() ) i = i.parent();
      else break;
  }
}

void LocationBar::invoke( int i_id )
{
  QModelIndex index = model->index( i_id, 0 );
493
  setIndex( index );
494 495
  emit invoked ( index );
}