interface_widgets.cpp 16.6 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>
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
44

45 46 47
#ifdef Q_WS_X11
# include <X11/Xlib.h>
# include <qx11info_x11.h>
48 49 50 51 52 53 54 55 56
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
57
#endif
58

59 60
#include <math.h>

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

66
VideoWidget::VideoWidget( intf_thread_t *_p_i ) : QFrame( NULL ), p_intf( _p_i )
67
{
68
    /* Init */
69
    reparentable = NULL;
70 71
    videoSize.rwidth() = -1;
    videoSize.rheight() = -1;
72 73 74 75

    hide();

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

78 79 80
    /* Indicates that the widget wants to draw directly onto the screen.
       Widgets with this attribute set do not participate in composition
       management */
81
    setAttribute( Qt::WA_PaintOnScreen, true );
82 83 84 85

    layout = new QHBoxLayout( this );
    layout->setContentsMargins( 0, 0, 0, 0 );
    setLayout( layout );
86 87
}

88 89 90
void VideoWidget::paintEvent(QPaintEvent *ev)
{
    QFrame::paintEvent(ev);
91 92 93
#ifdef Q_WS_X11
    XFlush( QX11Info::display() );
#endif
94 95
}

96 97
VideoWidget::~VideoWidget()
{
98
    /* Ensure we are not leaking the video output. This would crash. */
99
    assert( reparentable == NULL );
100 101
}

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

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

122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
    /* 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.
     */
    reparentable = new QWidget();
    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);
    stable->setAttribute( Qt::WA_PaintOnScreen, true );

    innerLayout->addWidget( stable );

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

146
    videoSync();
147
#ifndef NDEBUG
148 149
    msg_Dbg( p_intf, "embedded video ready (handle %p)",
             (void *)stable->winId() );
150
#endif
151
    return stable->winId();
152 153
}

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

167 168 169 170
void VideoWidget::SetFullScreen( bool b_fs )
{
    const Qt::WindowStates curstate = reparentable->windowState();
    Qt::WindowStates newstate = curstate;
171
    Qt::WindowFlags  newflags = reparentable->windowFlags();
172 173

    if( b_fs )
174
    {
175
        newstate |= Qt::WindowFullScreen;
176 177
        newflags |= Qt::WindowStaysOnTopHint;
    }
178
    else
179
    {
180
        newstate &= ~Qt::WindowFullScreen;
181 182
        newflags &= ~Qt::WindowStaysOnTopHint;
    }
183 184 185 186 187 188
    if( newstate == curstate )
        return; /* no changes needed */

    if( b_fs )
    {   /* Go full-screen */
        reparentable->setWindowState( newstate );
189
        reparentable->setParent( NULL );
190
        reparentable->setWindowFlags( newflags );
191 192 193 194
        reparentable->show();
    }
    else
    {   /* Go windowed */
195
        reparentable->setWindowFlags( newflags );
196
        layout->addWidget( reparentable );
197
        reparentable->setWindowState( newstate );
198
    }
199
    videoSync();
200 201
}

202
void VideoWidget::release( void )
203
{
Rémi Denis-Courmont's avatar
Typo  
Rémi Denis-Courmont committed
204
    msg_Dbg( p_intf, "Video is not needed anymore" );
205 206 207
    //layout->removeWidget( reparentable );
    delete reparentable;
    reparentable = NULL;
208 209
    videoSize.rwidth() = 0;
    videoSize.rheight() = 0;
210
    updateGeometry();
211
    hide();
212
}
213

214 215 216 217
QSize VideoWidget::sizeHint() const
{
    return videoSize;
}
218

219 220
/**********************************************************************
 * Background Widget. Show a simple image background. Currently,
221
 * it's album art if present or cone.
222
 **********************************************************************/
223 224
#define ICON_SIZE 128
#define MAX_BG_SIZE 400
225
#define MIN_BG_SIZE 128
226

227 228
BackgroundWidget::BackgroundWidget( intf_thread_t *_p_i )
                 :QWidget( NULL ), p_intf( _p_i )
229
{
230
    /* We should use that one to take the more size it can */
231
    setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding);
232

233
    /* A dark background */
Clément Stenac's avatar
Clément Stenac committed
234
    setAutoFillBackground( true );
235
    plt = palette();
236 237 238 239
    plt.setColor( QPalette::Active, QPalette::Window , Qt::black );
    plt.setColor( QPalette::Inactive, QPalette::Window , Qt::black );
    setPalette( plt );

240
    /* A cone in the middle */
241
    label = new QLabel;
242
    label->setMargin( 5 );
243 244
    label->setMaximumHeight( MAX_BG_SIZE );
    label->setMaximumWidth( MAX_BG_SIZE );
245 246
    label->setMinimumHeight( MIN_BG_SIZE );
    label->setMinimumWidth( MIN_BG_SIZE );
247
    if( QDate::currentDate().dayOfYear() >= 354 )
248
        label->setPixmap( QPixmap( ":/logo/vlc128-christmas.png" ) );
249
    else
