interface_widgets.cpp 26.3 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>
Angelo Haller's avatar
Angelo Haller committed
53
#include <QUrl>
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
54

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

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

63 64 65 66
/**********************************************************************
 * Video Widget. A simple frame on which video is drawn
 * This class handles resize issues
 **********************************************************************/
67

68
VideoWidget::VideoWidget( intf_thread_t *_p_i )
69
            : QFrame( NULL ) , p_intf( _p_i )
70
{
71
    /* Set the policy to expand in both directions */
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
72
    // setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding );
73

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

VideoWidget::~VideoWidget()
{
82
    /* Ensure we are not leaking the video output. This would crash. */
83 84 85 86 87 88 89 90 91 92 93
    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
94 95
}

96
/**
97
 * Request the video to avoid the conflicts
98
 **/
99
WId VideoWidget::request( int *pi_x, int *pi_y,
100 101
                          unsigned int *pi_width, unsigned int *pi_height,
                          bool b_keep_size )
102
{
103
    msg_Dbg( p_intf, "Video was requested %i, %i", *pi_x, *pi_y );
104

105
    if( stable )
106 107
    {
        msg_Dbg( p_intf, "embedded video already in use" );
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
108
        return 0;
109
    }
110 111 112 113 114 115
    if( b_keep_size )
    {
        *pi_width  = size().width();
        *pi_height = size().height();
    }

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

135
    layout->addWidget( stable );
136

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

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

153
/* Set the Widget to the correct Size */
154
/* Function has to be called by the parent
Rémi Denis-Courmont's avatar
typo  
Rémi Denis-Courmont committed
155
   Parent has to care about resizing itself */
156
void VideoWidget::SetSizing( unsigned int w, unsigned int h )
157
{
158 159
    resize( w, h );
    emit sizeChanged( w, h );
160 161 162 163 164
    /* 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
     */
165
    if( (unsigned)size().width() == w && (unsigned)size().height() == h )
166
        updateGeometry();
167
    sync();
168 169
}

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

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

181
    updateGeometry();
182
}
183

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

189
BackgroundWidget::BackgroundWidget( intf_thread_t *_p_i )
190
    :QWidget( NULL ), p_intf( _p_i ), b_expandPixmap( false ), b_withart( true )
191
{
192
    /* A dark background */
Clément Stenac's avatar
Clément Stenac committed
193
    setAutoFillBackground( true );
194
    QPalette plt = palette();
195 196 197 198
    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
199
    /* Init the cone art */
200
    defaultArt = QString( ":/logo/vlc128.png" );
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
201 202
    updateArt( "" );

203 204 205 206 207 208 209 210 211 212
    /* fade in animator */
    setProperty( "opacity", 1.0 );
    fadeAnimation = new QPropertyAnimation( this, "opacity", this );
    fadeAnimation->setDuration( 1000 );
    fadeAnimation->setStartValue( 0.0 );
    fadeAnimation->setEndValue( 1.0 );
    fadeAnimation->setEasingCurve( QEasingCurve::OutSine );
    CONNECT( fadeAnimation, valueChanged( const QVariant & ),
             this, update() );

213
    CONNECT( THEMIM->getIM(), artChanged( QString ),
214
             this, updateArt( const QString& ) );
215 216
}

217
void BackgroundWidget::updateArt( const QString& url )
218
{
François Cartegnie's avatar
François Cartegnie committed
219 220 221
    if ( !url.isEmpty() )
        pixmapUrl = url;
    else
222
        pixmapUrl = defaultArt;
223
    update();
François Cartegnie's avatar
François Cartegnie committed
224 225
}

226 227 228 229 230 231
void BackgroundWidget::showEvent( QShowEvent * e )
{
    Q_UNUSED( e );
    if ( b_withart ) fadeAnimation->start();
}

François Cartegnie's avatar
François Cartegnie committed
232 233
void BackgroundWidget::paintEvent( QPaintEvent *e )
{
234 235 236 237 238 239 240
    if ( !b_withart )
    {
        /* we just want background autofill */
        QWidget::paintEvent( e );
        return;
    }

François Cartegnie's avatar
François Cartegnie committed
241 242 243 244 245 246
    int i_maxwidth, i_maxheight;
    QPixmap pixmap = QPixmap( pixmapUrl );
    QPainter painter(this);
    QBitmap pMask;
    float f_alpha = 1.0;

247 248
    i_maxwidth  = __MIN( maximumWidth(), width() ) - MARGIN * 2;
    i_maxheight = __MIN( maximumHeight(), height() ) - MARGIN * 2;
François Cartegnie's avatar
François Cartegnie committed
249

250 251
    painter.setOpacity( property( "opacity" ).toFloat() );

François Cartegnie's avatar
François Cartegnie committed
252
    if ( height() > MARGIN * 2 )
Ilkka Ollakka's avatar
 
Ilkka Ollakka committed
253
    {
François Cartegnie's avatar
François Cartegnie committed
254 255 256 257 258 259 260 261 262
        /* 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() )
263
        {
François Cartegnie's avatar
François Cartegnie committed
264 265 266 267 268 269 270 271 272 273 274 275 276 277
            /* 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 );
278 279
        }

François Cartegnie's avatar
François Cartegnie committed
280 281 282 283
        painter.drawPixmap(
                MARGIN + ( i_maxwidth - pixmap.width() ) /2,
                MARGIN + ( i_maxheight - pixmap.height() ) /2,
                pixmap);
Ilkka Ollakka's avatar
 
Ilkka Ollakka committed
284
    }
François Cartegnie's avatar
François Cartegnie committed
285
    QWidget::paintEvent( e );
286 287
}

288 289
void BackgroundWidget::contextMenuEvent( QContextMenuEvent *event )
{
290
    VLCMenuBar::PopupMenu( p_intf, true );
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
291
    event->accept();
292
}
293

294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408
EasterEggBackgroundWidget::EasterEggBackgroundWidget( intf_thread_t *p_intf )
    : BackgroundWidget( p_intf )
{
    flakes = new QLinkedList<flake *>();
    i_rate = 2;
    i_speed = 1;
    b_enabled = false;
    timer = new QTimer( this );
    timer->setInterval( 100 );
    CONNECT( timer, timeout(), this, spawnFlakes() );
    if ( isVisible() && b_enabled ) timer->start();
    defaultArt = QString( ":/logo/vlc128-xmas.png" );
    updateArt( "" );
}

EasterEggBackgroundWidget::~EasterEggBackgroundWidget()
{
    timer->stop();
    delete timer;
    reset();
    delete flakes;
}

void EasterEggBackgroundWidget::showEvent( QShowEvent *e )
{
    if ( b_enabled ) timer->start();
    BackgroundWidget::showEvent( e );
}

void EasterEggBackgroundWidget::hideEvent( QHideEvent *e )
{
    timer->stop();
    reset();
    BackgroundWidget::hideEvent( e );
}

void EasterEggBackgroundWidget::resizeEvent( QResizeEvent *e )
{
    reset();
    BackgroundWidget::resizeEvent( e );
}

void EasterEggBackgroundWidget::animate()
{
    b_enabled = true;
    if ( isVisible() ) timer->start();
}

void EasterEggBackgroundWidget::spawnFlakes()
{
    if ( ! isVisible() ) return;

    double w = (double) width() / RAND_MAX;

    int i_spawn = ( (double) qrand() / RAND_MAX ) * i_rate;

    QLinkedList<flake *>::iterator it = flakes->begin();
    while( it != flakes->end() )
    {
        flake *current = *it;
        current->point.setY( current->point.y() + i_speed );
        if ( current->point.y() + i_speed >= height() )
        {
            delete current;
            it = flakes->erase( it );
        }
        else
            it++;
    }

    if ( flakes->size() < MAX_FLAKES )
    for ( int i=0; i<i_spawn; i++ )
    {
        flake *f = new flake;
        f->point.setX( qrand() * w );
        f->b_fat = ( qrand() < ( RAND_MAX * .33 ) );
        flakes->append( f );
    }
    update();
}

void EasterEggBackgroundWidget::reset()
{
    while ( !flakes->isEmpty() )
        delete flakes->takeFirst();
}

void EasterEggBackgroundWidget::paintEvent( QPaintEvent *e )
{
    QPainter painter(this);

    painter.setBrush( QBrush( QColor(Qt::white) ) );
    painter.setPen( QPen(Qt::white) );

    QLinkedList<flake *>::const_iterator it = flakes->constBegin();
    while( it != flakes->constEnd() )
    {
        const flake * const f = *(it++);
        if ( f->b_fat )
        {
            /* Xsnow like :p */
            painter.drawPoint( f->point.x(), f->point.y() -1 );
            painter.drawPoint( f->point.x() + 1, f->point.y() );
            painter.drawPoint( f->point.x(), f->point.y() +1 );
            painter.drawPoint( f->point.x() - 1, f->point.y() );
        }
        else
        {
            painter.drawPoint( f->point );
        }
    }

    BackgroundWidget::paintEvent( e );
}

Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
409
#if 0
410 411 412
#include <QPushButton>
#include <QHBoxLayout>

