interface_widgets.cpp 17.5 KB
Newer Older
1
/*****************************************************************************
2
 * interface_widgets.cpp : Custom widgets for the main interface
3
 ****************************************************************************
4
 * Copyright (C) 2006-2008 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 "components/interface_widgets.hpp"
32 33 34 35

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

#include <vlc_vout.h>
36

Clément Stenac's avatar
Clément Stenac committed
37
#include <QLabel>
38
#include <QToolButton>
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
39 40
#include <QPalette>
#include <QResizeEvent>
41
#include <QDate>
42 43
#include <QMenu>
#include <QWidgetAction>
44
#include <QDesktopWidget>
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
45

46 47 48
#ifdef Q_WS_X11
# include <X11/Xlib.h>
# include <qx11info_x11.h>
49 50 51 52 53 54 55 56 57
static void videoSync( void )
{
    /* 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 );
}
#else
# define videoSync() (void)0
58
#endif
59

60 61
#include <math.h>

62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
class ReparentableWidget : public QWidget
{
private:
    VideoWidget *owner;
public:
    ReparentableWidget( VideoWidget *owner ) : owner( owner )
    {
    }

protected:
    void keyPressEvent( QKeyEvent *e )
    {
        emit owner->keyPressed( e );
    }
};

78 79 80 81
/**********************************************************************
 * Video Widget. A simple frame on which video is drawn
 * This class handles resize issues
 **********************************************************************/
82

83
VideoWidget::VideoWidget( intf_thread_t *_p_i ) : QFrame( NULL ), p_intf( _p_i )
84
{
85
    /* Init */
86
    reparentable = NULL;
87 88
    videoSize.rwidth() = -1;
    videoSize.rheight() = -1;
89 90 91 92

    hide();

    /* Set the policy to expand in both directions */
93
//    setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding );
94

95 96 97
    layout = new QHBoxLayout( this );
    layout->setContentsMargins( 0, 0, 0, 0 );
    setLayout( layout );
98 99 100 101
}

VideoWidget::~VideoWidget()
{
102
    /* Ensure we are not leaking the video output. This would crash. */
103
    assert( reparentable == NULL );
104 105
}

106
/**
107
 * Request the video to avoid the conflicts
108
 **/
109
WId VideoWidget::request( int *pi_x, int *pi_y,
110 111
                          unsigned int *pi_width, unsigned int *pi_height,
                          bool b_keep_size )
112
{
113
    msg_Dbg( p_intf, "Video was requested %i, %i", *pi_x, *pi_y );
114

115 116 117 118 119
    if( reparentable != NULL )
    {
        msg_Dbg( p_intf, "embedded video already in use" );
        return NULL;
    }
120 121 122 123 124 125
    if( b_keep_size )
    {
        *pi_width  = size().width();
        *pi_height = size().height();
    }

126 127 128 129 130
    /* The Qt4 UI needs a fixed a widget ("this"), so that the parent layout is
     * not messed up when we the video is reparented. Hence, we create an extra
     * reparentable widget, that will be within the VideoWidget in windowed
     * mode, and within the root window (NULL parent) in full-screen mode.
     */
131
    reparentable = new ReparentableWidget( this );
132 133 134 135 136 137 138 139 140 141 142
    QLayout *innerLayout = new QHBoxLayout( reparentable );
    innerLayout->setContentsMargins( 0, 0, 0, 0 );

    /* 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. */
    QWidget *stable = new QWidget();
    QPalette plt = palette();
    plt.setColor( QPalette::Window, Qt::black );
    stable->setPalette( plt );
    stable->setAutoFillBackground(true);
143 144 145
    /* Indicates that the widget wants to draw directly onto the screen.
       Widgets with this attribute set do not participate in composition
       management */
146 147 148 149 150 151 152
    stable->setAttribute( Qt::WA_PaintOnScreen, true );

    innerLayout->addWidget( stable );

    reparentable->setLayout( innerLayout );
    layout->addWidget( reparentable );

153
    videoSync();
154
#ifndef NDEBUG
155 156
    msg_Dbg( p_intf, "embedded video ready (handle %p)",
             (void *)stable->winId() );
157
#endif
158
    return stable->winId();
159 160
}