250
        label->setPixmap( QPixmap( ":/logo/vlc128.png" ) );
251

252 253 254 255
    QGridLayout *backgroundLayout = new QGridLayout( this );
    backgroundLayout->addWidget( label, 0, 1 );
    backgroundLayout->setColumnStretch( 0, 1 );
    backgroundLayout->setColumnStretch( 2, 1 );
256

257
    CONNECT( THEMIM->getIM(), artChanged( QString ),
258
             this, updateArt( const QString& ) );
259 260
}

Clément Stenac's avatar
Clément Stenac committed
261
BackgroundWidget::~BackgroundWidget()
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
262
{}
Ilkka Ollakka's avatar
 
Ilkka Ollakka committed
263

264 265 266 267 268 269 270 271
void BackgroundWidget::resizeEvent( QResizeEvent * event )
{
    if( event->size().height() <= MIN_BG_SIZE )
        label->hide();
    else
        label->show();
}

272
void BackgroundWidget::updateArt( const QString& url )
273
{
274
    if( url.isEmpty() )
Ilkka Ollakka's avatar
 
Ilkka Ollakka committed
275
    {
276
        if( QDate::currentDate().dayOfYear() >= 354 )
277
            label->setPixmap( QPixmap( ":/logo/vlc128-christmas.png" ) );
278
        else
279
            label->setPixmap( QPixmap( ":/logo/vlc128.png" ) );
Ilkka Ollakka's avatar
 
Ilkka Ollakka committed
280
    }
281
    else
Ilkka Ollakka's avatar
 
Ilkka Ollakka committed
282
    {
283
        label->setPixmap( QPixmap( url ) );
Ilkka Ollakka's avatar
 
Ilkka Ollakka committed
284
    }
285 286
}

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

Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
293
#if 0
294 295 296
#include <QPushButton>
#include <QHBoxLayout>

Clément Stenac's avatar
Clément Stenac committed
297 298 299 300
/**********************************************************************
 * Visualization selector panel
 **********************************************************************/
VisualSelector::VisualSelector( intf_thread_t *_p_i ) :
301
                                QFrame( NULL ), p_intf( _p_i )
Clément Stenac's avatar
Clément Stenac committed
302 303
{
    QHBoxLayout *layout = new QHBoxLayout( this );
304
    layout->setMargin( 0 );
Clément Stenac's avatar
Clément Stenac committed
305
    QPushButton *prevButton = new QPushButton( "Prev" );
306
    QPushButton *nextButton = new QPushButton( "Next" );
Clément Stenac's avatar
Clément Stenac committed
307 308
    layout->addWidget( prevButton );
    layout->addWidget( nextButton );
Clément Stenac's avatar
Clément Stenac committed
309

310
    layout->addStretch( 10 );
311
    layout->addWidget( new QLabel( qtr( "Current visualization" ) ) );
Clément Stenac's avatar
Clément Stenac committed
312 313 314 315 316 317 318

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

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

Clément Stenac's avatar
Clément Stenac committed
319
    setLayout( layout );
320
    setMaximumHeight( 35 );
Clément Stenac's avatar
Clément Stenac committed
321 322 323
}

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

Clément Stenac's avatar
Clément Stenac committed
326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344
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
345
#endif
Clément Stenac's avatar
Clément Stenac committed
346

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
347 348 349
SpeedLabel::SpeedLabel( intf_thread_t *_p_intf, const QString& text,
                        QWidget *parent )
           : QLabel( text, parent ), p_intf( _p_intf )
350
{
351
    setToolTip( qtr( "Current playback speed.\nClick to adjust" ) );
352 353

    /* Create the Speed Control Widget */
354
    speedControl = new SpeedControlWidget( p_intf, this );
355 356 357 358 359 360 361 362 363
    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 ) );

364
    CONNECT( THEMIM, inputChanged( input_thread_t * ),
365
             speedControl, activateOnState() );
366

367
}
368 369 370 371 372
SpeedLabel::~SpeedLabel()
{
        delete speedControl;
        delete speedControlMenu;
}
373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391
/****************************************************************************
 * 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 );
}

392 393 394
/**********************************************************************
 * Speed control widget
 **********************************************************************/
395 396
SpeedControlWidget::SpeedControlWidget( intf_thread_t *_p_i, QWidget *_parent )
                    : QFrame( _parent ), p_intf( _p_i )
