interface_widgets.cpp 25.5 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

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

zorglub's avatar
zorglub committed
38
#include <QLabel>
39
#include <QToolButton>
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
40
#include <QPalette>
41
#include <QEvent>
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
42
#include <QResizeEvent>
43
#include <QDate>
44
45
#include <QMenu>
#include <QWidgetAction>
46
#include <QDesktopWidget>
47
#include <QPainter>
48
#include <QTimer>
49
#include <QSlider>
François Cartegnie's avatar
François Cartegnie committed
50
#include <QBitmap>
Angelo Haller's avatar
Angelo Haller committed
51
#include <QUrl>
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
52

53
54
55
56
#ifdef QT5_HAS_X11
# define Q_WS_X11
#endif

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

62
#include <math.h>
63
#include <assert.h>
64

65
66
67
#include <vlc_vout.h>
#include <vlc_vout_window.h>

68
69
70
71
/**********************************************************************
 * Video Widget. A simple frame on which video is drawn
 * This class handles resize issues
 **********************************************************************/
72

73
VideoWidget::VideoWidget( intf_thread_t *_p_i )
74
            : QFrame( NULL ) , p_intf( _p_i )
75
{
76
    /* Set the policy to expand in both directions */
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
77
    // setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding );
78

79
80
    layout = new QHBoxLayout( this );
    layout->setContentsMargins( 0, 0, 0, 0 );
81
    stable = NULL;
82
    p_window = NULL;
83
    show();
84
85
86
87
}

VideoWidget::~VideoWidget()
{
88
    /* Ensure we are not leaking the video output. This would crash. */
89
    assert( !stable );
90
    assert( !p_window );
91
92
93
94
95
96
97
98
99
100
}

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
101
102
}

103
/**
104
 * Request the video to avoid the conflicts
105
 **/
106
107
WId VideoWidget::request( struct vout_window_t *p_wnd, unsigned int *pi_width,
                          unsigned int *pi_height, bool b_keep_size )
108
{
109
    if( stable )
110
111
    {
        msg_Dbg( p_intf, "embedded video already in use" );
Hugo Beauzée-Luyssen's avatar
Hugo Beauzée-Luyssen committed
112
        return 0;
113
    }
114
115
    assert( !p_window );

116
117
118
119
120
121
    if( b_keep_size )
    {
        *pi_width  = size().width();
        *pi_height = size().height();
    }

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

141
    layout->addWidget( stable );
142

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

    XGetWindowAttributes( dpy, w, &attr );
152
153
    attr.your_event_mask &= ~(ButtonPressMask|ButtonReleaseMask);
    XSelectInput( dpy, w, attr.your_event_mask );
154
#endif
155
    sync();
156
    p_window = p_wnd;
157
    return stable->winId();
158
159
}

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

177
178
179
180
181
182
183
184
185
void VideoWidget::resizeEvent( QResizeEvent *event )
{
    if( p_window != NULL )
        vout_window_ReportSize( p_window, event->size().width(),
                                event->size().height() );

    QWidget::resizeEvent( event );
}

186
void VideoWidget::release( void )
187
{
Rémi Denis-Courmont's avatar
Typo    
Rémi Denis-Courmont committed
188
    msg_Dbg( p_intf, "Video is not needed anymore" );
189

190
191
192
193
194
    if( stable )
    {
        layout->removeWidget( stable );
        stable->deleteLater();
        stable = NULL;
195
        p_window = NULL;
196
    }
197

198
    updateGeometry();
199
}
200

201
202
/**********************************************************************
 * Background Widget. Show a simple image background. Currently,
203
 * it's album art if present or cone.
204
 **********************************************************************/
205

206
BackgroundWidget::BackgroundWidget( intf_thread_t *_p_i )
207
    :QWidget( NULL ), p_intf( _p_i ), b_expandPixmap( false ), b_withart( true )
208
{
209
    /* A dark background */
zorglub's avatar
zorglub committed
210
    setAutoFillBackground( true );
211
    QPalette plt = palette();
212
213
214
215
    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
216
    /* Init the cone art */
217
    defaultArt = QString( ":/logo/vlc128.png" );
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
218
219
    updateArt( "" );

220
221
222
223
224
225
226
227
228
229
    /* 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() );

230
    CONNECT( THEMIM->getIM(), artChanged( QString ),
231
             this, updateArt( const QString& ) );
232
233
}

234
void BackgroundWidget::updateArt( const QString& url )
235
{
François Cartegnie's avatar
François Cartegnie committed
236
237
238
    if ( !url.isEmpty() )
        pixmapUrl = url;
    else
239
        pixmapUrl = defaultArt;
240
    update();
François Cartegnie's avatar
François Cartegnie committed
241
242
}

243
244
245
246
247
248
void BackgroundWidget::showEvent( QShowEvent * e )
{
    Q_UNUSED( e );
    if ( b_withart ) fadeAnimation->start();
}

François Cartegnie's avatar
François Cartegnie committed
249
250
void BackgroundWidget::paintEvent( QPaintEvent *e )
{
251
252
253
254
255
256
257
    if ( !b_withart )
    {
        /* we just want background autofill */
        QWidget::paintEvent( e );
        return;
    }

