]> git.sesse.net Git - vlc/blob - modules/gui/qt4/components/interface_widgets.cpp
Qt: use __MIN when applicable
[vlc] / modules / gui / qt4 / components / interface_widgets.cpp
1 /*****************************************************************************
2  * interface_widgets.cpp : Custom widgets for the main interface
3  ****************************************************************************
4  * Copyright (C) 2006-2010 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 "qt4.hpp"
32 #include "components/interface_widgets.hpp"
33 #include "dialogs_provider.hpp"
34 #include "util/customwidgets.hpp"               // qtEventToVLCKey, QVLCStackedWidget
35
36 #include "menus.hpp"             /* Popup menu on bgWidget */
37
38 #include <vlc_vout.h>
39
40 #include <QLabel>
41 #include <QToolButton>
42 #include <QPalette>
43 #include <QEvent>
44 #include <QResizeEvent>
45 #include <QDate>
46 #include <QMenu>
47 #include <QWidgetAction>
48 #include <QDesktopWidget>
49 #include <QPainter>
50 #include <QTimer>
51 #include <QSlider>
52 #include <QBitmap>
53
54 #ifdef Q_WS_X11
55 #   include <X11/Xlib.h>
56 #   include <qx11info_x11.h>
57 #endif
58
59 #include <math.h>
60 #include <assert.h>
61
62 /**********************************************************************
63  * Video Widget. A simple frame on which video is drawn
64  * This class handles resize issues
65  **********************************************************************/
66
67 VideoWidget::VideoWidget( intf_thread_t *_p_i )
68             : QFrame( NULL ) , p_intf( _p_i )
69 {
70     /* Set the policy to expand in both directions */
71     // setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding );
72
73     layout = new QHBoxLayout( this );
74     layout->setContentsMargins( 0, 0, 0, 0 );
75     stable = NULL;
76     show();
77 }
78
79 VideoWidget::~VideoWidget()
80 {
81     /* Ensure we are not leaking the video output. This would crash. */
82     assert( !stable );
83 }
84
85 void VideoWidget::sync( void )
86 {
87 #ifdef Q_WS_X11
88     /* Make sure the X server has processed all requests.
89      * This protects other threads using distinct connections from getting
90      * the video widget window in an inconsistent states. */
91     XSync( QX11Info::display(), False );
92 #endif
93 }
94
95 /**
96  * Request the video to avoid the conflicts
97  **/
98 WId VideoWidget::request( int *pi_x, int *pi_y,
99                           unsigned int *pi_width, unsigned int *pi_height,
100                           bool b_keep_size )
101 {
102     msg_Dbg( p_intf, "Video was requested %i, %i", *pi_x, *pi_y );
103
104     if( stable )
105     {
106         msg_Dbg( p_intf, "embedded video already in use" );
107         return 0;
108     }
109     if( b_keep_size )
110     {
111         *pi_width  = size().width();
112         *pi_height = size().height();
113     }
114
115     /* The owner of the video window needs a stable handle (WinId). Reparenting
116      * in Qt4-X11 changes the WinId of the widget, so we need to create another
117      * dummy widget that stays within the reparentable widget. */
118     stable = new QWidget();
119     QPalette plt = palette();
120     plt.setColor( QPalette::Window, Qt::black );
121     stable->setPalette( plt );
122     stable->setAutoFillBackground(true);
123     /* Indicates that the widget wants to draw directly onto the screen.
124        Widgets with this attribute set do not participate in composition
125        management */
126     /* This is currently disabled on X11 as it does not seem to improve
127      * performance, but causes the video widget to be transparent... */
128 #ifndef Q_WS_X11
129     stable->setAttribute( Qt::WA_PaintOnScreen, true );
130 #endif
131
132     layout->addWidget( stable );
133
134 #ifdef Q_WS_X11
135     /* HACK: Only one X11 client can subscribe to mouse button press events.
136      * VLC currently handles those in the video display.
137      * Force Qt4 to unsubscribe from mouse press and release events. */
138     Display *dpy = QX11Info::display();
139     Window w = stable->winId();
140     XWindowAttributes attr;
141
142     XGetWindowAttributes( dpy, w, &attr );
143     attr.your_event_mask &= ~(ButtonPressMask|ButtonReleaseMask);
144     XSelectInput( dpy, w, attr.your_event_mask );
145 #endif
146     sync();
147 #ifndef NDEBUG
148     msg_Dbg( p_intf, "embedded video ready (handle %p)",
149              (void *)stable->winId() );
150 #endif
151     return stable->winId();
152 }
153
154 /* Set the Widget to the correct Size */
155 /* Function has to be called by the parent
156    Parent has to care about resizing itself */
157 void VideoWidget::SetSizing( unsigned int w, unsigned int h )
158 {
159     resize( w, h );
160     emit sizeChanged( w, h );
161     /* Work-around a bug?misconception? that would happen when vout core resize
162        twice to the same size and would make the vout not centered.
163        This cause a small flicker.
164        See #3621
165      */
166     if( (unsigned)size().width() == w && (unsigned)size().height() == h )
167         updateGeometry();
168     sync();
169 }
170
171 void VideoWidget::release( void )
172 {
173     msg_Dbg( p_intf, "Video is not needed anymore" );
174
175     if( stable )
176     {
177         layout->removeWidget( stable );
178         stable->deleteLater();
179         stable = NULL;
180     }
181
182     updateGeometry();
183 }
184
185 /**********************************************************************
186  * Background Widget. Show a simple image background. Currently,
187  * it's album art if present or cone.
188  **********************************************************************/
189
190 BackgroundWidget::BackgroundWidget( intf_thread_t *_p_i )
191     :QWidget( NULL ), p_intf( _p_i ), b_expandPixmap( false ), b_withart( true )
192 {
193     /* A dark background */
194     setAutoFillBackground( true );
195     QPalette plt = palette();
196     plt.setColor( QPalette::Active, QPalette::Window , Qt::black );
197     plt.setColor( QPalette::Inactive, QPalette::Window , Qt::black );
198     setPalette( plt );
199
200     /* Init the cone art */
201     updateArt( "" );
202
203     CONNECT( THEMIM->getIM(), artChanged( QString ),
204              this, updateArt( const QString& ) );
205 }
206
207 void BackgroundWidget::updateArt( const QString& url )
208 {
209     if ( !url.isEmpty() )
210     {
211         pixmapUrl = url;
212     }
213     else
214     {   /* Xmas joke */
215         if( QDate::currentDate().dayOfYear() >= QT_XMAS_JOKE_DAY && var_InheritBool( p_intf, "qt-icon-change" ) )
216             pixmapUrl = QString( ":/logo/vlc128-xmas.png" );
217         else
218             pixmapUrl = QString( ":/logo/vlc128.png" );
219     }
220     update();
221 }
222
223 void BackgroundWidget::paintEvent( QPaintEvent *e )
224 {
225     if ( !b_withart )
226     {
227         /* we just want background autofill */
228         QWidget::paintEvent( e );
229         return;
230     }
231
232     int i_maxwidth, i_maxheight;
233     QPixmap pixmap = QPixmap( pixmapUrl );
234     QPainter painter(this);
235     QBitmap pMask;
236     float f_alpha = 1.0;
237
238     i_maxwidth  = __MIN( maximumWidth(), width() ) - MARGIN * 2;
239     i_maxheight = __MIN( maximumHeight(), height() ) - MARGIN * 2;
240
241     if ( height() > MARGIN * 2 )
242     {
243         /* Scale down the pixmap if the widget is too small */
244         if( pixmap.width() > i_maxwidth || pixmap.height() > i_maxheight )
245         {
246             pixmap = pixmap.scaled( i_maxwidth, i_maxheight,
247                             Qt::KeepAspectRatio, Qt::SmoothTransformation );
248         }
249         else
250         if ( b_expandPixmap &&
251              pixmap.width() < width() && pixmap.height() < height() )
252         {
253             /* Scale up the pixmap to fill widget's size */
254             f_alpha = ( (float) pixmap.height() / (float) height() );
255             pixmap = pixmap.scaled(
256                     width() - MARGIN * 2,
257                     height() - MARGIN * 2,
258                     Qt::KeepAspectRatio,
259                     ( f_alpha < .2 )? /* Don't waste cpu when not visible */
260                         Qt::SmoothTransformation:
261                         Qt::FastTransformation
262                     );
263             /* Non agressive alpha compositing when sizing up */
264             pMask = QBitmap( pixmap.width(), pixmap.height() );
265             pMask.fill( QColor::fromRgbF( 1.0, 1.0, 1.0, f_alpha ) );
266             pixmap.setMask( pMask );
267         }
268
269         painter.drawPixmap(
270                 MARGIN + ( i_maxwidth - pixmap.width() ) /2,
271                 MARGIN + ( i_maxheight - pixmap.height() ) /2,
272                 pixmap);
273     }
274     QWidget::paintEvent( e );
275 }
276
277 void BackgroundWidget::contextMenuEvent( QContextMenuEvent *event )
278 {
279     QVLCMenu::PopupMenu( p_intf, true );
280     event->accept();
281 }
282
283 #if 0
284 #include <QPushButton>
285 #include <QHBoxLayout>
286
287 /**********************************************************************
288  * Visualization selector panel
289  **********************************************************************/
290 VisualSelector::VisualSelector( intf_thread_t *_p_i ) :
291                                 QFrame( NULL ), p_intf( _p_i )
292 {
293     QHBoxLayout *layout = new QHBoxLayout( this );
294     layout->setMargin( 0 );
295     QPushButton *prevButton = new QPushButton( "Prev" );
296     QPushButton *nextButton = new QPushButton( "Next" );
297     layout->addWidget( prevButton );
298     layout->addWidget( nextButton );
299
300     layout->addStretch( 10 );
301     layout->addWidget( new QLabel( qtr( "Current visualization" ) ) );
302
303     current = new QLabel( qtr( "None" ) );
304     layout->addWidget( current );
305
306     BUTTONACT( prevButton, prev() );
307     BUTTONACT( nextButton, next() );
308
309     setLayout( layout );
310     setMaximumHeight( 35 );
311 }
312
313 VisualSelector::~VisualSelector()
314 {}
315
316 void VisualSelector::prev()
317 {
318     char *psz_new = aout_VisualPrev( p_intf );
319     if( psz_new )
320     {
321         current->setText( qfu( psz_new ) );
322         free( psz_new );
323     }
324 }
325
326 void VisualSelector::next()
327 {
328     char *psz_new = aout_VisualNext( p_intf );
329     if( psz_new )
330     {
331         current->setText( qfu( psz_new ) );
332         free( psz_new );
333     }
334 }
335 #endif
336
337 SpeedLabel::SpeedLabel( intf_thread_t *_p_intf, QWidget *parent )
338            : QLabel( parent ), p_intf( _p_intf )
339 {
340     tooltipStringPattern = qtr( "Current playback speed: %1\nClick to adjust" );
341
342     /* Create the Speed Control Widget */
343     speedControl = new SpeedControlWidget( p_intf, this );
344     speedControlMenu = new QMenu( this );
345
346     QWidgetAction *widgetAction = new QWidgetAction( speedControl );
347     widgetAction->setDefaultWidget( speedControl );
348     speedControlMenu->addAction( widgetAction );
349
350     /* Change the SpeedRate in the Status Bar */
351     CONNECT( THEMIM->getIM(), rateChanged( float ), this, setRate( float ) );
352
353     DCONNECT( THEMIM, inputChanged( input_thread_t * ),
354               speedControl, activateOnState() );
355     setRate( var_InheritFloat( p_intf, "rate" ) );
356 }
357
358 SpeedLabel::~SpeedLabel()
359 {
360     delete speedControl;
361     delete speedControlMenu;
362 }
363
364 /****************************************************************************
365  * Small right-click menu for rate control
366  ****************************************************************************/
367
368 void SpeedLabel::showSpeedMenu( QPoint pos )
369 {
370     speedControlMenu->exec( QCursor::pos() - pos
371                           + QPoint( 0, height() ) );
372 }
373
374 void SpeedLabel::setRate( float rate )
375 {
376     QString str;
377     str.setNum( rate, 'f', 2 );
378     str.append( "x" );
379     setText( str );
380     setToolTip( tooltipStringPattern.arg( str ) );
381     speedControl->updateControls( rate );
382 }
383
384 /**********************************************************************
385  * Speed control widget
386  **********************************************************************/
387 SpeedControlWidget::SpeedControlWidget( intf_thread_t *_p_i, QWidget *_parent )
388                     : QFrame( _parent ), p_intf( _p_i )
389 {
390     QSizePolicy sizePolicy( QSizePolicy::Maximum, QSizePolicy::Fixed );
391     sizePolicy.setHorizontalStretch( 0 );
392     sizePolicy.setVerticalStretch( 0 );
393
394     speedSlider = new QSlider( this );
395     speedSlider->setSizePolicy( sizePolicy );
396     speedSlider->setMaximumSize( QSize( 80, 200 ) );
397     speedSlider->setOrientation( Qt::Vertical );
398     speedSlider->setTickPosition( QSlider::TicksRight );
399
400     speedSlider->setRange( -34, 34 );
401     speedSlider->setSingleStep( 1 );
402     speedSlider->setPageStep( 1 );
403     speedSlider->setTickInterval( 17 );
404
405     CONNECT( speedSlider, valueChanged( int ), this, updateRate( int ) );
406
407     QToolButton *normalSpeedButton = new QToolButton( this );
408     normalSpeedButton->setMaximumSize( QSize( 26, 20 ) );
409     normalSpeedButton->setAutoRaise( true );
410     normalSpeedButton->setText( "1x" );
411     normalSpeedButton->setToolTip( qtr( "Revert to normal play speed" ) );
412
413     CONNECT( normalSpeedButton, clicked(), this, resetRate() );
414
415     QVBoxLayout *speedControlLayout = new QVBoxLayout( this );
416     speedControlLayout->setContentsMargins( 4, 4, 4, 4 );
417     speedControlLayout->setSpacing( 4 );
418     speedControlLayout->addWidget( speedSlider );
419     speedControlLayout->addWidget( normalSpeedButton );
420
421     lastValue = 0;
422
423     activateOnState();
424 }
425
426 void SpeedControlWidget::activateOnState()
427 {
428     speedSlider->setEnabled( THEMIM->getIM()->hasInput() );
429 }
430
431 void SpeedControlWidget::updateControls( float rate )
432 {
433     if( speedSlider->isSliderDown() )
434     {
435         //We don't want to change anything if the user is using the slider
436         return;
437     }
438
439     double value = 17 * log( rate ) / log( 2. );
440     int sliderValue = (int) ( ( value > 0 ) ? value + .5 : value - .5 );
441
442     if( sliderValue < speedSlider->minimum() )
443     {
444         sliderValue = speedSlider->minimum();
445     }
446     else if( sliderValue > speedSlider->maximum() )
447     {
448         sliderValue = speedSlider->maximum();
449     }
450     lastValue = sliderValue;
451
452     speedSlider->setValue( sliderValue );
453 }
454
455 void SpeedControlWidget::updateRate( int sliderValue )
456 {
457     if( sliderValue == lastValue )
458         return;
459
460     double speed = pow( 2, (double)sliderValue / 17 );
461     int rate = INPUT_RATE_DEFAULT / speed;
462
463     THEMIM->getIM()->setRate(rate);
464 }
465
466 void SpeedControlWidget::resetRate()
467 {
468     THEMIM->getIM()->setRate( INPUT_RATE_DEFAULT );
469 }
470
471 CoverArtLabel::CoverArtLabel( QWidget *parent, intf_thread_t *_p_i )
472               : QLabel( parent ), p_intf( _p_i )
473 {
474     setContextMenuPolicy( Qt::ActionsContextMenu );
475     CONNECT( this, updateRequested(), this, askForUpdate() );
476
477     setMinimumHeight( 128 );
478     setMinimumWidth( 128 );
479     setMaximumHeight( 128 );
480     setMaximumWidth( 128 );
481     setScaledContents( false );
482     setAlignment( Qt::AlignCenter );
483
484     QList< QAction* > artActions = actions();
485     QAction *action = new QAction( qtr( "Download cover art" ), this );
486     CONNECT( action, triggered(), this, askForUpdate() );
487     addAction( action );
488
489     showArtUpdate( "" );
490 }
491
492 CoverArtLabel::~CoverArtLabel()
493 {
494     QList< QAction* > artActions = actions();
495     foreach( QAction *act, artActions )
496         removeAction( act );
497 }
498
499 void CoverArtLabel::showArtUpdate( const QString& url )
500 {
501     QPixmap pix;
502     if( !url.isEmpty() && pix.load( url ) )
503     {
504         pix = pix.scaled( maximumWidth(), maximumHeight(),
505                           Qt::KeepAspectRatioByExpanding,
506                           Qt::SmoothTransformation );
507     }
508     else
509     {
510         pix = QPixmap( ":/noart.png" );
511     }
512     setPixmap( pix );
513 }
514
515 void CoverArtLabel::askForUpdate()
516 {
517     THEMIM->getIM()->requestArtUpdate();
518 }
519
520 TimeLabel::TimeLabel( intf_thread_t *_p_intf  )
521     : QLabel(), p_intf( _p_intf ), bufTimer( new QTimer(this) ),
522       buffering( false ), showBuffering(false), bufVal( -1 )
523 {
524     b_remainingTime = false;
525     setText( " --:--/--:-- " );
526     setAlignment( Qt::AlignRight | Qt::AlignVCenter );
527     setToolTip( QString( "- " )
528         + qtr( "Click to toggle between elapsed and remaining time" )
529         + QString( "\n- " )
530         + qtr( "Double click to jump to a chosen time position" ) );
531     bufTimer->setSingleShot( true );
532
533     CONNECT( THEMIM->getIM(), positionUpdated( float, int64_t, int ),
534               this, setDisplayPosition( float, int64_t, int ) );
535     CONNECT( THEMIM->getIM(), cachingChanged( float ),
536               this, updateBuffering( float ) );
537     CONNECT( bufTimer, timeout(), this, updateBuffering() );
538 }
539
540 void TimeLabel::setDisplayPosition( float pos, int64_t t, int length )
541 {
542     showBuffering = false;
543     bufTimer->stop();
544
545     if( pos == -1.f )
546     {
547         setText( " --:--/--:-- " );
548         return;
549     }
550
551     int time = t / 1000000;
552
553     secstotimestr( psz_length, length );
554     secstotimestr( psz_time, ( b_remainingTime && length ) ? length - time
555                                                            : time );
556
557     QString timestr = QString( " %1%2/%3 " )
558             .arg( QString( (b_remainingTime && length) ? "-" : "" ) )
559             .arg( QString( psz_time ) )
560             .arg( QString( ( !length && time ) ? "--:--" : psz_length ) );
561
562     setText( timestr );
563
564     cachedLength = length;
565 }
566
567 void TimeLabel::setDisplayPosition( float pos )
568 {
569     if( pos == -1.f || cachedLength == 0 )
570     {
571         setText( " --:--/--:-- " );
572         return;
573     }
574
575     int time = pos * cachedLength;
576     secstotimestr( psz_time,
577                    ( b_remainingTime && cachedLength ?
578                    cachedLength - time : time ) );
579     QString timestr = QString( " %1%2/%3 " )
580         .arg( QString( (b_remainingTime && cachedLength) ? "-" : "" ) )
581         .arg( QString( psz_time ) )
582         .arg( QString( ( !cachedLength && time ) ? "--:--" : psz_length ) );
583
584     setText( timestr );
585 }
586
587
588 void TimeLabel::toggleTimeDisplay()
589 {
590     b_remainingTime = !b_remainingTime;
591 }
592
593
594 void TimeLabel::updateBuffering( float _buffered )
595 {
596     bufVal = _buffered;
597     if( !buffering || bufVal == 0 )
598     {
599         showBuffering = false;
600         buffering = true;
601         bufTimer->start(200);
602     }
603     else if( bufVal == 1 )
604     {
605         showBuffering = buffering = false;
606         bufTimer->stop();
607     }
608     update();
609 }
610
611 void TimeLabel::updateBuffering()
612 {
613     showBuffering = true;
614     update();
615 }
616
617 void TimeLabel::paintEvent( QPaintEvent* event )
618 {
619     if( showBuffering )
620     {
621         QRect r( rect() );
622         r.setLeft( r.width() * bufVal );
623         QPainter p( this );
624         p.setOpacity( 0.4 );
625         p.fillRect( r, palette().color( QPalette::Highlight ) );
626     }
627     QLabel::paintEvent( event );
628 }