interface_widgets.cpp 16.7 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 48
#ifdef Q_WS_X11
# include <X11/Xlib.h>
# include <qx11info_x11.h>
#endif
49

50 51
#include <math.h>

52 53 54 55
/**********************************************************************
 * Video Widget. A simple frame on which video is drawn
 * This class handles resize issues
 **********************************************************************/
56

57
VideoWidget::VideoWidget( intf_thread_t *_p_i ) : QFrame( NULL ), p_intf( _p_i )
58
{
59
    /* Init */
60
    p_vout = NULL;
61 62
    videoSize.rwidth() = -1;
    videoSize.rheight() = -1;
63 64 65 66

    hide();

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

69
    /* Black background is more coherent for a Video Widget */
70
    QPalette plt =  palette();
71
    plt.setColor( QPalette::Window, Qt::black );
72
    setPalette( plt );
73 74 75 76 77
    setAutoFillBackground(true);

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

80
    /* The core can ask through a callback to show the video. */
81 82 83
    connect( this, SIGNAL(askVideoWidgetToShow( unsigned int, unsigned int)),
             this, SLOT(SetSizing(unsigned int, unsigned int )),
             Qt::BlockingQueuedConnection );
84 85
}

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

94 95
VideoWidget::~VideoWidget()
{
96 97
    /* Ensure we are not leaking the video output. This would crash. */
    assert( !p_vout );
98 99
}

100
/**
101
 * Request the video to avoid the conflicts
102
 **/
103
void *VideoWidget::request( vout_thread_t *p_nvout, int *pi_x, int *pi_y,
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
104
                            unsigned int *pi_width, unsigned int *pi_height )
105
{
106
    msg_Dbg( p_intf, "Video was requested %i, %i", *pi_x, *pi_y );
107
    emit askVideoWidgetToShow( *pi_width, *pi_height );
108
    if( p_vout )
109 110 111 112
    {
        msg_Dbg( p_intf, "embedded video already in use" );
        return NULL;
    }
113
    p_vout = p_nvout;
114
#ifndef NDEBUG
115
    msg_Dbg( p_intf, "embedded video ready (handle %p)", winId() );
116
#endif
117
    return ( void* )winId();
118 119
}

120
/* Set the Widget to the correct Size */
121 122
/* Function has to be called by the parent
   Parent has to care about resizing himself*/
123
void VideoWidget::SetSizing( unsigned int w, unsigned int h )
124
{
125 126 127
    msg_Dbg( p_intf, "Video is resizing to: %i %i", w, h );
    videoSize.rwidth() = w;
    videoSize.rheight() = h;
128
    if( isHidden() ) show();
129
    updateGeometry(); // Needed for deinterlace
130 131
}

132
void VideoWidget::release( void )
133
{
Rémi Denis-Courmont's avatar
Typo  
Rémi Denis-Courmont committed
134
    msg_Dbg( p_intf, "Video is not needed anymore" );
135
    p_vout = NULL;
136 137
    videoSize.rwidth() = 0;
    videoSize.rheight() = 0;
138
    updateGeometry();
139
    hide();
140
}
141

142 143 144 145
QSize VideoWidget::sizeHint() const
{
    return videoSize;
}
146

147 148
/**********************************************************************
 * Background Widget. Show a simple image background. Currently,
149
 * it's album art if present or cone.
150
 **********************************************************************/
151 152
#define ICON_SIZE 128
#define MAX_BG_SIZE 400
153
#define MIN_BG_SIZE 128
154

155 156
BackgroundWidget::BackgroundWidget( intf_thread_t *_p_i )
                 :QWidget( NULL ), p_intf( _p_i )
157
{
158
    /* We should use that one to take the more size it can */
159
    setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding);
160

161
    /* A dark background */
Clément Stenac's avatar
Clément Stenac committed
162
    setAutoFillBackground( true );
163
    plt = palette();
164 165 166 167
    plt.setColor( QPalette::Active, QPalette::Window , Qt::black );
    plt.setColor( QPalette::Inactive, QPalette::Window , Qt::black );
    setPalette( plt );

168
    /* A cone in the middle */
169
    label = new QLabel;
170
    label->setMargin( 5 );