François Cartegnie's avatar
François Cartegnie committed
258
259
260
261
262
263
    int i_maxwidth, i_maxheight;
    QPixmap pixmap = QPixmap( pixmapUrl );
    QPainter painter(this);
    QBitmap pMask;
    float f_alpha = 1.0;

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

267
268
    painter.setOpacity( property( "opacity" ).toFloat() );

François Cartegnie's avatar
François Cartegnie committed
269
    if ( height() > MARGIN * 2 )
Ilkka Ollakka's avatar
   
Ilkka Ollakka committed
270
    {
François Cartegnie's avatar
François Cartegnie committed
271
272
273
274
275
276
277
278
279
        /* 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() )
280
        {
François Cartegnie's avatar
François Cartegnie committed
281
282
283
284
285
286
287
288
289
290
291
292
293
294
            /* 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 );
295
296
        }

François Cartegnie's avatar
François Cartegnie committed
297
298
299
300
        painter.drawPixmap(
                MARGIN + ( i_maxwidth - pixmap.width() ) /2,
                MARGIN + ( i_maxheight - pixmap.height() ) /2,
                pixmap);
Ilkka Ollakka's avatar
   
Ilkka Ollakka committed
301
    }
François Cartegnie's avatar
François Cartegnie committed
302
    QWidget::paintEvent( e );
303
304
}

305
306
void BackgroundWidget::contextMenuEvent( QContextMenuEvent *event )
{
307
    THEDP->setPopupMenu();
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
308
    event->accept();
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
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
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
426
#if 0
427
428
429
#include <QPushButton>
#include <QHBoxLayout>

zorglub's avatar
zorglub committed
430
431
432
433
/**********************************************************************
 * Visualization selector panel
 **********************************************************************/
VisualSelector::VisualSelector( intf_thread_t *_p_i ) :
434
                                QFrame( NULL ), p_intf( _p_i )
zorglub's avatar
zorglub committed
435
436
{
    QHBoxLayout *layout = new QHBoxLayout( this );
437
    layout->setMargin( 0 );
zorglub's avatar
zorglub committed
438
    QPushButton *prevButton = new QPushButton( "Prev" );
439
    QPushButton *nextButton = new QPushButton( "Next" );
zorglub's avatar
zorglub committed
440
441
    layout->addWidget( prevButton );
    layout->addWidget( nextButton );
zorglub's avatar
zorglub committed
442

443
    layout->addStretch( 10 );
444
    layout->addWidget( new QLabel( qtr( "Current visualization" ) ) );
zorglub's avatar
zorglub committed
445
446
447
448
449
450
451

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

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

zorglub's avatar
zorglub committed
452
    setLayout( layout );
453
    setMaximumHeight( 35 );
zorglub's avatar
zorglub committed
454
455
456
}

VisualSelector::~VisualSelector()
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
457
{}
zorglub's avatar
zorglub committed
458

zorglub's avatar
zorglub committed
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
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
478
#endif
zorglub's avatar
zorglub committed
479

480
481
SpeedLabel::SpeedLabel( intf_thread_t *_p_intf, QWidget *parent )
           : QLabel( parent ), p_intf( _p_intf )
482
{
483
    tooltipStringPattern = qtr( "Current playback speed: %1\nClick to adjust" );
484
485

    /* Create the Speed Control Widget */
486
    speedControl = new SpeedControlWidget( p_intf, this );
487
488
489
490
491
492
    speedControlMenu = new QMenu( this );

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

493
    /* Change the SpeedRate in the Label */
494
    CONNECT( THEMIM->getIM(), rateChanged( float ), this, setRate( float ) );
495

496
    DCONNECT( THEMIM, inputChanged( ),
497
              speedControl, activateOnState() );
498

499
    setContentsMargins(4, 0, 4, 0);
500
    setRate( var_InheritFloat( THEPL, "rate" ) );
501
}
502

503
504
SpeedLabel::~SpeedLabel()
{
505
    delete speedControlMenu;
506
}
507

508
509
510
/****************************************************************************
 * Small right-click menu for rate control
 ****************************************************************************/
511

512
513
514
void SpeedLabel::showSpeedMenu( QPoint pos )
{
    speedControlMenu->exec( QCursor::pos() - pos
515
                            + QPoint( -70 + width()/2, height() ) );
516
517
}

518
void SpeedLabel::setRate( float rate )
519
520
{
    QString str;
521
    str.setNum( rate, 'f', 2 );
522
523
    str.append( "x" );
    setText( str );
524
    setToolTip( tooltipStringPattern.arg( str ) );
525
526
527
    speedControl->updateControls( rate );
}

528
529
530
/**********************************************************************
 * Speed control widget
 **********************************************************************/
531
532
SpeedControlWidget::SpeedControlWidget( intf_thread_t *_p_i, QWidget *_parent )
                    : QFrame( _parent ), p_intf( _p_i )
533
{
534
    QSizePolicy sizePolicy( QSizePolicy::Fixed, QSizePolicy::Maximum );
535
536
    sizePolicy.setHorizontalStretch( 0 );
    sizePolicy.setVerticalStretch( 0 );
537

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
538
    speedSlider = new QSlider( this );
539
    speedSlider->setSizePolicy( sizePolicy );
540
541
    speedSlider->setMinimumSize( QSize( 140, 20 ) );
    speedSlider->setOrientation( Qt::Horizontal );
542
    speedSlider->setTickPosition( QSlider::TicksBelow );
543

544
    speedSlider->setRange( -34, 34 );
545
546
    speedSlider->setSingleStep( 1 );
    speedSlider->setPageStep( 1 );
547
    speedSlider->setTickInterval( 17 );
548

549
    CONNECT( speedSlider, valueChanged( int ), this, updateRate( int ) );
550

551
    QToolButton *normalSpeedButton = new QToolButton( this );
552
    normalSpeedButton->setMaximumSize( QSize( 26, 16 ) );
553
    normalSpeedButton->setAutoRaise( true );
554
    normalSpeedButton->setText( "1x" );
555
    normalSpeedButton->setToolTip( qtr( "Revert to normal play speed" ) );
556

557
    CONNECT( normalSpeedButton, clicked(), this, resetRate() );
558

559
560
561
562
563
564
565
566
567
568
569
570
571
572
    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() );

573
574
575
576
577
578
579
580
581
582
583
/*    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 );
584
585
586
    speedControlLayout->addWidget( slowerButton, 1, 0 );
    speedControlLayout->addWidget( normalSpeedButton, 1, 1, 1, 1, Qt::AlignRight );
    speedControlLayout->addWidget( fasterButton, 1, 2, 1, 1, Qt::AlignRight );
587
588
589
    //speedControlLayout->addWidget( spinBox );
    speedControlLayout->setContentsMargins( 0, 0, 0, 0 );
    speedControlLayout->setSpacing( 0 );
590

591
592
    lastValue = 0;

593
594
    activateOnState();
}
595

596
void SpeedControlWidget::activateOnState()
597
{
598
    speedSlider->setEnabled( THEMIM->getIM()->hasInput() );
599
    //spinBox->setEnabled( THEMIM->getIM()->hasInput() );
600
601
}

602
void SpeedControlWidget::updateControls( float rate )
603
{
604
605
606
607
608
    if( speedSlider->isSliderDown() )
    {
        //We don't want to change anything if the user is using the slider
        return;
    }
609

610
    double value = 17 * log( rate ) / log( 2. );
611
    int sliderValue = (int) ( ( value > 0 ) ? value + .5 : value - .5 );
612

613
    if( sliderValue < speedSlider->minimum() )
614
    {
615
        sliderValue = speedSlider->minimum();
616
    }
617
    else if( sliderValue > speedSlider->maximum() )
618
    {
619
        sliderValue = speedSlider->maximum();
620
    }
621
    lastValue = sliderValue;
622

623
    speedSlider->setValue( sliderValue );
624
    //spinBox->setValue( rate );
625
626
627
628
}

void SpeedControlWidget::updateRate( int sliderValue )
{
629
630
631
    if( sliderValue == lastValue )
        return;

632
    double speed = pow( 2, (double)sliderValue / 17 );
633
    int rate = INPUT_RATE_DEFAULT / speed;
634

635
    THEMIM->getIM()->setRate(rate);
636
637
638
639
640
641
    //spinBox->setValue( var_InheritFloat( THEPL, "rate" ) );
}

void SpeedControlWidget::updateSpinBoxRate( double r )
{
    var_SetFloat( THEPL, "rate", r );
642
643
644
645
}

void SpeedControlWidget::resetRate()
{
646
    THEMIM->getIM()->setRate( INPUT_RATE_DEFAULT );
647
}
648

649
CoverArtLabel::CoverArtLabel( QWidget *parent, intf_thread_t *_p_i )
650
    : QLabel( parent ), p_intf( _p_i ), p_item( NULL )
651
652
{
    setContextMenuPolicy( Qt::ActionsContextMenu );
653
654
    CONNECT( THEMIM->getIM(), artChanged( input_item_t * ),
             this, showArtUpdate( input_item_t * ) );
655

656
    setMinimumHeight( 128 );
657
    setMinimumWidth( 128 );
658
659
    setScaledContents( false );
    setAlignment( Qt::AlignCenter );
660

661
    QAction *action = new QAction( qtr( "Download cover art" ), this );
662
    CONNECT( action, triggered(), this, askForUpdate() );
663
    addAction( action );
664

Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
665
    action = new QAction( qtr( "Add cover art from file" ), this );
Angelo Haller's avatar
Angelo Haller committed
666
667
668
    CONNECT( action, triggered(), this, setArtFromFile() );
    addAction( action );

669
670
    p_item = THEMIM->currentInputItem();
    if( p_item )
671
672
    {
        vlc_gc_incref( p_item );
673
        showArtUpdate( p_item );
674
    }
675
676
    else
        showArtUpdate( "" );
677
678
}

679
680
681
682
683
CoverArtLabel::~CoverArtLabel()
{
    QList< QAction* > artActions = actions();
    foreach( QAction *act, artActions )
        removeAction( act );
684
685
686
687
688
689
690
691
    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 );
692
693
}

694
void CoverArtLabel::showArtUpdate( const QString& url )
695
{
696
    QPixmap pix;
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
697
    if( !url.isEmpty() && pix.load( url ) )
698
    {
699
        pix = pix.scaled( minimumWidth(), minimumHeight(),
700
701
                          Qt::KeepAspectRatioByExpanding,
                          Qt::SmoothTransformation );
702
703
704
    }
    else
    {
705
        pix = QPixmap( ":/noart.png" );
706
    }
707
    setPixmap( pix );
708
709
}

710
711
712
713
714
715
716
717
718
719
720
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 );
}

721
void CoverArtLabel::askForUpdate()
722
{
723
    THEMIM->getIM()->requestArtUpdate( p_item, true );
724
725
}

Angelo Haller's avatar
Angelo Haller committed
726
727
728
729
730
void CoverArtLabel::setArtFromFile()
{
    if( !p_item )
        return;

Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
731
    QString filePath = QFileDialog::getOpenFileName( this, qtr( "Choose Cover Art" ),
Angelo Haller's avatar
Angelo Haller committed
732
733
734
735
736
737
738
739
740
741
        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 );
}

742
743
744
745
746
void CoverArtLabel::clear()
{
    showArtUpdate( "" );
}

747
TimeLabel::TimeLabel( intf_thread_t *_p_intf, TimeLabel::Display _displayType  )
748
    : ClickableQLabel(), p_intf( _p_intf ), displayType( _displayType )
749
{
750
    b_remainingTime = false;
751
752
    if( _displayType != TimeLabel::Elapsed )
        b_remainingTime = getSettings()->value( "MainWindow/ShowRemainingTime", false ).toBool();
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
    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;
    }
773
    setAlignment( Qt::AlignRight | Qt::AlignVCenter );
774

775
776
    CONNECT( THEMIM->getIM(), positionUpdated( float, int64_t, int ),
              this, setDisplayPosition( float, int64_t, int ) );
777

778
    setStyleSheet( "QLabel { padding-left: 4px; padding-right: 4px; }" );
779
780
}

781
void TimeLabel::setDisplayPosition( float pos, int64_t t, int length )
782
{
783
    if( pos == -1.f )
784
    {
785
        setMinimumSize( QSize( 0, 0 ) );
786
        if( displayType == TimeLabel::Both )
787
            setText( "--:--/--:--" );
788
        else
789
            setText( "--:--" );
790
791
        return;
    }
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
792

793
    int time = t / 1000000;
794

795
796
797
    secstotimestr( psz_length, length );
    secstotimestr( psz_time, ( b_remainingTime && length ) ? length - time
                                                           : time );
798
799
800

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

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

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

    setText( timestr );
864
865
}

866

867
868
869
void TimeLabel::toggleTimeDisplay()
{
    b_remainingTime = !b_remainingTime;
870
    getSettings()->setValue( "MainWindow/ShowRemainingTime", b_remainingTime );
871
}
872