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

43 44 45 46
#ifdef Q_WS_X11
# include <X11/Xlib.h>
# include <qx11info_x11.h>
#endif
47

48 49
#include <math.h>

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

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

    hide();

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

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

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

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

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

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

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

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

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

140 141 142 143
QSize VideoWidget::sizeHint() const
{
    return videoSize;
}
144

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

283 284 285 286 287
/**********************************************************************
 * Speed control widget
 **********************************************************************/
SpeedControlWidget::SpeedControlWidget( intf_thread_t *_p_i ) :
                             QFrame( NULL ), p_intf( _p_i )
288
{
289
    QSizePolicy sizePolicy( QSizePolicy::Maximum, QSizePolicy::Fixed );
290 291
    sizePolicy.setHorizontalStretch( 0 );
    sizePolicy.setVerticalStretch( 0 );
292

293 294 295 296 297 298
    speedSlider = new QSlider;
    speedSlider->setSizePolicy( sizePolicy );
    speedSlider->setMaximumSize( QSize( 80, 200 ) );
    speedSlider->setOrientation( Qt::Vertical );
    speedSlider->setTickPosition( QSlider::TicksRight );

299
    speedSlider->setRange( -34, 34 );
300 301
    speedSlider->setSingleStep( 1 );
    speedSlider->setPageStep( 1 );
302
    speedSlider->setTickInterval( 17 );
303

304
    CONNECT( speedSlider, valueChanged( int ), this, updateRate( int ) );
305

306
    QToolButton *normalSpeedButton = new QToolButton( this );
307
    normalSpeedButton->setMaximumSize( QSize( 26, 20 ) );
308
    normalSpeedButton->setAutoRaise( true );
309
    normalSpeedButton->setText( "1x" );
310
    normalSpeedButton->setToolTip( qtr( "Revert to normal play speed" ) );
311

312
    CONNECT( normalSpeedButton, clicked(), this, resetRate() );
313

314
    QVBoxLayout *speedControlLayout = new QVBoxLayout;
315 316
    speedControlLayout->setLayoutMargins( 4, 4, 4, 4, 4 );
    speedControlLayout->setSpacing( 4 );
317 318 319
    speedControlLayout->addWidget( speedSlider );
    speedControlLayout->addWidget( normalSpeedButton );
    setLayout( speedControlLayout );
320 321 322
}

SpeedControlWidget::~SpeedControlWidget()
323
{}
324

325 326 327 328 329
void SpeedControlWidget::setEnable( bool b_enable )
{
    speedSlider->setEnabled( b_enable );
}

330 331
void SpeedControlWidget::updateControls( int rate )
{
332 333 334 335 336
    if( speedSlider->isSliderDown() )
    {
        //We don't want to change anything if the user is using the slider
        return;
    }
337

338
    double value = 17 * log( (double)INPUT_RATE_DEFAULT / rate ) / log( 2 );
339
    int sliderValue = (int) ( ( value > 0 ) ? value + .5 : value - .5 );
340

341
    if( sliderValue < speedSlider->minimum() )
342
    {
343
        sliderValue = speedSlider->minimum();
344
    }
345
    else if( sliderValue > speedSlider->maximum() )
346
    {
347
        sliderValue = speedSlider->maximum();
348
    }
349

350 351 352 353
    //Block signals to avoid feedback loop
    speedSlider->blockSignals( true );
    speedSlider->setValue( sliderValue );
    speedSlider->blockSignals( false );
354 355 356 357
}

void SpeedControlWidget::updateRate( int sliderValue )
{
358
    double speed = pow( 2, (double)sliderValue / 17 );
359
    int rate = INPUT_RATE_DEFAULT / speed;
360

361
    THEMIM->getIM()->setRate(rate);
362 363 364 365
}

void SpeedControlWidget::resetRate()
{
366
    THEMIM->getIM()->setRate( INPUT_RATE_DEFAULT );
367
}
368 369 370 371 372 373 374 375 376 377 378 379 380 381 382

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;
}

383 384 385 386
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()
387 388 389 390
{
    setContextMenuPolicy( Qt::ActionsContextMenu );
    CONNECT( this, updateRequested(), this, doUpdate() );

Antoine Cellerier's avatar
Antoine Cellerier committed
391
    playlist_t *p_playlist = pl_Hold( p_this );
392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408
    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
409
        playlist_t *p_playlist = pl_Hold( p_this );
410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432
        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 )
433 434 435 436 437 438 439 440 441 442 443
            {
                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" ) );
                }
            }
444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470
            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 );
    }
}

471 472 473 474 475 476 477 478
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" ) );


479 480
   CONNECT( THEMIM->getIM(), statusChanged( int ),
            this, setStatus( int ) );
481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505
   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;
}

506 507 508 509 510 511 512 513
void TimeLabel::setStatus( int i_status )
{
    msg_Warn( p_intf, "Status: %i", i_status );

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

514