397
{
398
    QSizePolicy sizePolicy( QSizePolicy::Maximum, QSizePolicy::Fixed );
399 400
    sizePolicy.setHorizontalStretch( 0 );
    sizePolicy.setVerticalStretch( 0 );
401

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
402
    speedSlider = new QSlider( this );
403 404 405 406 407
    speedSlider->setSizePolicy( sizePolicy );
    speedSlider->setMaximumSize( QSize( 80, 200 ) );
    speedSlider->setOrientation( Qt::Vertical );
    speedSlider->setTickPosition( QSlider::TicksRight );

408
    speedSlider->setRange( -34, 34 );
409 410
    speedSlider->setSingleStep( 1 );
    speedSlider->setPageStep( 1 );
411
    speedSlider->setTickInterval( 17 );
412

413
    CONNECT( speedSlider, valueChanged( int ), this, updateRate( int ) );
414

415
    QToolButton *normalSpeedButton = new QToolButton( this );
416
    normalSpeedButton->setMaximumSize( QSize( 26, 20 ) );
417
    normalSpeedButton->setAutoRaise( true );
418
    normalSpeedButton->setText( "1x" );
419
    normalSpeedButton->setToolTip( qtr( "Revert to normal play speed" ) );
420

421
    CONNECT( normalSpeedButton, clicked(), this, resetRate() );
422

Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
423
    QVBoxLayout *speedControlLayout = new QVBoxLayout( this );
424 425
    speedControlLayout->setLayoutMargins( 4, 4, 4, 4, 4 );
    speedControlLayout->setSpacing( 4 );
426 427
    speedControlLayout->addWidget( speedSlider );
    speedControlLayout->addWidget( normalSpeedButton );
428

429 430
    activateOnState();
}
431

432
void SpeedControlWidget::activateOnState()
433
{
434
    speedSlider->setEnabled( THEMIM->getIM()->hasInput() );
435 436
}

437 438
void SpeedControlWidget::updateControls( int rate )
{
439 440 441 442 443
    if( speedSlider->isSliderDown() )
    {
        //We don't want to change anything if the user is using the slider
        return;
    }
444

445
    double value = 17 * log( (double)INPUT_RATE_DEFAULT / rate ) / log( 2 );
446
    int sliderValue = (int) ( ( value > 0 ) ? value + .5 : value - .5 );
447

448
    if( sliderValue < speedSlider->minimum() )
449
    {
450
        sliderValue = speedSlider->minimum();
451
    }
452
    else if( sliderValue > speedSlider->maximum() )
453
    {
454
        sliderValue = speedSlider->maximum();
455
    }
456

457 458 459 460
    //Block signals to avoid feedback loop
    speedSlider->blockSignals( true );
    speedSlider->setValue( sliderValue );
    speedSlider->blockSignals( false );
461 462 463 464
}

void SpeedControlWidget::updateRate( int sliderValue )
{
465
    double speed = pow( 2, (double)sliderValue / 17 );
466
    int rate = INPUT_RATE_DEFAULT / speed;
467

468
    THEMIM->getIM()->setRate(rate);
469 470 471 472
}

void SpeedControlWidget::resetRate()
{
473
    THEMIM->getIM()->setRate( INPUT_RATE_DEFAULT );
474
}
475

476
CoverArtLabel::CoverArtLabel( QWidget *parent, intf_thread_t *_p_i )
477
              : QLabel( parent ), p_intf( _p_i )
478 479
{
    setContextMenuPolicy( Qt::ActionsContextMenu );
480
    CONNECT( this, updateRequested(), this, askForUpdate() );
481
    CONNECT( THEMIM->getIM(), artChanged( QString ),
482
             this, showArtUpdate( const QString& ) );
483 484 485 486 487 488

    setMinimumHeight( 128 );
    setMinimumWidth( 128 );
    setMaximumHeight( 128 );
    setMaximumWidth( 128 );
    setScaledContents( true );
489

490 491
    QList< QAction* > artActions = actions();
    QAction *action = new QAction( qtr( "Download cover art" ), this );
492
    CONNECT( action, triggered(), this, askForUpdate() );
493
    addAction( action );
494

495
    showArtUpdate( "" );
496 497
}

498 499 500 501 502 503 504
CoverArtLabel::~CoverArtLabel()
{
    QList< QAction* > artActions = actions();
    foreach( QAction *act, artActions )
        removeAction( act );
}

505
void CoverArtLabel::showArtUpdate( const QString& url )
506
{
507 508
    QPixmap pix;
    if( !url.isEmpty()  && pix.load( url ) )
509
    {
510 511 512 513 514
        setPixmap( pix );
    }
    else
    {
        setPixmap( QPixmap( ":/noart.png" ) );
515 516 517
    }
}

518
void CoverArtLabel::askForUpdate()
519
{
520
    THEMIM->getIM()->requestArtUpdate();
521 522
}

523 524 525 526 527 528 529 530
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" ) );


531 532
   CONNECT( THEMIM->getIM(), cachingChanged( float ),
            this, setCaching( float ) );
533 534 535 536 537 538
   CONNECT( THEMIM->getIM(), positionUpdated( float, int, int ),
             this, setDisplayPosition( float, int, int ) );
}

void TimeLabel::setDisplayPosition( float pos, int time, int length )
{
539
    if( pos == -1.f )
540 541 542 543
    {
        setText( " --:--/--:-- " );
        return;
    }
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
544

545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562
    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;
}
563

564 565
void TimeLabel::setCaching( float f_cache )
{
566 567 568
    QString amount;
    amount.setNum( (int)(100 * f_cache) );
    msg_Dbg( p_intf, "New caching: %d", (int)(100*f_cache));
569
    setText( "Buff: " + amount + "%" );
570 571
}

572