X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=modules%2Fgui%2Fqt4%2Fcomponents%2Finterface_widgets.cpp;h=756630b603d228be8c08884d60edf26c75eec159;hb=7bfa5717b05eb83164ec4fe93718696ff7f9997b;hp=5da79af8b216a4138444890ace1668ac8c07db51;hpb=9c1f8ba149532713c7912eb233409b283c150155;p=vlc diff --git a/modules/gui/qt4/components/interface_widgets.cpp b/modules/gui/qt4/components/interface_widgets.cpp index 5da79af8b2..756630b603 100644 --- a/modules/gui/qt4/components/interface_widgets.cpp +++ b/modules/gui/qt4/components/interface_widgets.cpp @@ -1,7 +1,7 @@ /***************************************************************************** * interface_widgets.cpp : Custom widgets for the main interface **************************************************************************** - * Copyright (C) 2006-2008 the VideoLAN team + * Copyright (C) 2006-2010 the VideoLAN team * $Id$ * * Authors: Clément Stenac @@ -41,82 +41,133 @@ #include #include #include +#include +#include +#include +#include +#include #ifdef Q_WS_X11 # include # include +static void videoSync( void ) +{ + /* Make sure the X server has processed all requests. + * This protects other threads using distinct connections from getting + * the video widget window in an inconsistent states. */ + XSync( QX11Info::display(), False ); +} +#else +# define videoSync() (void)0 #endif #include +#include + +class ReparentableWidget : public QWidget +{ +private: + VideoWidget *owner; +public: + ReparentableWidget( VideoWidget *owner ) : owner( owner ) + { + } + +protected: + void keyPressEvent( QKeyEvent *e ) + { + emit owner->keyPressed( e ); + } +}; /********************************************************************** * Video Widget. A simple frame on which video is drawn * This class handles resize issues **********************************************************************/ -VideoWidget::VideoWidget( intf_thread_t *_p_i ) : QFrame( NULL ), p_intf( _p_i ) +VideoWidget::VideoWidget( intf_thread_t *_p_i ) + : QFrame( NULL ) + , p_intf( _p_i ) + , reparentable( NULL ) { - /* Init */ - p_vout = NULL; - videoSize.rwidth() = -1; - videoSize.rheight() = -1; - - hide(); - /* Set the policy to expand in both directions */ -// setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding ); - - /* Black background is more coherent for a Video Widget */ - QPalette plt = palette(); - plt.setColor( QPalette::Window, Qt::black ); - setPalette( plt ); - setAutoFillBackground(true); + // setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding ); - /* Indicates that the widget wants to draw directly onto the screen. - Widgets with this attribute set do not participate in composition - management */ - setAttribute( Qt::WA_PaintOnScreen, true ); -} - -void VideoWidget::paintEvent(QPaintEvent *ev) -{ - QFrame::paintEvent(ev); -#ifdef Q_WS_X11 - XFlush( QX11Info::display() ); -#endif + layout = new QHBoxLayout( this ); + layout->setContentsMargins( 0, 0, 0, 0 ); + setLayout( layout ); } VideoWidget::~VideoWidget() { /* Ensure we are not leaking the video output. This would crash. */ - assert( !p_vout ); + assert( reparentable == NULL ); } /** * Request the video to avoid the conflicts **/ -WId VideoWidget::request( vout_thread_t *p_nvout, int *pi_x, int *pi_y, +WId VideoWidget::request( int *pi_x, int *pi_y, unsigned int *pi_width, unsigned int *pi_height, bool b_keep_size ) { msg_Dbg( p_intf, "Video was requested %i, %i", *pi_x, *pi_y ); + if( reparentable != NULL ) + { + msg_Dbg( p_intf, "embedded video already in use" ); + return NULL; + } if( b_keep_size ) { *pi_width = size().width(); *pi_height = size().height(); } - if( p_vout ) - { - msg_Dbg( p_intf, "embedded video already in use" ); - return NULL; - } - p_vout = p_nvout; + /* The Qt4 UI needs a fixed a widget ("this"), so that the parent layout is + * not messed up when we the video is reparented. Hence, we create an extra + * reparentable widget, that will be within the VideoWidget in windowed + * mode, and within the root window (NULL parent) in full-screen mode. + */ + reparentable = new ReparentableWidget( this ); + QLayout *innerLayout = new QHBoxLayout( reparentable ); + innerLayout->setContentsMargins( 0, 0, 0, 0 ); + + /* The owner of the video window needs a stable handle (WinId). Reparenting + * in Qt4-X11 changes the WinId of the widget, so we need to create another + * dummy widget that stays within the reparentable widget. */ + QWidget *stable = new QWidget(); + QPalette plt = palette(); + plt.setColor( QPalette::Window, Qt::black ); + stable->setPalette( plt ); + stable->setAutoFillBackground(true); + /* Indicates that the widget wants to draw directly onto the screen. + Widgets with this attribute set do not participate in composition + management */ + stable->setAttribute( Qt::WA_PaintOnScreen, true ); + + innerLayout->addWidget( stable ); + + layout->addWidget( reparentable ); + +#ifdef Q_WS_X11 + /* HACK: Only one X11 client can subscribe to mouse button press events. + * VLC currently handles those in the video display. + * Force Qt4 to unsubscribe from mouse press and release events. */ + Display *dpy = QX11Info::display(); + Window w = stable->winId(); + XWindowAttributes attr; + + XGetWindowAttributes( dpy, w, &attr ); + attr.your_event_mask &= ~(ButtonPressMask|ButtonReleaseMask); + XSelectInput( dpy, w, attr.your_event_mask ); +#endif + videoSync(); #ifndef NDEBUG - msg_Dbg( p_intf, "embedded video ready (handle %p)", (void *)winId() ); + msg_Dbg( p_intf, "embedded video ready (handle %p)", + (void *)stable->winId() ); #endif - return winId(); + return stable->winId(); } /* Set the Widget to the correct Size */ @@ -124,94 +175,188 @@ WId VideoWidget::request( vout_thread_t *p_nvout, int *pi_x, int *pi_y, Parent has to care about resizing itself */ void VideoWidget::SetSizing( unsigned int w, unsigned int h ) { - msg_Dbg( p_intf, "Video is resizing to: %i %i", w, h ); - videoSize.rwidth() = w; - videoSize.rheight() = h; + if (reparentable->windowState() & Qt::WindowFullScreen ) + return; if( !isVisible() ) show(); - updateGeometry(); // Needed for deinterlace + resize( w, h ); + emit sizeChanged( w, h ); + videoSync(); +} + +void VideoWidget::SetFullScreen( bool b_fs ) +{ + const Qt::WindowStates curstate = reparentable->windowState(); + Qt::WindowStates newstate = curstate; + Qt::WindowFlags newflags = reparentable->windowFlags(); + + + if( b_fs ) + { + newstate |= Qt::WindowFullScreen; + newflags |= Qt::WindowStaysOnTopHint; + } + else + { + newstate &= ~Qt::WindowFullScreen; + newflags &= ~Qt::WindowStaysOnTopHint; + } + if( newstate == curstate ) + return; /* no changes needed */ + + if( b_fs ) + { /* Go full-screen */ + int numscreen = var_InheritInteger( p_intf, "qt-fullscreen-screennumber" ); + /* if user hasn't defined screennumber, or screennumber that is bigger + * than current number of screens, take screennumber where current interface + * is + */ + if( numscreen == -1 || numscreen > QApplication::desktop()->numScreens() ) + numscreen = QApplication::desktop()->screenNumber( p_intf->p_sys->p_mi ); + + QRect screenres = QApplication::desktop()->screenGeometry( numscreen ); + + reparentable->setParent( NULL, newflags ); + reparentable->setWindowState( newstate ); + /* To be sure window is on proper-screen in xinerama */ + if( !screenres.contains( reparentable->pos() ) ) + { + msg_Dbg( p_intf, "Moving video to correct screen"); + reparentable->move( QPoint( screenres.x(), screenres.y() ) ); + } + reparentable->show(); + } + else + { /* Go windowed */ + reparentable->setWindowFlags( newflags ); + reparentable->setWindowState( newstate ); + layout->addWidget( reparentable ); + } + videoSync(); } void VideoWidget::release( void ) { msg_Dbg( p_intf, "Video is not needed anymore" ); - p_vout = NULL; - videoSize.rwidth() = 0; - videoSize.rheight() = 0; + //layout->removeWidget( reparentable ); + +#ifdef WIN32 + /* Come back to default thumbnail for Windows 7 taskbar */ + LPTASKBARLIST3 p_taskbl; + + CoInitialize( 0 ); + + if( S_OK == CoCreateInstance( &clsid_ITaskbarList, + NULL, CLSCTX_INPROC_SERVER, + &IID_ITaskbarList3, + (void **)&p_taskbl) ) + { + p_taskbl->vt->HrInit(p_taskbl); + + HWND hroot = GetAncestor(reparentable->winId(),GA_ROOT); + + if (S_OK != p_taskbl->vt->SetThumbnailClip(p_taskbl, hroot, NULL)) + msg_Err(p_intf, "SetThumbNailClip failed"); + msg_Err(p_intf, "Releasing taskbar | root handle = %08x", hroot); + p_taskbl->vt->Release(p_taskbl); + } + CoUninitialize(); + +#endif + + delete reparentable; + reparentable = NULL; updateGeometry(); hide(); } -QSize VideoWidget::sizeHint() const -{ - return videoSize; -} - /********************************************************************** * Background Widget. Show a simple image background. Currently, * it's album art if present or cone. **********************************************************************/ -#define ICON_SIZE 128 -#define MAX_BG_SIZE 400 -#define MIN_BG_SIZE 128 BackgroundWidget::BackgroundWidget( intf_thread_t *_p_i ) - :QWidget( NULL ), p_intf( _p_i ) + :QWidget( NULL ), p_intf( _p_i ), b_expandPixmap( false ) { - /* We should use that one to take the more size it can */ - setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding); - /* A dark background */ setAutoFillBackground( true ); - plt = palette(); + QPalette plt = palette(); plt.setColor( QPalette::Active, QPalette::Window , Qt::black ); plt.setColor( QPalette::Inactive, QPalette::Window , Qt::black ); setPalette( plt ); - /* A cone in the middle */ - label = new QLabel; - label->setMargin( 5 ); - label->setMaximumHeight( MAX_BG_SIZE ); - label->setMaximumWidth( MAX_BG_SIZE ); - label->setMinimumHeight( MIN_BG_SIZE ); - label->setMinimumWidth( MIN_BG_SIZE ); - if( QDate::currentDate().dayOfYear() >= 354 ) - label->setPixmap( QPixmap( ":/vlc128-christmas.png" ) ); - else - label->setPixmap( QPixmap( ":/vlc128.png" ) ); - - QGridLayout *backgroundLayout = new QGridLayout( this ); - backgroundLayout->addWidget( label, 0, 1 ); - backgroundLayout->setColumnStretch( 0, 1 ); - backgroundLayout->setColumnStretch( 2, 1 ); + /* Init the cone art */ + updateArt( "" ); CONNECT( THEMIM->getIM(), artChanged( QString ), this, updateArt( const QString& ) ); } -BackgroundWidget::~BackgroundWidget() -{} - void BackgroundWidget::resizeEvent( QResizeEvent * event ) { - if( event->size().height() <= MIN_BG_SIZE ) - label->hide(); - else - label->show(); + updateArt( "" ); + QWidget::resizeEvent( event ); } void BackgroundWidget::updateArt( const QString& url ) { - if( url.isEmpty() ) + if ( !url.isEmpty() ) { + pixmapUrl = url; + } + else + { /* Xmas joke */ if( QDate::currentDate().dayOfYear() >= 354 ) - label->setPixmap( QPixmap( ":/vlc128-christmas.png" ) ); + pixmapUrl = QString( ":/logo/vlc128-christmas.png" ); else - label->setPixmap( QPixmap( ":/vlc128.png" ) ); + pixmapUrl = QString( ":/logo/vlc128.png" ); } - else +} + +void BackgroundWidget::paintEvent( QPaintEvent *e ) +{ + int i_maxwidth, i_maxheight; + QPixmap pixmap = QPixmap( pixmapUrl ); + QPainter painter(this); + QBitmap pMask; + float f_alpha = 1.0; + + i_maxwidth = std::min( maximumWidth(), width() ) - MARGIN * 2; + i_maxheight = std::min( maximumHeight(), height() ) - MARGIN * 2; + + if ( height() > MARGIN * 2 ) { - label->setPixmap( QPixmap( url ) ); + /* Scale down the pixmap if the widget is too small */ + if( pixmap.width() > i_maxwidth || pixmap.height() > i_maxheight ) + { + pixmap = pixmap.scaled( i_maxwidth, i_maxheight, + Qt::KeepAspectRatio, Qt::SmoothTransformation ); + } + else + if ( b_expandPixmap && + pixmap.width() < width() && pixmap.height() < height() ) + { + /* Scale up the pixmap to fill widget's size */ + f_alpha = ( (float) pixmap.height() / (float) height() ); + pixmap = pixmap.scaled( + width() - MARGIN * 2, + height() - MARGIN * 2, + Qt::KeepAspectRatio, + ( f_alpha < .2 )? /* Don't waste cpu when not visible */ + Qt::SmoothTransformation: + Qt::FastTransformation + ); + /* Non agressive alpha compositing when sizing up */ + pMask = QBitmap( pixmap.width(), pixmap.height() ); + pMask.fill( QColor::fromRgbF( 1.0, 1.0, 1.0, f_alpha ) ); + pixmap.setMask( pMask ); + } + + painter.drawPixmap( + MARGIN + ( i_maxwidth - pixmap.width() ) /2, + MARGIN + ( i_maxheight - pixmap.height() ) /2, + pixmap); } + QWidget::paintEvent( e ); } void BackgroundWidget::contextMenuEvent( QContextMenuEvent *event ) @@ -274,9 +419,8 @@ void VisualSelector::next() } #endif -SpeedLabel::SpeedLabel( intf_thread_t *_p_intf, const QString& text, - QWidget *parent ) - : QLabel( text, parent ), p_intf( _p_intf ) +SpeedLabel::SpeedLabel( intf_thread_t *_p_intf, QWidget *parent ) + : QLabel( parent ), p_intf( _p_intf ) { setToolTip( qtr( "Current playback speed.\nClick to adjust" ) ); @@ -289,30 +433,33 @@ SpeedLabel::SpeedLabel( intf_thread_t *_p_intf, const QString& text, speedControlMenu->addAction( widgetAction ); /* Change the SpeedRate in the Status Bar */ - CONNECT( THEMIM->getIM(), rateChanged( int ), this, setRate( int ) ); - - CONNECT( THEMIM, inputChanged( input_thread_t * ), - speedControl, activateOnState() ); + CONNECT( THEMIM->getIM(), rateChanged( float ), this, setRate( float ) ); + DCONNECT( THEMIM, inputChanged( input_thread_t * ), + speedControl, activateOnState() ); + setRate( var_InheritFloat( p_intf, "rate" ) ); } + SpeedLabel::~SpeedLabel() { - delete speedControl; - delete speedControlMenu; + delete speedControl; + delete speedControlMenu; } + /**************************************************************************** * Small right-click menu for rate control ****************************************************************************/ + void SpeedLabel::showSpeedMenu( QPoint pos ) { speedControlMenu->exec( QCursor::pos() - pos + QPoint( 0, height() ) ); } -void SpeedLabel::setRate( int rate ) +void SpeedLabel::setRate( float rate ) { QString str; - str.setNum( ( 1000 / (double)rate ), 'f', 2 ); + str.setNum( rate, 'f', 2 ); str.append( "x" ); setText( str ); setToolTip( str ); @@ -351,7 +498,7 @@ SpeedControlWidget::SpeedControlWidget( intf_thread_t *_p_i, QWidget *_parent ) CONNECT( normalSpeedButton, clicked(), this, resetRate() ); QVBoxLayout *speedControlLayout = new QVBoxLayout( this ); - speedControlLayout->setLayoutMargins( 4, 4, 4, 4, 4 ); + speedControlLayout->setContentsMargins( 4, 4, 4, 4 ); speedControlLayout->setSpacing( 4 ); speedControlLayout->addWidget( speedSlider ); speedControlLayout->addWidget( normalSpeedButton ); @@ -364,7 +511,7 @@ void SpeedControlWidget::activateOnState() speedSlider->setEnabled( THEMIM->getIM()->hasInput() ); } -void SpeedControlWidget::updateControls( int rate ) +void SpeedControlWidget::updateControls( float rate ) { if( speedSlider->isSliderDown() ) { @@ -372,7 +519,7 @@ void SpeedControlWidget::updateControls( int rate ) return; } - double value = 17 * log( (double)INPUT_RATE_DEFAULT / rate ) / log( 2 ); + double value = 17 * log( rate ) / log( 2 ); int sliderValue = (int) ( ( value > 0 ) ? value + .5 : value - .5 ); if( sliderValue < speedSlider->minimum() ) @@ -384,10 +531,7 @@ void SpeedControlWidget::updateControls( int rate ) sliderValue = speedSlider->maximum(); } - //Block signals to avoid feedback loop - speedSlider->blockSignals( true ); speedSlider->setValue( sliderValue ); - speedSlider->blockSignals( false ); } void SpeedControlWidget::updateRate( int sliderValue ) @@ -404,24 +548,24 @@ void SpeedControlWidget::resetRate() } CoverArtLabel::CoverArtLabel( QWidget *parent, intf_thread_t *_p_i ) - : QLabel( parent ), p_intf( _p_i ) + : QLabel( parent ), p_intf( _p_i ) { setContextMenuPolicy( Qt::ActionsContextMenu ); - CONNECT( this, updateRequested(), this, doUpdate() ); - CONNECT( THEMIM->getIM(), artChanged( QString ), - this, doUpdate( const QString& ) ); + CONNECT( this, updateRequested(), this, askForUpdate() ); setMinimumHeight( 128 ); setMinimumWidth( 128 ); setMaximumHeight( 128 ); setMaximumWidth( 128 ); - setScaledContents( true ); + setScaledContents( false ); + setAlignment( Qt::AlignCenter ); + QList< QAction* > artActions = actions(); QAction *action = new QAction( qtr( "Download cover art" ), this ); + CONNECT( action, triggered(), this, askForUpdate() ); addAction( action ); - CONNECT( action, triggered(), this, doUpdate() ); - doUpdate(); + showArtUpdate( "" ); } CoverArtLabel::~CoverArtLabel() @@ -431,71 +575,127 @@ CoverArtLabel::~CoverArtLabel() removeAction( act ); } -void CoverArtLabel::doUpdate( const QString& url ) +void CoverArtLabel::showArtUpdate( const QString& url ) { QPixmap pix; - if( !url.isEmpty() && pix.load( url ) ) + if( !url.isEmpty() && pix.load( url ) ) { - setPixmap( pix ); + pix = pix.scaled( maximumWidth(), maximumHeight(), + Qt::KeepAspectRatioByExpanding ); } else { - setPixmap( QPixmap( ":/noart.png" ) ); + pix = QPixmap( ":/noart.png" ); } + setPixmap( pix ); } -void CoverArtLabel::doUpdate() +void CoverArtLabel::askForUpdate() { THEMIM->getIM()->requestArtUpdate(); } -TimeLabel::TimeLabel( intf_thread_t *_p_intf ) :QLabel(), p_intf( _p_intf ) +TimeLabel::TimeLabel( intf_thread_t *_p_intf ) + : QLabel(), p_intf( _p_intf ), bufTimer( new QTimer(this) ), + buffering( false ), showBuffering(false), bufVal( -1 ) { - b_remainingTime = false; - setText( " --:--/--:-- " ); - setAlignment( Qt::AlignRight | Qt::AlignVCenter ); - setToolTip( qtr( "Toggle between elapsed and remaining time" ) ); - - - CONNECT( THEMIM->getIM(), cachingChanged( float ), - this, setCaching( float ) ); - CONNECT( THEMIM->getIM(), positionUpdated( float, int, int ), - this, setDisplayPosition( float, int, int ) ); + b_remainingTime = false; + setText( " --:--/--:-- " ); + setAlignment( Qt::AlignRight | Qt::AlignVCenter ); + setToolTip( qtr( "Toggle between elapsed and remaining time" ) ); + bufTimer->setSingleShot( true ); + + CONNECT( THEMIM->getIM(), positionUpdated( float, int64_t, int ), + this, setDisplayPosition( float, int64_t, int ) ); + CONNECT( THEMIM->getIM(), cachingChanged( float ), + this, updateBuffering( float ) ); + CONNECT( bufTimer, timeout(), this, updateBuffering() ); } -void TimeLabel::setDisplayPosition( float pos, int time, int length ) +void TimeLabel::setDisplayPosition( float pos, int64_t t, int length ) { + showBuffering = false; + bufTimer->stop(); + if( pos == -1.f ) { setText( " --:--/--:-- " ); return; } - char psz_length[MSTRTIME_MAX_SIZE], psz_time[MSTRTIME_MAX_SIZE]; + int time = t / 1000000; + secstotimestr( psz_length, length ); secstotimestr( psz_time, ( b_remainingTime && length ) ? length - time : time ); QString timestr; - timestr.sprintf( "%s/%s", psz_time, - ( !length && time ) ? "--:--" : psz_length ); + timestr.sprintf( " %s%s/%s ", (b_remainingTime && length) ? "-" : "", + psz_time, ( !length && time ) ? "--:--" : psz_length ); - /* Add a minus to remaining time*/ - if( b_remainingTime && length ) setText( " -"+timestr+" " ); - else setText( " "+timestr+" " ); + setText( timestr ); + + cachedLength = length; } +void TimeLabel::setDisplayPosition( float pos ) +{ + if( pos == -1.f || cachedLength == 0 ) + { + setText( " --:--/--:-- " ); + return; + } + + int time = pos * cachedLength; + secstotimestr( psz_time, + ( b_remainingTime && cachedLength ? + cachedLength - time : time ) ); + QString timestr; + timestr.sprintf( " %s%s/%s ", (b_remainingTime && cachedLength) ? "-" : "", + psz_time, ( !cachedLength && time ) ? "--:--" : psz_length ); + + setText( timestr ); +} + + void TimeLabel::toggleTimeDisplay() { b_remainingTime = !b_remainingTime; } -void TimeLabel::setCaching( float f_cache ) + +void TimeLabel::updateBuffering( float _buffered ) { - QString amount; - amount.setNum( (int)(100 * f_cache) ); - msg_Dbg( p_intf, "New caching: %d", (int)(100*f_cache)); - setText( "Buff: " + amount + "%" ); + bufVal = _buffered; + if( !buffering || bufVal == 0 ) + { + showBuffering = false; + buffering = true; + bufTimer->start(200); + } + else if( bufVal == 1 ) + { + showBuffering = buffering = false; + bufTimer->stop(); + } + update(); } +void TimeLabel::updateBuffering() +{ + showBuffering = true; + update(); +} +void TimeLabel::paintEvent( QPaintEvent* event ) +{ + if( showBuffering ) + { + QRect r( rect() ); + r.setLeft( r.width() * bufVal ); + QPainter p( this ); + p.setOpacity( 0.4 ); + p.fillRect( r, palette().color( QPalette::Highlight ) ); + } + QLabel::paintEvent( event ); +}