171 172
    label->setMaximumHeight( MAX_BG_SIZE );
    label->setMaximumWidth( MAX_BG_SIZE );
173 174
    label->setMinimumHeight( MIN_BG_SIZE );
    label->setMinimumWidth( MIN_BG_SIZE );
175 176 177 178
    if( QDate::currentDate().dayOfYear() >= 354 )
        label->setPixmap( QPixmap( ":/vlc128-christmas.png" ) );
    else
        label->setPixmap( QPixmap( ":/vlc128.png" ) );
179

180 181 182 183
    QGridLayout *backgroundLayout = new QGridLayout( this );
    backgroundLayout->addWidget( label, 0, 1 );
    backgroundLayout->setColumnStretch( 0, 1 );
    backgroundLayout->setColumnStretch( 2, 1 );
184

185 186
    CONNECT( THEMIM->getIM(), artChanged( input_item_t* ),
             this, updateArt( input_item_t* ) );
187 188
}

Clément Stenac's avatar
Clément Stenac committed
189
BackgroundWidget::~BackgroundWidget()
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
190
{}
Ilkka Ollakka's avatar
 
Ilkka Ollakka committed
191

192 193 194 195 196 197 198 199
void BackgroundWidget::resizeEvent( QResizeEvent * event )
{
    if( event->size().height() <= MIN_BG_SIZE )
        label->hide();
    else
        label->show();
}

200
void BackgroundWidget::updateArt( input_item_t *p_item )
201
{
202 203 204 205 206 207 208 209
    QString url;
    if( p_item )
    {
        char *psz_art = input_item_GetArtURL( p_item );
        url = psz_art;
        free( psz_art );
    }

210
    if( url.isEmpty() )
Ilkka Ollakka's avatar
 
Ilkka Ollakka committed
211
    {
212 213 214 215
        if( QDate::currentDate().dayOfYear() >= 354 )
            label->setPixmap( QPixmap( ":/vlc128-christmas.png" ) );
        else
            label->setPixmap( QPixmap( ":/vlc128.png" ) );
Ilkka Ollakka's avatar
 
Ilkka Ollakka committed
216
    }
217
    else
Ilkka Ollakka's avatar
 
Ilkka Ollakka committed
218
    {
219 220 221
        url = url.replace( "file://", QString("" ) );
        /* Taglib seems to define a attachment://, It won't work yet */
        url = url.replace( "attachment://", QString("" ) );
222
        label->setPixmap( QPixmap( url ) );
Ilkka Ollakka's avatar
 
Ilkka Ollakka committed
223
    }
224 225
}

226 227 228 229
void BackgroundWidget::contextMenuEvent( QContextMenuEvent *event )
{
    QVLCMenu::PopupMenu( p_intf, true );
}
230

Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
231
#if 0
232 233 234
#include <QPushButton>
#include <QHBoxLayout>

Clément Stenac's avatar
Clément Stenac committed
235 236 237 238
/**********************************************************************
 * Visualization selector panel
 **********************************************************************/
VisualSelector::VisualSelector( intf_thread_t *_p_i ) :
239
                                QFrame( NULL ), p_intf( _p_i )
Clément Stenac's avatar
Clément Stenac committed
240 241
{
    QHBoxLayout *layout = new QHBoxLayout( this );
242
    layout->setMargin( 0 );
Clément Stenac's avatar
Clément Stenac committed
243
    QPushButton *prevButton = new QPushButton( "Prev" );
244
    QPushButton *nextButton = new QPushButton( "Next" );
Clément Stenac's avatar
Clément Stenac committed
245 246
    layout->addWidget( prevButton );
    layout->addWidget( nextButton );
Clément Stenac's avatar
Clément Stenac committed
247

248
    layout->addStretch( 10 );
249
    layout->addWidget( new QLabel( qtr( "Current visualization" ) ) );
Clément Stenac's avatar
Clément Stenac committed
250 251 252 253 254 255 256

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

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

Clément Stenac's avatar
Clément Stenac committed
257
    setLayout( layout );
258
    setMaximumHeight( 35 );
Clément Stenac's avatar
Clément Stenac committed
259 260 261
}

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

