interface_widgets.cpp 14.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 104 105
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 )
106
{
107
    msg_Dbg( p_intf, "Video was requested %i, %i", *pi_x, *pi_y );
108 109 110 111 112 113 114

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

115
    emit askVideoWidgetToShow( *pi_width, *pi_height );
116
    if( p_vout )
117 118 119 120
    {
        msg_Dbg( p_intf, "embedded video already in use" );
        return NULL;
    }
121
    p_vout = p_nvout;
122
#ifndef NDEBUG
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
123
    msg_Dbg( p_intf, "embedded video ready (handle %p)", (void *)winId() );
124
#endif
125
    return winId();
126 127
}

128
/* Set the Widget to the correct Size */
129 130
/* Function has to be called by the parent
   Parent has to care about resizing himself*/
131
void VideoWidget::SetSizing( unsigned int w, unsigned int h )
132
{
133 134 135
    msg_Dbg( p_intf, "Video is resizing to: %i %i", w, h );
    videoSize.rwidth() = w;
    videoSize.rheight() = h;
136
    if( isHidden() ) show();
137
    updateGeometry(); // Needed for deinterlace
138 139
}

140
void VideoWidget::release( void )
141
{
Rémi Denis-Courmont's avatar
Typo  
Rémi Denis-Courmont committed
142
    msg_Dbg( p_intf, "Video is not needed anymore" );
143
    p_vout = NULL;
144 145
    videoSize.rwidth() = 0;
    videoSize.rheight() = 0;
146
    updateGeometry();
147
    hide();
148
}
149

150 151 152 153
QSize VideoWidget::sizeHint() const
{
    return videoSize;
}
154

155 156
/**********************************************************************
 * Background Widget. Show a simple image background. Currently,
157
 * it's album art if present or cone.
158
 **********************************************************************/
159 160
#define ICON_SIZE 128
#define MAX_BG_SIZE 400
161
#define MIN_BG_SIZE 128
162

163 164
BackgroundWidget::BackgroundWidget( intf_thread_t *_p_i )
                 :QWidget( NULL ), p_intf( _p_i )
165
{
166
    /* We should use that one to take the more size it can */
167
    setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding);
168

169
    /* A dark background */
Clément Stenac's avatar
Clément Stenac committed
170
    setAutoFillBackground( true );
171
    plt = palette();
172 173 174 175
    plt.setColor( QPalette::Active, QPalette::Window , Qt::black );
    plt.setColor( QPalette::Inactive, QPalette::Window , Qt::black );
    setPalette( plt );

176
    /* A cone in the middle */
177
    label = new QLabel;
178
    label->setMargin( 5 );
179 180
    label->setMaximumHeight( MAX_BG_SIZE );
    label->setMaximumWidth( MAX_BG_SIZE );
181 182
    label->setMinimumHeight( MIN_BG_SIZE );
    label->setMinimumWidth( MIN_BG_SIZE );
183 184 185 186
    if( QDate::currentDate().dayOfYear() >= 354 )
        label->setPixmap( QPixmap( ":/vlc128-christmas.png" ) );
    else
        label->setPixmap( QPixmap( ":/vlc128.png" ) );
187

188 189 190 191
    QGridLayout *backgroundLayout = new QGridLayout( this );
    backgroundLayout->addWidget( label, 0, 1 );
    backgroundLayout->setColumnStretch( 0, 1 );
    backgroundLayout->setColumnStretch( 2, 1 );
192

193 194
    CONNECT( THEMIM->getIM(), artChanged( QString ),
             this, updateArt( QString ) );
195 196
}

Clément Stenac's avatar
Clément Stenac committed
197
BackgroundWidget::~BackgroundWidget()
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
198
{}
Ilkka Ollakka's avatar
 
Ilkka Ollakka committed
199

200 201 202 203 204 205 206 207
void BackgroundWidget::resizeEvent( QResizeEvent * event )
{
    if( event->size().height() <= MIN_BG_SIZE )
        label->hide();
    else
        label->show();
}

208
void BackgroundWidget::updateArt( QString url )
209
{
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
        label->setPixmap( QPixmap( url ) );
Ilkka Ollakka's avatar
 
Ilkka Ollakka committed
220
    }
221 222
}

223 224 225
void BackgroundWidget::contextMenuEvent( QContextMenuEvent *event )
{
    QVLCMenu::PopupMenu( p_intf, true );
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
226
    event->accept();
227
}
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 288 289
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 */
290
    speedControl = new SpeedControlWidget( p_intf, this );
