]> git.sesse.net Git - vlc/blob - modules/gui/qt4/components/interface_widgets.cpp
0136648110cbd44648bb641cceb2b9ed3c34ab96
[vlc] / modules / gui / qt4 / components / interface_widgets.cpp
1 /*****************************************************************************
2  * interface_widgets.cpp : Custom widgets for the main interface
3  ****************************************************************************
4  * Copyright ( C ) 2006 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Clément Stenac <zorglub@videolan.org>
8  *          Jean-Baptiste Kempf <jb@videolan.org>
9  *          Rafaël Carré <funman@videolanorg>
10  *          Ilkka Ollakka <ileoo@videolan.org>
11  *
12  * This program is free software; you can redistribute it and/or modify
13  * it under the terms of the GNU General Public License as published by
14  * the Free Software Foundation; either version 2 of the License, or
15  * ( at your option ) any later version.
16  *
17  * This program is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  * GNU General Public License for more details.
21  *
22  * You should have received a copy of the GNU General Public License
23  * along with this program; if not, write to the Free Software
24  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
25  *****************************************************************************/
26
27 #ifdef HAVE_CONFIG_H
28 # include "config.h"
29 #endif
30
31 #include <vlc_vout.h>
32
33 #include "dialogs_provider.hpp"
34 #include "components/interface_widgets.hpp"
35 #include "main_interface.hpp"
36 #include "input_manager.hpp"
37 #include "menus.hpp"
38 #include "util/input_slider.hpp"
39 #include "util/customwidgets.hpp"
40
41 #include <QLabel>
42 #include <QSpacerItem>
43 #include <QCursor>
44 #include <QPushButton>
45 #include <QToolButton>
46 #include <QHBoxLayout>
47 #include <QMenu>
48 #include <QPalette>
49 #include <QResizeEvent>
50 #include <QDate>
51
52 #ifdef Q_WS_X11
53 # include <X11/Xlib.h>
54 # include <qx11info_x11.h>
55 #endif
56
57 #include <math.h>
58
59 #define I_PLAY_TOOLTIP N_("Play\nIf the playlist is empty, open a media")
60
61 /**********************************************************************
62  * Video Widget. A simple frame on which video is drawn
63  * This class handles resize issues
64  **********************************************************************/
65
66 VideoWidget::VideoWidget( intf_thread_t *_p_i ) : QFrame( NULL ), p_intf( _p_i )
67 {
68     /* Init */
69     p_vout = NULL;
70     videoSize.rwidth() = -1;
71     videoSize.rheight() = -1;
72
73     hide();
74
75     /* Set the policy to expand in both directions */
76 //    setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding );
77
78     /* Black background is more coherent for a Video Widget */
79     QPalette plt =  palette();
80     plt.setColor( QPalette::Window, Qt::black );
81     setPalette( plt );
82     setAutoFillBackground(true);
83
84     /* Indicates that the widget wants to draw directly onto the screen.
85        Widgets with this attribute set do not participate in composition
86        management */
87     setAttribute( Qt::WA_PaintOnScreen, true );
88
89     /* The core can ask through a callback to show the video. */
90 #if HAS_QT43
91     connect( this, SIGNAL(askVideoWidgetToShow( unsigned int, unsigned int)),
92              this, SLOT(SetSizing(unsigned int, unsigned int )),
93              Qt::BlockingQueuedConnection );
94 #else
95 #warning This is broken. Fix it with a QEventLoop with a processEvents ()
96     connect( this, SIGNAL(askVideoWidgetToShow( unsigned int, unsigned int)),
97              this, SLOT(SetSizing(unsigned int, unsigned int )) );
98 #endif
99 }
100
101 void VideoWidget::paintEvent(QPaintEvent *ev)
102 {
103     QFrame::paintEvent(ev);
104 #ifdef Q_WS_X11
105     XFlush( QX11Info::display() );
106 #endif
107 }
108
109 /* Kill the vout at Destruction */
110 VideoWidget::~VideoWidget()
111 {
112     if( !p_vout )
113         return;
114     if( vout_Control( p_vout, VOUT_CLOSE ) != VLC_SUCCESS )
115         vout_Control( p_vout, VOUT_REPARENT );
116 }
117
118 /**
119  * Request the video to avoid the conflicts
120  **/
121 void *VideoWidget::request( vout_thread_t *p_nvout, int *pi_x, int *pi_y,
122                             unsigned int *pi_width, unsigned int *pi_height )
123 {
124     msg_Dbg( p_intf, "Video was requested %i, %i", *pi_x, *pi_y );
125     emit askVideoWidgetToShow( *pi_width, *pi_height );
126     if( p_vout )
127     {
128         msg_Dbg( p_intf, "embedded video already in use" );
129         return NULL;
130     }
131     p_vout = p_nvout;
132 #ifndef NDEBUG
133     msg_Dbg( p_intf, "embedded video ready (handle %p)", winId() );
134 #endif
135     return ( void* )winId();
136 }
137
138 /* Set the Widget to the correct Size */
139 /* Function has to be called by the parent
140    Parent has to care about resizing himself*/
141 void VideoWidget::SetSizing( unsigned int w, unsigned int h )
142 {
143     msg_Dbg( p_intf, "Video is resizing to: %i %i", w, h );
144     videoSize.rwidth() = w;
145     videoSize.rheight() = h;
146     if( isHidden() ) show();
147     updateGeometry(); // Needed for deinterlace
148 }
149
150 void VideoWidget::release( void *p_win )
151 {
152     msg_Dbg( p_intf, "Video is not needed anymore" );
153     p_vout = NULL;
154     videoSize.rwidth() = 0;
155     videoSize.rheight() = 0;
156     updateGeometry();
157     hide();
158 }
159
160 QSize VideoWidget::sizeHint() const
161 {
162     return videoSize;
163 }
164
165 /**********************************************************************
166  * Background Widget. Show a simple image background. Currently,
167  * it's album art if present or cone.
168  **********************************************************************/
169 #define ICON_SIZE 128
170 #define MAX_BG_SIZE 400
171 #define MIN_BG_SIZE 128
172
173 BackgroundWidget::BackgroundWidget( intf_thread_t *_p_i )
174                  :QWidget( NULL ), p_intf( _p_i )
175 {
176     /* We should use that one to take the more size it can */
177     setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding);
178
179     /* A dark background */
180     setAutoFillBackground( true );
181     plt = palette();
182     plt.setColor( QPalette::Active, QPalette::Window , Qt::black );
183     plt.setColor( QPalette::Inactive, QPalette::Window , Qt::black );
184     setPalette( plt );
185
186     /* A cone in the middle */
187     label = new QLabel;
188     label->setMargin( 5 );
189     label->setMaximumHeight( MAX_BG_SIZE );
190     label->setMaximumWidth( MAX_BG_SIZE );
191     label->setMinimumHeight( MIN_BG_SIZE );
192     label->setMinimumWidth( MIN_BG_SIZE );
193     if( QDate::currentDate().dayOfYear() >= 354 )
194         label->setPixmap( QPixmap( ":/vlc128-christmas.png" ) );
195     else
196         label->setPixmap( QPixmap( ":/vlc128.png" ) );
197
198     QGridLayout *backgroundLayout = new QGridLayout( this );
199     backgroundLayout->addWidget( label, 0, 1 );
200     backgroundLayout->setColumnStretch( 0, 1 );
201     backgroundLayout->setColumnStretch( 2, 1 );
202
203     CONNECT( THEMIM->getIM(), artChanged( input_item_t* ),
204              this, updateArt( input_item_t* ) );
205 }
206
207 BackgroundWidget::~BackgroundWidget()
208 {}
209
210 void BackgroundWidget::resizeEvent( QResizeEvent * event )
211 {
212     if( event->size().height() <= MIN_BG_SIZE )
213         label->hide();
214     else
215         label->show();
216 }
217
218 void BackgroundWidget::updateArt( input_item_t *p_item )
219 {
220     QString url;
221     if( p_item )
222     {
223         char *psz_art = input_item_GetArtURL( p_item );
224         url = psz_art;
225         free( psz_art );
226     }
227
228     if( url.isEmpty() )
229     {
230         if( QDate::currentDate().dayOfYear() >= 354 )
231             label->setPixmap( QPixmap( ":/vlc128-christmas.png" ) );
232         else
233             label->setPixmap( QPixmap( ":/vlc128.png" ) );
234     }
235     else
236     {
237         url = url.replace( "file://", QString("" ) );
238         /* Taglib seems to define a attachment://, It won't work yet */
239         url = url.replace( "attachment://", QString("" ) );
240         label->setPixmap( QPixmap( url ) );
241     }
242 }
243
244 void BackgroundWidget::contextMenuEvent( QContextMenuEvent *event )
245 {
246     QVLCMenu::PopupMenu( p_intf, true );
247 }
248
249 #if 0
250 /**********************************************************************
251  * Visualization selector panel
252  **********************************************************************/
253 VisualSelector::VisualSelector( intf_thread_t *_p_i ) :
254                                 QFrame( NULL ), p_intf( _p_i )
255 {
256     QHBoxLayout *layout = new QHBoxLayout( this );
257     layout->setMargin( 0 );
258     QPushButton *prevButton = new QPushButton( "Prev" );
259     QPushButton *nextButton = new QPushButton( "Next" );
260     layout->addWidget( prevButton );
261     layout->addWidget( nextButton );
262
263     layout->addStretch( 10 );
264     layout->addWidget( new QLabel( qtr( "Current visualization" ) ) );
265
266     current = new QLabel( qtr( "None" ) );
267     layout->addWidget( current );
268
269     BUTTONACT( prevButton, prev() );
270     BUTTONACT( nextButton, next() );
271
272     setLayout( layout );
273     setMaximumHeight( 35 );
274 }
275
276 VisualSelector::~VisualSelector()
277 {}
278
279 void VisualSelector::prev()
280 {
281     char *psz_new = aout_VisualPrev( p_intf );
282     if( psz_new )
283     {
284         current->setText( qfu( psz_new ) );
285         free( psz_new );
286     }
287 }
288
289 void VisualSelector::next()
290 {
291     char *psz_new = aout_VisualNext( p_intf );
292     if( psz_new )
293     {
294         current->setText( qfu( psz_new ) );
295         free( psz_new );
296     }
297 }
298 #endif
299
300 /**********************************************************************
301  * Speed control widget
302  **********************************************************************/
303 SpeedControlWidget::SpeedControlWidget( intf_thread_t *_p_i ) :
304                              QFrame( NULL ), p_intf( _p_i )
305 {
306     QSizePolicy sizePolicy( QSizePolicy::Maximum, QSizePolicy::Fixed );
307     sizePolicy.setHorizontalStretch( 0 );
308     sizePolicy.setVerticalStretch( 0 );
309
310     speedSlider = new QSlider;
311     speedSlider->setSizePolicy( sizePolicy );
312     speedSlider->setMaximumSize( QSize( 80, 200 ) );
313     speedSlider->setOrientation( Qt::Vertical );
314     speedSlider->setTickPosition( QSlider::TicksRight );
315
316     speedSlider->setRange( -24, 24 );
317     speedSlider->setSingleStep( 1 );
318     speedSlider->setPageStep( 1 );
319     speedSlider->setTickInterval( 12 );
320
321     CONNECT( speedSlider, valueChanged( int ), this, updateRate( int ) );
322
323     QToolButton *normalSpeedButton = new QToolButton( this );
324     normalSpeedButton->setMaximumSize( QSize( 26, 20 ) );
325     normalSpeedButton->setAutoRaise( true );
326     normalSpeedButton->setText( "1x" );
327     normalSpeedButton->setToolTip( qtr( "Revert to normal play speed" ) );
328
329     CONNECT( normalSpeedButton, clicked(), this, resetRate() );
330
331     QVBoxLayout *speedControlLayout = new QVBoxLayout;
332     speedControlLayout->setLayoutMargins( 4, 4, 4, 4, 4 );
333     speedControlLayout->setSpacing( 4 );
334     speedControlLayout->addWidget( speedSlider );
335     speedControlLayout->addWidget( normalSpeedButton );
336     setLayout( speedControlLayout );
337 }
338
339 SpeedControlWidget::~SpeedControlWidget()
340 {}
341
342 void SpeedControlWidget::setEnable( bool b_enable )
343 {
344     speedSlider->setEnabled( b_enable );
345 }
346
347 void SpeedControlWidget::updateControls( int rate )
348 {
349     if( speedSlider->isSliderDown() )
350     {
351         //We don't want to change anything if the user is using the slider
352         return;
353     }
354
355     double value = 12 * log( (double)INPUT_RATE_DEFAULT / rate ) / log( 2 );
356     int sliderValue = (int) ( ( value > 0 ) ? value + .5 : value - .5 );
357
358     if( sliderValue < speedSlider->minimum() )
359     {
360         sliderValue = speedSlider->minimum();
361     }
362     else if( sliderValue > speedSlider->maximum() )
363     {
364         sliderValue = speedSlider->maximum();
365     }
366
367     //Block signals to avoid feedback loop
368     speedSlider->blockSignals( true );
369     speedSlider->setValue( sliderValue );
370     speedSlider->blockSignals( false );
371 }
372
373 void SpeedControlWidget::updateRate( int sliderValue )
374 {
375     double speed = pow( 2, (double)sliderValue / 12 );
376     int rate = INPUT_RATE_DEFAULT / speed;
377
378     THEMIM->getIM()->setRate(rate);
379 }
380
381 void SpeedControlWidget::resetRate()
382 {
383     THEMIM->getIM()->setRate(INPUT_RATE_DEFAULT);
384 }
385
386
387
388 static int downloadCoverCallback( vlc_object_t *p_this,
389                                   char const *psz_var,
390                                   vlc_value_t oldvar, vlc_value_t newvar,
391                                   void *data )
392 {
393     if( !strcmp( psz_var, "item-change" ) )
394     {
395         CoverArtLabel *art = static_cast< CoverArtLabel* >( data );
396         if( art )
397             art->requestUpdate();
398     }
399     return VLC_SUCCESS;
400 }
401
402 CoverArtLabel::CoverArtLabel( QWidget *parent,
403                               vlc_object_t *_p_this,
404                               input_item_t *_p_input )
405         : QLabel( parent ), p_this( _p_this), p_input( _p_input ), prevArt()
406 {
407     setContextMenuPolicy( Qt::ActionsContextMenu );
408     CONNECT( this, updateRequested(), this, doUpdate() );
409
410     playlist_t *p_playlist = pl_Hold( p_this );
411     var_AddCallback( p_playlist, "item-change",
412                      downloadCoverCallback, this );
413     pl_Release( p_this );
414
415     setMinimumHeight( 128 );
416     setMinimumWidth( 128 );
417     setMaximumHeight( 128 );
418     setMaximumWidth( 128 );
419     setScaledContents( true );
420
421     doUpdate();
422 }
423
424 void CoverArtLabel::downloadCover()
425 {
426     if( p_input )
427     {
428         playlist_t *p_playlist = pl_Hold( p_this );
429         playlist_AskForArtEnqueue( p_playlist, p_input );
430         pl_Release( p_this );
431     }
432 }
433
434 void CoverArtLabel::doUpdate()
435 {
436     if( !p_input )
437     {
438         setPixmap( QPixmap( ":/noart.png" ) );
439         QList< QAction* > artActions = actions();
440         if( !artActions.isEmpty() )
441             foreach( QAction *act, artActions )
442                 removeAction( act );
443         prevArt = "";
444     }
445     else
446     {
447         char *psz_meta = input_item_GetArtURL( p_input );
448         if( psz_meta && !strncmp( psz_meta, "file://", 7 ) )
449         {
450             QString artUrl = qfu( psz_meta ).replace( "file://", "" );
451             if( artUrl != prevArt )
452             {
453                 QPixmap pix;
454                 if( pix.load( artUrl ) )
455                     setPixmap( pix );
456                 else
457                 {
458                     msg_Dbg( p_this, "Qt could not load image '%s'",
459                              qtu( artUrl ) );
460                     setPixmap( QPixmap( ":/noart.png" ) );
461                 }
462             }
463             QList< QAction* > artActions = actions();
464             if( !artActions.isEmpty() )
465             {
466                 foreach( QAction *act, artActions )
467                     removeAction( act );
468             }
469             prevArt = artUrl;
470         }
471         else
472         {
473             if( prevArt != "" )
474                 setPixmap( QPixmap( ":/noart.png" ) );
475             prevArt = "";
476             QList< QAction* > artActions = actions();
477             if( artActions.isEmpty() )
478             {
479                 QAction *action = new QAction( qtr( "Download cover art" ),
480                                                this );
481                 addAction( action );
482                 CONNECT( action, triggered(),
483                          this, downloadCover() );
484             }
485         }
486         free( psz_meta );
487     }
488 }
489
490 TimeLabel::TimeLabel( intf_thread_t *_p_intf  ) :QLabel(), p_intf( _p_intf )
491 {
492    b_remainingTime = false;
493    setText( " --:--/--:-- " );
494    setAlignment( Qt::AlignRight | Qt::AlignVCenter );
495    setToolTip( qtr( "Toggle between elapsed and remaining time" ) );
496
497
498    CONNECT( THEMIM->getIM(), positionUpdated( float, int, int ),
499              this, setDisplayPosition( float, int, int ) );
500 }
501
502 void TimeLabel::setDisplayPosition( float pos, int time, int length )
503 {
504     char psz_length[MSTRTIME_MAX_SIZE], psz_time[MSTRTIME_MAX_SIZE];
505     secstotimestr( psz_length, length );
506     secstotimestr( psz_time, ( b_remainingTime && length ) ? length - time
507                                                            : time );
508
509     QString timestr;
510     timestr.sprintf( "%s/%s", psz_time,
511                             ( !length && time ) ? "--:--" : psz_length );
512
513     /* Add a minus to remaining time*/
514     if( b_remainingTime && length ) setText( " -"+timestr+" " );
515     else setText( " "+timestr+" " );
516 }
517
518 void TimeLabel::toggleTimeDisplay()
519 {
520     b_remainingTime = !b_remainingTime;
521 }
522
523 bool VolumeClickHandler::eventFilter( QObject *obj, QEvent *e )
524 {
525     if (e->type() == QEvent::MouseButtonPress  )
526     {
527         aout_VolumeMute( p_intf, NULL );
528         audio_volume_t i_volume;
529         aout_VolumeGet( p_intf, &i_volume );
530 //        m->updateVolume( i_volume *  VOLUME_MAX / (AOUT_VOLUME_MAX/2) );
531         return true;
532     }
533     return false;
534 }
535