interface_widgets.cpp 22.4 KB
Newer Older
1
/*****************************************************************************
2
 * interface_widgets.cpp : Custom widgets for the main interface
3
 ****************************************************************************
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
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 13 14
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
15
 * ( at your option ) any later version.
16 17 18 19 20 21 22 23 24 25
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 *****************************************************************************/
26

27 28 29
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
30

31
#include "qt4.hpp"
32
#include "components/interface_widgets.hpp"
33 34
#include "dialogs_provider.hpp"
#include "util/customwidgets.hpp"               // qtEventToVLCKey, QVLCStackedWidget
35 36 37 38

#include "menus.hpp"             /* Popup menu on bgWidget */

#include <vlc_vout.h>
39

Clément Stenac's avatar
Clément Stenac committed
40
#include <QLabel>
41
#include <QToolButton>
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
42
#include <QPalette>
43
#include <QEvent>
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
44
#include <QResizeEvent>
45
#include <QDate>
46 47
#include <QMenu>
#include <QWidgetAction>
48
#include <QDesktopWidget>
49
#include <QPainter>
50
#include <QTimer>
51
#include <QSlider>
François Cartegnie's avatar
François Cartegnie committed
52
#include <QBitmap>
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
53

54
#ifdef Q_WS_X11
55 56
#   include <X11/Xlib.h>
#   include <qx11info_x11.h>
57
#endif
58

59
#include <math.h>
60
#include <assert.h>
61

62 63 64 65
/**********************************************************************
 * Video Widget. A simple frame on which video is drawn
 * This class handles resize issues
 **********************************************************************/
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 */
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
71
    // setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding );
72

73 74
    layout = new QHBoxLayout( this );
    layout->setContentsMargins( 0, 0, 0, 0 );
75
    stable = NULL;
76
    show();
77 78 79 80
}

VideoWidget::~VideoWidget()
{
81
    /* Ensure we are not leaking the video output. This would crash. */
82 83 84 85 86 87 88 89 90 91 92
    assert( !stable );
}

void VideoWidget::sync( void )
{
#ifdef Q_WS_X11
    /* 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 );
#endif
93 94
}

95
/**
96
 * Request the video to avoid the conflicts
97
 **/
98
WId VideoWidget::request( int *pi_x, int *pi_y,
99 100
                          unsigned int *pi_width, unsigned int *pi_height,
                          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" );
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
107
        return 0;
108
    }
109 110 111 112 113 114
    if( b_keep_size )
    {
        *pi_width  = size().width();
        *pi_height = size().height();
    }

115 116 117
    /* 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. */
118
    stable = new QWidget();
119 120 121 122
    QPalette plt = palette();
    plt.setColor( QPalette::Window, Qt::black );
    stable->setPalette( plt );
    stable->setAutoFillBackground(true);
123 124
    /* Force the widget to be native so that it gets a winId() */
    stable->setAttribute( Qt::WA_NativeWindow, true );
125 126 127
    /* Indicates that the widget wants to draw directly onto the screen.
       Widgets with this attribute set do not participate in composition
       management */
128 129
    /* This is currently disabled on X11 as it does not seem to improve
     * performance, but causes the video widget to be transparent... */
130
#if !defined (Q_WS_X11) && !defined (Q_WS_QPA)
131
    stable->setAttribute( Qt::WA_PaintOnScreen, true );
132
#endif
133

134
    layout->addWidget( stable );
135

136
#ifdef Q_WS_X11
137
    /* HACK: Only one X11 client can subscribe to mouse button press events.
138
     * VLC currently handles those in the video display.
139
     * Force Qt4 to unsubscribe from mouse press and release events. */
140 141 142 143 144
    Display *dpy = QX11Info::display();
    Window w = stable->winId();
    XWindowAttributes attr;

    XGetWindowAttributes( dpy, w, &attr );
145 146
    attr.your_event_mask &= ~(ButtonPressMask|ButtonReleaseMask);
    XSelectInput( dpy, w, attr.your_event_mask );
147
#endif
148
    sync();
149
    return stable->winId();
150 151
}

152
/* Set the Widget to the correct Size */
153
/* Function has to be called by the parent
Rémi Denis-Courmont's avatar
typo  
Rémi Denis-Courmont committed
154
   Parent has to care about resizing itself */