291 292 293 294 295 296 297 298 299 300 301 302 303 304
    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 ) );

305
    CONNECT( THEMIM, inputChanged( input_thread_t * ),
306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327
             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 );
}

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

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

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

349
    CONNECT( speedSlider, valueChanged( int ), this, updateRate( int ) );
350

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

357
    CONNECT( normalSpeedButton, clicked(), this, resetRate() );
358

Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
359
    QVBoxLayout *speedControlLayout = new QVBoxLayout( this );
360 361
    speedControlLayout->setLayoutMargins( 4, 4, 4, 4, 4 );
    speedControlLayout->setSpacing( 4 );
362 363
    speedControlLayout->addWidget( speedSlider );
    speedControlLayout->addWidget( normalSpeedButton );
364

365 366
    activateOnState();
}
367

368
void SpeedControlWidget::activateOnState()
369
{
370
    speedSlider->setEnabled( THEMIM->getIM()->hasInput() );
371 372
}

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

381
    double value = 17 * log( (double)INPUT_RATE_DEFAULT / rate ) / log( 2 );
382
    int sliderValue = (int) ( ( value > 0 ) ? value + .5 : value - .5 );
383

384
    if( sliderValue < speedSlider->minimum() )
385
    {
386
        sliderValue = speedSlider->minimum();
387
    }
388
    else if( sliderValue > speedSlider->maximum() )
389
    {
390
        sliderValue = speedSlider->maximum();
391
    }
392

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

void SpeedControlWidget::updateRate( int sliderValue )
{
401
    double speed = pow( 2, (double)sliderValue / 17 );
402
    int rate = INPUT_RATE_DEFAULT / speed;
403

404
    THEMIM->getIM()->setRate(rate);
405 406 407 408
}

void SpeedControlWidget::resetRate()
{
409
    THEMIM->getIM()->setRate( INPUT_RATE_DEFAULT );
410
}
411

412 413
CoverArtLabel::CoverArtLabel( QWidget *parent, intf_thread_t *_p_i )
        : QLabel( parent ), p_intf( _p_i )
414 415 416
{
    setContextMenuPolicy( Qt::ActionsContextMenu );
    CONNECT( this, updateRequested(), this, doUpdate() );
417 418
    CONNECT( THEMIM->getIM(), artChanged( QString ),
             this, doUpdate( QString ) );
419 420 421 422 423 424

    setMinimumHeight( 128 );
    setMinimumWidth( 128 );
    setMaximumHeight( 128 );
    setMaximumWidth( 128 );
    setScaledContents( true );
425 426 427 428
    QList< QAction* > artActions = actions();
    QAction *action = new QAction( qtr( "Download cover art" ), this );
    addAction( action );
    CONNECT( action, triggered(), this, doUpdate() );
429 430 431 432

    doUpdate();
}

433 434 435 436 437 438 439
CoverArtLabel::~CoverArtLabel()
{
    QList< QAction* > artActions = actions();
    foreach( QAction *act, artActions )
        removeAction( act );
}

440
void CoverArtLabel::doUpdate( QString url )
441
{
442 443
    QPixmap pix;
    if( !url.isEmpty()  && pix.load( url ) )
444
    {
445 446 447 448 449
        setPixmap( pix );
    }
    else
    {
        setPixmap( QPixmap( ":/noart.png" ) );
450 451 452 453 454
    }
}

void CoverArtLabel::doUpdate()
{
455
    THEMIM->getIM()->requestArtUpdate();
456 457
}

458 459 460 461 462 463 464 465
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" ) );


466 467
   CONNECT( THEMIM->getIM(), cachingChanged( float ),
            this, setCaching( float ) );
468 469 470 471 472 473
   CONNECT( THEMIM->getIM(), positionUpdated( float, int, int ),
             this, setDisplayPosition( float, int, int ) );
}

void TimeLabel::setDisplayPosition( float pos, int time, int length )
{
474
    if( pos == -1.f )
475 476 477 478
    {
        setText( " --:--/--:-- " );
        return;
    }
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
479

480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497
    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;
}
498

499 500
void TimeLabel::setCaching( float f_cache )
{
501 502 503 504
    QString amount;
    amount.setNum( (int)(100 * f_cache) );
    msg_Dbg( p_intf, "New caching: %d", (int)(100*f_cache));
    setText( "Buffering " + amount + "%" );
505 506
}

507