interface_widgets.cpp 14.8 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
             speedControl, activateOnState() );
}
309 310 311 312 313
SpeedLabel::~SpeedLabel()
{
        delete speedControl;
        delete speedControlMenu;
}
314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332
/****************************************************************************
 * 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 );
}

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

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

349
    speedSlider->setRange( -34, 34 );
350 351
    speedSlider->setSingleStep( 1 );
    speedSlider->setPageStep( 1 );
352
    speedSlider->setTickInterval( 17 );
353

354
    CONNECT( speedSlider, valueChanged( int ), this, updateRate( int ) );
355

356
    QToolButton *normalSpeedButton = new QToolButton( this );
357
    normalSpeedButton->setMaximumSize( QSize( 26, 20 ) );
358
    normalSpeedButton->setAutoRaise( true );
359
    normalSpeedButton->setText( "1x" );
360
    normalSpeedButton->setToolTip( qtr( "Revert to normal play speed" ) );
361

362
    CONNECT( normalSpeedButton, clicked(), this, resetRate() );
363

Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
364
    QVBoxLayout *speedControlLayout = new QVBoxLayout( this );
365 366
    speedControlLayout->setLayoutMargins( 4, 4, 4, 4, 4 );
    speedControlLayout->setSpacing( 4 );
367 368
    speedControlLayout->addWidget( speedSlider );
    speedControlLayout->addWidget( normalSpeedButton );
369

370 371
    activateOnState();
}
372

373
void SpeedControlWidget::activateOnState()
374
{
375
    speedSlider->setEnabled( THEMIM->getIM()->hasInput() );
376 377
}

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

386
    double value = 17 * log( (double)INPUT_RATE_DEFAULT / rate ) / log( 2 );
387
    int sliderValue = (int) ( ( value > 0 ) ? value + .5 : value - .5 );
388

389
    if( sliderValue < speedSlider->minimum() )
390
    {
391
        sliderValue = speedSlider->minimum();
392
    }
393
    else if( sliderValue > speedSlider->maximum() )
394
    {
395
        sliderValue = speedSlider->maximum();
396
    }
397

398 399 400 401
    //Block signals to avoid feedback loop
    speedSlider->blockSignals( true );
    speedSlider->setValue( sliderValue );
    speedSlider->blockSignals( false );
402 403 404 405
}

void SpeedControlWidget::updateRate( int sliderValue )
{
406
    double speed = pow( 2, (double)sliderValue / 17 );
407
    int rate = INPUT_RATE_DEFAULT / speed;
408

409
    THEMIM->getIM()->setRate(rate);
410 411 412 413
}

void SpeedControlWidget::resetRate()
{
414
    THEMIM->getIM()->setRate( INPUT_RATE_DEFAULT );
415
}
416

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

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

    doUpdate();
}

438 439 440 441 442 443 444
CoverArtLabel::~CoverArtLabel()
{
    QList< QAction* > artActions = actions();
    foreach( QAction *act, artActions )
        removeAction( act );
}

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

void CoverArtLabel::doUpdate()
{
460
    THEMIM->getIM()->requestArtUpdate();
461 462
}

463 464 465 466 467 468 469 470
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" ) );


471 472
   CONNECT( THEMIM->getIM(), cachingChanged( float ),
            this, setCaching( float ) );
473 474 475 476 477 478
   CONNECT( THEMIM->getIM(), positionUpdated( float, int, int ),
             this, setDisplayPosition( float, int, int ) );
}

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

485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502
    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;
}
503

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

512