161
/* Set the Widget to the correct Size */
162
/* Function has to be called by the parent
Rémi Denis-Courmont's avatar
typo  
Rémi Denis-Courmont committed
163
   Parent has to care about resizing itself */
164
void VideoWidget::SetSizing( unsigned int w, unsigned int h )
165
{
166 167 168
    msg_Dbg( p_intf, "Video is resizing to: %i %i", w, h );
    videoSize.rwidth() = w;
    videoSize.rheight() = h;
169
    if( !isVisible() ) show();
170
    updateGeometry(); // Needed for deinterlace
171
    videoSync();
172 173
}

174 175 176 177
void VideoWidget::SetFullScreen( bool b_fs )
{
    const Qt::WindowStates curstate = reparentable->windowState();
    Qt::WindowStates newstate = curstate;
178
    Qt::WindowFlags  newflags = reparentable->windowFlags();
179

180

181
    if( b_fs )
182
    {
183
        newstate |= Qt::WindowFullScreen;
184 185
        newflags |= Qt::WindowStaysOnTopHint;
    }
186
    else
187
    {
188
        newstate &= ~Qt::WindowFullScreen;
189 190
        newflags &= ~Qt::WindowStaysOnTopHint;
    }
191 192 193 194 195
    if( newstate == curstate )
        return; /* no changes needed */

    if( b_fs )
    {   /* Go full-screen */
196 197 198 199
        int numscreen = QApplication::desktop()->screenNumber( p_intf->p_sys->p_mi );
        QRect screenres = QApplication::desktop()->screenGeometry( numscreen );

        reparentable->setWindowState( newstate );
200
        reparentable->setParent( NULL );
201
        reparentable->setWindowFlags( newflags );
202 203 204 205 206 207
        /* To be sure window is on proper-screen in xinerama */
        if( !screenres.contains( reparentable->pos() ) )
        {
            msg_Dbg( p_intf, "Moving video to correct screen");
            reparentable->move( QPoint( screenres.x(), screenres.y() ) );
        }
208 209 210 211
        reparentable->show();
    }
    else
    {   /* Go windowed */
212
        reparentable->setWindowFlags( newflags );
213
        layout->addWidget( reparentable );
214
        reparentable->setWindowState( newstate );
215
    }
216
    videoSync();
217 218
}

219
void VideoWidget::release( void )
220
{
Rémi Denis-Courmont's avatar
Typo  
Rémi Denis-Courmont committed
221
    msg_Dbg( p_intf, "Video is not needed anymore" );
222 223 224
    //layout->removeWidget( reparentable );
    delete reparentable;
    reparentable = NULL;
225 226
    videoSize.rwidth() = 0;
    videoSize.rheight() = 0;
227
    updateGeometry();
228
    hide();
229
}
230

231 232 233 234
QSize VideoWidget::sizeHint() const
{
    return videoSize;
}
235

236 237
/**********************************************************************
 * Background Widget. Show a simple image background. Currently,
238
 * it's album art if present or cone.
239
 **********************************************************************/
240 241
#define ICON_SIZE 128
#define MAX_BG_SIZE 400
242
#define MIN_BG_SIZE 128
243

244 245
BackgroundWidget::BackgroundWidget( intf_thread_t *_p_i )
                 :QWidget( NULL ), p_intf( _p_i )
246
{
247
    /* We should use that one to take the more size it can */
248
    setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding);
249

250
    /* A dark background */
Clément Stenac's avatar
Clément Stenac committed
251
    setAutoFillBackground( true );
252
    plt = palette();
253 254 255 256
    plt.setColor( QPalette::Active, QPalette::Window , Qt::black );
    plt.setColor( QPalette::Inactive, QPalette::Window , Qt::black );
    setPalette( plt );

257
    /* A cone in the middle */
258
    label = new QLabel;
