interface_widgets.cpp 14.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>
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
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
115
    msg_Dbg( p_intf, "embedded video ready (handle %p)", (void *)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( QString ),
             this, updateArt( QString ) );
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( QString url )
201
{
202
    if( url.isEmpty() )
Ilkka Ollakka's avatar
 
Ilkka Ollakka committed
203
    {
204 205 206 207
        if( QDate::currentDate().dayOfYear() >= 354 )
            label->setPixmap( QPixmap( ":/vlc128-christmas.png" ) );
        else
            label->setPixmap( QPixmap( ":/vlc128.png" ) );
Ilkka Ollakka's avatar
 
Ilkka Ollakka committed
208
    }
209
    else
Ilkka Ollakka's avatar
 
Ilkka Ollakka committed
210
    {
211
        label->setPixmap( QPixmap( url ) );
Ilkka Ollakka's avatar
 
Ilkka Ollakka committed
212
    }
213 214
}

215 216 217
void BackgroundWidget::contextMenuEvent( QContextMenuEvent *event )
{
    QVLCMenu::PopupMenu( p_intf, true );
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
218
    event->accept();
219
}
220

Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
221
#if 0
222 223 224
#include <QPushButton>
#include <QHBoxLayout>

Clément Stenac's avatar
Clément Stenac committed
225 226 227 228
/**********************************************************************
 * Visualization selector panel
 **********************************************************************/
VisualSelector::VisualSelector( intf_thread_t *_p_i ) :
229
                                QFrame( NULL ), p_intf( _p_i )
Clément Stenac's avatar
Clément Stenac committed
230 231
{
    QHBoxLayout *layout = new QHBoxLayout( this );
232
    layout->setMargin( 0 );
Clément Stenac's avatar
Clément Stenac committed
233
    QPushButton *prevButton = new QPushButton( "Prev" );
234
    QPushButton *nextButton = new QPushButton( "Next" );
Clément Stenac's avatar
Clément Stenac committed
235 236
    layout->addWidget( prevButton );
    layout->addWidget( nextButton );
Clément Stenac's avatar
Clément Stenac committed
237

238
    layout->addStretch( 10 );
239
    layout->addWidget( new QLabel( qtr( "Current visualization" ) ) );
Clément Stenac's avatar
Clément Stenac committed
240 241 242 243 244 245 246

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

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

Clément Stenac's avatar
Clément Stenac committed
247
    setLayout( layout );
248
    setMaximumHeight( 35 );
Clément Stenac's avatar
Clément Stenac committed
249 250 251
}

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

Clément Stenac's avatar
Clément Stenac committed
254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272
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
273
#endif
Clément Stenac's avatar
Clément Stenac committed
274

275 276 277 278 279 280 281
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 */
282
    speedControl = new SpeedControlWidget( p_intf, this );
283 284 285 286 287 288 289 290 291 292 293 294 295 296
    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 ) );

297
    CONNECT( THEMIM, inputChanged( input_thread_t * ),
298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319
             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 );
}

320 321 322
/**********************************************************************
 * Speed control widget
 **********************************************************************/
323 324
SpeedControlWidget::SpeedControlWidget( intf_thread_t *_p_i, QWidget *_parent )
                    : QFrame( _parent ), p_intf( _p_i )
325
{
326
    QSizePolicy sizePolicy( QSizePolicy::Maximum, QSizePolicy::Fixed );
327 328
    sizePolicy.setHorizontalStretch( 0 );
    sizePolicy.setVerticalStretch( 0 );
329

330 331 332 333 334 335
    speedSlider = new QSlider;
    speedSlider->setSizePolicy( sizePolicy );
    speedSlider->setMaximumSize( QSize( 80, 200 ) );
    speedSlider->setOrientation( Qt::Vertical );
    speedSlider->setTickPosition( QSlider::TicksRight );

336
    speedSlider->setRange( -34, 34 );
337 338
    speedSlider->setSingleStep( 1 );
    speedSlider->setPageStep( 1 );
339
    speedSlider->setTickInterval( 17 );
340

341
    CONNECT( speedSlider, valueChanged( int ), this, updateRate( int ) );
342

343
    QToolButton *normalSpeedButton = new QToolButton( this );
344
    normalSpeedButton->setMaximumSize( QSize( 26, 20 ) );
345
    normalSpeedButton->setAutoRaise( true );
346
    normalSpeedButton->setText( "1x" );
347
    normalSpeedButton->setToolTip( qtr( "Revert to normal play speed" ) );
348

349
    CONNECT( normalSpeedButton, clicked(), this, resetRate() );
350

Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
351
    QVBoxLayout *speedControlLayout = new QVBoxLayout( this );
352 353
    speedControlLayout->setLayoutMargins( 4, 4, 4, 4, 4 );
    speedControlLayout->setSpacing( 4 );
354 355
    speedControlLayout->addWidget( speedSlider );
    speedControlLayout->addWidget( normalSpeedButton );
356

357 358
    activateOnState();
}
359

