]> git.sesse.net Git - vlc/blob - modules/gui/qt4/components/playlist/playlist.cpp
Qt4: don't create multiple playlist models
[vlc] / modules / gui / qt4 / components / playlist / playlist.cpp
1 /*****************************************************************************
2  * playlist.cpp : Custom widgets for the playlist
3  ****************************************************************************
4  * Copyright © 2007-2010 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Clément Stenac <zorglub@videolan.org>
8  *          Jean-Baptiste Kempf <jb@videolan.org>
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * ( at your option ) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23  *****************************************************************************/
24
25 #ifdef HAVE_CONFIG_H
26 # include "config.h"
27 #endif
28
29 #include "components/playlist/standardpanel.hpp"
30 #include "components/playlist/selector.hpp"
31 #include "components/playlist/playlist.hpp"
32 #include "components/playlist/playlist_model.hpp"
33
34 #include "input_manager.hpp" /* art signal */
35 #include "main_interface.hpp" /* DropEvent TODO remove this*/
36
37 #include <QGroupBox>
38 #include <QMenu>
39
40 #include <iostream>
41 /**********************************************************************
42  * Playlist Widget. The embedded playlist
43  **********************************************************************/
44
45 PlaylistWidget::PlaylistWidget( intf_thread_t *_p_i, QWidget *_par )
46                : QSplitter( _par ), p_intf ( _p_i )
47 {
48     setContentsMargins( 3, 3, 3, 3 );
49
50     /*******************
51      * Left            *
52      *******************/
53     /* We use a QSplitter for the left part */
54     leftSplitter = new QSplitter( Qt::Vertical, this );
55
56     /* Source Selector */
57     selector = new PLSelector( this, p_intf );
58     leftSplitter->addWidget( selector);
59
60     /* Create a Container for the Art Label
61        in order to have a beautiful resizing for the selector above it */
62     QWidget *artContainer = new QWidget;
63     QHBoxLayout *artContLay = new QHBoxLayout( artContainer );
64     artContLay->setMargin( 0 );
65     artContLay->setSpacing( 0 );
66     artContainer->setMaximumHeight( 128 );
67
68     /* Art label */
69     art = new ArtLabel( artContainer, p_intf );
70     art->setToolTip( qtr( "Double click to get media information" ) );
71     artContLay->addWidget( art, 1 );
72
73     CONNECT( THEMIM->getIM(), artChanged( QString ),
74              art, showArtUpdate( const QString& ) );
75
76     leftSplitter->addWidget( artContainer );
77
78     /*******************
79      * Right           *
80      *******************/
81     /* Initialisation of the playlist */
82     playlist_t * p_playlist = THEPL;
83     PL_LOCK;
84     playlist_item_t *p_root = THEPL->p_playing;
85     PL_UNLOCK;
86
87     QWidget *rightPanel = new QWidget( this );
88     QGridLayout *layout = new QGridLayout( rightPanel );
89     layout->setSpacing( 0 ); layout->setMargin( 0 );
90     setMinimumWidth( 300 );
91
92     PLModel *model = new PLModel( p_playlist, p_intf, p_root, this );
93     mainView = new StandardPLPanel( this, p_intf, THEPL, p_root, selector, model );
94
95     /* Location Bar */
96     locationBar = new LocationBar( model );
97     locationBar->setSizePolicy( QSizePolicy::Ignored, QSizePolicy::Preferred );
98     layout->addWidget( locationBar, 0, 0 );
99     layout->setColumnStretch( 0, 5 );
100     CONNECT( locationBar, invoked( const QModelIndex & ),
101              mainView, browseInto( const QModelIndex & ) );
102
103     /* Button to switch views */
104     QToolButton *viewButton = new QToolButton( this );
105     viewButton->setIcon( style()->standardIcon( QStyle::SP_FileDialogDetailedView ) );
106     viewButton->setToolTip( qtr("Change playlistview") );
107     layout->addWidget( viewButton, 0, 1 );
108
109     /* View selection menu */
110     viewSelectionMapper = new QSignalMapper( this );
111     CONNECT( viewSelectionMapper, mapped( int ), mainView, showView( int ) );
112
113     QActionGroup *actionGroup = new QActionGroup( this );
114     for( int i = 0; i < StandardPLPanel::VIEW_COUNT; i++ )
115     {
116         viewActions[i] = actionGroup->addAction( viewNames[i] );
117         viewActions[i]->setCheckable( true );
118         viewSelectionMapper->setMapping( viewActions[i], i );
119         CONNECT( viewActions[i], triggered(), viewSelectionMapper, map() );
120     }
121
122     CONNECT( viewButton, clicked(), mainView, cycleViews() );
123     QMenu *viewMenu = new QMenu( this );
124     viewMenu->addActions( actionGroup->actions() );
125     viewButton->setMenu( viewMenu );
126
127     /* Search */
128     searchEdit = new SearchLineEdit( this );
129     searchEdit->setMaximumWidth( 250 );
130     searchEdit->setMinimumWidth( 80 );
131     layout->addWidget( searchEdit, 0, 2 );
132     CONNECT( searchEdit, textEdited( const QString& ),
133              mainView, search( const QString& ) );
134     CONNECT( searchEdit, searchDelayedChanged( const QString& ),
135              mainView, searchDelayed( const QString & ) );
136     CONNECT( mainView, viewChanged( const QModelIndex& ),
137              this, changeView( const QModelIndex &) );
138     layout->setColumnStretch( 2, 3 );
139
140     /* Connect the activation of the selector to a redefining of the PL */
141     DCONNECT( selector, activated( playlist_item_t * ),
142               mainView, setRoot( playlist_item_t * ) );
143
144     mainView->setRoot( p_root );
145     layout->addWidget( mainView, 1, 0, 1, -1 );
146
147     /* Add the two sides of the QSplitter */
148     addWidget( leftSplitter );
149     addWidget( rightPanel );
150
151     QList<int> sizeList;
152     sizeList << 180 << 420 ;
153     setSizes( sizeList );
154     //setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Expanding );
155     setStretchFactor( 0, 0 );
156     setStretchFactor( 1, 3 );
157     leftSplitter->setMaximumWidth( 250 );
158     setCollapsible( 1, false );
159
160     /* In case we want to keep the splitter information */
161     // components shall never write there setting to a fixed location, may infer
162     // with other uses of the same component...
163     getSettings()->beginGroup("Playlist");
164     restoreState( getSettings()->value("splitterSizes").toByteArray());
165     leftSplitter->restoreState( getSettings()->value("leftSplitterGeometry").toByteArray() );
166     getSettings()->endGroup();
167
168     setAcceptDrops( true );
169     setWindowTitle( qtr( "Playlist" ) );
170     setWindowRole( "vlc-playlist" );
171     setWindowIcon( QApplication::windowIcon() );
172 }
173
174 PlaylistWidget::~PlaylistWidget()
175 {
176     getSettings()->beginGroup("Playlist");
177     getSettings()->setValue( "splitterSizes", saveState() );
178     getSettings()->setValue( "leftSplitterGeometry", leftSplitter->saveState() );
179     getSettings()->endGroup();
180     msg_Dbg( p_intf, "Playlist Destroyed" );
181 }
182
183 void PlaylistWidget::dropEvent( QDropEvent *event )
184 {
185     if( p_intf->p_sys->p_mi )
186         p_intf->p_sys->p_mi->dropEventPlay( event, false );
187 }
188 void PlaylistWidget::dragEnterEvent( QDragEnterEvent *event )
189 {
190     event->acceptProposedAction();
191 }
192
193 void PlaylistWidget::closeEvent( QCloseEvent *event )
194 {
195     if( THEDP->isDying() )
196     {
197         p_intf->p_sys->p_mi->playlistVisible = true;
198         event->accept();
199     }
200     else
201     {
202         p_intf->p_sys->p_mi->playlistVisible = false;
203         hide();
204         event->ignore();
205     }
206 }
207
208 void PlaylistWidget::forceHide()
209 {
210     leftSplitter->hide();
211     mainView->hide();
212     updateGeometry();
213 }
214
215 void PlaylistWidget::forceShow()
216 {
217     leftSplitter->show();
218     mainView->show();
219     updateGeometry();
220 }
221
222 void PlaylistWidget::changeView( const QModelIndex& index )
223 {
224     searchEdit->clear();
225     locationBar->setIndex( index );
226 }
227
228
229 #include <QSignalMapper>
230 #include <QMenu>
231 #include <QPainter>
232 LocationBar::LocationBar( PLModel *m )
233 {
234     model = m;
235     mapper = new QSignalMapper( this );
236     CONNECT( mapper, mapped( int ), this, invoke( int ) );
237
238     btnMore = new LocationButton( "...", false, true, this );
239     menuMore = new QMenu( this );
240     btnMore->setMenu( menuMore );
241 }
242
243 void LocationBar::setIndex( const QModelIndex &index )
244 {
245     qDeleteAll( buttons );
246     buttons.clear();
247     qDeleteAll( actions );
248     actions.clear();
249
250     QModelIndex i = index;
251     bool first = true;
252
253     while( true )
254     {
255         PLItem *item = model->getItem( i );
256
257         char *fb_name = input_item_GetTitleFbName( item->inputItem() );
258         QString text = qfu(fb_name);
259         free(fb_name);
260
261         QAbstractButton *btn = new LocationButton( text, first, !first, this );
262         btn->setSizePolicy( QSizePolicy::Maximum, QSizePolicy::Fixed );
263         buttons.append( btn );
264
265         QAction *action = new QAction( text, this );
266         actions.append( action );
267         CONNECT( btn, clicked(), action, trigger() );
268
269         mapper->setMapping( action, item->id() );
270         CONNECT( action, triggered(), mapper, map() );
271
272         first = false;
273
274         if( i.isValid() ) i = i.parent();
275         else break;
276     }
277
278     QString prefix;
279     for( int a = actions.count() - 1; a >= 0 ; a-- )
280     {
281         actions[a]->setText( prefix + actions[a]->text() );
282         prefix += QString("  ");
283     }
284
285     if( isVisible() ) layOut( size() );
286 }
287
288 void LocationBar::setRootIndex()
289 {
290     setIndex( QModelIndex() );
291 }
292
293 void LocationBar::invoke( int i_id )
294 {
295     QModelIndex index = model->index( i_id, 0 );
296     emit invoked ( index );
297 }
298
299 void LocationBar::layOut( const QSize& size )
300 {
301     menuMore->clear();
302     widths.clear();
303
304     int count = buttons.count();
305     int totalWidth = 0;
306     for( int i = 0; i < count; i++ )
307     {
308         int w = buttons[i]->sizeHint().width();
309         widths.append( w );
310         totalWidth += w;
311         if( totalWidth > size.width() ) break;
312     }
313
314     int x = 0;
315     int shown = widths.count();
316
317     if( totalWidth > size.width() && count > 1 )
318     {
319         QSize sz = btnMore->sizeHint();
320         btnMore->setGeometry( 0, 0, sz.width(), size.height() );
321         btnMore->show();
322         x = sz.width();
323         totalWidth += x;
324     }
325     else
326     {
327         btnMore->hide();
328     }
329     for( int i = count - 1; i >= 0; i-- )
330     {
331         if( totalWidth <= size.width() || i == 0)
332         {
333             buttons[i]->setGeometry( x, 0, qMin( size.width() - x, widths[i] ), size.height() );
334             buttons[i]->show();
335             x += widths[i];
336             totalWidth -= widths[i];
337         }
338         else
339         {
340             menuMore->addAction( actions[i] );
341             buttons[i]->hide();
342             if( i < shown ) totalWidth -= widths[i];
343         }
344     }
345 }
346
347 void LocationBar::resizeEvent ( QResizeEvent * event )
348 {
349     layOut( event->size() );
350 }
351
352 QSize LocationBar::sizeHint() const
353 {
354     return btnMore->sizeHint();
355 }
356
357 LocationButton::LocationButton( const QString &text, bool bold,
358                                 bool arrow, QWidget * parent )
359   : b_arrow( arrow ), QPushButton( parent )
360 {
361     QFont font;
362     font.setBold( bold );
363     setFont( font );
364     setText( text );
365 }
366
367 #define PADDING 4
368
369 void LocationButton::paintEvent ( QPaintEvent * event )
370 {
371     QStyleOptionButton option;
372     option.initFrom( this );
373     option.state |= QStyle::State_Enabled;
374     QPainter p( this );
375
376     if( underMouse() )
377     {
378         p.save();
379         p.setRenderHint( QPainter::Antialiasing, true );
380         QColor c = palette().color( QPalette::Highlight );
381         p.setPen( c );
382         p.setBrush( c.lighter( 150 ) );
383         p.setOpacity( 0.2 );
384         p.drawRoundedRect( option.rect.adjusted( 0, 2, 0, -2 ), 5, 5 );
385         p.restore();
386     }
387
388     QRect r = option.rect.adjusted( PADDING, 0, -PADDING - (b_arrow ? 10 : 0), 0 );
389
390     QString str( text() );
391     /* This check is absurd, but either it is not done properly inside elidedText(),
392        or boundingRect() is wrong */
393     if( r.width() < fontMetrics().boundingRect( text() ).width() )
394         str = fontMetrics().elidedText( text(), Qt::ElideRight, r.width() );
395     p.drawText( r, Qt::AlignVCenter | Qt::AlignLeft, str );
396
397     if( b_arrow )
398     {
399         option.rect.setWidth( 10 );
400         option.rect.moveRight( rect().right() );
401         style()->drawPrimitive( QStyle::PE_IndicatorArrowRight, &option, &p );
402     }
403 }
404
405 QSize LocationButton::sizeHint() const
406 {
407     QSize s( fontMetrics().boundingRect( text() ).size() );
408     /* Add two pixels to width: font metrics are buggy, if you pass text through elidation
409        with exactly the width of its bounding rect, sometimes it still elides */
410     s.setWidth( s.width() + ( 2 * PADDING ) + ( b_arrow ? 10 : 0 ) + 2 );
411     s.setHeight( s.height() + 2 * PADDING );
412     return s;
413 }
414
415 #undef PADDING