X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=modules%2Fgui%2Fqt4%2Fcomponents%2Fplaylist%2Fplaylist.cpp;h=382a7efc7d15d50d399ed0c86aaa0984198c644f;hb=639eca0ef43b79aba3deeaf2c414c3556c33c7aa;hp=8f078449c94bb01a178308702d879d0292b1a3a2;hpb=fcde47951ad8224954a73d423d59da046d69497e;p=vlc diff --git a/modules/gui/qt4/components/playlist/playlist.cpp b/modules/gui/qt4/components/playlist/playlist.cpp index 8f078449c9..382a7efc7d 100644 --- a/modules/gui/qt4/components/playlist/playlist.cpp +++ b/modules/gui/qt4/components/playlist/playlist.cpp @@ -1,12 +1,11 @@ /***************************************************************************** - * interface_widgets.cpp : Custom widgets for the main interface + * playlist.cpp : Custom widgets for the playlist **************************************************************************** - * Copyright ( C ) 2006 the VideoLAN team + * Copyright © 2007-2010 the VideoLAN team * $Id$ * * Authors: Clément Stenac * Jean-Baptiste Kempf - * Rafaël Carré * * 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 @@ -23,112 +22,446 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. *****************************************************************************/ -#include "components/playlist/panels.hpp" -#include "components/playlist/selector.hpp" +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + #include "components/playlist/playlist.hpp" +#include "components/playlist/standardpanel.hpp" /* MainView */ +#include "components/playlist/selector.hpp" /* PLSelector */ +#include "components/playlist/playlist_model.hpp" /* PLModel */ +#include "components/playlist/ml_model.hpp" /* MLModel */ +#include "components/interface_widgets.hpp" /* CoverArtLabel */ + +#include "util/searchlineedit.hpp" -#include -#include -#include -#include -#include -#include +#include "input_manager.hpp" /* art signal */ +#include "main_interface.hpp" /* DropEvent TODO remove this*/ + +#include +#include +#include +#include /********************************************************************** * Playlist Widget. The embedded playlist **********************************************************************/ -PlaylistWidget::PlaylistWidget( intf_thread_t *_p_i, QSettings *settings ) : - p_intf ( _p_i ) +PlaylistWidget::PlaylistWidget( intf_thread_t *_p_i, QWidget *_par ) + : QWidget( _par ), p_intf ( _p_i ) { - /* Left Part and design */ - QWidget *leftW = new QWidget( this ); - QVBoxLayout *left = new QVBoxLayout( leftW ); + + setContentsMargins( 0, 3, 0, 3 ); + + QGridLayout *layout = new QGridLayout( this ); + layout->setMargin( 0 ); layout->setSpacing( 0 ); + + /******************* + * Left * + *******************/ + /* We use a QSplitter for the left part */ + leftSplitter = new QSplitter( Qt::Vertical, this ); /* Source Selector */ - selector = new PLSelector( this, p_intf, THEPL ); - left->addWidget( selector ); + PLSelector *selector = new PLSelector( this, p_intf ); + leftSplitter->addWidget( selector); + + /* Create a Container for the Art Label + in order to have a beautiful resizing for the selector above it */ + artContainer = new QStackedWidget; + artContainer->setMaximumHeight( 128 ); /* Art label */ - art = new QLabel( "" ); - art->setMinimumHeight( 128 ); - art->setMinimumWidth( 128 ); - art->setMaximumHeight( 128 ); - art->setMaximumWidth( 128 ); - art->setScaledContents( true ); - art->setPixmap( QPixmap( ":/noart.png" ) ); - left->addWidget( art ); + CoverArtLabel *art = new CoverArtLabel( artContainer, p_intf ); + art->setToolTip( qtr( "Double click to get media information" ) ); + artContainer->addWidget( art ); + CONNECT( THEMIM->getIM(), artChanged( QString ), + art, showArtUpdate( const QString& ) ); + + leftSplitter->addWidget( artContainer ); + + /******************* + * Right * + *******************/ /* Initialisation of the playlist */ - playlist_item_t *p_root = playlist_GetPreferredNode( THEPL, - THEPL->p_local_category ); + playlist_t * p_playlist = THEPL; + PL_LOCK; + playlist_item_t *p_root = p_playlist->p_playing; + PL_UNLOCK; - rightPanel = qobject_cast( new StandardPLPanel( this, - p_intf, THEPL, p_root ) ); + setMinimumWidth( 400 ); - /* Connects */ - CONNECT( selector, activated( int ), rightPanel, setRoot( int ) ); + PLModel *model = PLModel::getPLModel( p_intf ); +#ifdef MEDIA_LIBRARY + MLModel *mlmodel = new MLModel( p_intf, this ); + mainView = new StandardPLPanel( this, p_intf, p_root, selector, model, mlmodel ); +#else + mainView = new StandardPLPanel( this, p_intf, p_root, selector, model, NULL ); +#endif - CONNECT( qobject_cast( rightPanel )->model, - artSet( QString ) , this, setArt( QString ) ); - /* Forward removal requests from the selector to the main panel */ - CONNECT( qobject_cast( selector )->model, - shouldRemove( int ), - qobject_cast( rightPanel ), removeItem( int ) ); + /* Location Bar */ + locationBar = new LocationBar( model ); + locationBar->setSizePolicy( QSizePolicy::Ignored, QSizePolicy::Preferred ); + layout->addWidget( locationBar, 0, 0, 1, 2 ); + layout->setColumnStretch( 0, 5 ); + CONNECT( locationBar, invoked( const QModelIndex & ), + mainView, browseInto( const QModelIndex & ) ); - connect( selector, SIGNAL( activated( int ) ), - this, SIGNAL( rootChanged( int ) ) ); - emit rootChanged( p_root->i_id ); + QHBoxLayout *topbarLayout = new QHBoxLayout(); + layout->addLayout( topbarLayout, 0, 1 ); + topbarLayout->setSpacing( 10 ); - /* Add the two sides of the QSplitter */ - addWidget( leftW ); - addWidget( rightPanel ); + /* Button to switch views */ + QToolButton *viewButton = new QToolButton( this ); + viewButton->setIcon( style()->standardIcon( QStyle::SP_FileDialogDetailedView ) ); + viewButton->setToolTip( qtr("Change playlistview") ); + topbarLayout->addWidget( viewButton ); + + /* View selection menu */ + QSignalMapper *viewSelectionMapper = new QSignalMapper( this ); + CONNECT( viewSelectionMapper, mapped( int ), mainView, showView( int ) ); + + QActionGroup *actionGroup = new QActionGroup( this ); + +#ifndef NDEBUG +# define MAX_VIEW StandardPLPanel::VIEW_COUNT +#else +# define MAX_VIEW StandardPLPanel::VIEW_COUNT - 1 +#endif + for( int i = 0; i < MAX_VIEW; i++ ) + { + viewActions[i] = actionGroup->addAction( viewNames[i] ); + viewActions[i]->setCheckable( true ); + viewSelectionMapper->setMapping( viewActions[i], i ); + CONNECT( viewActions[i], triggered(), viewSelectionMapper, map() ); + } + viewActions[0]->setChecked( true ); + + QMenu *viewMenu = new QMenu( viewButton ); + viewMenu->addActions( actionGroup->actions() ); + viewButton->setMenu( viewMenu ); + CONNECT( viewButton, clicked(), mainView, cycleViews() ); + + /* Search */ + searchEdit = new SearchLineEdit( this ); + searchEdit->setMaximumWidth( 250 ); + searchEdit->setMinimumWidth( 80 ); + searchEdit->setToolTip( qtr("Search the playlist") ); + topbarLayout->addWidget( searchEdit ); + CONNECT( searchEdit, textChanged( const QString& ), + mainView, search( const QString& ) ); + CONNECT( searchEdit, searchDelayedChanged( const QString& ), + mainView, searchDelayed( const QString & ) ); + + CONNECT( mainView, viewChanged( const QModelIndex& ), + this, changeView( const QModelIndex &) ); - leftW->setMaximumWidth( 250 ); - setCollapsible( 1, false ); + /* Connect the activation of the selector to a redefining of the PL */ + DCONNECT( selector, categoryActivated( playlist_item_t *, bool ), + mainView, setRootItem( playlist_item_t *, bool ) ); + mainView->setRootItem( p_root, false ); + + /* */ + split = new PlaylistSplitter( this ); + + /* Add the two sides of the QSplitter */ + split->addWidget( leftSplitter ); + split->addWidget( mainView ); QList sizeList; sizeList << 180 << 420 ; - setSizes( sizeList ); - setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Expanding ); + split->setSizes( sizeList ); + split->setStretchFactor( 0, 0 ); + split->setStretchFactor( 1, 3 ); + split->setCollapsible( 1, false ); + leftSplitter->setMaximumWidth( 250 ); + + /* In case we want to keep the splitter information */ + // components shall never write there setting to a fixed location, may infer + // with other uses of the same component... + getSettings()->beginGroup("Playlist"); + split->restoreState( getSettings()->value("splitterSizes").toByteArray()); + leftSplitter->restoreState( getSettings()->value("leftSplitterGeometry").toByteArray() ); + getSettings()->endGroup(); + + layout->addWidget( split, 1, 0, 1, -1 ); + + setAcceptDrops( true ); + setWindowTitle( qtr( "Playlist" ) ); + setWindowRole( "vlc-playlist" ); + setWindowIcon( QApplication::windowIcon() ); +} + +PlaylistWidget::~PlaylistWidget() +{ + getSettings()->beginGroup("Playlist"); + getSettings()->setValue( "splitterSizes", split->saveState() ); + getSettings()->setValue( "leftSplitterGeometry", leftSplitter->saveState() ); + getSettings()->endGroup(); + msg_Dbg( p_intf, "Playlist Destroyed" ); +} - /* In case we want to keep the splitter informations */ - settings->beginGroup( "playlist" ); - restoreState( settings->value("splitterSizes").toByteArray()); - resize( settings->value("size", QSize(600, 300)).toSize()); - move( settings->value("pos", QPoint( 0, 400)).toPoint()); - settings->endGroup(); +void PlaylistWidget::dropEvent( QDropEvent *event ) +{ + if( p_intf->p_sys->p_mi ) + p_intf->p_sys->p_mi->dropEventPlay( event, false ); +} +void PlaylistWidget::dragEnterEvent( QDragEnterEvent *event ) +{ + event->acceptProposedAction(); } -void PlaylistWidget::setArt( QString url ) +void PlaylistWidget::closeEvent( QCloseEvent *event ) { - if( url.isNull() ) + if( THEDP->isDying() ) { - art->setPixmap( QPixmap( ":/noart.png" ) ); - emit artSet( url ); + p_intf->p_sys->p_mi->playlistVisible = true; + event->accept(); } - else if( prevArt != url ) + else { - art->setPixmap( QPixmap( url ) ); - prevArt = url; - emit artSet( url ); + p_intf->p_sys->p_mi->playlistVisible = false; + hide(); + event->ignore(); } } -QSize PlaylistWidget::sizeHint() const +void PlaylistWidget::forceHide() { - return QSize( 600 , 300 ); + leftSplitter->hide(); + mainView->hide(); + updateGeometry(); } -PlaylistWidget::~PlaylistWidget() -{} +void PlaylistWidget::forceShow() +{ + leftSplitter->show(); + mainView->show(); + updateGeometry(); +} + +void PlaylistWidget::changeView( const QModelIndex& index ) +{ + searchEdit->clear(); + locationBar->setIndex( index ); + int i = mainView->currentViewIndex(); + viewActions[i]->setChecked(true); +} + +#include +#include +#include +LocationBar::LocationBar( PLModel *m ) +{ + model = m; + mapper = new QSignalMapper( this ); + CONNECT( mapper, mapped( int ), this, invoke( int ) ); + + btnMore = new LocationButton( "...", false, true, this ); + menuMore = new QMenu( this ); + btnMore->setMenu( menuMore ); +} -void PlaylistWidget::savingSettings( QSettings *settings ) +void LocationBar::setIndex( const QModelIndex &index ) { - settings->beginGroup( "playlist" ); - settings->setValue( "pos", pos() ); - settings->setValue( "size", size() ); - settings->setValue("splitterSizes", saveState() ); - settings->endGroup(); + qDeleteAll( buttons ); + buttons.clear(); + qDeleteAll( actions ); + actions.clear(); + + QModelIndex i = index; + bool first = true; + + while( true ) + { + PLItem *item = model->getItem( i ); + QString text; + + char *fb_name = input_item_GetTitle( item->inputItem() ); + if( EMPTY_STR( fb_name ) ) + { + free( fb_name ); + fb_name = input_item_GetName( item->inputItem() ); + } + text = qfu(fb_name); + free(fb_name); + + QAbstractButton *btn = new LocationButton( text, first, !first, this ); + btn->setSizePolicy( QSizePolicy::Maximum, QSizePolicy::Fixed ); + buttons.append( btn ); + + QAction *action = new QAction( text, this ); + actions.append( action ); + CONNECT( btn, clicked(), action, trigger() ); + + mapper->setMapping( action, item->id() ); + CONNECT( action, triggered(), mapper, map() ); + + first = false; + + if( i.isValid() ) i = i.parent(); + else break; + } + + QString prefix; + for( int a = actions.count() - 1; a >= 0 ; a-- ) + { + actions[a]->setText( prefix + actions[a]->text() ); + prefix += QString(" "); + } + + if( isVisible() ) layOut( size() ); } +void LocationBar::setRootIndex() +{ + setIndex( QModelIndex() ); +} + +void LocationBar::invoke( int i_id ) +{ + QModelIndex index = model->index( i_id, 0 ); + emit invoked ( index ); +} + +void LocationBar::layOut( const QSize& size ) +{ + menuMore->clear(); + widths.clear(); + + int count = buttons.count(); + int totalWidth = 0; + for( int i = 0; i < count; i++ ) + { + int w = buttons[i]->sizeHint().width(); + widths.append( w ); + totalWidth += w; + if( totalWidth > size.width() ) break; + } + + int x = 0; + int shown = widths.count(); + + if( totalWidth > size.width() && count > 1 ) + { + QSize sz = btnMore->sizeHint(); + btnMore->setGeometry( 0, 0, sz.width(), size.height() ); + btnMore->show(); + x = sz.width(); + totalWidth += x; + } + else + { + btnMore->hide(); + } + for( int i = count - 1; i >= 0; i-- ) + { + if( totalWidth <= size.width() || i == 0) + { + buttons[i]->setGeometry( x, 0, qMin( size.width() - x, widths[i] ), size.height() ); + buttons[i]->show(); + x += widths[i]; + totalWidth -= widths[i]; + } + else + { + menuMore->addAction( actions[i] ); + buttons[i]->hide(); + if( i < shown ) totalWidth -= widths[i]; + } + } +} + +void LocationBar::resizeEvent ( QResizeEvent * event ) +{ + layOut( event->size() ); +} + +QSize LocationBar::sizeHint() const +{ + return btnMore->sizeHint(); +} + +LocationButton::LocationButton( const QString &text, bool bold, + bool arrow, QWidget * parent ) + : QPushButton( parent ), b_arrow( arrow ) +{ + QFont font; + font.setBold( bold ); + setFont( font ); + setText( text ); +} + +#define PADDING 4 + +void LocationButton::paintEvent ( QPaintEvent * ) +{ + QStyleOptionButton option; + option.initFrom( this ); + option.state |= QStyle::State_Enabled; + QPainter p( this ); + + if( underMouse() ) + { + p.save(); + p.setRenderHint( QPainter::Antialiasing, true ); + QColor c = palette().color( QPalette::Highlight ); + p.setPen( c ); + p.setBrush( c.lighter( 150 ) ); + p.setOpacity( 0.2 ); + p.drawRoundedRect( option.rect.adjusted( 0, 2, 0, -2 ), 5, 5 ); + p.restore(); + } + + QRect r = option.rect.adjusted( PADDING, 0, -PADDING - (b_arrow ? 10 : 0), 0 ); + + QString str( text() ); + /* This check is absurd, but either it is not done properly inside elidedText(), + or boundingRect() is wrong */ + if( r.width() < fontMetrics().boundingRect( text() ).width() ) + str = fontMetrics().elidedText( text(), Qt::ElideRight, r.width() ); + p.drawText( r, Qt::AlignVCenter | Qt::AlignLeft, str ); + + if( b_arrow ) + { + option.rect.setWidth( 10 ); + option.rect.moveRight( rect().right() ); + style()->drawPrimitive( QStyle::PE_IndicatorArrowRight, &option, &p ); + } +} + +QSize LocationButton::sizeHint() const +{ + QSize s( fontMetrics().boundingRect( text() ).size() ); + /* Add two pixels to width: font metrics are buggy, if you pass text through elidation + with exactly the width of its bounding rect, sometimes it still elides */ + s.setWidth( s.width() + ( 2 * PADDING ) + ( b_arrow ? 10 : 0 ) + 2 ); + s.setHeight( s.height() + 2 * PADDING ); + return s; +} + +#undef PADDING + +#ifdef Q_WS_MAC +QSplitterHandle *PlaylistSplitter::createHandle() +{ + return new SplitterHandle( orientation(), this ); +} + +SplitterHandle::SplitterHandle( Qt::Orientation orientation, QSplitter * parent ) + : QSplitterHandle( orientation, parent) +{ +}; + +QSize SplitterHandle::sizeHint() const +{ + return (orientation() == Qt::Horizontal) ? QSize( 1, height() ) : QSize( width(), 1 ); +} + +void SplitterHandle::paintEvent(QPaintEvent *event) +{ + QPainter painter( this ); + painter.fillRect( event->rect(), QColor(81, 81, 81) ); +} +#endif /* __APPLE__ */