Clément Stenac's avatar
Clément Stenac committed
413 414 415 416
/**********************************************************************
 * Visualization selector panel
 **********************************************************************/
VisualSelector::VisualSelector( intf_thread_t *_p_i ) :
417
                                QFrame( NULL ), p_intf( _p_i )
Clément Stenac's avatar
Clément Stenac committed
418 419
{
    QHBoxLayout *layout = new QHBoxLayout( this );
420
    layout->setMargin( 0 );
Clément Stenac's avatar
Clément Stenac committed
421
    QPushButton *prevButton = new QPushButton( "Prev" );
422
    QPushButton *nextButton = new QPushButton( "Next" );
Clément Stenac's avatar
Clément Stenac committed
423 424
    layout->addWidget( prevButton );
    layout->addWidget( nextButton );
Clément Stenac's avatar
Clément Stenac committed
425

426
    layout->addStretch( 10 );
427
    layout->addWidget( new QLabel( qtr( "Current visualization" ) ) );
Clément Stenac's avatar
Clément Stenac committed
428 429 430 431 432 433 434

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

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

Clément Stenac's avatar
Clément Stenac committed
435
    setLayout( layout );
436
    setMaximumHeight( 35 );
Clément Stenac's avatar
Clément Stenac committed
437 438 439
}

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

