qt(wayland): fix video widget failing to display video.
[vlc.git] / modules / gui / qt / components / interface_widgets.cpp
blob98dfdebfbe1311ed52b8f4d6548e480bb05c6da0
1 /*****************************************************************************
2 * interface_widgets.cpp : Custom widgets for the main interface
3 ****************************************************************************
4 * Copyright (C) 2006-2010 the VideoLAN team
5 * $Id$
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 *****************************************************************************/
27 #ifdef HAVE_CONFIG_H
28 # include "config.h"
29 #endif
31 #include "qt.hpp"
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 */
38 #include <QLabel>
39 #include <QToolButton>
40 #include <QPalette>
41 #include <QEvent>
42 #include <QResizeEvent>
43 #include <QDate>
44 #include <QMenu>
45 #include <QWidgetAction>
46 #include <QDesktopWidget>
47 #include <QPainter>
48 #include <QTimer>
49 #include <QSlider>
50 #include <QBitmap>
51 #include <QUrl>
53 #if defined (QT5_HAS_X11)
54 # include <X11/Xlib.h>
55 # include <QX11Info>
56 # if defined(QT5_HAS_XCB)
57 # include <xcb/xproto.h>
58 # endif
59 #endif
60 #ifdef QT5_HAS_WAYLAND
61 # include QPNI_HEADER
62 # include <QWindow>
63 #endif
65 #if defined(_WIN32)
66 #include <QWindow>
67 #include <qpa/qplatformnativeinterface.h>
68 #endif
70 #include <math.h>
71 #include <assert.h>
73 #include <vlc_vout.h>
74 #include <vlc_vout_window.h>
76 /**********************************************************************
77 * Video Widget. A simple frame on which video is drawn
78 * This class handles resize issues
79 **********************************************************************/
81 VideoWidget::VideoWidget( intf_thread_t *_p_i, QWidget* p_parent )
82 : QFrame( p_parent ) , p_intf( _p_i )
84 /* Set the policy to expand in both directions */
85 // setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding );
87 layout = new QHBoxLayout( this );
88 layout->setContentsMargins( 0, 0, 0, 0 );
89 stable = NULL;
90 p_window = NULL;
91 show();
94 VideoWidget::~VideoWidget()
96 /* Ensure we are not leaking the video output. This would crash. */
97 assert( !stable );
98 assert( !p_window );
101 void VideoWidget::sync( void )
103 /* Make sure the X server has processed all requests.
104 * This protects other threads using distinct connections from getting
105 * the video widget window in an inconsistent states. */
106 #ifdef QT5_HAS_X11
107 if( QX11Info::isPlatformX11() )
108 XSync( QX11Info::display(), False );
109 #endif
113 * Request the video to avoid the conflicts
115 bool VideoWidget::request( struct vout_window_t *p_wnd )
117 if( stable )
119 msg_Dbg( p_intf, "embedded video already in use" );
120 return false;
122 assert( !p_window );
124 /* The owner of the video window needs a stable handle (WinId). Reparenting
125 * in Qt4-X11 changes the WinId of the widget, so we need to create another
126 * dummy widget that stays within the reparentable widget. */
127 stable = new QWidget();
128 stable->setContextMenuPolicy( Qt::PreventContextMenu );
129 QPalette plt = palette();
130 plt.setColor( QPalette::Window, Qt::black );
131 stable->setPalette( plt );
132 stable->setAutoFillBackground(true);
133 /* Force the widget to be native so that it gets a winId() */
134 stable->setAttribute( Qt::WA_NativeWindow, true );
135 /* Indicates that the widget wants to draw directly onto the screen.
136 Widgets with this attribute set do not participate in composition
137 management */
138 /* This is currently disabled on X11 as it does not seem to improve
139 * performance, but causes the video widget to be transparent... */
140 #if !defined (QT5_HAS_X11)
141 stable->setAttribute( Qt::WA_PaintOnScreen, true );
142 #else
143 stable->setMouseTracking( true );
144 setMouseTracking( true );
145 #endif
146 layout->addWidget( stable );
148 sync();
149 p_window = p_wnd;
151 p_wnd->type = p_intf->p_sys->voutWindowType;
152 switch( p_wnd->type )
154 case VOUT_WINDOW_TYPE_XID:
155 p_wnd->handle.xid = stable->winId();
156 p_wnd->display.x11 = NULL;
157 break;
158 case VOUT_WINDOW_TYPE_HWND:
159 p_wnd->handle.hwnd = (void *)stable->winId();
160 break;
161 case VOUT_WINDOW_TYPE_NSOBJECT:
162 p_wnd->handle.nsobject = (void *)stable->winId();
163 break;
164 #ifdef QT5_HAS_WAYLAND
165 case VOUT_WINDOW_TYPE_WAYLAND:
167 /* Ensure only the video widget is native (needed for Wayland) */
168 stable->setAttribute( Qt::WA_DontCreateNativeAncestors, true);
170 QWindow *window = stable->windowHandle();
171 assert(window != NULL);
172 window->create();
174 QPlatformNativeInterface *qni = qApp->platformNativeInterface();
175 assert(qni != NULL);
177 p_wnd->handle.wl = static_cast<wl_surface*>(
178 qni->nativeResourceForWindow(QByteArrayLiteral("surface"),
179 window));
180 p_wnd->display.wl = static_cast<wl_display*>(
181 qni->nativeResourceForIntegration(QByteArrayLiteral("wl_display")));
182 break;
184 #endif
185 default:
186 vlc_assert_unreachable();
188 return true;
191 QSize VideoWidget::physicalSize() const
193 #ifdef QT5_HAS_X11
194 if ( QX11Info::isPlatformX11() )
196 Display *p_x_display = QX11Info::display();
197 Window x_window = stable->winId();
198 XWindowAttributes x_attributes;
200 XGetWindowAttributes( p_x_display, x_window, &x_attributes );
202 return QSize( x_attributes.width, x_attributes.height );
204 #endif
205 #if defined(_WIN32)
206 HWND hwnd;
207 RECT rect;
209 QWindow *window = windowHandle();
210 hwnd = static_cast<HWND>(QGuiApplication::platformNativeInterface()->nativeResourceForWindow("handle", window));
212 GetClientRect(hwnd, &rect);
214 return QSize( rect.right, rect.bottom );
215 #endif
217 QSize current_size = size();
219 # if HAS_QT56
220 /* Android-like scaling */
221 current_size *= devicePixelRatioF();
222 # else
223 /* OSX-like scaling */
224 current_size *= devicePixelRatio();
225 # endif
227 return current_size;
230 void VideoWidget::reportSize()
232 if( !p_window )
233 return;
235 QSize size = physicalSize();
236 vout_window_ReportSize( p_window, size.width(), size.height() );
239 /* Set the Widget to the correct Size */
240 /* Function has to be called by the parent
241 Parent has to care about resizing itself */
242 void VideoWidget::setSize( unsigned int w, unsigned int h )
244 /* If the size changed, resizeEvent will be called, otherwise not,
245 * in which case we need to tell the vout what the size actually is
247 if( (unsigned)size().width() == w && (unsigned)size().height() == h )
249 reportSize();
250 return;
253 resize( w, h );
254 emit sizeChanged( w, h );
255 /* Work-around a bug?misconception? that would happen when vout core resize
256 twice to the same size and would make the vout not centered.
257 This cause a small flicker.
258 See #3621
260 if( (unsigned)size().width() == w && (unsigned)size().height() == h )
261 updateGeometry();
262 sync();
265 bool VideoWidget::nativeEvent( const QByteArray& eventType, void* message, long* )
267 #if defined(QT5_HAS_X11)
268 # if defined(QT5_HAS_XCB)
269 if ( eventType == "xcb_generic_event_t" )
271 const xcb_generic_event_t* xev = static_cast<const xcb_generic_event_t*>( message );
273 if ( xev->response_type == XCB_CONFIGURE_NOTIFY )
274 reportSize();
276 # endif
277 #endif
278 #ifdef _WIN32
279 if ( eventType == "windows_generic_MSG" )
281 MSG* msg = static_cast<MSG*>( message );
282 if ( msg->message == WM_SIZE )
283 reportSize();
285 #endif
286 // Let Qt handle that event in any case
287 return false;
290 void VideoWidget::resizeEvent( QResizeEvent *event )
292 QWidget::resizeEvent( event );
294 if ( p_intf->p_sys->voutWindowType == VOUT_WINDOW_TYPE_XID ||
295 p_intf->p_sys->voutWindowType == VOUT_WINDOW_TYPE_HWND )
296 return;
297 reportSize();
300 int VideoWidget::qtMouseButton2VLC( Qt::MouseButton qtButton )
302 if( p_window == NULL )
303 return -1;
304 switch( qtButton )
306 case Qt::LeftButton:
307 return 0;
308 case Qt::RightButton:
309 return 2;
310 case Qt::MiddleButton:
311 return 1;
312 default:
313 return -1;
317 void VideoWidget::mouseReleaseEvent( QMouseEvent *event )
319 int vlc_button = qtMouseButton2VLC( event->button() );
320 if( vlc_button >= 0 )
322 vout_window_ReportMouseReleased( p_window, vlc_button );
323 event->accept();
325 else
326 event->ignore();
329 void VideoWidget::mousePressEvent( QMouseEvent* event )
331 int vlc_button = qtMouseButton2VLC( event->button() );
332 if( vlc_button >= 0 )
334 vout_window_ReportMousePressed( p_window, vlc_button );
335 event->accept();
337 else
338 event->ignore();
341 void VideoWidget::mouseMoveEvent( QMouseEvent *event )
343 if( p_window != NULL )
345 vout_window_ReportMouseMoved( p_window, event->x(), event->y() );
346 event->accept();
348 else
349 event->ignore();
352 void VideoWidget::mouseDoubleClickEvent( QMouseEvent *event )
354 int vlc_button = qtMouseButton2VLC( event->button() );
355 if( vlc_button >= 0 )
357 vout_window_ReportMouseDoubleClick( p_window, vlc_button );
358 event->accept();
360 else
361 event->ignore();
365 void VideoWidget::release( void )
367 msg_Dbg( p_intf, "Video is not needed anymore" );
369 if( stable )
371 layout->removeWidget( stable );
372 stable->deleteLater();
373 stable = NULL;
374 p_window = NULL;
377 updateGeometry();
380 /**********************************************************************
381 * Background Widget. Show a simple image background. Currently,
382 * it's album art if present or cone.
383 **********************************************************************/
385 BackgroundWidget::BackgroundWidget( intf_thread_t *_p_i )
386 :QWidget( NULL ), p_intf( _p_i ), b_expandPixmap( false ), b_withart( true )
388 /* A dark background */
389 setAutoFillBackground( true );
390 QPalette plt = palette();
391 plt.setColor( QPalette::Active, QPalette::Window , Qt::black );
392 plt.setColor( QPalette::Inactive, QPalette::Window , Qt::black );
393 setPalette( plt );
395 /* Init the cone art */
396 defaultArt = QString( ":/logo/vlc128.png" );
397 updateArt( "" );
399 /* fade in animator */
400 setProperty( "opacity", 1.0 );
401 fadeAnimation = new QPropertyAnimation( this, "opacity", this );
402 fadeAnimation->setDuration( 1000 );
403 fadeAnimation->setStartValue( 0.0 );
404 fadeAnimation->setEndValue( 1.0 );
405 fadeAnimation->setEasingCurve( QEasingCurve::OutSine );
406 CONNECT( fadeAnimation, valueChanged( const QVariant & ),
407 this, update() );
409 CONNECT( THEMIM->getIM(), artChanged( QString ),
410 this, updateArt( const QString& ) );
413 void BackgroundWidget::updateArt( const QString& url )
415 if ( !url.isEmpty() )
416 pixmapUrl = url;
417 else
418 pixmapUrl = defaultArt;
419 update();
422 void BackgroundWidget::showEvent( QShowEvent * e )
424 Q_UNUSED( e );
425 if ( b_withart ) fadeAnimation->start();
428 void BackgroundWidget::paintEvent( QPaintEvent *e )
430 if ( !b_withart )
432 /* we just want background autofill */
433 QWidget::paintEvent( e );
434 return;
437 int i_maxwidth, i_maxheight;
438 QPixmap pixmap = QPixmap( pixmapUrl );
439 QPainter painter(this);
441 #if HAS_QT56
442 qreal dpr = devicePixelRatioF();
443 #else
444 qreal dpr = devicePixelRatio();
445 #endif
446 pixmap.setDevicePixelRatio( dpr );
448 i_maxwidth = __MIN( maximumWidth(), width() ) - MARGIN * 2;
449 i_maxheight = __MIN( maximumHeight(), height() ) - MARGIN * 2;
451 painter.setOpacity( property( "opacity" ).toFloat() );
453 if ( height() > MARGIN * 2 )
455 /* Scale down the pixmap if the widget is too small */
456 if( pixmap.width() > i_maxwidth || pixmap.height() > i_maxheight )
458 pixmap = pixmap.scaled( i_maxwidth * dpr, i_maxheight * dpr ,
459 Qt::KeepAspectRatio, Qt::SmoothTransformation );
461 else
462 if ( b_expandPixmap &&
463 pixmap.width() < width() && pixmap.height() < height() )
465 pixmap = pixmap.scaled(
466 (width() - MARGIN * 2) * dpr,
467 (height() - MARGIN * 2) * dpr ,
468 Qt::KeepAspectRatio, Qt::SmoothTransformation);
470 else if (dpr != 1.0)
472 pixmap = pixmap.scaled( pixmap.width() * dpr, pixmap.height() * dpr,
473 Qt::KeepAspectRatio, Qt::SmoothTransformation );
476 painter.drawPixmap(
477 MARGIN + ( i_maxwidth - ( pixmap.width() / dpr ) ) / 2,
478 MARGIN + ( i_maxheight - ( pixmap.height() / dpr ) ) / 2,
479 pixmap);
481 QWidget::paintEvent( e );
484 void BackgroundWidget::contextMenuEvent( QContextMenuEvent *event )
486 THEDP->setPopupMenu();
487 event->accept();
490 EasterEggBackgroundWidget::EasterEggBackgroundWidget( intf_thread_t *p_intf )
491 : BackgroundWidget( p_intf )
493 flakes = new QLinkedList<flake *>();
494 i_rate = 2;
495 i_speed = 1;
496 b_enabled = false;
497 timer = new QTimer( this );
498 timer->setInterval( 100 );
499 CONNECT( timer, timeout(), this, spawnFlakes() );
500 if ( isVisible() && b_enabled ) timer->start();
501 defaultArt = QString( ":/logo/vlc128-xmas.png" );
502 updateArt( "" );
505 EasterEggBackgroundWidget::~EasterEggBackgroundWidget()
507 timer->stop();
508 delete timer;
509 reset();
510 delete flakes;
513 void EasterEggBackgroundWidget::showEvent( QShowEvent *e )
515 if ( b_enabled ) timer->start();
516 BackgroundWidget::showEvent( e );
519 void EasterEggBackgroundWidget::hideEvent( QHideEvent *e )
521 timer->stop();
522 reset();
523 BackgroundWidget::hideEvent( e );
526 void EasterEggBackgroundWidget::resizeEvent( QResizeEvent *e )
528 reset();
529 BackgroundWidget::resizeEvent( e );
532 void EasterEggBackgroundWidget::animate()
534 b_enabled = true;
535 if ( isVisible() ) timer->start();
538 void EasterEggBackgroundWidget::spawnFlakes()
540 if ( ! isVisible() ) return;
542 double w = (double) width() / RAND_MAX;
544 int i_spawn = ( (double) qrand() / RAND_MAX ) * i_rate;
546 QLinkedList<flake *>::iterator it = flakes->begin();
547 while( it != flakes->end() )
549 flake *current = *it;
550 current->point.setY( current->point.y() + i_speed );
551 if ( current->point.y() + i_speed >= height() )
553 delete current;
554 it = flakes->erase( it );
556 else
557 ++it;
560 if ( flakes->size() < MAX_FLAKES )
561 for ( int i=0; i<i_spawn; i++ )
563 flake *f = new flake;
564 f->point.setX( qrand() * w );
565 f->b_fat = ( qrand() < ( RAND_MAX * .33 ) );
566 flakes->append( f );
568 update();
571 void EasterEggBackgroundWidget::reset()
573 while ( !flakes->isEmpty() )
574 delete flakes->takeFirst();
577 void EasterEggBackgroundWidget::paintEvent( QPaintEvent *e )
579 QPainter painter(this);
581 painter.setBrush( QBrush( QColor(Qt::white) ) );
582 painter.setPen( QPen(Qt::white) );
584 QLinkedList<flake *>::const_iterator it = flakes->constBegin();
585 while( it != flakes->constEnd() )
587 const flake * const f = *(it++);
588 if ( f->b_fat )
590 /* Xsnow like :p */
591 painter.drawPoint( f->point.x(), f->point.y() -1 );
592 painter.drawPoint( f->point.x() + 1, f->point.y() );
593 painter.drawPoint( f->point.x(), f->point.y() +1 );
594 painter.drawPoint( f->point.x() - 1, f->point.y() );
596 else
598 painter.drawPoint( f->point );
602 BackgroundWidget::paintEvent( e );
605 SpeedLabel::SpeedLabel( intf_thread_t *_p_intf, QWidget *parent )
606 : QLabel( parent ), p_intf( _p_intf )
608 tooltipStringPattern = qtr( "Current playback speed: %1\nClick to adjust" );
610 /* Create the Speed Control Widget */
611 speedControl = new SpeedControlWidget( p_intf, this );
612 speedControlMenu = new QMenu( this );
614 widgetAction = new QWidgetAction( speedControl );
615 widgetAction->setDefaultWidget( speedControl );
616 speedControlMenu->addAction( widgetAction );
618 /* Change the SpeedRate in the Label */
619 CONNECT( THEMIM->getIM(), rateChanged( float ), this, setRate( float ) );
621 DCONNECT( THEMIM, inputChanged( bool ),
622 speedControl, activateOnState() );
624 setContentsMargins(4, 0, 4, 0);
625 setRate( var_InheritFloat( THEPL, "rate" ) );
628 SpeedLabel::~SpeedLabel()
630 widgetAction->setParent( this );
631 delete speedControlMenu;
634 /****************************************************************************
635 * Small right-click menu for rate control
636 ****************************************************************************/
638 void SpeedLabel::showSpeedMenu( QPoint pos )
640 speedControlMenu->exec( QCursor::pos() - pos
641 + QPoint( -70 + width()/2, height() ) );
644 void SpeedLabel::setRate( float rate )
646 QString str;
647 str.setNum( rate, 'f', 2 );
648 str.append( "x" );
649 setText( str );
650 setToolTip( tooltipStringPattern.arg( str ) );
651 speedControl->updateControls( rate );
654 /**********************************************************************
655 * Speed control widget
656 **********************************************************************/
657 SpeedControlWidget::SpeedControlWidget( intf_thread_t *_p_i, QWidget *_parent )
658 : QFrame( _parent ), p_intf( _p_i )
660 QSizePolicy sizePolicy( QSizePolicy::Fixed, QSizePolicy::Maximum );
661 sizePolicy.setHorizontalStretch( 0 );
662 sizePolicy.setVerticalStretch( 0 );
664 speedSlider = new QSlider( this );
665 speedSlider->setSizePolicy( sizePolicy );
666 speedSlider->setMinimumSize( QSize( 140, 20 ) );
667 speedSlider->setOrientation( Qt::Horizontal );
668 speedSlider->setTickPosition( QSlider::TicksBelow );
670 speedSlider->setRange( -34, 34 );
671 speedSlider->setSingleStep( 1 );
672 speedSlider->setPageStep( 1 );
673 speedSlider->setTickInterval( 17 );
675 CONNECT( speedSlider, valueChanged( int ), this, updateRate( int ) );
677 QToolButton *normalSpeedButton = new QToolButton( this );
678 normalSpeedButton->setMaximumSize( QSize( 26, 16 ) );
679 normalSpeedButton->setAutoRaise( true );
680 normalSpeedButton->setText( "1x" );
681 normalSpeedButton->setToolTip( qtr( "Revert to normal play speed" ) );
683 CONNECT( normalSpeedButton, clicked(), this, resetRate() );
685 QToolButton *slowerButton = new QToolButton( this );
686 slowerButton->setMaximumSize( QSize( 26, 16 ) );
687 slowerButton->setAutoRaise( true );
688 slowerButton->setToolTip( tooltipL[SLOWER_BUTTON] );
689 slowerButton->setIcon( QIcon( iconL[SLOWER_BUTTON] ) );
690 CONNECT( slowerButton, clicked(), THEMIM->getIM(), slower() );
692 QToolButton *fasterButton = new QToolButton( this );
693 fasterButton->setMaximumSize( QSize( 26, 16 ) );
694 fasterButton->setAutoRaise( true );
695 fasterButton->setToolTip( tooltipL[FASTER_BUTTON] );
696 fasterButton->setIcon( QIcon( iconL[FASTER_BUTTON] ) );
697 CONNECT( fasterButton, clicked(), THEMIM->getIM(), faster() );
699 /* spinBox = new QDoubleSpinBox();
700 spinBox->setDecimals( 2 );
701 spinBox->setMaximum( 32 );
702 spinBox->setMinimum( 0.03F );
703 spinBox->setSingleStep( 0.10F );
704 spinBox->setAlignment( Qt::AlignRight );
706 CONNECT( spinBox, valueChanged( double ), this, updateSpinBoxRate( double ) ); */
708 QGridLayout* speedControlLayout = new QGridLayout( this );
709 speedControlLayout->addWidget( speedSlider, 0, 0, 1, 3 );
710 speedControlLayout->addWidget( slowerButton, 1, 0 );
711 speedControlLayout->addWidget( normalSpeedButton, 1, 1, 1, 1, Qt::AlignRight );
712 speedControlLayout->addWidget( fasterButton, 1, 2, 1, 1, Qt::AlignRight );
713 //speedControlLayout->addWidget( spinBox );
714 speedControlLayout->setContentsMargins( 0, 0, 0, 0 );
715 speedControlLayout->setSpacing( 0 );
717 lastValue = 0;
719 activateOnState();
722 void SpeedControlWidget::activateOnState()
724 speedSlider->setEnabled( THEMIM->getIM()->hasInput() );
725 //spinBox->setEnabled( THEMIM->getIM()->hasInput() );
728 void SpeedControlWidget::updateControls( float rate )
730 if( speedSlider->isSliderDown() )
732 //We don't want to change anything if the user is using the slider
733 return;
736 double value = 17 * log( rate ) / log( 2. );
737 int sliderValue = (int) ( ( value > 0 ) ? value + .5 : value - .5 );
739 if( sliderValue < speedSlider->minimum() )
741 sliderValue = speedSlider->minimum();
743 else if( sliderValue > speedSlider->maximum() )
745 sliderValue = speedSlider->maximum();
747 lastValue = sliderValue;
749 speedSlider->setValue( sliderValue );
750 //spinBox->setValue( rate );
753 void SpeedControlWidget::updateRate( int sliderValue )
755 if( sliderValue == lastValue )
756 return;
758 double speed = pow( 2, (double)sliderValue / 17 );
759 int rate = INPUT_RATE_DEFAULT / speed;
761 THEMIM->getIM()->setRate(rate);
762 //spinBox->setValue( var_InheritFloat( THEPL, "rate" ) );
765 void SpeedControlWidget::updateSpinBoxRate( double r )
767 var_SetFloat( THEPL, "rate", r );
770 void SpeedControlWidget::resetRate()
772 THEMIM->getIM()->setRate( INPUT_RATE_DEFAULT );
775 CoverArtLabel::CoverArtLabel( QWidget *parent, intf_thread_t *_p_i )
776 : QLabel( parent ), p_intf( _p_i ), p_item( NULL )
778 setContextMenuPolicy( Qt::ActionsContextMenu );
779 CONNECT( THEMIM->getIM(), artChanged( input_item_t * ),
780 this, showArtUpdate( input_item_t * ) );
782 setMinimumHeight( 128 );
783 setMinimumWidth( 128 );
784 setScaledContents( false );
785 setAlignment( Qt::AlignCenter );
787 QAction *action = new QAction( qtr( "Download cover art" ), this );
788 CONNECT( action, triggered(), this, askForUpdate() );
789 addAction( action );
791 action = new QAction( qtr( "Add cover art from file" ), this );
792 CONNECT( action, triggered(), this, setArtFromFile() );
793 addAction( action );
795 p_item = THEMIM->currentInputItem();
796 if( p_item )
798 input_item_Hold( p_item );
799 showArtUpdate( p_item );
801 else
802 showArtUpdate( "" );
805 CoverArtLabel::~CoverArtLabel()
807 QList< QAction* > artActions = actions();
808 foreach( QAction *act, artActions )
809 removeAction( act );
810 if ( p_item ) input_item_Release( p_item );
813 void CoverArtLabel::setItem( input_item_t *_p_item )
815 if ( p_item ) input_item_Release( p_item );
816 p_item = _p_item;
817 if ( p_item ) input_item_Hold( p_item );
820 void CoverArtLabel::showArtUpdate( const QString& url )
822 QPixmap pix;
823 if( !url.isEmpty() && pix.load( url ) )
825 pix = pix.scaled( minimumWidth(), minimumHeight(),
826 Qt::KeepAspectRatioByExpanding,
827 Qt::SmoothTransformation );
829 else
831 pix = QPixmap( ":/noart.png" );
833 setPixmap( pix );
836 void CoverArtLabel::showArtUpdate( input_item_t *_p_item )
838 /* not for me */
839 if ( _p_item != p_item )
840 return;
842 QString url;
843 if ( _p_item ) url = THEMIM->getIM()->decodeArtURL( _p_item );
844 showArtUpdate( url );
847 void CoverArtLabel::askForUpdate()
849 THEMIM->getIM()->requestArtUpdate( p_item, true );
852 void CoverArtLabel::setArtFromFile()
854 if( !p_item )
855 return;
857 QString filePath = QFileDialog::getOpenFileName( this, qtr( "Choose Cover Art" ),
858 p_intf->p_sys->filepath, qtr( "Image Files (*.gif *.jpg *.jpeg *.png)" ) );
860 if( filePath.isEmpty() )
861 return;
863 QString fileUrl = QUrl::fromLocalFile( filePath ).toString();
865 THEMIM->getIM()->setArt( p_item, fileUrl );
868 void CoverArtLabel::clear()
870 showArtUpdate( "" );
873 TimeLabel::TimeLabel( intf_thread_t *_p_intf, TimeLabel::Display _displayType )
874 : ClickableQLabel(), p_intf( _p_intf ), displayType( _displayType )
876 b_remainingTime = false;
877 if( _displayType != TimeLabel::Elapsed )
878 b_remainingTime = getSettings()->value( "MainWindow/ShowRemainingTime", false ).toBool();
879 switch( _displayType ) {
880 case TimeLabel::Elapsed:
881 setText( " --:-- " );
882 setToolTip( qtr("Elapsed time") );
883 break;
884 case TimeLabel::Remaining:
885 setText( " --:-- " );
886 setToolTip( qtr("Total/Remaining time")
887 + QString("\n-")
888 + qtr("Click to toggle between total and remaining time")
890 break;
891 case TimeLabel::Both:
892 setText( " --:--/--:-- " );
893 setToolTip( QString( "- " )
894 + qtr( "Click to toggle between elapsed and remaining time" )
895 + QString( "\n- " )
896 + qtr( "Double click to jump to a chosen time position" ) );
897 break;
899 setAlignment( Qt::AlignRight | Qt::AlignVCenter );
901 CONNECT( THEMIM->getIM(), positionUpdated( float, int64_t, int ),
902 this, setDisplayPosition( float, int64_t, int ) );
904 connect( this, SIGNAL( broadcastRemainingTime( bool ) ),
905 THEMIM->getIM(), SIGNAL( remainingTimeChanged( bool ) ) );
907 CONNECT( THEMIM->getIM(), remainingTimeChanged( bool ),
908 this, setRemainingTime( bool ) );
910 setStyleSheet( "QLabel { padding-left: 4px; padding-right: 4px; }" );
913 void TimeLabel::setRemainingTime( bool remainingTime )
915 b_remainingTime = remainingTime;
918 void TimeLabel::setDisplayPosition( float pos, int64_t t, int length )
920 if( pos == -1.f )
922 setMinimumSize( QSize( 0, 0 ) );
923 if( displayType == TimeLabel::Both )
924 setText( "--:--/--:--" );
925 else
926 setText( "--:--" );
927 return;
930 int time = t / 1000000;
932 secstotimestr( psz_length, length );
933 secstotimestr( psz_time, ( b_remainingTime && length ) ? length - time
934 : time );
936 // compute the minimum size that will be required for the psz_length
937 // and use it to enforce a minimal size to avoid "dancing" widgets
938 QSize minsize( 0, 0 );
939 if ( length > 0 )
941 QMargins margins = contentsMargins();
942 minsize += QSize(
943 fontMetrics().size( 0, QString( psz_length ), 0, 0 ).width(),
944 sizeHint().height()
946 minsize += QSize( margins.left() + margins.right() + 8, 0 ); /* +padding */
948 if ( b_remainingTime )
949 minsize += QSize( fontMetrics().size( 0, "-", 0, 0 ).width(), 0 );
952 switch( displayType )
954 case TimeLabel::Elapsed:
955 setMinimumSize( minsize );
956 setText( QString( psz_time ) );
957 break;
958 case TimeLabel::Remaining:
959 if( b_remainingTime )
961 setMinimumSize( minsize );
962 setText( QString("-") + QString( psz_time ) );
964 else
966 setMinimumSize( QSize( 0, 0 ) );
967 setText( QString( psz_length ) );
969 break;
970 case TimeLabel::Both:
971 default:
972 QString timestr = QString( "%1%2/%3" )
973 .arg( QString( (b_remainingTime && length) ? "-" : "" ) )
974 .arg( QString( psz_time ) )
975 .arg( QString( ( !length && time ) ? "--:--" : psz_length ) );
977 setText( timestr );
978 break;
980 cachedLength = length;
983 void TimeLabel::setDisplayPosition( float pos )
985 if( pos == -1.f || cachedLength == 0 )
987 setText( " --:--/--:-- " );
988 return;
991 int time = pos * cachedLength;
992 secstotimestr( psz_time,
993 ( b_remainingTime && cachedLength ?
994 cachedLength - time : time ) );
995 QString timestr = QString( "%1%2/%3" )
996 .arg( QString( (b_remainingTime && cachedLength) ? "-" : "" ) )
997 .arg( QString( psz_time ) )
998 .arg( QString( ( !cachedLength && time ) ? "--:--" : psz_length ) );
1000 setText( timestr );
1004 void TimeLabel::toggleTimeDisplay()
1006 b_remainingTime = !b_remainingTime;
1007 getSettings()->setValue( "MainWindow/ShowRemainingTime", b_remainingTime );
1008 emit broadcastRemainingTime( b_remainingTime );