interface_widgets.cpp 23 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 200 201
    /* Init the cone art */
    updateArt( "" );

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Clément Stenac's avatar
Clément Stenac committed
315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333
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
334
#endif
Clément Stenac's avatar
Clément Stenac committed
335

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

418 419 420 421 422 423 424 425 426 427 428 429 430 431
    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() );

432 433 434 435 436 437 438 439 440 441 442
/*    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 );
443 444 445
    speedControlLayout->addWidget( slowerButton, 1, 0 );
    speedControlLayout->addWidget( normalSpeedButton, 1, 1, 1, 1, Qt::AlignRight );
    speedControlLayout->addWidget( fasterButton, 1, 2, 1, 1, Qt::AlignRight );
446 447 448
    //speedControlLayout->addWidget( spinBox );
    speedControlLayout->setContentsMargins( 0, 0, 0, 0 );
    speedControlLayout->setSpacing( 0 );
449

450 451
    lastValue = 0;

452 453
    activateOnState();
}
454

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

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

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

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

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

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

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

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

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

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

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

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

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

Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
524
    action = new QAction( qtr( "Add cover art from file" ), this );
Angelo Haller's avatar
Angelo Haller committed
525 526 527
    CONNECT( action, triggered(), this, setArtFromFile() );
    addAction( action );

528
    showArtUpdate( "" );
529 530
}

531 532 533 534 535
CoverArtLabel::~CoverArtLabel()
{
    QList< QAction* > artActions = actions();
    foreach( QAction *act, artActions )
        removeAction( act );
536 537 538 539 540 541 542 543
    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 );
544 545
}

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

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

573
void CoverArtLabel::askForUpdate()
574
{
575
    THEMIM->getIM()->requestArtUpdate( p_item );
576 577
}

Angelo Haller's avatar
Angelo Haller committed
578 579 580 581 582
void CoverArtLabel::setArtFromFile()
{
    if( !p_item )
        return;

Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
583
    QString filePath = QFileDialog::getOpenFileName( this, qtr( "Choose Cover Art" ),
Angelo Haller's avatar
Angelo Haller committed
584 585 586 587 588 589 590 591 592 593
        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 );
}

594
TimeLabel::TimeLabel( intf_thread_t *_p_intf, TimeLabel::Display _displayType  )
595
    : ClickableQLabel(), p_intf( _p_intf ), bufTimer( new QTimer(this) ),
596
      buffering( false ), showBuffering(false), bufVal( -1 ), displayType( _displayType )
597
{
598
    b_remainingTime = false;
599 600
    if( _displayType != TimeLabel::Elapsed )
        b_remainingTime = getSettings()->value( "MainWindow/ShowRemainingTime", false ).toBool();
601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620
    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;
    }
621
    setAlignment( Qt::AlignRight | Qt::AlignVCenter );
622

623
    bufTimer->setSingleShot( true );
624

625 626 627 628 629
    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() );
630

631
    setStyleSheet( "padding-left: 4px; padding-right: 4px;" );
632 633
}

634
void TimeLabel::setDisplayPosition( float pos, int64_t t, int length )
635
{
636 637 638
    showBuffering = false;
    bufTimer->stop();

639
    if( pos == -1.f )
640
    {
641
        setMinimumSize( QSize( 0, 0 ) );
642
        if( displayType == TimeLabel::Both )
643
            setText( "--:--/--:--" );
644
        else
645
            setText( "--:--" );
646 647
        return;
    }
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
648

649
    int time = t / 1000000;
650

651 652 653
    secstotimestr( psz_length, length );
    secstotimestr( psz_time, ( b_remainingTime && length ) ? length - time
                                                           : time );
654 655 656

    // compute the minimum size that will be required for the psz_length
    // and use it to enforce a minimal size to avoid "dancing" widgets
657
    QSize minsize( 0, 0 );
658 659 660 661 662 663 664 665 666 667 668 669 670
    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 );
    }

671 672 673
    switch( displayType )
    {
        case TimeLabel::Elapsed:
674
            setMinimumSize( minsize );
675
            setText( QString( psz_time ) );
676 677 678
            break;
        case TimeLabel::Remaining:
            if( b_remainingTime )
679 680
            {
                setMinimumSize( minsize );
681
                setText( QString("-") + QString( psz_time ) );
682
            }
683
            else
684 685
            {
                setMinimumSize( QSize( 0, 0 ) );
686
                setText( QString( psz_length ) );
687
            }
688 689 690
            break;
        case TimeLabel::Both:
        default:
691
            QString timestr = QString( "%1%2/%3" )
692 693 694
            .arg( QString( (b_remainingTime && length) ? "-" : "" ) )
            .arg( QString( psz_time ) )
            .arg( QString( ( !length && time ) ? "--:--" : psz_length ) );
695

696 697 698
            setText( timestr );
            break;
    }
699 700 701 702 703 704 705 706 707 708 709 710 711 712 713
    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 ) );
714
    QString timestr = QString( "%1%2/%3" )
715 716 717
        .arg( QString( (b_remainingTime && cachedLength) ? "-" : "" ) )
        .arg( QString( psz_time ) )
        .arg( QString( ( !cachedLength && time ) ? "--:--" : psz_length ) );
718 719

    setText( timestr );
720 721
}

722

723 724 725
void TimeLabel::toggleTimeDisplay()
{
    b_remainingTime = !b_remainingTime;
726
    getSettings()->setValue( "MainWindow/ShowRemainingTime", b_remainingTime );
727
}
728 729


730
void TimeLabel::updateBuffering( float _buffered )
731
{
732 733 734 735 736 737 738 739 740 741
    bufVal = _buffered;
    if( !buffering || bufVal == 0 )
    {
        showBuffering = false;
        buffering = true;
        bufTimer->start(200);
    }
    else if( bufVal == 1 )
    {
        showBuffering = buffering = false;
742
        bufTimer->stop();
743 744
    }
    update();
745
}
746

747
void TimeLabel::updateBuffering()
748
{
749 750
    showBuffering = true;
    update();
751 752
}

753
void TimeLabel::paintEvent( QPaintEvent* event )
754
{
755 756 757 758 759 760 761 762
    if( showBuffering )
    {
        QRect r( rect() );
        r.setLeft( r.width() * bufVal );
        QPainter p( this );
        p.setOpacity( 0.4 );
        p.fillRect( r, palette().color( QPalette::Highlight ) );
    }
763 764
    QLabel::paintEvent( event );
}