259
    label->setMargin( 5 );
260 261
    label->setMaximumHeight( MAX_BG_SIZE );
    label->setMaximumWidth( MAX_BG_SIZE );
262 263
    label->setMinimumHeight( MIN_BG_SIZE );
    label->setMinimumWidth( MIN_BG_SIZE );
264
    label->setAlignment( Qt::AlignCenter );
265
    if( QDate::currentDate().dayOfYear() >= 354 )
266
        label->setPixmap( QPixmap( ":/logo/vlc128-christmas.png" ) );
267
    else
268
        label->setPixmap( QPixmap( ":/logo/vlc128.png" ) );
269

270 271 272 273
    QGridLayout *backgroundLayout = new QGridLayout( this );
    backgroundLayout->addWidget( label, 0, 1 );
    backgroundLayout->setColumnStretch( 0, 1 );
    backgroundLayout->setColumnStretch( 2, 1 );
274

275
    CONNECT( THEMIM->getIM(), artChanged( QString ),
276
             this, updateArt( const QString& ) );
277 278
}

Clément Stenac's avatar
Clément Stenac committed
279
BackgroundWidget::~BackgroundWidget()
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
280
{}
Ilkka Ollakka's avatar
 
Ilkka Ollakka committed
281

282 283 284 285 286 287 288 289
void BackgroundWidget::resizeEvent( QResizeEvent * event )
{
    if( event->size().height() <= MIN_BG_SIZE )
        label->hide();
    else
        label->show();
}

290
void BackgroundWidget::updateArt( const QString& url )
291
{
292
    if( url.isEmpty() )
Ilkka Ollakka's avatar
 
Ilkka Ollakka committed
293
    {
294
        if( QDate::currentDate().dayOfYear() >= 354 )
295
            label->setPixmap( QPixmap( ":/logo/vlc128-christmas.png" ) );
296
        else
297
            label->setPixmap( QPixmap( ":/logo/vlc128.png" ) );
Ilkka Ollakka's avatar
 
Ilkka Ollakka committed
298
    }
299
    else
Ilkka Ollakka's avatar
 
Ilkka Ollakka committed
300
    {
301 302 303 304 305 306 307 308 309
        QPixmap pixmap( url );
        if( pixmap.width() > label->maximumWidth() ||
            pixmap.height() > label->maximumHeight() )
        {
            pixmap = pixmap.scaled( label->maximumWidth(),
                          label->maximumHeight(), Qt::KeepAspectRatio );
        }

        label->setPixmap( pixmap );
Ilkka Ollakka's avatar
 
Ilkka Ollakka committed
310
    }
311 312
}

313 314 315
void BackgroundWidget::contextMenuEvent( QContextMenuEvent *event )
{
    QVLCMenu::PopupMenu( p_intf, true );
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
316
    event->accept();
317
}
318

Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
319
#if 0
320 321 322
#include <QPushButton>
#include <QHBoxLayout>

Clément Stenac's avatar
Clément Stenac committed
323 324 325 326
/**********************************************************************
 * Visualization selector panel
 **********************************************************************/
VisualSelector::VisualSelector( intf_thread_t *_p_i ) :
327
                                QFrame( NULL ), p_intf( _p_i )
Clément Stenac's avatar
Clément Stenac committed
328 329
{
    QHBoxLayout *layout = new QHBoxLayout( this );
330
    layout->setMargin( 0 );
Clément Stenac's avatar
Clément Stenac committed
331
    QPushButton *prevButton = new QPushButton( "Prev" );
332
    QPushButton *nextButton = new QPushButton( "Next" );
Clément Stenac's avatar
Clément Stenac committed
333 334
    layout->addWidget( prevButton );
    layout->addWidget( nextButton );
Clément Stenac's avatar
Clément Stenac committed
335

336
    layout->addStretch( 10 );
337
    layout->addWidget( new QLabel( qtr( "Current visualization" ) ) );
Clément Stenac's avatar
Clément Stenac committed
338 339 340 341 342 343 344

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

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

Clément Stenac's avatar
Clément Stenac committed
345
    setLayout( layout );
346
    setMaximumHeight( 35 );
Clément Stenac's avatar
Clément Stenac committed
347 348 349
}

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

