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>
55 # include <X11/Xlib.h>
56 # include <qx11info_x11.h>
62 /**********************************************************************
63 * Video Widget. A simple frame on which video is drawn
64 * This class handles resize issues
65 **********************************************************************/
67 VideoWidget::VideoWidget( intf_thread_t
*_p_i
)
68 : QFrame( NULL
) , p_intf( _p_i
)
70 /* Set the policy to expand in both directions */
71 // setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding );
73 layout
= new QHBoxLayout( this );
74 layout
->setContentsMargins( 0, 0, 0, 0 );
79 VideoWidget::~VideoWidget()
81 /* Ensure we are not leaking the video output. This would crash. */
85 void VideoWidget::sync( void )
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
);
96 * Request the video to avoid the conflicts
98 WId
VideoWidget::request( int *pi_x
, int *pi_y
,
99 unsigned int *pi_width
, unsigned int *pi_height
,
102 msg_Dbg( p_intf
, "Video was requested %i, %i", *pi_x
, *pi_y
);
106 msg_Dbg( p_intf
, "embedded video already in use" );
111 *pi_width
= size().width();
112 *pi_height
= size().height();
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
126 /* This is currently disabled on X11 as it does not seem to improve
127 * performance, but causes the video widget to be transparent... */
129 stable
->setAttribute( Qt::WA_PaintOnScreen
, true );
132 layout
->addWidget( stable
);
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
;
142 XGetWindowAttributes( dpy
, w
, &attr
);
143 attr
.your_event_mask
&= ~(ButtonPressMask
|ButtonReleaseMask
);
144 XSelectInput( dpy
, w
, attr
.your_event_mask
);
147 return stable
->winId();
150 /* Set the Widget to the correct Size */
151 /* Function has to be called by the parent
152 Parent has to care about resizing itself */
153 void VideoWidget::SetSizing( unsigned int w
, unsigned int h
)
156 emit
sizeChanged( w
, h
);
157 /* Work-around a bug?misconception? that would happen when vout core resize
158 twice to the same size and would make the vout not centered.
159 This cause a small flicker.
162 if( (unsigned)size().width() == w
&& (unsigned)size().height() == h
)
167 void VideoWidget::release( void )
169 msg_Dbg( p_intf
, "Video is not needed anymore" );
173 layout
->removeWidget( stable
);
174 stable
->deleteLater();
181 /**********************************************************************
182 * Background Widget. Show a simple image background. Currently,
183 * it's album art if present or cone.
184 **********************************************************************/
186 BackgroundWidget::BackgroundWidget( intf_thread_t
*_p_i
)
187 :QWidget( NULL
), p_intf( _p_i
), b_expandPixmap( false ), b_withart( true )
189 /* A dark background */
190 setAutoFillBackground( true );
191 QPalette plt
= palette();
192 plt
.setColor( QPalette::Active
, QPalette::Window
, Qt::black
);
193 plt
.setColor( QPalette::Inactive
, QPalette::Window
, Qt::black
);
196 /* Init the cone art */
199 CONNECT( THEMIM
->getIM(), artChanged( QString
),
200 this, updateArt( const QString
& ) );
203 void BackgroundWidget::updateArt( const QString
& url
)
205 if ( !url
.isEmpty() )
211 if( QDate::currentDate().dayOfYear() >= QT_XMAS_JOKE_DAY
&& var_InheritBool( p_intf
, "qt-icon-change" ) )
212 pixmapUrl
= QString( ":/logo/vlc128-xmas.png" );
214 pixmapUrl
= QString( ":/logo/vlc128.png" );
219 void BackgroundWidget::paintEvent( QPaintEvent
*e
)
223 /* we just want background autofill */
224 QWidget::paintEvent( e
);
228 int i_maxwidth
, i_maxheight
;
229 QPixmap pixmap
= QPixmap( pixmapUrl
);
230 QPainter
painter(this);
234 i_maxwidth
= __MIN( maximumWidth(), width() ) - MARGIN
* 2;
235 i_maxheight
= __MIN( maximumHeight(), height() ) - MARGIN
* 2;
237 if ( height() > MARGIN
* 2 )
239 /* Scale down the pixmap if the widget is too small */
240 if( pixmap
.width() > i_maxwidth
|| pixmap
.height() > i_maxheight
)
242 pixmap
= pixmap
.scaled( i_maxwidth
, i_maxheight
,
243 Qt::KeepAspectRatio
, Qt::SmoothTransformation
);
246 if ( b_expandPixmap
&&
247 pixmap
.width() < width() && pixmap
.height() < height() )
249 /* Scale up the pixmap to fill widget's size */
250 f_alpha
= ( (float) pixmap
.height() / (float) height() );
251 pixmap
= pixmap
.scaled(
252 width() - MARGIN
* 2,
253 height() - MARGIN
* 2,
255 ( f_alpha
< .2 )? /* Don't waste cpu when not visible */
256 Qt::SmoothTransformation
:
257 Qt::FastTransformation
259 /* Non agressive alpha compositing when sizing up */
260 pMask
= QBitmap( pixmap
.width(), pixmap
.height() );
261 pMask
.fill( QColor::fromRgbF( 1.0, 1.0, 1.0, f_alpha
) );
262 pixmap
.setMask( pMask
);
266 MARGIN
+ ( i_maxwidth
- pixmap
.width() ) /2,
267 MARGIN
+ ( i_maxheight
- pixmap
.height() ) /2,
270 QWidget::paintEvent( e
);
273 void BackgroundWidget::contextMenuEvent( QContextMenuEvent
*event
)
275 VLCMenuBar::PopupMenu( p_intf
, true );
280 #include <QPushButton>
281 #include <QHBoxLayout>
283 /**********************************************************************
284 * Visualization selector panel
285 **********************************************************************/
286 VisualSelector::VisualSelector( intf_thread_t
*_p_i
) :
287 QFrame( NULL
), p_intf( _p_i
)
289 QHBoxLayout
*layout
= new QHBoxLayout( this );
290 layout
->setMargin( 0 );
291 QPushButton
*prevButton
= new QPushButton( "Prev" );
292 QPushButton
*nextButton
= new QPushButton( "Next" );
293 layout
->addWidget( prevButton
);
294 layout
->addWidget( nextButton
);
296 layout
->addStretch( 10 );
297 layout
->addWidget( new QLabel( qtr( "Current visualization" ) ) );
299 current
= new QLabel( qtr( "None" ) );
300 layout
->addWidget( current
);
302 BUTTONACT( prevButton
, prev() );
303 BUTTONACT( nextButton
, next() );
306 setMaximumHeight( 35 );
309 VisualSelector::~VisualSelector()
312 void VisualSelector::prev()
314 char *psz_new
= aout_VisualPrev( p_intf
);
317 current
->setText( qfu( psz_new
) );
322 void VisualSelector::next()
324 char *psz_new
= aout_VisualNext( p_intf
);
327 current
->setText( qfu( psz_new
) );
333 SpeedLabel::SpeedLabel( intf_thread_t
*_p_intf
, QWidget
*parent
)
334 : QLabel( parent
), p_intf( _p_intf
)
336 tooltipStringPattern
= qtr( "Current playback speed: %1\nClick to adjust" );
338 /* Create the Speed Control Widget */
339 speedControl
= new SpeedControlWidget( p_intf
, this );
340 speedControlMenu
= new QMenu( this );
342 QWidgetAction
*widgetAction
= new QWidgetAction( speedControl
);
343 widgetAction
->setDefaultWidget( speedControl
);
344 speedControlMenu
->addAction( widgetAction
);
346 /* Change the SpeedRate in the Label */
347 CONNECT( THEMIM
->getIM(), rateChanged( float ), this, setRate( float ) );
349 DCONNECT( THEMIM
, inputChanged( input_thread_t
* ),
350 speedControl
, activateOnState() );
352 setFrameStyle( QFrame::StyledPanel
| QFrame::Raised
);
355 setRate( var_InheritFloat( THEPL
, "rate" ) );
358 SpeedLabel::~SpeedLabel()
361 delete speedControlMenu
;
364 /****************************************************************************
365 * Small right-click menu for rate control
366 ****************************************************************************/
368 void SpeedLabel::showSpeedMenu( QPoint pos
)
370 speedControlMenu
->exec( QCursor::pos() - pos
371 + QPoint( -70 + width()/2, height() ) );
374 void SpeedLabel::setRate( float rate
)
377 str
.setNum( rate
, 'f', 2 );
380 setToolTip( tooltipStringPattern
.arg( str
) );
381 speedControl
->updateControls( rate
);
384 /**********************************************************************
385 * Speed control widget
386 **********************************************************************/
387 SpeedControlWidget::SpeedControlWidget( intf_thread_t
*_p_i
, QWidget
*_parent
)
388 : QFrame( _parent
), p_intf( _p_i
)
390 QSizePolicy
sizePolicy( QSizePolicy::Fixed
, QSizePolicy::Maximum
);
391 sizePolicy
.setHorizontalStretch( 0 );
392 sizePolicy
.setVerticalStretch( 0 );
394 speedSlider
= new QSlider( this );
395 speedSlider
->setSizePolicy( sizePolicy
);
396 speedSlider
->setMinimumSize( QSize( 140, 20 ) );
397 speedSlider
->setOrientation( Qt::Horizontal
);
398 speedSlider
->setTickPosition( QSlider::TicksBelow
);
400 speedSlider
->setRange( -34, 34 );
401 speedSlider
->setSingleStep( 1 );
402 speedSlider
->setPageStep( 1 );
403 speedSlider
->setTickInterval( 17 );
405 CONNECT( speedSlider
, valueChanged( int ), this, updateRate( int ) );
407 QToolButton
*normalSpeedButton
= new QToolButton( this );
408 normalSpeedButton
->setMaximumSize( QSize( 26, 16 ) );
409 normalSpeedButton
->setAutoRaise( true );
410 normalSpeedButton
->setText( "1x" );
411 normalSpeedButton
->setToolTip( qtr( "Revert to normal play speed" ) );
413 CONNECT( normalSpeedButton
, clicked(), this, resetRate() );
415 QToolButton
*slowerButton
= new QToolButton( this );
416 slowerButton
->setMaximumSize( QSize( 26, 16 ) );
417 slowerButton
->setAutoRaise( true );
418 slowerButton
->setToolTip( tooltipL
[SLOWER_BUTTON
] );
419 slowerButton
->setIcon( QIcon( iconL
[SLOWER_BUTTON
] ) );
420 CONNECT( slowerButton
, clicked(), THEMIM
->getIM(), slower() );
422 QToolButton
*fasterButton
= new QToolButton( this );
423 fasterButton
->setMaximumSize( QSize( 26, 16 ) );
424 fasterButton
->setAutoRaise( true );
425 fasterButton
->setToolTip( tooltipL
[FASTER_BUTTON
] );
426 fasterButton
->setIcon( QIcon( iconL
[FASTER_BUTTON
] ) );
427 CONNECT( fasterButton
, clicked(), THEMIM
->getIM(), faster() );
429 /* spinBox = new QDoubleSpinBox();
430 spinBox->setDecimals( 2 );
431 spinBox->setMaximum( 32 );
432 spinBox->setMinimum( 0.03F );
433 spinBox->setSingleStep( 0.10F );
434 spinBox->setAlignment( Qt::AlignRight );
436 CONNECT( spinBox, valueChanged( double ), this, updateSpinBoxRate( double ) ); */
438 QGridLayout
* speedControlLayout
= new QGridLayout( this );
439 speedControlLayout
->addWidget( speedSlider
, 0, 0, 1, 3 );
440 speedControlLayout
->addWidget( slowerButton
, 1, 0 );
441 speedControlLayout
->addWidget( normalSpeedButton
, 1, 1, 1, 1, Qt::AlignRight
);
442 speedControlLayout
->addWidget( fasterButton
, 1, 2, 1, 1, Qt::AlignRight
);
443 //speedControlLayout->addWidget( spinBox );
444 speedControlLayout
->setContentsMargins( 0, 0, 0, 0 );
445 speedControlLayout
->setSpacing( 0 );
452 void SpeedControlWidget::activateOnState()
454 speedSlider
->setEnabled( THEMIM
->getIM()->hasInput() );
455 //spinBox->setEnabled( THEMIM->getIM()->hasInput() );
458 void SpeedControlWidget::updateControls( float rate
)
460 if( speedSlider
->isSliderDown() )
462 //We don't want to change anything if the user is using the slider
466 double value
= 17 * log( rate
) / log( 2. );
467 int sliderValue
= (int) ( ( value
> 0 ) ? value
+ .5 : value
- .5 );
469 if( sliderValue
< speedSlider
->minimum() )
471 sliderValue
= speedSlider
->minimum();
473 else if( sliderValue
> speedSlider
->maximum() )
475 sliderValue
= speedSlider
->maximum();
477 lastValue
= sliderValue
;
479 speedSlider
->setValue( sliderValue
);
480 //spinBox->setValue( rate );
483 void SpeedControlWidget::updateRate( int sliderValue
)
485 if( sliderValue
== lastValue
)
488 double speed
= pow( 2, (double)sliderValue
/ 17 );
489 int rate
= INPUT_RATE_DEFAULT
/ speed
;
491 THEMIM
->getIM()->setRate(rate
);
492 //spinBox->setValue( var_InheritFloat( THEPL, "rate" ) );
495 void SpeedControlWidget::updateSpinBoxRate( double r
)
497 var_SetFloat( THEPL
, "rate", r
);
500 void SpeedControlWidget::resetRate()
502 THEMIM
->getIM()->setRate( INPUT_RATE_DEFAULT
);
505 CoverArtLabel::CoverArtLabel( QWidget
*parent
, intf_thread_t
*_p_i
)
506 : QLabel( parent
), p_intf( _p_i
)
508 setContextMenuPolicy( Qt::ActionsContextMenu
);
509 CONNECT( this, updateRequested(), this, askForUpdate() );
511 setMinimumHeight( 128 );
512 setMinimumWidth( 128 );
513 setMaximumHeight( 128 );
514 setScaledContents( false );
515 setAlignment( Qt::AlignCenter
);
517 QList
< QAction
* > artActions
= actions();
518 QAction
*action
= new QAction( qtr( "Download cover art" ), this );
519 CONNECT( action
, triggered(), this, askForUpdate() );
525 CoverArtLabel::~CoverArtLabel()
527 QList
< QAction
* > artActions
= actions();
528 foreach( QAction
*act
, artActions
)
532 void CoverArtLabel::showArtUpdate( const QString
& url
)
535 if( !url
.isEmpty() && pix
.load( url
) )
537 pix
= pix
.scaled( minimumWidth(), maximumHeight(),
538 Qt::KeepAspectRatioByExpanding
,
539 Qt::SmoothTransformation
);
543 pix
= QPixmap( ":/noart.png" );
548 void CoverArtLabel::askForUpdate()
550 THEMIM
->getIM()->requestArtUpdate();
553 TimeLabel::TimeLabel( intf_thread_t
*_p_intf
, TimeLabel::Display _displayType
)
554 : ClickableQLabel(), p_intf( _p_intf
), bufTimer( new QTimer(this) ),
555 buffering( false ), showBuffering(false), bufVal( -1 ), displayType( _displayType
)
557 b_remainingTime
= false;
558 if( _displayType
!= TimeLabel::Elapsed
)
559 b_remainingTime
= getSettings()->value( "MainWindow/ShowRemainingTime", false ).toBool();
560 switch( _displayType
) {
561 case TimeLabel::Elapsed
:
562 setText( " --:-- " );
563 setToolTip( qtr("Elapsed time") );
565 case TimeLabel::Remaining
:
566 setText( " --:-- " );
567 setToolTip( qtr("Total/Remaining time")
569 + qtr("Click to toggle between total and remaining time")
572 case TimeLabel::Both
:
573 setText( " --:--/--:-- " );
574 setToolTip( QString( "- " )
575 + qtr( "Click to toggle between elapsed and remaining time" )
577 + qtr( "Double click to jump to a chosen time position" ) );
580 setAlignment( Qt::AlignRight
| Qt::AlignVCenter
);
582 bufTimer
->setSingleShot( true );
584 CONNECT( THEMIM
->getIM(), positionUpdated( float, int64_t, int ),
585 this, setDisplayPosition( float, int64_t, int ) );
586 CONNECT( THEMIM
->getIM(), cachingChanged( float ),
587 this, updateBuffering( float ) );
588 CONNECT( bufTimer
, timeout(), this, updateBuffering() );
590 setStyleSheet( "padding-left: 4px; padding-right: 4px;" );
593 void TimeLabel::setDisplayPosition( float pos
, int64_t t
, int length
)
595 showBuffering
= false;
600 setMinimumSize( QSize( 0, 0 ) );
601 if( displayType
== TimeLabel::Both
)
602 setText( "--:--/--:--" );
608 int time
= t
/ 1000000;
610 secstotimestr( psz_length
, length
);
611 secstotimestr( psz_time
, ( b_remainingTime
&& length
) ? length
- time
614 // compute the minimum size that will be required for the psz_length
615 // and use it to enforce a minimal size to avoid "dancing" widgets
616 QSize
minsize( 0, 0 );
619 QMargins margins
= contentsMargins();
621 fontMetrics().size( 0, QString( psz_length
), 0, 0 ).width(),
624 minsize
+= QSize( margins
.left() + margins
.right() + 8, 0 ); /* +padding */
626 if ( b_remainingTime
)
627 minsize
+= QSize( fontMetrics().size( 0, "-", 0, 0 ).width(), 0 );
630 switch( displayType
)
632 case TimeLabel::Elapsed
:
633 setMinimumSize( minsize
);
634 setText( QString( psz_time
) );
636 case TimeLabel::Remaining
:
637 if( b_remainingTime
)
639 setMinimumSize( minsize
);
640 setText( QString("-") + QString( psz_time
) );
644 setMinimumSize( QSize( 0, 0 ) );
645 setText( QString( psz_length
) );
648 case TimeLabel::Both
:
650 QString timestr
= QString( "%1%2/%3" )
651 .arg( QString( (b_remainingTime
&& length
) ? "-" : "" ) )
652 .arg( QString( psz_time
) )
653 .arg( QString( ( !length
&& time
) ? "--:--" : psz_length
) );
658 cachedLength
= length
;
661 void TimeLabel::setDisplayPosition( float pos
)
663 if( pos
== -1.f
|| cachedLength
== 0 )
665 setText( " --:--/--:-- " );
669 int time
= pos
* cachedLength
;
670 secstotimestr( psz_time
,
671 ( b_remainingTime
&& cachedLength
?
672 cachedLength
- time
: time
) );
673 QString timestr
= QString( "%1%2/%3" )
674 .arg( QString( (b_remainingTime
&& cachedLength
) ? "-" : "" ) )
675 .arg( QString( psz_time
) )
676 .arg( QString( ( !cachedLength
&& time
) ? "--:--" : psz_length
) );
682 void TimeLabel::toggleTimeDisplay()
684 b_remainingTime
= !b_remainingTime
;
685 getSettings()->setValue( "MainWindow/ShowRemainingTime", b_remainingTime
);
689 void TimeLabel::updateBuffering( float _buffered
)
692 if( !buffering
|| bufVal
== 0 )
694 showBuffering
= false;
696 bufTimer
->start(200);
698 else if( bufVal
== 1 )
700 showBuffering
= buffering
= false;
706 void TimeLabel::updateBuffering()
708 showBuffering
= true;
712 void TimeLabel::paintEvent( QPaintEvent
* event
)
717 r
.setLeft( r
.width() * bufVal
);
720 p
.fillRect( r
, palette().color( QPalette::Highlight
) );
722 QLabel::paintEvent( event
);