1 /*****************************************************************************
2 * interface_widgets.cpp : Custom widgets for the main interface
3 ****************************************************************************
4 * Copyright (C) 2006-2010 the VideoLAN team
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>
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.
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.
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 *****************************************************************************/
32 #include "components/interface_widgets.hpp"
33 #include "dialogs_provider.hpp"
34 #include "util/customwidgets.hpp" // qtEventToVLCKey, QVLCStackedWidget
36 #include "menus.hpp" /* Popup menu on bgWidget */
41 #include <QToolButton>
44 #include <QResizeEvent>
47 #include <QWidgetAction>
48 #include <QDesktopWidget>
56 # include <X11/Xlib.h>
57 # include <qx11info_x11.h>
63 /**********************************************************************
64 * Video Widget. A simple frame on which video is drawn
65 * This class handles resize issues
66 **********************************************************************/
68 VideoWidget::VideoWidget( intf_thread_t *_p_i )
69 : QFrame( NULL ) , p_intf( _p_i )
71 /* Set the policy to expand in both directions */
72 // setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding );
74 layout = new QHBoxLayout( this );
75 layout->setContentsMargins( 0, 0, 0, 0 );
80 VideoWidget::~VideoWidget()
82 /* Ensure we are not leaking the video output. This would crash. */
86 void VideoWidget::sync( void )
89 /* Make sure the X server has processed all requests.
90 * This protects other threads using distinct connections from getting
91 * the video widget window in an inconsistent states. */
92 XSync( QX11Info::display(), False );
97 * Request the video to avoid the conflicts
99 WId VideoWidget::request( int *pi_x, int *pi_y,
100 unsigned int *pi_width, unsigned int *pi_height,
103 msg_Dbg( p_intf, "Video was requested %i, %i", *pi_x, *pi_y );
107 msg_Dbg( p_intf, "embedded video already in use" );
112 *pi_width = size().width();
113 *pi_height = size().height();
116 /* The owner of the video window needs a stable handle (WinId). Reparenting
117 * in Qt4-X11 changes the WinId of the widget, so we need to create another
118 * dummy widget that stays within the reparentable widget. */
119 stable = new QWidget();
120 QPalette plt = palette();
121 plt.setColor( QPalette::Window, Qt::black );
122 stable->setPalette( plt );
123 stable->setAutoFillBackground(true);
124 /* Force the widget to be native so that it gets a winId() */
125 stable->setAttribute( Qt::WA_NativeWindow, true );
126 /* Indicates that the widget wants to draw directly onto the screen.
127 Widgets with this attribute set do not participate in composition
129 /* This is currently disabled on X11 as it does not seem to improve
130 * performance, but causes the video widget to be transparent... */
131 #if !defined (Q_WS_X11) && !defined (Q_WS_QPA)
132 stable->setAttribute( Qt::WA_PaintOnScreen, true );
135 layout->addWidget( stable );
138 /* HACK: Only one X11 client can subscribe to mouse button press events.
139 * VLC currently handles those in the video display.
140 * Force Qt4 to unsubscribe from mouse press and release events. */
141 Display *dpy = QX11Info::display();
142 Window w = stable->winId();
143 XWindowAttributes attr;
145 XGetWindowAttributes( dpy, w, &attr );
146 attr.your_event_mask &= ~(ButtonPressMask|ButtonReleaseMask);
147 XSelectInput( dpy, w, attr.your_event_mask );
150 return stable->winId();
153 /* Set the Widget to the correct Size */
154 /* Function has to be called by the parent
155 Parent has to care about resizing itself */
156 void VideoWidget::SetSizing( unsigned int w, unsigned int h )
159 emit sizeChanged( w, h );
160 /* Work-around a bug?misconception? that would happen when vout core resize
161 twice to the same size and would make the vout not centered.
162 This cause a small flicker.
165 if( (unsigned)size().width() == w && (unsigned)size().height() == h )
170 void VideoWidget::release( void )
172 msg_Dbg( p_intf, "Video is not needed anymore" );
176 layout->removeWidget( stable );
177 stable->deleteLater();
184 /**********************************************************************
185 * Background Widget. Show a simple image background. Currently,
186 * it's album art if present or cone.
187 **********************************************************************/
189 BackgroundWidget::BackgroundWidget( intf_thread_t *_p_i )
190 :QWidget( NULL ), p_intf( _p_i ), b_expandPixmap( false ), b_withart( true )
192 /* A dark background */
193 setAutoFillBackground( true );
194 QPalette plt = palette();
195 plt.setColor( QPalette::Active, QPalette::Window , Qt::black );
196 plt.setColor( QPalette::Inactive, QPalette::Window , Qt::black );
199 /* Init the cone art */
202 /* fade in animator */
203 setProperty( "opacity", 1.0 );
204 fadeAnimation = new QPropertyAnimation( this, "opacity", this );
205 fadeAnimation->setDuration( 1000 );
206 fadeAnimation->setStartValue( 0.0 );
207 fadeAnimation->setEndValue( 1.0 );
208 fadeAnimation->setEasingCurve( QEasingCurve::OutSine );
209 CONNECT( fadeAnimation, valueChanged( const QVariant & ),
212 CONNECT( THEMIM->getIM(), artChanged( QString ),
213 this, updateArt( const QString& ) );
216 void BackgroundWidget::updateArt( const QString& url )
218 if ( !url.isEmpty() )
224 if( QDate::currentDate().dayOfYear() >= QT_XMAS_JOKE_DAY && var_InheritBool( p_intf, "qt-icon-change" ) )
225 pixmapUrl = QString( ":/logo/vlc128-xmas.png" );
227 pixmapUrl = QString( ":/logo/vlc128.png" );
232 void BackgroundWidget::showEvent( QShowEvent * e )
235 if ( b_withart ) fadeAnimation->start();
238 void BackgroundWidget::paintEvent( QPaintEvent *e )
242 /* we just want background autofill */
243 QWidget::paintEvent( e );
247 int i_maxwidth, i_maxheight;
248 QPixmap pixmap = QPixmap( pixmapUrl );
249 QPainter painter(this);
253 i_maxwidth = __MIN( maximumWidth(), width() ) - MARGIN * 2;
254 i_maxheight = __MIN( maximumHeight(), height() ) - MARGIN * 2;
256 painter.setOpacity( property( "opacity" ).toFloat() );
258 if ( height() > MARGIN * 2 )
260 /* Scale down the pixmap if the widget is too small */
261 if( pixmap.width() > i_maxwidth || pixmap.height() > i_maxheight )
263 pixmap = pixmap.scaled( i_maxwidth, i_maxheight,
264 Qt::KeepAspectRatio, Qt::SmoothTransformation );
267 if ( b_expandPixmap &&
268 pixmap.width() < width() && pixmap.height() < height() )
270 /* Scale up the pixmap to fill widget's size */
271 f_alpha = ( (float) pixmap.height() / (float) height() );
272 pixmap = pixmap.scaled(
273 width() - MARGIN * 2,
274 height() - MARGIN * 2,
276 ( f_alpha < .2 )? /* Don't waste cpu when not visible */
277 Qt::SmoothTransformation:
278 Qt::FastTransformation
280 /* Non agressive alpha compositing when sizing up */
281 pMask = QBitmap( pixmap.width(), pixmap.height() );
282 pMask.fill( QColor::fromRgbF( 1.0, 1.0, 1.0, f_alpha ) );
283 pixmap.setMask( pMask );
287 MARGIN + ( i_maxwidth - pixmap.width() ) /2,
288 MARGIN + ( i_maxheight - pixmap.height() ) /2,
291 QWidget::paintEvent( e );
294 void BackgroundWidget::contextMenuEvent( QContextMenuEvent *event )
296 VLCMenuBar::PopupMenu( p_intf, true );
301 #include <QPushButton>
302 #include <QHBoxLayout>
304 /**********************************************************************
305 * Visualization selector panel
306 **********************************************************************/
307 VisualSelector::VisualSelector( intf_thread_t *_p_i ) :
308 QFrame( NULL ), p_intf( _p_i )
310 QHBoxLayout *layout = new QHBoxLayout( this );
311 layout->setMargin( 0 );
312 QPushButton *prevButton = new QPushButton( "Prev" );
313 QPushButton *nextButton = new QPushButton( "Next" );
314 layout->addWidget( prevButton );
315 layout->addWidget( nextButton );
317 layout->addStretch( 10 );
318 layout->addWidget( new QLabel( qtr( "Current visualization" ) ) );
320 current = new QLabel( qtr( "None" ) );
321 layout->addWidget( current );
323 BUTTONACT( prevButton, prev() );
324 BUTTONACT( nextButton, next() );
327 setMaximumHeight( 35 );
330 VisualSelector::~VisualSelector()
333 void VisualSelector::prev()
335 char *psz_new = aout_VisualPrev( p_intf );
338 current->setText( qfu( psz_new ) );
343 void VisualSelector::next()
345 char *psz_new = aout_VisualNext( p_intf );
348 current->setText( qfu( psz_new ) );
354 SpeedLabel::SpeedLabel( intf_thread_t *_p_intf, QWidget *parent )
355 : QLabel( parent ), p_intf( _p_intf )
357 tooltipStringPattern = qtr( "Current playback speed: %1\nClick to adjust" );
359 /* Create the Speed Control Widget */
360 speedControl = new SpeedControlWidget( p_intf, this );
361 speedControlMenu = new QMenu( this );
363 QWidgetAction *widgetAction = new QWidgetAction( speedControl );
364 widgetAction->setDefaultWidget( speedControl );
365 speedControlMenu->addAction( widgetAction );
367 /* Change the SpeedRate in the Label */
368 CONNECT( THEMIM->getIM(), rateChanged( float ), this, setRate( float ) );
370 DCONNECT( THEMIM, inputChanged( input_thread_t * ),
371 speedControl, activateOnState() );
373 setFrameStyle( QFrame::StyledPanel | QFrame::Raised );
376 setRate( var_InheritFloat( THEPL, "rate" ) );
379 SpeedLabel::~SpeedLabel()
382 delete speedControlMenu;
385 /****************************************************************************
386 * Small right-click menu for rate control
387 ****************************************************************************/
389 void SpeedLabel::showSpeedMenu( QPoint pos )
391 speedControlMenu->exec( QCursor::pos() - pos
392 + QPoint( -70 + width()/2, height() ) );
395 void SpeedLabel::setRate( float rate )
398 str.setNum( rate, 'f', 2 );
401 setToolTip( tooltipStringPattern.arg( str ) );
402 speedControl->updateControls( rate );
405 /**********************************************************************
406 * Speed control widget
407 **********************************************************************/
408 SpeedControlWidget::SpeedControlWidget( intf_thread_t *_p_i, QWidget *_parent )
409 : QFrame( _parent ), p_intf( _p_i )
411 QSizePolicy sizePolicy( QSizePolicy::Fixed, QSizePolicy::Maximum );
412 sizePolicy.setHorizontalStretch( 0 );
413 sizePolicy.setVerticalStretch( 0 );
415 speedSlider = new QSlider( this );
416 speedSlider->setSizePolicy( sizePolicy );
417 speedSlider->setMinimumSize( QSize( 140, 20 ) );
418 speedSlider->setOrientation( Qt::Horizontal );
419 speedSlider->setTickPosition( QSlider::TicksBelow );
421 speedSlider->setRange( -34, 34 );
422 speedSlider->setSingleStep( 1 );
423 speedSlider->setPageStep( 1 );
424 speedSlider->setTickInterval( 17 );
426 CONNECT( speedSlider, valueChanged( int ), this, updateRate( int ) );
428 QToolButton *normalSpeedButton = new QToolButton( this );
429 normalSpeedButton->setMaximumSize( QSize( 26, 16 ) );
430 normalSpeedButton->setAutoRaise( true );
431 normalSpeedButton->setText( "1x" );
432 normalSpeedButton->setToolTip( qtr( "Revert to normal play speed" ) );
434 CONNECT( normalSpeedButton, clicked(), this, resetRate() );
436 QToolButton *slowerButton = new QToolButton( this );
437 slowerButton->setMaximumSize( QSize( 26, 16 ) );
438 slowerButton->setAutoRaise( true );
439 slowerButton->setToolTip( tooltipL[SLOWER_BUTTON] );
440 slowerButton->setIcon( QIcon( iconL[SLOWER_BUTTON] ) );
441 CONNECT( slowerButton, clicked(), THEMIM->getIM(), slower() );
443 QToolButton *fasterButton = new QToolButton( this );
444 fasterButton->setMaximumSize( QSize( 26, 16 ) );
445 fasterButton->setAutoRaise( true );
446 fasterButton->setToolTip( tooltipL[FASTER_BUTTON] );
447 fasterButton->setIcon( QIcon( iconL[FASTER_BUTTON] ) );
448 CONNECT( fasterButton, clicked(), THEMIM->getIM(), faster() );
450 /* spinBox = new QDoubleSpinBox();
451 spinBox->setDecimals( 2 );
452 spinBox->setMaximum( 32 );
453 spinBox->setMinimum( 0.03F );
454 spinBox->setSingleStep( 0.10F );
455 spinBox->setAlignment( Qt::AlignRight );
457 CONNECT( spinBox, valueChanged( double ), this, updateSpinBoxRate( double ) ); */
459 QGridLayout* speedControlLayout = new QGridLayout( this );
460 speedControlLayout->addWidget( speedSlider, 0, 0, 1, 3 );
461 speedControlLayout->addWidget( slowerButton, 1, 0 );
462 speedControlLayout->addWidget( normalSpeedButton, 1, 1, 1, 1, Qt::AlignRight );
463 speedControlLayout->addWidget( fasterButton, 1, 2, 1, 1, Qt::AlignRight );
464 //speedControlLayout->addWidget( spinBox );
465 speedControlLayout->setContentsMargins( 0, 0, 0, 0 );
466 speedControlLayout->setSpacing( 0 );
473 void SpeedControlWidget::activateOnState()
475 speedSlider->setEnabled( THEMIM->getIM()->hasInput() );
476 //spinBox->setEnabled( THEMIM->getIM()->hasInput() );
479 void SpeedControlWidget::updateControls( float rate )
481 if( speedSlider->isSliderDown() )
483 //We don't want to change anything if the user is using the slider
487 double value = 17 * log( rate ) / log( 2. );
488 int sliderValue = (int) ( ( value > 0 ) ? value + .5 : value - .5 );
490 if( sliderValue < speedSlider->minimum() )
492 sliderValue = speedSlider->minimum();
494 else if( sliderValue > speedSlider->maximum() )
496 sliderValue = speedSlider->maximum();
498 lastValue = sliderValue;
500 speedSlider->setValue( sliderValue );
501 //spinBox->setValue( rate );
504 void SpeedControlWidget::updateRate( int sliderValue )
506 if( sliderValue == lastValue )
509 double speed = pow( 2, (double)sliderValue / 17 );
510 int rate = INPUT_RATE_DEFAULT / speed;
512 THEMIM->getIM()->setRate(rate);
513 //spinBox->setValue( var_InheritFloat( THEPL, "rate" ) );
516 void SpeedControlWidget::updateSpinBoxRate( double r )
518 var_SetFloat( THEPL, "rate", r );
521 void SpeedControlWidget::resetRate()
523 THEMIM->getIM()->setRate( INPUT_RATE_DEFAULT );
526 CoverArtLabel::CoverArtLabel( QWidget *parent, intf_thread_t *_p_i )
527 : QLabel( parent ), p_intf( _p_i ), p_item( NULL )
529 setContextMenuPolicy( Qt::ActionsContextMenu );
530 CONNECT( THEMIM->getIM(), artChanged( input_item_t * ),
531 this, showArtUpdate( input_item_t * ) );
533 setMinimumHeight( 128 );
534 setMinimumWidth( 128 );
535 setScaledContents( false );
536 setAlignment( Qt::AlignCenter );
538 QAction *action = new QAction( qtr( "Download cover art" ), this );
539 CONNECT( action, triggered(), this, askForUpdate() );
542 action = new QAction( qtr( "Add cover art from file" ), this );
543 CONNECT( action, triggered(), this, setArtFromFile() );
546 p_item = THEMIM->currentInputItem();
548 showArtUpdate( p_item );
553 CoverArtLabel::~CoverArtLabel()
555 QList< QAction* > artActions = actions();
556 foreach( QAction *act, artActions )
558 if ( p_item ) vlc_gc_decref( p_item );
561 void CoverArtLabel::setItem( input_item_t *_p_item )
563 if ( p_item ) vlc_gc_decref( p_item );
565 if ( p_item ) vlc_gc_incref( p_item );
568 void CoverArtLabel::showArtUpdate( const QString& url )
571 if( !url.isEmpty() && pix.load( url ) )
573 pix = pix.scaled( minimumWidth(), minimumHeight(),
574 Qt::KeepAspectRatioByExpanding,
575 Qt::SmoothTransformation );
579 pix = QPixmap( ":/noart.png" );
584 void CoverArtLabel::showArtUpdate( input_item_t *_p_item )
587 if ( _p_item != p_item )
591 if ( _p_item ) url = THEMIM->getIM()->decodeArtURL( _p_item );
592 showArtUpdate( url );
595 void CoverArtLabel::askForUpdate()
597 THEMIM->getIM()->requestArtUpdate( p_item );
600 void CoverArtLabel::setArtFromFile()
605 QString filePath = QFileDialog::getOpenFileName( this, qtr( "Choose Cover Art" ),
606 p_intf->p_sys->filepath, qtr( "Image Files (*.gif *.jpg *.jpeg *.png)" ) );
608 if( filePath.isEmpty() )
611 QString fileUrl = QUrl::fromLocalFile( filePath ).toString();
613 THEMIM->getIM()->setArt( p_item, fileUrl );
616 TimeLabel::TimeLabel( intf_thread_t *_p_intf, TimeLabel::Display _displayType )
617 : ClickableQLabel(), p_intf( _p_intf ), bufTimer( new QTimer(this) ),
618 buffering( false ), showBuffering(false), bufVal( -1 ), displayType( _displayType )
620 b_remainingTime = false;
621 if( _displayType != TimeLabel::Elapsed )
622 b_remainingTime = getSettings()->value( "MainWindow/ShowRemainingTime", false ).toBool();
623 switch( _displayType ) {
624 case TimeLabel::Elapsed:
625 setText( " --:-- " );
626 setToolTip( qtr("Elapsed time") );
628 case TimeLabel::Remaining:
629 setText( " --:-- " );
630 setToolTip( qtr("Total/Remaining time")
632 + qtr("Click to toggle between total and remaining time")
635 case TimeLabel::Both:
636 setText( " --:--/--:-- " );
637 setToolTip( QString( "- " )
638 + qtr( "Click to toggle between elapsed and remaining time" )
640 + qtr( "Double click to jump to a chosen time position" ) );
643 setAlignment( Qt::AlignRight | Qt::AlignVCenter );
645 bufTimer->setSingleShot( true );
647 CONNECT( THEMIM->getIM(), positionUpdated( float, int64_t, int ),
648 this, setDisplayPosition( float, int64_t, int ) );
649 CONNECT( THEMIM->getIM(), cachingChanged( float ),
650 this, updateBuffering( float ) );
651 CONNECT( bufTimer, timeout(), this, updateBuffering() );
653 setStyleSheet( "padding-left: 4px; padding-right: 4px;" );
656 void TimeLabel::setDisplayPosition( float pos, int64_t t, int length )
658 showBuffering = false;
663 setMinimumSize( QSize( 0, 0 ) );
664 if( displayType == TimeLabel::Both )
665 setText( "--:--/--:--" );
671 int time = t / 1000000;
673 secstotimestr( psz_length, length );
674 secstotimestr( psz_time, ( b_remainingTime && length ) ? length - time
677 // compute the minimum size that will be required for the psz_length
678 // and use it to enforce a minimal size to avoid "dancing" widgets
679 QSize minsize( 0, 0 );
682 QMargins margins = contentsMargins();
684 fontMetrics().size( 0, QString( psz_length ), 0, 0 ).width(),
687 minsize += QSize( margins.left() + margins.right() + 8, 0 ); /* +padding */
689 if ( b_remainingTime )
690 minsize += QSize( fontMetrics().size( 0, "-", 0, 0 ).width(), 0 );
693 switch( displayType )
695 case TimeLabel::Elapsed:
696 setMinimumSize( minsize );
697 setText( QString( psz_time ) );
699 case TimeLabel::Remaining:
700 if( b_remainingTime )
702 setMinimumSize( minsize );
703 setText( QString("-") + QString( psz_time ) );
707 setMinimumSize( QSize( 0, 0 ) );
708 setText( QString( psz_length ) );
711 case TimeLabel::Both:
713 QString timestr = QString( "%1%2/%3" )
714 .arg( QString( (b_remainingTime && length) ? "-" : "" ) )
715 .arg( QString( psz_time ) )
716 .arg( QString( ( !length && time ) ? "--:--" : psz_length ) );
721 cachedLength = length;
724 void TimeLabel::setDisplayPosition( float pos )
726 if( pos == -1.f || cachedLength == 0 )
728 setText( " --:--/--:-- " );
732 int time = pos * cachedLength;
733 secstotimestr( psz_time,
734 ( b_remainingTime && cachedLength ?
735 cachedLength - time : time ) );
736 QString timestr = QString( "%1%2/%3" )
737 .arg( QString( (b_remainingTime && cachedLength) ? "-" : "" ) )
738 .arg( QString( psz_time ) )
739 .arg( QString( ( !cachedLength && time ) ? "--:--" : psz_length ) );
745 void TimeLabel::toggleTimeDisplay()
747 b_remainingTime = !b_remainingTime;
748 getSettings()->setValue( "MainWindow/ShowRemainingTime", b_remainingTime );
752 void TimeLabel::updateBuffering( float _buffered )
755 if( !buffering || bufVal == 0 )
757 showBuffering = false;
759 bufTimer->start(200);
761 else if( bufVal == 1 )
763 showBuffering = buffering = false;
769 void TimeLabel::updateBuffering()
771 showBuffering = true;
775 void TimeLabel::paintEvent( QPaintEvent* event )
780 r.setLeft( r.width() * bufVal );
783 p.fillRect( r, palette().color( QPalette::Highlight ) );
785 QLabel::paintEvent( event );