Clément Stenac's avatar
Clément Stenac committed
352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370
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
371
#endif
Clément Stenac's avatar
Clément Stenac committed
372

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
373 374 375
SpeedLabel::SpeedLabel( intf_thread_t *_p_intf, const QString& text,
                        QWidget *parent )
           : QLabel( text, parent ), p_intf( _p_intf )
376
{
377
    setToolTip( qtr( "Current playback speed.\nClick to adjust" ) );
378 379

    /* Create the Speed Control Widget */
380
    speedControl = new SpeedControlWidget( p_intf, this );
381 382 383 384 385 386 387 388 389
    speedControlMenu = new QMenu( this );

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

    /* Change the SpeedRate in the Status Bar */
    CONNECT( THEMIM->getIM(), rateChanged( int ), this, setRate( int ) );

390
    CONNECT( THEMIM, inputChanged( input_thread_t * ),
391
             speedControl, activateOnState() );
392

393
}
394 395 396 397 398
SpeedLabel::~SpeedLabel()
{
        delete speedControl;
        delete speedControlMenu;
}
399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417
/****************************************************************************
 * Small right-click menu for rate control
 ****************************************************************************/
void SpeedLabel::showSpeedMenu( QPoint pos )
{
    speedControlMenu->exec( QCursor::pos() - pos
                          + QPoint( 0, height() ) );
}

void SpeedLabel::setRate( int rate )
{
    QString str;
    str.setNum( ( 1000 / (double)rate ), 'f', 2 );
    str.append( "x" );
    setText( str );
    setToolTip( str );
    speedControl->updateControls( rate );
}

418 419 420
/**********************************************************************
 * Speed control widget
 **********************************************************************/
421 422
SpeedControlWidget::SpeedControlWidget( intf_thread_t *_p_i, QWidget *_parent )
                    : QFrame( _parent ), p_intf( _p_i )
423
{
424
    QSizePolicy sizePolicy( QSizePolicy::Maximum, QSizePolicy::Fixed );
425 426
    sizePolicy.setHorizontalStretch( 0 );
    sizePolicy.setVerticalStretch( 0 );
427

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
428
    speedSlider = new QSlider( this );
429 430 431 432 433
    speedSlider->setSizePolicy( sizePolicy );
    speedSlider->setMaximumSize( QSize( 80, 200 ) );
    speedSlider->setOrientation( Qt::Vertical );
    speedSlider->setTickPosition( QSlider::TicksRight );

434
    speedSlider->setRange( -34, 34 );
435 436
    speedSlider->setSingleStep( 1 );
    speedSlider->setPageStep( 1 );
437
    speedSlider->setTickInterval( 17 );
438

439
    CONNECT( speedSlider, valueChanged( int ), this, updateRate( int ) );
440

441
    QToolButton *normalSpeedButton = new QToolButton( this );
442
    normalSpeedButton->setMaximumSize( QSize( 26, 20 ) );
443
    normalSpeedButton->setAutoRaise( true );
444
    normalSpeedButton->setText( "1x" );
445
    normalSpeedButton->setToolTip( qtr( "Revert to normal play speed" ) );
446

447
    CONNECT( normalSpeedButton, clicked(), this, resetRate() );
448

Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
449
    QVBoxLayout *speedControlLayout = new QVBoxLayout( this );
450 451
    speedControlLayout->setLayoutMargins( 4, 4, 4, 4, 4 );
    speedControlLayout->setSpacing( 4 );
452 453
    speedControlLayout->addWidget( speedSlider );
    speedControlLayout->addWidget( normalSpeedButton );
454

455 456
    activateOnState();
}
457

458
void SpeedControlWidget::activateOnState()
459
{
460
    speedSlider->setEnabled( THEMIM->getIM()->hasInput() );
461 462
}

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

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

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

