interface_widgets.cpp 14.3 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
}

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

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

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

    if( b_keep_size )
    {
        *pi_width  = size().width();
        *pi_height = size().height();
    }

110
    if( p_vout )
111 112 113 114
    {
        msg_Dbg( p_intf, "embedded video already in use" );
        return NULL;
    }
115
    p_vout = p_nvout;
116
#ifndef NDEBUG
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
117
    msg_Dbg( p_intf, "embedded video ready (handle %p)", (void *)winId() );
118
#endif
119
    return winId();
120 121
}

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

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

144 145 146 147
QSize VideoWidget::sizeHint() const
{
    return videoSize;
}
148

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

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

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

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

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

187
    CONNECT( THEMIM->getIM(), artChanged( QString ),
188
             this, updateArt( const QString& ) );
189 190
}

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

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

202
void BackgroundWidget::updateArt( const QString& url )
203
{
204
    if( url.isEmpty() )
Ilkka Ollakka's avatar
 
Ilkka Ollakka committed
205
    {
206 207 208 209
        if( QDate::currentDate().dayOfYear() >= 354 )
            label->setPixmap( QPixmap( ":/vlc128-christmas.png" ) );
        else
            label->setPixmap( QPixmap( ":/vlc128.png" ) );
Ilkka Ollakka's avatar
 
Ilkka Ollakka committed
210
    }
211
    else
Ilkka Ollakka's avatar
 
Ilkka Ollakka committed
212
    {
213
        label->setPixmap( QPixmap( url ) );
Ilkka Ollakka's avatar
 
Ilkka Ollakka committed
214
    }
215 216
}

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

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

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

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

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

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

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

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

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

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
277 278 279
SpeedLabel::SpeedLabel( intf_thread_t *_p_intf, const QString& text,
                        QWidget *parent )
           : QLabel( text, parent ), p_intf( _p_intf )
280
{
281
    setToolTip( qtr( "Current playback speed.\nClick to adjust" ) );
282 283

    /* Create the Speed Control Widget */
284
    speedControl = new SpeedControlWidget( p_intf, this );
285 286 287 288 289 290 291 292 293
    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 ) );

294
    CONNECT( THEMIM, inputChanged( input_thread_t * ),
295
             speedControl, activateOnState() );
296

297
}
298 299 300 301 302
SpeedLabel::~SpeedLabel()
{
        delete speedControl;
        delete speedControlMenu;
}
303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321
/****************************************************************************
 * 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 );
}

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

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
332
    speedSlider = new QSlider( this );
333 334 335 336 337
    speedSlider->setSizePolicy( sizePolicy );
    speedSlider->setMaximumSize( QSize( 80, 200 ) );
    speedSlider->setOrientation( Qt::Vertical );
    speedSlider->setTickPosition( QSlider::TicksRight );

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

343
    CONNECT( speedSlider, valueChanged( int ), this, updateRate( int ) );
344

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

351
    CONNECT( normalSpeedButton, clicked(), this, resetRate() );
352

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

359 360
    activateOnState();
}
361

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

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

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

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

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

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

398
    THEMIM->getIM()->setRate(rate);
399 400 401 402
}

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

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

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

    doUpdate();
}

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

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

void CoverArtLabel::doUpdate()
{
449
    THEMIM->getIM()->requestArtUpdate();
450 451
}

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


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

void TimeLabel::setDisplayPosition( float pos, int time, int length )
{
468
    if( pos == -1.f )
469 470 471 472
    {
        setText( " --:--/--:-- " );
        return;
    }
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
473

474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491
    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;
}
492

493 494
void TimeLabel::setCaching( float f_cache )
{
495 496 497
    QString amount;
    amount.setNum( (int)(100 * f_cache) );
    msg_Dbg( p_intf, "New caching: %d", (int)(100*f_cache));
498
    setText( "Buff: " + amount + "%" );
499 500
}

501