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

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
283 284 285
SpeedLabel::SpeedLabel( intf_thread_t *_p_intf, const QString& text,
                        QWidget *parent )
           : QLabel( text, parent ), p_intf( _p_intf )
286 287 288 289 290
{
    setToolTip( qtr( "Current playback speed.\nRight click to adjust" ) );
    setContextMenuPolicy ( Qt::CustomContextMenu );

    /* Create the Speed Control Widget */
291
    speedControl = new SpeedControlWidget( p_intf, this );
292 293 294 295 296 297 298 299 300 301 302 303 304 305
    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 ) );

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

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

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
339
    speedSlider = new QSlider( this );
340 341 342 343 344
    speedSlider->setSizePolicy( sizePolicy );
    speedSlider->setMaximumSize( QSize( 80, 200 ) );
    speedSlider->setOrientation( Qt::Vertical );
    speedSlider->setTickPosition( QSlider::TicksRight );

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

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

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

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

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

366 367
    activateOnState();
}
368

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

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

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

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

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

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

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

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

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

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

    doUpdate();
}

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

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

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

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


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

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

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

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

508