interface_widgets.cpp 16.4 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
    layout = new QHBoxLayout( this );
    layout->setContentsMargins( 0, 0, 0, 0 );
    setLayout( layout );
81 82 83 84
}

VideoWidget::~VideoWidget()
{
85
    /* Ensure we are not leaking the video output. This would crash. */
86
    assert( reparentable == NULL );
87 88
}

89
/**
90
 * Request the video to avoid the conflicts
91
 **/
92
WId VideoWidget::request( int *pi_x, int *pi_y,
93 94
                          unsigned int *pi_width, unsigned int *pi_height,
                          bool b_keep_size )
95
{
96
    msg_Dbg( p_intf, "Video was requested %i, %i", *pi_x, *pi_y );
97

98 99 100 101 102
    if( reparentable != NULL )
    {
        msg_Dbg( p_intf, "embedded video already in use" );
        return NULL;
    }
103 104 105 106 107 108
    if( b_keep_size )
    {
        *pi_width  = size().width();
        *pi_height = size().height();
    }

109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
    /* 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);
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 131 132 133 134 135
    stable->setAttribute( Qt::WA_PaintOnScreen, true );

    innerLayout->addWidget( stable );

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

136
    videoSync();
137
#ifndef NDEBUG
138 139
    msg_Dbg( p_intf, "embedded video ready (handle %p)",
             (void *)stable->winId() );
140
#endif
141
    return stable->winId();
142 143
}

144
/* Set the Widget to the correct Size */
145
/* Function has to be called by the parent
Rémi Denis-Courmont's avatar
typo  
Rémi Denis-Courmont committed
146
   Parent has to care about resizing itself */
147
void VideoWidget::SetSizing( unsigned int w, unsigned int h )
148
{
149 150 151
    msg_Dbg( p_intf, "Video is resizing to: %i %i", w, h );
    videoSize.rwidth() = w;
    videoSize.rheight() = h;
152
    if( !isVisible() ) show();
153
    updateGeometry(); // Needed for deinterlace
154
    videoSync();
155 156
}

157 158 159 160
void VideoWidget::SetFullScreen( bool b_fs )
{
    const Qt::WindowStates curstate = reparentable->windowState();
    Qt::WindowStates newstate = curstate;
161
    Qt::WindowFlags  newflags = reparentable->windowFlags();
162 163

    if( b_fs )
164
    {
165
        newstate |= Qt::WindowFullScreen;
166 167
        newflags |= Qt::WindowStaysOnTopHint;
    }
168
    else
169
    {
170
        newstate &= ~Qt::WindowFullScreen;
171 172
        newflags &= ~Qt::WindowStaysOnTopHint;
    }
173 174 175 176 177 178
    if( newstate == curstate )
        return; /* no changes needed */

    if( b_fs )
    {   /* Go full-screen */
        reparentable->setWindowState( newstate );
179
        reparentable->setParent( NULL );
180
        reparentable->setWindowFlags( newflags );
181 182 183 184
        reparentable->show();
    }
    else
    {   /* Go windowed */
185
        reparentable->setWindowFlags( newflags );
186
        layout->addWidget( reparentable );
187
        reparentable->setWindowState( newstate );
188
    }
189
    videoSync();
190 191
}

192
void VideoWidget::release( void )
193
{
Rémi Denis-Courmont's avatar
Typo  
Rémi Denis-Courmont committed
194
    msg_Dbg( p_intf, "Video is not needed anymore" );
195 196 197
    //layout->removeWidget( reparentable );
    delete reparentable;
    reparentable = NULL;
198 199
    videoSize.rwidth() = 0;
    videoSize.rheight() = 0;
200
    updateGeometry();
201
    hide();
202
}
203

204 205 206 207
QSize VideoWidget::sizeHint() const
{
    return videoSize;
}
208

209 210
/**********************************************************************
 * Background Widget. Show a simple image background. Currently,
211
 * it's album art if present or cone.
212
 **********************************************************************/
213 214
#define ICON_SIZE 128
#define MAX_BG_SIZE 400
215
#define MIN_BG_SIZE 128
216

217 218
BackgroundWidget::BackgroundWidget( intf_thread_t *_p_i )
                 :QWidget( NULL ), p_intf( _p_i )
219
{
220
    /* We should use that one to take the more size it can */
221
    setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding);
222

223
    /* A dark background */
Clément Stenac's avatar
Clément Stenac committed
224
    setAutoFillBackground( true );
225
    plt = palette();
226 227 228 229
    plt.setColor( QPalette::Active, QPalette::Window , Qt::black );
    plt.setColor( QPalette::Inactive, QPalette::Window , Qt::black );
    setPalette( plt );

230
    /* A cone in the middle */
231
    label = new QLabel;
232
    label->setMargin( 5 );
233 234
    label->setMaximumHeight( MAX_BG_SIZE );
    label->setMaximumWidth( MAX_BG_SIZE );
235 236
    label->setMinimumHeight( MIN_BG_SIZE );
    label->setMinimumWidth( MIN_BG_SIZE );
237
    if( QDate::currentDate().dayOfYear() >= 354 )
238
        label->setPixmap( QPixmap( ":/logo/vlc128-christmas.png" ) );
239
    else
240
        label->setPixmap( QPixmap( ":/logo/vlc128.png" ) );
241

242 243 244 245
    QGridLayout *backgroundLayout = new QGridLayout( this );
    backgroundLayout->addWidget( label, 0, 1 );
    backgroundLayout->setColumnStretch( 0, 1 );
    backgroundLayout->setColumnStretch( 2, 1 );
246

247
    CONNECT( THEMIM->getIM(), artChanged( QString ),
248
             this, updateArt( const QString& ) );
249 250
}