360
void SpeedControlWidget::activateOnState()
361
{
362
    speedSlider->setEnabled( THEMIM->getIM()->hasInput() );
363 364
}

365 366
void SpeedControlWidget::updateControls( int rate )
{
367 368 369 370 371
    if( speedSlider->isSliderDown() )
    {
        //We don't want to change anything if the user is using the slider
        return;
    }
372

373
    double value = 17 * log( (double)INPUT_RATE_DEFAULT / rate ) / log( 2 );
374
    int sliderValue = (int) ( ( value > 0 ) ? value + .5 : value - .5 );
375

376
    if( sliderValue < speedSlider->minimum() )
377
    {
378
        sliderValue = speedSlider->minimum();
379
    }
380
    else if( sliderValue > speedSlider->maximum() )
381
    {
382
        sliderValue = speedSlider->maximum();
383
    }
384

385 386 387 388
    //Block signals to avoid feedback loop
    speedSlider->blockSignals( true );
    speedSlider->setValue( sliderValue );
    speedSlider->blockSignals( false );
389 390 391 392
}

void SpeedControlWidget::updateRate( int sliderValue )
{
393
    double speed = pow( 2, (double)sliderValue / 17 );
394
    int rate = INPUT_RATE_DEFAULT / speed;
395

396
    THEMIM->getIM()->setRate(rate);
397 398 399 400
}

void SpeedControlWidget::resetRate()
{
401
    THEMIM->getIM()->setRate( INPUT_RATE_DEFAULT );
402
}
403

404 405
CoverArtLabel::CoverArtLabel( QWidget *parent, intf_thread_t *_p_i )
        : QLabel( parent ), p_intf( _p_i )
406 407 408
{
    setContextMenuPolicy( Qt::ActionsContextMenu );
    CONNECT( this, updateRequested(), this, doUpdate() );
409 410
    CONNECT( THEMIM->getIM(), artChanged( QString ),
             this, doUpdate( QString ) );
411 412 413 414 415 416

    setMinimumHeight( 128 );
    setMinimumWidth( 128 );
    setMaximumHeight( 128 );
    setMaximumWidth( 128 );
    setScaledContents( true );
417 418 419 420
    QList< QAction* > artActions = actions();
    QAction *action = new QAction( qtr( "Download cover art" ), this );
    addAction( action );
    CONNECT( action, triggered(), this, doUpdate() );
421 422 423 424

    doUpdate();
}

425 426 427 428 429 430 431
CoverArtLabel::~CoverArtLabel()
{
    QList< QAction* > artActions = actions();
    foreach( QAction *act, artActions )
        removeAction( act );
}

432
void CoverArtLabel::doUpdate( QString url )
433
{
434 435
    QPixmap pix;
    if( !url.isEmpty()  && pix.load( url ) )
436
    {
437 438 439 440 441
        setPixmap( pix );
    }
    else
    {
        setPixmap( QPixmap( ":/noart.png" ) );
442 443 444 445 446
    }
}

void CoverArtLabel::doUpdate()
{
447
    THEMIM->getIM()->requestArtUpdate();
448 449
}

450 451 452 453 454 455 456 457
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" ) );


458 459
   CONNECT( THEMIM->getIM(), cachingChanged( float ),
            this, setCaching( float ) );
460 461 462 463 464 465
   CONNECT( THEMIM->getIM(), positionUpdated( float, int, int ),
             this, setDisplayPosition( float, int, int ) );
}

void TimeLabel::setDisplayPosition( float pos, int time, int length )
{
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
466 467
    VLC_UNUSED( pos );

468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485
    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;
}
486

487 488
void TimeLabel::setCaching( float f_cache )
{
489 490 491 492
    QString amount;
    amount.setNum( (int)(100 * f_cache) );
    msg_Dbg( p_intf, "New caching: %d", (int)(100*f_cache));
    setText( "Buffering " + amount + "%" );
493 494
}

495