155
void VideoWidget::SetSizing( unsigned int w, unsigned int h )
156
{
157 158
    resize( w, h );
    emit sizeChanged( w, h );
159 160 161 162 163
    /* Work-around a bug?misconception? that would happen when vout core resize
       twice to the same size and would make the vout not centered.
       This cause a small flicker.
       See #3621
     */
164
    if( (unsigned)size().width() == w && (unsigned)size().height() == h )
165
        updateGeometry();
166
    sync();
167 168
}

169
void VideoWidget::release( void )
170
{
Rémi Denis-Courmont's avatar
Typo  
Rémi Denis-Courmont committed
171
    msg_Dbg( p_intf, "Video is not needed anymore" );
172

173 174 175 176 177 178
    if( stable )
    {
        layout->removeWidget( stable );
        stable->deleteLater();
        stable = NULL;
    }
179

180
    updateGeometry();
181
}
182

183 184
/**********************************************************************
 * Background Widget. Show a simple image background. Currently,
185
 * it's album art if present or cone.
186
 **********************************************************************/
187

188
BackgroundWidget::BackgroundWidget( intf_thread_t *_p_i )
189
    :QWidget( NULL ), p_intf( _p_i ), b_expandPixmap( false ), b_withart( true )
190
{
191
    /* A dark background */
Clément Stenac's avatar
Clément Stenac committed
192
    setAutoFillBackground( true );
193
    QPalette plt = palette();
194 195 196 197
    plt.setColor( QPalette::Active, QPalette::Window , Qt::black );
    plt.setColor( QPalette::Inactive, QPalette::Window , Qt::black );
    setPalette( plt );

Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
198 199 200
    /* Init the cone art */
    updateArt( "" );

201
    CONNECT( THEMIM->getIM(), artChanged( QString ),
202
             this, updateArt( const QString& ) );
203 204
}

205
void BackgroundWidget::updateArt( const QString& url )
206
{
François Cartegnie's avatar
François Cartegnie committed
207
    if ( !url.isEmpty() )
Ilkka Ollakka's avatar
 
Ilkka Ollakka committed
208
    {
François Cartegnie's avatar
François Cartegnie committed
209 210 211 212
        pixmapUrl = url;
    }
    else
    {   /* Xmas joke */
213
        if( QDate::currentDate().dayOfYear() >= QT_XMAS_JOKE_DAY && var_InheritBool( p_intf, "qt-icon-change" ) )
214
            pixmapUrl = QString( ":/logo/vlc128-xmas.png" );
215
        else
François Cartegnie's avatar
François Cartegnie committed
216
            pixmapUrl = QString( ":/logo/vlc128.png" );
Ilkka Ollakka's avatar
 
Ilkka Ollakka committed
217
    }
218
    update();
François Cartegnie's avatar
François Cartegnie committed
219 220 221 222
}