Clément Stenac's avatar
Clément Stenac committed
264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282
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
283
#endif
Clément Stenac's avatar
Clément Stenac committed
284

285 286 287 288 289 290 291
SpeedLabel::SpeedLabel( intf_thread_t *_p_intf, const QString text )
           : QLabel( text ), p_intf( _p_intf )
{
    setToolTip( qtr( "Current playback speed.\nRight click to adjust" ) );
    setContextMenuPolicy ( Qt::CustomContextMenu );

    /* Create the Speed Control Widget */
292
    speedControl = new SpeedControlWidget( p_intf, this );
293 294 295 296 297 298 299 300 301 302 303 304 305 306
    speedControlMenu = new QMenu( this );

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

    /* Speed Label behaviour:
       - right click gives the vertical speed slider */
    CONNECT( this, customContextMenuRequested( QPoint ),
             this, showSpeedMenu( QPoint ) );

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

307
    CONNECT( THEMIM, inputChanged( input_thread_t * ),
308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329
             speedControl, activateOnState() );
}

/****************************************************************************
 * 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 );
}

330 331 332
/**********************************************************************
 * Speed control widget
 **********************************************************************/
333 334
SpeedControlWidget::SpeedControlWidget( intf_thread_t *_p_i, QWidget *_parent )
                    : QFrame( _parent ), p_intf( _p_i )
335
{
336
    QSizePolicy sizePolicy( QSizePolicy::Maximum, QSizePolicy::Fixed );
337 338
    sizePolicy.setHorizontalStretch( 0 );
    sizePolicy.setVerticalStretch( 0 );
339

340 341 342 343 344 345
    speedSlider = new QSlider;
    speedSlider->setSizePolicy( sizePolicy );
    speedSlider->setMaximumSize( QSize( 80, 200 ) );
    speedSlider->setOrientation( Qt::Vertical );
    speedSlider->setTickPosition( QSlider::TicksRight );

346
    speedSlider->setRange( -34, 34 );
347 348
    speedSlider->setSingleStep( 1 );
    speedSlider->setPageStep( 1 );
349
    speedSlider->setTickInterval( 17 );
350

351
    CONNECT( speedSlider, valueChanged( int ), this, updateRate( int ) );
352

353
    QToolButton *normalSpeedButton = new QToolButton( this );
354
    normalSpeedButton->setMaximumSize( QSize( 26, 20 ) );
355
    normalSpeedButton->setAutoRaise( true );
356
    normalSpeedButton->setText( "1x" );
357
    normalSpeedButton->setToolTip( qtr( "Revert to normal play speed" ) );
358

359
    CONNECT( normalSpeedButton, clicked(), this, resetRate() );
360

361
    QVBoxLayout *speedControlLayout = new QVBoxLayout;
362 363
    speedControlLayout->setLayoutMargins( 4, 4, 4, 4, 4 );
    speedControlLayout->setSpacing( 4 );
364 365 366
    speedControlLayout->addWidget( speedSlider );
    speedControlLayout->addWidget( normalSpeedButton );
    setLayout( speedControlLayout );
367

368 369
    activateOnState();
}
370

371
void SpeedControlWidget::activateOnState()
372
{
373
    speedSlider->setEnabled( THEMIM->getIM()->hasInput() );
374 375
}

376 377
void SpeedControlWidget::updateControls( int rate )
{
378 379 380 381 382
    if( speedSlider->isSliderDown() )
    {
        //We don't want to change anything if the user is using the slider
        return;
    }
383

384
    double value = 17 * log( (double)INPUT_RATE_DEFAULT / rate ) / log( 2 );
385
    int sliderValue = (int) ( ( value > 0 ) ? value + .5 : value - .5 );
386

387
    if( sliderValue < speedSlider->minimum() )
388
    {
389
        sliderValue = speedSlider->minimum();
390
    }
391
    else if( sliderValue > speedSlider->maximum() )
392
    {
393
        sliderValue = speedSlider->maximum();
394
    }
395

396 397 398 399
    //Block signals to avoid feedback loop
    speedSlider->blockSignals( true );
    speedSlider->setValue( sliderValue );
    speedSlider->blockSignals( false );
400 401 402 403
}