483 484 485 486
    //Block signals to avoid feedback loop
    speedSlider->blockSignals( true );
    speedSlider->setValue( sliderValue );
    speedSlider->blockSignals( false );
487 488 489 490
}

void SpeedControlWidget::updateRate( int sliderValue )
{
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
}

void SpeedControlWidget::resetRate()
{
499
    THEMIM->getIM()->setRate( INPUT_RATE_DEFAULT );
500
}
501

502
CoverArtLabel::CoverArtLabel( QWidget *parent, intf_thread_t *_p_i )
503
              : QLabel( parent ), p_intf( _p_i )
504 505
{
    setContextMenuPolicy( Qt::ActionsContextMenu );
506
    CONNECT( this, updateRequested(), this, askForUpdate() );
507 508 509 510 511

    setMinimumHeight( 128 );
    setMinimumWidth( 128 );
    setMaximumHeight( 128 );
    setMaximumWidth( 128 );
512 513
    setScaledContents( false );
    setAlignment( Qt::AlignCenter );
514

515 516
    QList< QAction* > artActions = actions();
    QAction *action = new QAction( qtr( "Download cover art" ), this );
517
    CONNECT( action, triggered(), this, askForUpdate() );
518
    addAction( action );
519

520
    showArtUpdate( "" );
521 522
}

523 524 525 526 527 528 529
CoverArtLabel::~CoverArtLabel()
{
    QList< QAction* > artActions = actions();
    foreach( QAction *act, artActions )
        removeAction( act );
}

530
void CoverArtLabel::showArtUpdate( const QString& url )
531
{
532 533
    QPixmap pix;
    if( !url.isEmpty()  && pix.load( url ) )
534
    {
535 536
        pix = pix.scaled( maximumWidth(), maximumHeight(),
                          Qt::KeepAspectRatioByExpanding );
537 538 539
    }
    else
    {
540
        pix = QPixmap( ":/noart.png" );
541
    }
542
    setPixmap( pix );
543 544
}

545
void CoverArtLabel::askForUpdate()
546
{
547
    THEMIM->getIM()->requestArtUpdate();
548 549
}

550 551 552 553 554 555 556 557
TimeLabel::TimeLabel( intf_thread_t *_p_intf  ) :QLabel(), p_intf( _p_intf )
{
   b_remainingTime = false;
   setText( " --:--/--:-- " );
   setAlignment( Qt::AlignRight | Qt::AlignVCenter );
   setToolTip( qtr( "Toggle between elapsed and remaining time" ) );


558 559
   CONNECT( THEMIM->getIM(), cachingChanged( float ),
            this, setCaching( float ) );
560 561 562 563 564 565
   CONNECT( THEMIM->getIM(), positionUpdated( float, int, int ),
             this, setDisplayPosition( float, int, int ) );
}

void TimeLabel::setDisplayPosition( float pos, int time, int length )
{
566
    if( pos == -1.f )
567 568 569 570
    {
        setText( " --:--/--:-- " );
        return;
    }
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
571

572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589
    char psz_length[MSTRTIME_MAX_SIZE], psz_time[MSTRTIME_MAX_SIZE];
    secstotimestr( psz_length, length );
    secstotimestr( psz_time, ( b_remainingTime && length ) ? length - time
                                                           : time );

    QString timestr;
    timestr.sprintf( "%s/%s", psz_time,
                            ( !length && time ) ? "--:--" : psz_length );

    /* Add a minus to remaining time*/
    if( b_remainingTime && length ) setText( " -"+timestr+" " );
    else setText( " "+timestr+" " );
}

void TimeLabel::toggleTimeDisplay()
{
    b_remainingTime = !b_remainingTime;
}
590

591 592
void TimeLabel::setCaching( float f_cache )
{
593 594 595
    QString amount;
    amount.setNum( (int)(100 * f_cache) );
    msg_Dbg( p_intf, "New caching: %d", (int)(100*f_cache));
596
    setText( "Buff: " + amount + "%" );
597 598
}

599