void BackgroundWidget::paintEvent( QPaintEvent *e )
{
223 224 225 226 227 228 229
    if ( !b_withart )
    {
        /* we just want background autofill */
        QWidget::paintEvent( e );
        return;
    }

François Cartegnie's avatar
François Cartegnie committed
230 231 232 233 234 235
    int i_maxwidth, i_maxheight;
    QPixmap pixmap = QPixmap( pixmapUrl );
    QPainter painter(this);
    QBitmap pMask;
    float f_alpha = 1.0;

236 237
    i_maxwidth  = __MIN( maximumWidth(), width() ) - MARGIN * 2;
    i_maxheight = __MIN( maximumHeight(), height() ) - MARGIN * 2;
François Cartegnie's avatar
François Cartegnie committed
238 239

    if ( height() > MARGIN * 2 )
Ilkka Ollakka's avatar
 
Ilkka Ollakka committed
240
    {
François Cartegnie's avatar
François Cartegnie committed
241 242 243 244 245 246 247 248 249
        /* 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() )
250
        {
François Cartegnie's avatar
François Cartegnie committed
251 252 253 254 255 256 257 258 259 260 261 262 263 264
            /* 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 );
265 266
        }

François Cartegnie's avatar
François Cartegnie committed
267 268 269 270
        painter.drawPixmap(
                MARGIN + ( i_maxwidth - pixmap.width() ) /2,
                MARGIN + ( i_maxheight - pixmap.height() ) /2,
                pixmap);
Ilkka Ollakka's avatar
 
Ilkka Ollakka committed
271
    }
François Cartegnie's avatar
François Cartegnie committed
272
    QWidget::paintEvent( e );
273 274
}

275 276
void BackgroundWidget::contextMenuEvent( QContextMenuEvent *event )
{
277
    VLCMenuBar::PopupMenu( p_intf, true );
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
278
    event->accept();
279
}
280

Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
281
#if 0
282 283 284
#include <QPushButton>
#include <QHBoxLayout>

Clément Stenac's avatar
Clément Stenac committed
285 286 287 288
/**********************************************************************
 * Visualization selector panel
 **********************************************************************/
VisualSelector::VisualSelector( intf_thread_t *_p_i ) :
289
                                QFrame( NULL ), p_intf( _p_i )
Clément Stenac's avatar
Clément Stenac committed
290 291
{
    QHBoxLayout *layout = new QHBoxLayout( this );
292
    layout->setMargin( 0 );
Clément Stenac's avatar
Clément Stenac committed
293
    QPushButton *prevButton = new QPushButton( "Prev" );
294
    QPushButton *nextButton = new QPushButton( "Next" );
Clément Stenac's avatar
Clément Stenac committed
295 296
    layout->addWidget( prevButton );
    layout->addWidget( nextButton );
Clément Stenac's avatar
Clément Stenac committed
297

298
    layout->addStretch( 10 );
299
    layout->addWidget( new QLabel( qtr( "Current visualization" ) ) );
Clément Stenac's avatar
Clément Stenac committed
300 301 302 303 304 305 306

    current = new QLabel( qtr( "None" ) );
    layout->addWidget( current );

    BUTTONACT( prevButton, prev() );
    BUTTONACT( nextButton, next() );

Clément Stenac's avatar
Clément Stenac committed
307
    setLayout( layout );
308
    setMaximumHeight( 35 );
Clément Stenac's avatar
Clément Stenac committed
309 310 311
}

VisualSelector::~VisualSelector()
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
312
{}
Clément Stenac's avatar
Clément Stenac committed
313

Clément Stenac's avatar
Clément Stenac committed
314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332
void VisualSelector::prev()
{
    char *psz_new = aout_VisualPrev( p_intf );
    if( psz_new )
    {
        current->setText( qfu( psz_new ) );
        free( psz_new );
    }
}

void VisualSelector::next()
{
    char *psz_new = aout_VisualNext( p_intf );
    if( psz_new )
    {
        current->setText( qfu( psz_new ) );
        free( psz_new );
    }
}
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
333
#endif
Clément Stenac's avatar
Clément Stenac committed
334

335 336
SpeedLabel::SpeedLabel( intf_thread_t *_p_intf, QWidget *parent )
           : QLabel( parent ), p_intf( _p_intf )
337
{
338
    tooltipStringPattern = qtr( "Current playback speed: %1\nClick to adjust" );
339 340

    /* Create the Speed Control Widget */
341
    speedControl = new SpeedControlWidget( p_intf, this );
342 343 344 345 346 347
    speedControlMenu = new QMenu( this );

    QWidgetAction *widgetAction = new QWidgetAction( speedControl );
    widgetAction->setDefaultWidget( speedControl );
    speedControlMenu->addAction( widgetAction );

348
    /* Change the SpeedRate in the Label */
349
    CONNECT( THEMIM->getIM(), rateChanged( float ), this, setRate( float ) );
350

351 352
    DCONNECT( THEMIM, inputChanged( input_thread_t * ),
              speedControl, activateOnState() );
353 354 355 356

    setFrameStyle( QFrame::StyledPanel | QFrame::Raised );
    setLineWidth( 1 );

357
    setRate( var_InheritFloat( THEPL, "rate" ) );
358
}
359

360 361
SpeedLabel::~SpeedLabel()
{
362 363
    delete speedControl;
    delete speedControlMenu;
364
}
365

366 367 368
/****************************************************************************
 * Small right-click menu for rate control
 ****************************************************************************/
369

370 371 372
void SpeedLabel::showSpeedMenu( QPoint pos )
{
    speedControlMenu->exec( QCursor::pos() - pos
373
                            + QPoint( -70 + width()/2, height() ) );
374 375
}

376
void SpeedLabel::setRate( float rate )
377 378
{
    QString str;
379
    str.setNum( rate, 'f', 2 );
380 381
    str.append( "x" );
    setText( str );
382
    setToolTip( tooltipStringPattern.arg( str ) );
383 384 385
    speedControl->updateControls( rate );
}

386 387 388
/**********************************************************************
 * Speed control widget
 **********************************************************************/
389 390
SpeedControlWidget::SpeedControlWidget( intf_thread_t *_p_i, QWidget *_parent )
                    : QFrame( _parent ), p_intf( _p_i )
391
{
392
    QSizePolicy sizePolicy( QSizePolicy::Fixed, QSizePolicy::Maximum );
393 394
    sizePolicy.setHorizontalStretch( 0 );
    sizePolicy.setVerticalStretch( 0 );
395

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
396
    speedSlider = new QSlider( this );
397
    speedSlider->setSizePolicy( sizePolicy );
398 399
    speedSlider->setMinimumSize( QSize( 140, 20 ) );
    speedSlider->setOrientation( Qt::Horizontal );
400
    speedSlider->setTickPosition( QSlider::TicksBelow );
401

402
    speedSlider->setRange( -34, 34 );
403 404
    speedSlider->setSingleStep( 1 );
    speedSlider->setPageStep( 1 );
405
    speedSlider->setTickInterval( 17 );
406

407
    CONNECT( speedSlider, valueChanged( int ), this, updateRate( int ) );
408

409
    QToolButton *normalSpeedButton = new QToolButton( this );
410
    normalSpeedButton->setMaximumSize( QSize( 26, 16 ) );
411
    normalSpeedButton->setAutoRaise( true );
412
    normalSpeedButton->setText( "1x" );
413
    normalSpeedButton->setToolTip( qtr( "Revert to normal play speed" ) );
414

415
    CONNECT( normalSpeedButton, clicked(), this, resetRate() );
416

417 418 419 420 421 422 423 424 425 426 427 428 429 430
    QToolButton *slowerButton = new QToolButton( this );
    slowerButton->setMaximumSize( QSize( 26, 16 ) );
    slowerButton->setAutoRaise( true );
    slowerButton->setToolTip( tooltipL[SLOWER_BUTTON] );
    slowerButton->setIcon( QIcon( iconL[SLOWER_BUTTON] ) );
    CONNECT( slowerButton, clicked(), THEMIM->getIM(), slower() );

    QToolButton *fasterButton = new QToolButton( this );
    fasterButton->setMaximumSize( QSize( 26, 16 ) );
    fasterButton->setAutoRaise( true );
    fasterButton->setToolTip( tooltipL[FASTER_BUTTON] );
    fasterButton->setIcon( QIcon( iconL[FASTER_BUTTON] ) );
    CONNECT( fasterButton, clicked(), THEMIM->getIM(), faster() );

431 432 433 434 435 436 437 438 439 440 441
/*    spinBox = new QDoubleSpinBox();
    spinBox->setDecimals( 2 );
    spinBox->setMaximum( 32 );
    spinBox->setMinimum( 0.03F );
    spinBox->setSingleStep( 0.10F );
    spinBox->setAlignment( Qt::AlignRight );

    CONNECT( spinBox, valueChanged( double ), this, updateSpinBoxRate( double ) ); */

    QGridLayout* speedControlLayout = new QGridLayout( this );
    speedControlLayout->addWidget( speedSlider, 0, 0, 1, 3 );
442 443 444
    speedControlLayout->addWidget( slowerButton, 1, 0 );
    speedControlLayout->addWidget( normalSpeedButton, 1, 1, 1, 1, Qt::AlignRight );
    speedControlLayout->addWidget( fasterButton, 1, 2, 1, 1, Qt::AlignRight );
445 446 447
    //speedControlLayout->addWidget( spinBox );
    speedControlLayout->setContentsMargins( 0, 0, 0, 0 );
    speedControlLayout->setSpacing( 0 );
448

449 450
    lastValue = 0;

451 452
    activateOnState();
}
453

454
void SpeedControlWidget::activateOnState()
455
{
456
    speedSlider->setEnabled( THEMIM->getIM()->hasInput() );
457
    //spinBox->setEnabled( THEMIM->getIM()->hasInput() );
458 459
}

460
void SpeedControlWidget::updateControls( float rate )
461
{
462 463 464 465 466
    if( speedSlider->isSliderDown() )
    {
        //We don't want to change anything if the user is using the slider
        return;
    }
467

468
    double value = 17 * log( rate ) / log( 2. );
469
    int sliderValue = (int) ( ( value > 0 ) ? value + .5 : value - .5 );
470

471
    if( sliderValue < speedSlider->minimum() )
472
    {
473
        sliderValue = speedSlider->minimum();
474
    }
475
    else if( sliderValue > speedSlider->maximum() )
476
    {
477
        sliderValue = speedSlider->maximum();
478
    }
479
    lastValue = sliderValue;
480

481
    speedSlider->setValue( sliderValue );
482
    //spinBox->setValue( rate );
483 484 485 486
}

void SpeedControlWidget::updateRate( int sliderValue )
{
487 488 489
    if( sliderValue == lastValue )
        return;

490
    double speed = pow( 2, (double)sliderValue / 17 );
491
    int rate = INPUT_RATE_DEFAULT / speed;
492

493
    THEMIM->getIM()->setRate(rate);
494 495 496 497 498 499
    //spinBox->setValue( var_InheritFloat( THEPL, "rate" ) );
}

void SpeedControlWidget::updateSpinBoxRate( double r )
{
    var_SetFloat( THEPL, "rate", r );
500 501 502 503
}

void SpeedControlWidget::resetRate()
{
504
    THEMIM->getIM()->setRate( INPUT_RATE_DEFAULT );
505
}
506

507
CoverArtLabel::CoverArtLabel( QWidget *parent, intf_thread_t *_p_i )
508
    : QLabel( parent ), p_intf( _p_i ), p_item( NULL )
509 510
{
    setContextMenuPolicy( Qt::ActionsContextMenu );
511 512
    CONNECT( THEMIM->getIM(), artChanged( input_item_t * ),
             this, showArtUpdate( input_item_t * ) );
513

514
    setMinimumHeight( 128 );
515
    setMinimumWidth( 128 );
516 517
    setScaledContents( false );
    setAlignment( Qt::AlignCenter );
518

519
    QAction *action = new QAction( qtr( "Download cover art" ), this );
520
    CONNECT( action, triggered(), this, askForUpdate() );
521
    addAction( action );
522

523
    showArtUpdate( "" );
524 525
}

526 527 528 529 530
CoverArtLabel::~CoverArtLabel()
{
    QList< QAction* > artActions = actions();
    foreach( QAction *act, artActions )
        removeAction( act );
531 532 533 534 535 536 537 538
    if ( p_item ) vlc_gc_decref( p_item );
}

void CoverArtLabel::setItem( input_item_t *_p_item )
{
    if ( p_item ) vlc_gc_decref( p_item );
    p_item = _p_item;
    if ( p_item ) vlc_gc_incref( p_item );
539 540
}

541
void CoverArtLabel::showArtUpdate( const QString& url )
542
{
543
    QPixmap pix;
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
544
    if( !url.isEmpty() && pix.load( url ) )
545
    {
546
        pix = pix.scaled( minimumWidth(), minimumHeight(),
547 548
                          Qt::KeepAspectRatioByExpanding,
                          Qt::SmoothTransformation );
549 550 551
    }
    else
    {
552
        pix = QPixmap( ":/noart.png" );
553
    }
554
    setPixmap( pix );
555 556
}

557 558 559 560 561 562 563 564 565 566 567
void CoverArtLabel::showArtUpdate( input_item_t *_p_item )
{
    /* not for me */
    if ( _p_item != p_item )
        return;

    QString url;
    if ( _p_item ) url = THEMIM->getIM()->decodeArtURL( _p_item );
    showArtUpdate( url );
}

568
void CoverArtLabel::askForUpdate()
569
{
570
    THEMIM->getIM()->requestArtUpdate( p_item );
571 572
}

573
TimeLabel::TimeLabel( intf_thread_t *_p_intf, TimeLabel::Display _displayType  )
574
    : ClickableQLabel(), p_intf( _p_intf ), bufTimer( new QTimer(this) ),
575
      buffering( false ), showBuffering(false), bufVal( -1 ), displayType( _displayType )
576
{
577
    b_remainingTime = false;
578 579
    if( _displayType != TimeLabel::Elapsed )
        b_remainingTime = getSettings()->value( "MainWindow/ShowRemainingTime", false ).toBool();
580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599
    switch( _displayType ) {
        case TimeLabel::Elapsed:
            setText( " --:-- " );
            setToolTip( qtr("Elapsed time") );
            break;
        case TimeLabel::Remaining:
            setText( " --:-- " );
            setToolTip( qtr("Total/Remaining time")
                        + QString("\n-")
                        + qtr("Click to toggle between total and remaining time")
                      );
            break;
        case TimeLabel::Both:
            setText( " --:--/--:-- " );
            setToolTip( QString( "- " )
                + qtr( "Click to toggle between elapsed and remaining time" )
                + QString( "\n- " )
                + qtr( "Double click to jump to a chosen time position" ) );
            break;
    }
600
    setAlignment( Qt::AlignRight | Qt::AlignVCenter );
601

602
    bufTimer->setSingleShot( true );
603

604 605 606 607 608
    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() );
609

610
    setStyleSheet( "padding-left: 4px; padding-right: 4px;" );
611 612
}

613
void TimeLabel::setDisplayPosition( float pos, int64_t t, int length )
614
{
615 616 617
    showBuffering = false;
    bufTimer->stop();

618
    if( pos == -1.f )
619
    {
620
        setMinimumSize( QSize( 0, 0 ) );
621
        if( displayType == TimeLabel::Both )
622
            setText( "--:--/--:--" );
623
        else
624
            setText( "--:--" );
625 626
        return;
    }
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
627

628
    int time = t / 1000000;
629

630 631 632
    secstotimestr( psz_length, length );
    secstotimestr( psz_time, ( b_remainingTime && length ) ? length - time
                                                           : time );
633 634 635

    // compute the minimum size that will be required for the psz_length
    // and use it to enforce a minimal size to avoid "dancing" widgets
636
    QSize minsize( 0, 0 );
637 638 639 640 641 642 643 644 645 646 647 648 649
    if ( length > 0 )
    {
        QMargins margins = contentsMargins();
        minsize += QSize(
                  fontMetrics().size( 0, QString( psz_length ), 0, 0 ).width(),
                  sizeHint().height()
                );
        minsize += QSize( margins.left() + margins.right() + 8, 0 ); /* +padding */

        if ( b_remainingTime )
            minsize += QSize( fontMetrics().size( 0, "-", 0, 0 ).width(), 0 );
    }

650 651 652
    switch( displayType )
    {
        case TimeLabel::Elapsed:
653
            setMinimumSize( minsize );
654
            setText( QString( psz_time ) );
655 656 657
            break;
        case TimeLabel::Remaining:
            if( b_remainingTime )
658 659
            {
                setMinimumSize( minsize );
660
                setText( QString("-") + QString( psz_time ) );
661
            }
662
            else
663 664
            {
                setMinimumSize( QSize( 0, 0 ) );
665
                setText( QString( psz_length ) );
666
            }
667 668 669
            break;
        case TimeLabel::Both:
        default:
670
            QString timestr = QString( "%1%2/%3" )
671 672 673
            .arg( QString( (b_remainingTime && length) ? "-" : "" ) )
            .arg( QString( psz_time ) )
            .arg( QString( ( !length && time ) ? "--:--" : psz_length ) );
674

675 676 677
            setText( timestr );
            break;
    }
678 679 680 681 682 683 684 685 686 687 688 689 690 691 692
    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 ) );
