interface_widgets.cpp 13.9 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( 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 218
void BackgroundWidget::contextMenuEvent( QContextMenuEvent *event )
{
    QVLCMenu::PopupMenu( p_intf, true );
}
219

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

356 357
    activateOnState();
}
358

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

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

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

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

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

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

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

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

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

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

    doUpdate();
}

420
void CoverArtLabel::doUpdate( QString url )
421
{
422 423
    QPixmap pix;
    if( !url.isEmpty()  && pix.load( url ) )
424
    {
425 426 427 428 429
        setPixmap( pix );
    }
    else
    {
        setPixmap( QPixmap( ":/noart.png" ) );
430 431 432 433 434
    }
}

void CoverArtLabel::doUpdate()
{
435
    THEMIM->getIM()->requestArtUpdate();
436 437
}

438 439 440 441 442 443 444 445
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" ) );


446 447
   CONNECT( THEMIM->getIM(), cachingChanged( float ),
            this, setCaching( float ) );
448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471
   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;
}
472

473 474 475
void TimeLabel::setCaching( float f_cache )
{
    setText( "Buffering" );
476 477
}

478