Clément Stenac's avatar
Clément Stenac committed
442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460
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
461
#endif
Clément Stenac's avatar
Clément Stenac committed
462

463 464
SpeedLabel::SpeedLabel( intf_thread_t *_p_intf, QWidget *parent )
           : QLabel( parent ), p_intf( _p_intf )
465
{
466
    tooltipStringPattern = qtr( "Current playback speed: %1\nClick to adjust" );
467 468

    /* Create the Speed Control Widget */
469
    speedControl = new SpeedControlWidget( p_intf, this );
470 471 472 473 474 475
    speedControlMenu = new QMenu( this );

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

476
    /* Change the SpeedRate in the Label */
477
    CONNECT( THEMIM->getIM(), rateChanged( float ), this, setRate( float ) );
478

479 480
    DCONNECT( THEMIM, inputChanged( input_thread_t * ),
              speedControl, activateOnState() );
481 482 483 484

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

485
    setRate( var_InheritFloat( THEPL, "rate" ) );
486
}
487

488 489
SpeedLabel::~SpeedLabel()
{
490 491
    delete speedControl;
    delete speedControlMenu;
492
}
493

494 495 496
/****************************************************************************
 * Small right-click menu for rate control
 ****************************************************************************/
497

498 499 500
void SpeedLabel::showSpeedMenu( QPoint pos )
{
    speedControlMenu->exec( QCursor::pos() - pos
501
                            + QPoint( -70 + width()/2, height() ) );
502 503
}