693
    QString timestr = QString( "%1%2/%3" )
694 695 696
        .arg( QString( (b_remainingTime && cachedLength) ? "-" : "" ) )
        .arg( QString( psz_time ) )
        .arg( QString( ( !cachedLength && time ) ? "--:--" : psz_length ) );
697 698

    setText( timestr );
699 700
}

701

702 703 704
void TimeLabel::toggleTimeDisplay()
{
    b_remainingTime = !b_remainingTime;
705
    getSettings()->setValue( "MainWindow/ShowRemainingTime", b_remainingTime );
706
}
707 708


709
void TimeLabel::updateBuffering( float _buffered )
710
{
711 712 713 714 715 716 717 718 719 720
    bufVal = _buffered;
    if( !buffering || bufVal == 0 )
    {
        showBuffering = false;
        buffering = true;
        bufTimer->start(200);
    }
    else if( bufVal == 1 )
    {
        showBuffering = buffering = false;
721
        bufTimer->stop();
722 723
    }
    update();
724
}
725

726
void TimeLabel::updateBuffering()
727
{
728 729
    showBuffering = true;
    update();
730 731
}

732
void TimeLabel::paintEvent( QPaintEvent* event )
733
{
734 735 736 737 738 739 740 741
    if( showBuffering )
    {
        QRect r( rect() );
        r.setLeft( r.width() * bufVal );
        QPainter p( this );
        p.setOpacity( 0.4 );
        p.fillRect( r, palette().color( QPalette::Highlight ) );
    }
742 743
    QLabel::paintEvent( event );
}