Clément Stenac's avatar
Clément Stenac committed
251
BackgroundWidget::~BackgroundWidget()
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
252
{}
Ilkka Ollakka's avatar
 
Ilkka Ollakka committed
253

254 255 256 257 258 259 260 261
void BackgroundWidget::resizeEvent( QResizeEvent * event )
{
    if( event->size().height() <= MIN_BG_SIZE )
        label->hide();
    else
        label->show();
}

262
void BackgroundWidget::updateArt( const QString& url )
263
{
264
    if( url.isEmpty() )
Ilkka Ollakka's avatar
 
Ilkka Ollakka committed
265
    {
266
        if( QDate::currentDate().dayOfYear() >= 354 )
267
            label->setPixmap( QPixmap( ":/logo/vlc128-christmas.png" ) );
268
        else
269
            label->setPixmap( QPixmap( ":/logo/vlc128.png" ) );
Ilkka Ollakka's avatar
 
Ilkka Ollakka committed
270
    }
271
    else
Ilkka Ollakka's avatar
 
Ilkka Ollakka committed
272
    {
273
        label->setPixmap( QPixmap( url ) );
Ilkka Ollakka's avatar
 
Ilkka Ollakka committed
274
    }
275 276
}

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

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

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

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

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

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

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

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

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

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
337 338 339
SpeedLabel::SpeedLabel( intf_thread_t *_p_intf, const QString& text,
                        QWidget *parent )
           : QLabel( text, parent ), p_intf( _p_intf )
340
{
341
    setToolTip( qtr( "Current playback speed.\nClick to adjust" ) );
342 343

    /* Create the Speed Control Widget */
344
    speedControl = new SpeedControlWidget( p_intf, this );
345 346 347 348 349 350 351 352 353
    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 ) );

354
    CONNECT( THEMIM, inputChanged( input_thread_t * ),
355
             speedControl, activateOnState() );
356

357
}
358 359 360 361 362
SpeedLabel::~SpeedLabel()
{
        delete speedControl;
        delete speedControlMenu;
}
363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381
/****************************************************************************
 * 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 );
}

382 383 384
/**********************************************************************
 * Speed control widget
 **********************************************************************/
385 386
SpeedControlWidget::SpeedControlWidget( intf_thread_t *_p_i, QWidget *_parent )
                    : QFrame( _parent ), p_intf( _p_i )
387
{
388
    QSizePolicy sizePolicy( QSizePolicy::Maximum, QSizePolicy::Fixed );
389 390
    sizePolicy.setHorizontalStretch( 0 );
    sizePolicy.setVerticalStretch( 0 );
391

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
392
    speedSlider = new QSlider( this );
393 394 395 396 397
    speedSlider->setSizePolicy( sizePolicy );
    speedSlider->setMaximumSize( QSize( 80, 200 ) );
    speedSlider->setOrientation( Qt::Vertical );
    speedSlider->setTickPosition( QSlider::TicksRight );

398
    speedSlider->setRange( -34, 34 );
399 400
    speedSlider->setSingleStep( 1 );
    speedSlider->setPageStep( 1 );
401
    speedSlider->setTickInterval( 17 );
402

403
    CONNECT( speedSlider, valueChanged( int ), this, updateRate( int ) );
404

405
    QToolButton *normalSpeedButton = new QToolButton( this );
406
    normalSpeedButton->setMaximumSize( QSize( 26, 20 ) );
407
    normalSpeedButton->setAutoRaise( true );
408
    normalSpeedButton->setText( "1x" );
409
    normalSpeedButton->setToolTip( qtr( "Revert to normal play speed" ) );
410

411
    CONNECT( normalSpeedButton, clicked(), this, resetRate() );
412

Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
413
    QVBoxLayout *speedControlLayout = new QVBoxLayout( this );
414 415
    speedControlLayout->setLayoutMargins( 4, 4, 4, 4, 4 );
    speedControlLayout->setSpacing( 4 );
416 417
    speedControlLayout->addWidget( speedSlider );
    speedControlLayout->addWidget( normalSpeedButton );
418

419 420
    activateOnState();
}
421