504
void SpeedLabel::setRate( float rate )
505 506
{
    QString str;
507
    str.setNum( rate, 'f', 2 );
508 509
    str.append( "x" );
    setText( str );
510
    setToolTip( tooltipStringPattern.arg( str ) );
511 512 513
    speedControl->updateControls( rate );
}

514 515 516
/**********************************************************************
 * Speed control widget
 **********************************************************************/
517 518
SpeedControlWidget::SpeedControlWidget( intf_thread_t *_p_i, QWidget *_parent )
                    : QFrame( _parent ), p_intf( _p_i )
519
{
520
    QSizePolicy sizePolicy( QSizePolicy::Fixed, QSizePolicy::Maximum );
521 522
    sizePolicy.setHorizontalStretch( 0 );
    sizePolicy.setVerticalStretch( 0 );
523

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
524
    speedSlider = new QSlider( this );
525
    speedSlider->setSizePolicy( sizePolicy );
526 527
    speedSlider->setMinimumSize( QSize( 140, 20 ) );
    speedSlider->setOrientation( Qt::Horizontal );
528
    speedSlider->setTickPosition( QSlider::TicksBelow );
529

530
    speedSlider->setRange( -34, 34 );
531 532
    speedSlider->setSingleStep( 1 );
    speedSlider->setPageStep( 1 );
533
    speedSlider->setTickInterval( 17 );
534

535
    CONNECT( speedSlider, valueChanged( int ), this, updateRate( int ) );
536

537
    QToolButton *normalSpeedButton = new QToolButton( this );
538
    normalSpeedButton->setMaximumSize( QSize( 26, 16 ) );
539
    normalSpeedButton->setAutoRaise( true );
540
    normalSpeedButton->setText( "1x" );
541
    normalSpeedButton->setToolTip( qtr( "Revert to normal play speed" ) );
542

543
    CONNECT( normalSpeedButton, clicked(), this, resetRate() );
544

545 546 547 548 549 550 551 552 553 554 555 556 557 558
    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() );

