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

zorglub's avatar
zorglub 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 */
zorglub's avatar
zorglub 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>

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

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

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

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

zorglub's avatar
zorglub committed
435
    setLayout( layout );
436
    setMaximumHeight( 35 );
zorglub's avatar
zorglub committed
437
438
439
}

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

zorglub's avatar
zorglub 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
zorglub's avatar
zorglub 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
    delete speedControlMenu;
491
}
492

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

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

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

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

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

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

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

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

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

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

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

576
577
    lastValue = 0;

578
579
    activateOnState();
}
580

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
716
    QString filePath = QFileDialog::getOpenFileName( this, qtr( "Choose Cover Art" ),
Angelo Haller's avatar
Angelo Haller committed
717
718
719
720
721
722
723
724
725
726
        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 );
}

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

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

761
    bufTimer->setSingleShot( true );
762

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

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

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

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

787
    int time = t / 1000000;
788

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

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

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

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

    setText( timestr );
858
859
}

860

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


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

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

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