422
void SpeedControlWidget::activateOnState()
423
{
424
    speedSlider->setEnabled( THEMIM->getIM()->hasInput() );
425 426
}

427 428
void SpeedControlWidget::updateControls( int rate )
{
429 430 431 432 433
    if( speedSlider->isSliderDown() )
    {
        //We don't want to change anything if the user is using the slider
        return;
    }
434

435
    double value = 17 * log( (double)INPUT_RATE_DEFAULT / rate ) / log( 2 );
436
    int sliderValue = (int) ( ( value > 0 ) ? value + .5 : value - .5 );
437

438
    if( sliderValue < speedSlider->minimum() )
439
    {
440
        sliderValue = speedSlider->minimum();
441
    }
442
    else if( sliderValue > speedSlider->maximum() )
443
    {
444
        sliderValue = speedSlider->maximum();
445
    }
446

447 448 449 450
    //Block signals to avoid feedback loop
    speedSlider->blockSignals( true );
    speedSlider->setValue( sliderValue );
    speedSlider->blockSignals( false );
451 452 453 454
}

void SpeedControlWidget::updateRate( int sliderValue )
{
455
    double speed = pow( 2, (double)sliderValue / 17 );
456
    int rate = INPUT_RATE_DEFAULT / speed;
457

458
    THEMIM->getIM()->setRate(rate);
459 460 461 462
}

void SpeedControlWidget::resetRate()
{
463
    THEMIM->getIM()->setRate( INPUT_RATE_DEFAULT );
464
}
465

466
CoverArtLabel::CoverArtLabel( QWidget *parent, intf_thread_t *_p_i )
467
              : QLabel( parent ), p_intf( _p_i )
468 469
{
    setContextMenuPolicy( Qt::ActionsContextMenu );
470
    CONNECT( this, updateRequested(), this, askForUpdate() );
471
    CONNECT( THEMIM->getIM(), artChanged( QString ),
472
             this, showArtUpdate( const QString& ) );
473 474 475 476 477 478

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

480 481
    QList< QAction* > artActions = actions();
    QAction *action = new QAction( qtr( "Download cover art" ), this );
482
    CONNECT( action, triggered(), this, askForUpdate() );
483
    addAction( action );
484

485
    showArtUpdate( "" );
486 487
}

488 489 490 491 492 493 494
CoverArtLabel::~CoverArtLabel()
{
    QList< QAction* > artActions = actions();
    foreach( QAction *act, artActions )
        removeAction( act );
}

495
void CoverArtLabel::showArtUpdate( const QString& url )
496
{
497 498
    QPixmap pix;
    if( !url.isEmpty()  && pix.load( url ) )
499
    {
500 501 502 503 504
        setPixmap( pix );
    }
    else
    {
        setPixmap( QPixmap( ":/noart.png" ) );
505 506 507
    }
}

508
void CoverArtLabel::askForUpdate()
509
{
510
    THEMIM->getIM()->requestArtUpdate();
511 512
}

513 514 515 516 517 518 519 520
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" ) );


521 522
   CONNECT( THEMIM->getIM(), cachingChanged( float ),
            this, setCaching( float ) );
523 524 525 526 527 528
   CONNECT( THEMIM->getIM(), positionUpdated( float, int, int ),
             this, setDisplayPosition( float, int, int ) );
}

void TimeLabel::setDisplayPosition( float pos, int time, int length )
{
529
    if( pos == -1.f )
530 531 532 533
    {
        setText( " --:--/--:-- " );
        return;
    }
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
534

535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552
    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;
}
553

554 555
void TimeLabel::setCaching( float f_cache )
{
556 557 558
    QString amount;
    amount.setNum( (int)(100 * f_cache) );
    msg_Dbg( p_intf, "New caching: %d", (int)(100*f_cache));
559
    setText( "Buff: " + amount + "%" );
560 561
}

562