559 560 561 562 563 564 565 566 567 568 569
/*    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 );
570 571 572
    speedControlLayout->addWidget( slowerButton, 1, 0 );
    speedControlLayout->addWidget( normalSpeedButton, 1, 1, 1, 1, Qt::AlignRight );
    speedControlLayout->addWidget( fasterButton, 1, 2, 1, 1, Qt::AlignRight );
573 574 575
    //speedControlLayout->addWidget( spinBox );
    speedControlLayout->setContentsMargins( 0, 0, 0, 0 );
    speedControlLayout->setSpacing( 0 );
576

577 578
    lastValue = 0;

579 580
    activateOnState();
}
581

582
void SpeedControlWidget::activateOnState()
583
{
584
    speedSlider->setEnabled( THEMIM->getIM()->hasInput() );
585
    //spinBox->setEnabled( THEMIM->getIM()->hasInput() );
586 587
}

588
void SpeedControlWidget::updateControls( float rate )
589
{
590 591 592 593 594
    if( speedSlider->isSliderDown() )
    {
        //We don't want to change anything if the user is using the slider
        return;
    }
595

596
    double value = 17 * log( rate ) / log( 2. );
597
    int sliderValue = (int) ( ( value > 0 ) ? value + .5 : value - .5 );
598

599
    if( sliderValue < speedSlider->minimum() )
600
    {
601
        sliderValue = speedSlider->minimum();
602
    }
603
    else if( sliderValue > speedSlider->maximum() )
604
    {
605
        sliderValue = speedSlider->maximum();
606
    }
607
    lastValue = sliderValue;
608

609
    speedSlider->setValue( sliderValue );
610
    //spinBox->setValue( rate );
611 612 613 614
}

void SpeedControlWidget::updateRate( int sliderValue )
{
615 616 617
    if( sliderValue == lastValue )
        return;

618
    double speed = pow( 2, (double)sliderValue / 17 );
619
    int rate = INPUT_RATE_DEFAULT / speed;
620

621
    THEMIM->getIM()->setRate(rate);
622 623 624 625 626 627
    //spinBox->setValue( var_InheritFloat( THEPL, "rate" ) );
}

void SpeedControlWidget::updateSpinBoxRate( double r )
{
    var_SetFloat( THEPL, "rate", r );
628 629 630 631
}

void SpeedControlWidget::resetRate()
{
632
    THEMIM->getIM()->setRate( INPUT_RATE_DEFAULT );
633
}
634

635
CoverArtLabel::CoverArtLabel( QWidget *parent, intf_thread_t *_p_i )
636
    : QLabel( parent ), p_intf( _p_i ), p_item( NULL )
637 638
{
    setContextMenuPolicy( Qt::ActionsContextMenu );
639 640
    CONNECT( THEMIM->getIM(), artChanged( input_item_t * ),
             this, showArtUpdate( input_item_t * ) );
641

642
    setMinimumHeight( 128 );
643
    setMinimumWidth( 128 );
644 645
    setScaledContents( false );
    setAlignment( Qt::AlignCenter );
646

647
    QAction *action = new QAction( qtr( "Download cover art" ), this );
648
    CONNECT( action, triggered(), this, askForUpdate() );
649
    addAction( action );
650

Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
651
    action = new QAction( qtr( "Add cover art from file" ), this );
Angelo Haller's avatar
Angelo Haller committed
652 653 654
    CONNECT( action, triggered(), this, setArtFromFile() );
    addAction( action );

655 656
    p_item = THEMIM->currentInputItem();
    if( p_item )
657 658
    {
        vlc_gc_incref( p_item );
659
        showArtUpdate( p_item );
660
    }
661 662
    else
        showArtUpdate( "" );
663 664
}

665 666 667 668 669
CoverArtLabel::~CoverArtLabel()
{
    QList< QAction* > artActions = actions();
    foreach( QAction *act, artActions )
        removeAction( act );
670 671 672 673 674 675 676 677
    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 );
678 679
}

680
void CoverArtLabel::showArtUpdate( const QString& url )
681
{
682
    QPixmap pix;
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
683
    if( !url.isEmpty() && pix.load( url ) )
684
    {
685
        pix = pix.scaled( minimumWidth(), minimumHeight(),
686 687
                          Qt::KeepAspectRatioByExpanding,
                          Qt::SmoothTransformation );
688 689 690
    }
    else
    {
691
        pix = QPixmap( ":/noart.png" );
692
    }
693
    setPixmap( pix );
694 695
}

696 697 698 699 700 701 702 703 704 705 706
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 );
}

707
void CoverArtLabel::askForUpdate()
708
{
709
    THEMIM->getIM()->requestArtUpdate( p_item );
710 711
}

Angelo Haller's avatar
Angelo Haller committed
712 713 714 715 716
void CoverArtLabel::setArtFromFile()
{
    if( !p_item )
        return;

Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
717
    QString filePath = QFileDialog::getOpenFileName( this, qtr( "Choose Cover Art" ),
Angelo Haller's avatar
Angelo Haller committed
718 719 720 721 722 723 724 725 726 727
        p_intf->p_sys->filepath, qtr( "Image Files (*.gif *.jpg *.jpeg *.png)" ) );

    if( filePath.isEmpty() )
        return;

    QString fileUrl = QUrl::fromLocalFile( filePath ).toString();

    THEMIM->getIM()->setArt( p_item, fileUrl );
}

728 729 730 731 732
void CoverArtLabel::clear()
{
    showArtUpdate( "" );
}

733
TimeLabel::TimeLabel( intf_thread_t *_p_intf, TimeLabel::Display _displayType  )
734
    : ClickableQLabel(), p_intf( _p_intf ), bufTimer( new QTimer(this) ),
735
      buffering( false ), showBuffering(false), bufVal( -1 ), displayType( _displayType )
736
{
737
    b_remainingTime = false;
738 739
    if( _displayType != TimeLabel::Elapsed )
        b_remainingTime = getSettings()->value( "MainWindow/ShowRemainingTime", false ).toBool();
740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759
    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;
    }
760
    setAlignment( Qt::AlignRight | Qt::AlignVCenter );
761

762
    bufTimer->setSingleShot( true );
763

764 765 766 767 768
    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() );
769

770
    setStyleSheet( "padding-left: 4px; padding-right: 4px;" );
771 772
}

773
void TimeLabel::setDisplayPosition( float pos, int64_t t, int length )
774
{
775 776 777
    showBuffering = false;
    bufTimer->stop();

778
    if( pos == -1.f )
779
    {
780
        setMinimumSize( QSize( 0, 0 ) );
781
        if( displayType == TimeLabel::Both )
782
            setText( "--:--/--:--" );
783
        else
784
            setText( "--:--" );
785 786
        return;
    }
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
787

788
    int time = t / 1000000;
789

790 791 792
    secstotimestr( psz_length, length );
    secstotimestr( psz_time, ( b_remainingTime && length ) ? length - time
                                                           : time );
793 794 795

    // compute the minimum size that will be required for the psz_length
    // and use it to enforce a minimal size to avoid "dancing" widgets
796
    QSize minsize( 0, 0 );
797 798 799 800 801 802 803 804 805 806 807 808 809
    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 );
    }

810 811 812
    switch( displayType )
    {
        case TimeLabel::Elapsed:
813
            setMinimumSize( minsize );
814
            setText( QString( psz_time ) );
815 816 817
            break;
        case TimeLabel::Remaining:
            if( b_remainingTime )
818 819
            {
                setMinimumSize( minsize );
820
                setText( QString("-") + QString( psz_time ) );
821
            }
822
            else
823 824
            {
                setMinimumSize( QSize( 0, 0 ) );
825
                setText( QString( psz_length ) );
826
            }
827 828 829
            break;
        case TimeLabel::Both:
        default:
830
            QString timestr = QString( "%1%2/%3" )
831 832 833
            .arg( QString( (b_remainingTime && length) ? "-" : "" ) )
            .arg( QString( psz_time ) )
            .arg( QString( ( !length && time ) ? "--:--" : psz_length ) );
834

835 836 837
            setText( timestr );
            break;
    }
838 839 840 841 842 843 844 845 846 847 848 849 850 851 852
    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 ) );
853
    QString timestr = QString( "%1%2/%3" )
854 855 856
        .arg( QString( (b_remainingTime && cachedLength) ? "-" : "" ) )
        .arg( QString( psz_time ) )
        .arg( QString( ( !cachedLength && time ) ? "--:--" : psz_length ) );
857 858

    setText( timestr );
859 860
}

861

862 863 864
void TimeLabel::toggleTimeDisplay()
{
    b_remainingTime = !b_remainingTime;
865
    getSettings()->setValue( "MainWindow/ShowRemainingTime", b_remainingTime );
866
}
867 868


869
void TimeLabel::updateBuffering( float _buffered )
870
{
871 872 873 874 875 876 877 878 879 880
    bufVal = _buffered;
    if( !buffering || bufVal == 0 )
    {
        showBuffering = false;
        buffering = true;
        bufTimer->start(200);
    }
    else if( bufVal == 1 )
    {
        showBuffering = buffering = false;
881
        bufTimer->stop();
882 883
    }
    update();
884
}
885

886
void TimeLabel::updateBuffering()
887
{
888 889
    showBuffering = true;
    update();
890 891
}

892
void TimeLabel::paintEvent( QPaintEvent* event )
893
{
894 895 896 897 898 899 900 901
    if( showBuffering )
    {
        QRect r( rect() );
        r.setLeft( r.width() * bufVal );
        QPainter p( this );
        p.setOpacity( 0.4 );
        p.fillRect( r, palette().color( QPalette::Highlight ) );
    }
902 903
    QLabel::paintEvent( event );
}