void SpeedControlWidget::updateRate( int sliderValue )
{
404
    double speed = pow( 2, (double)sliderValue / 17 );
405
    int rate = INPUT_RATE_DEFAULT / speed;
406

407
    THEMIM->getIM()->setRate(rate);
408 409 410 411
}

void SpeedControlWidget::resetRate()
{
412
    THEMIM->getIM()->setRate( INPUT_RATE_DEFAULT );
413
}
414 415 416 417 418 419 420 421 422 423 424 425 426 427 428

static int downloadCoverCallback( vlc_object_t *p_this,
                                  char const *psz_var,
                                  vlc_value_t oldvar, vlc_value_t newvar,
                                  void *data )
{
    if( !strcmp( psz_var, "item-change" ) )
    {
        CoverArtLabel *art = static_cast< CoverArtLabel* >( data );
        if( art )
            art->requestUpdate();
    }
    return VLC_SUCCESS;
}

429 430 431 432
CoverArtLabel::CoverArtLabel( QWidget *parent,
                              vlc_object_t *_p_this,
                              input_item_t *_p_input )
        : QLabel( parent ), p_this( _p_this), p_input( _p_input ), prevArt()
433 434 435 436
{
    setContextMenuPolicy( Qt::ActionsContextMenu );
    CONNECT( this, updateRequested(), this, doUpdate() );

Antoine Cellerier's avatar
Antoine Cellerier committed
437
    playlist_t *p_playlist = pl_Hold( p_this );
438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454
    var_AddCallback( p_playlist, "item-change",
                     downloadCoverCallback, this );
    pl_Release( p_this );

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

    doUpdate();
}

void CoverArtLabel::downloadCover()
{
    if( p_input )
    {
Antoine Cellerier's avatar
Antoine Cellerier committed
455
        playlist_t *p_playlist = pl_Hold( p_this );
456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478
        playlist_AskForArtEnqueue( p_playlist, p_input );
        pl_Release( p_this );
    }
}

void CoverArtLabel::doUpdate()
{
    if( !p_input )
    {
        setPixmap( QPixmap( ":/noart.png" ) );
        QList< QAction* > artActions = actions();
        if( !artActions.isEmpty() )
            foreach( QAction *act, artActions )
                removeAction( act );
        prevArt = "";
    }
    else
    {
        char *psz_meta = input_item_GetArtURL( p_input );
        if( psz_meta && !strncmp( psz_meta, "file://", 7 ) )
        {
            QString artUrl = qfu( psz_meta ).replace( "file://", "" );
            if( artUrl != prevArt )
479 480 481 482 483 484 485 486 487 488 489
            {
                QPixmap pix;
                if( pix.load( artUrl ) )
                    setPixmap( pix );
                else
                {
                    msg_Dbg( p_this, "Qt could not load image '%s'",
                             qtu( artUrl ) );
                    setPixmap( QPixmap( ":/noart.png" ) );
                }
            }
490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516
            QList< QAction* > artActions = actions();
            if( !artActions.isEmpty() )
            {
                foreach( QAction *act, artActions )
                    removeAction( act );
            }
            prevArt = artUrl;
        }
        else
        {
            if( prevArt != "" )
                setPixmap( QPixmap( ":/noart.png" ) );
            prevArt = "";
            QList< QAction* > artActions = actions();
            if( artActions.isEmpty() )
            {
                QAction *action = new QAction( qtr( "Download cover art" ),
                                               this );
                addAction( action );
                CONNECT( action, triggered(),
                         this, downloadCover() );
            }
        }
        free( psz_meta );
    }
}

517 518 519 520 521 522 523 524
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" ) );


525 526
/*   CONNECT( THEMIM->getIM(), statusChanged( int ),
            this, setStatus( int ) ); Remove */
527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550
   CONNECT( THEMIM->getIM(), positionUpdated( float, int, int ),
             this, setDisplayPosition( float, int, int ) );
}

void TimeLabel::setDisplayPosition( float pos, int time, int length )
{
    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;
}
551
/* This is wrong remove */
552 553 554 555 556 557 558 559
void TimeLabel::setStatus( int i_status )
{
    msg_Warn( p_intf, "Status: %i", i_status );

    if( i_status == OPENING_S )
        setText( "Buffering" );
}

560