interface_widgets.cpp 15.7 KB
Newer Older
1
/*****************************************************************************
2
 * interface_widgets.cpp : Custom widgets for the main interface
3
 ****************************************************************************
4
 * Copyright ( C ) 2006 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

Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
31 32
#include <vlc_vout.h>

33
#include "dialogs_provider.hpp"
34
#include "components/interface_widgets.hpp"
35
#include "main_interface.hpp"
36
#include "input_manager.hpp"
37
#include "menus.hpp"
38
#include "util/input_slider.hpp"
39
#include "util/customwidgets.hpp"
40

Clément Stenac's avatar
Clément Stenac committed
41 42
#include <QLabel>
#include <QSpacerItem>
Clément Stenac's avatar
Clément Stenac committed
43
#include <QCursor>
Clément Stenac's avatar
Clément Stenac committed
44
#include <QPushButton>
45
#include <QToolButton>
46
#include <QHBoxLayout>
Clément Stenac's avatar
Clément Stenac committed
47
#include <QMenu>
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
48 49
#include <QPalette>
#include <QResizeEvent>
50
#include <QDate>
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
51

52 53 54 55
#ifdef Q_WS_X11
# include <X11/Xlib.h>
# include <qx11info_x11.h>
#endif
56

57 58
#include <math.h>

Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
59
#define I_PLAY_TOOLTIP N_("Play\nIf the playlist is empty, open a media")
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
60

61 62 63 64
/**********************************************************************
 * Video Widget. A simple frame on which video is drawn
 * This class handles resize issues
 **********************************************************************/
65

66
VideoWidget::VideoWidget( intf_thread_t *_p_i ) : QFrame( NULL ), p_intf( _p_i )
67
{
68
    /* Init */
69
    p_vout = NULL;
70 71
    videoSize.rwidth() = -1;
    videoSize.rheight() = -1;
72 73 74 75

    hide();

    /* Set the policy to expand in both directions */
76
//    setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding );
77

78
    /* Black background is more coherent for a Video Widget */
79
    QPalette plt =  palette();
80
    plt.setColor( QPalette::Window, Qt::black );
81
    setPalette( plt );
82 83 84 85 86
    setAutoFillBackground(true);

    /* Indicates that the widget wants to draw directly onto the screen.
       Widgets with this attribute set do not participate in composition
       management */
87
    setAttribute( Qt::WA_PaintOnScreen, true );
88

89
    /* The core can ask through a callback to show the video. */
90
#if HAS_QT43
91 92 93
    connect( this, SIGNAL(askVideoWidgetToShow( unsigned int, unsigned int)),
             this, SLOT(SetSizing(unsigned int, unsigned int )),
             Qt::BlockingQueuedConnection );
94
#else
95
#warning This is broken. Fix it with a QEventLoop with a processEvents ()
96 97 98
    connect( this, SIGNAL(askVideoWidgetToShow( unsigned int, unsigned int)),
             this, SLOT(SetSizing(unsigned int, unsigned int )) );
#endif
99 100
}

101 102 103
void VideoWidget::paintEvent(QPaintEvent *ev)
{
    QFrame::paintEvent(ev);
104 105 106
#ifdef Q_WS_X11
    XFlush( QX11Info::display() );
#endif
107 108
}

109
/* Kill the vout at Destruction */
110 111
VideoWidget::~VideoWidget()
{
112 113 114 115
    if( !p_vout )
        return;
    if( vout_Control( p_vout, VOUT_CLOSE ) != VLC_SUCCESS )
        vout_Control( p_vout, VOUT_REPARENT );
116 117
}

118
/**
119
 * Request the video to avoid the conflicts
120
 **/
121
void *VideoWidget::request( vout_thread_t *p_nvout, int *pi_x, int *pi_y,
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
122
                            unsigned int *pi_width, unsigned int *pi_height )
123
{
124
    msg_Dbg( p_intf, "Video was requested %i, %i", *pi_x, *pi_y );
125
    emit askVideoWidgetToShow( *pi_width, *pi_height );
126
    if( p_vout )
127 128 129 130
    {
        msg_Dbg( p_intf, "embedded video already in use" );
        return NULL;
    }
131
    p_vout = p_nvout;
132
#ifndef NDEBUG
133
    msg_Dbg( p_intf, "embedded video ready (handle %p)", winId() );
134
#endif
135
    return ( void* )winId();
136 137
}

138
/* Set the Widget to the correct Size */
139 140
/* Function has to be called by the parent
   Parent has to care about resizing himself*/
141
void VideoWidget::SetSizing( unsigned int w, unsigned int h )
142
{
143 144 145
    msg_Dbg( p_intf, "Video is resizing to: %i %i", w, h );
    videoSize.rwidth() = w;
    videoSize.rheight() = h;
146
    if( isHidden() ) show();
147
    updateGeometry(); // Needed for deinterlace
148 149
}

150
void VideoWidget::release( void *p_win )
151
{
Rémi Denis-Courmont's avatar
Typo  
Rémi Denis-Courmont committed
152
    msg_Dbg( p_intf, "Video is not needed anymore" );
153
    p_vout = NULL;
154 155
    videoSize.rwidth() = 0;
    videoSize.rheight() = 0;
156
    updateGeometry();
157
    hide();
158
}
159

160 161 162 163
QSize VideoWidget::sizeHint() const
{
    return videoSize;
}
164

165 166
/**********************************************************************
 * Background Widget. Show a simple image background. Currently,
167
 * it's album art if present or cone.
168
 **********************************************************************/
169 170
#define ICON_SIZE 128
#define MAX_BG_SIZE 400
171
#define MIN_BG_SIZE 128
172

173 174
BackgroundWidget::BackgroundWidget( intf_thread_t *_p_i )
                 :QWidget( NULL ), p_intf( _p_i )
175
{
176
    /* We should use that one to take the more size it can */
177
    setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding);
178

179
    /* A dark background */
Clément Stenac's avatar
Clément Stenac committed
180
    setAutoFillBackground( true );
181
    plt = palette();
182 183 184 185
    plt.setColor( QPalette::Active, QPalette::Window , Qt::black );
    plt.setColor( QPalette::Inactive, QPalette::Window , Qt::black );
    setPalette( plt );

186
    /* A cone in the middle */
187
    label = new QLabel;
188
    label->setMargin( 5 );
189 190
    label->setMaximumHeight( MAX_BG_SIZE );
    label->setMaximumWidth( MAX_BG_SIZE );
191 192
    label->setMinimumHeight( MIN_BG_SIZE );
    label->setMinimumWidth( MIN_BG_SIZE );
193 194 195 196
    if( QDate::currentDate().dayOfYear() >= 354 )
        label->setPixmap( QPixmap( ":/vlc128-christmas.png" ) );
    else
        label->setPixmap( QPixmap( ":/vlc128.png" ) );
197

198 199 200 201
    QGridLayout *backgroundLayout = new QGridLayout( this );
    backgroundLayout->addWidget( label, 0, 1 );
    backgroundLayout->setColumnStretch( 0, 1 );
    backgroundLayout->setColumnStretch( 2, 1 );
202

203 204
    CONNECT( THEMIM->getIM(), artChanged( input_item_t* ),
             this, updateArt( input_item_t* ) );
205 206
}

Clément Stenac's avatar
Clément Stenac committed
207
BackgroundWidget::~BackgroundWidget()
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
208
{}
Ilkka Ollakka's avatar
 
Ilkka Ollakka committed
209

210 211 212 213 214 215 216 217
void BackgroundWidget::resizeEvent( QResizeEvent * event )
{
    if( event->size().height() <= MIN_BG_SIZE )
        label->hide();
    else
        label->show();
}

218
void BackgroundWidget::updateArt( input_item_t *p_item )
219
{
220 221 222 223 224 225 226 227
    QString url;
    if( p_item )
    {
        char *psz_art = input_item_GetArtURL( p_item );
        url = psz_art;
        free( psz_art );
    }

228
    if( url.isEmpty() )
Ilkka Ollakka's avatar
 
Ilkka Ollakka committed
229
    {
230 231 232 233
        if( QDate::currentDate().dayOfYear() >= 354 )
            label->setPixmap( QPixmap( ":/vlc128-christmas.png" ) );
        else
            label->setPixmap( QPixmap( ":/vlc128.png" ) );
Ilkka Ollakka's avatar
 
Ilkka Ollakka committed
234
    }
235
    else
Ilkka Ollakka's avatar
 
Ilkka Ollakka committed
236
    {
237 238 239
        url = url.replace( "file://", QString("" ) );
        /* Taglib seems to define a attachment://, It won't work yet */
        url = url.replace( "attachment://", QString("" ) );
240
        label->setPixmap( QPixmap( url ) );
Ilkka Ollakka's avatar
 
Ilkka Ollakka committed
241
    }
242 243
}

244 245 246 247
void BackgroundWidget::contextMenuEvent( QContextMenuEvent *event )
{
    QVLCMenu::PopupMenu( p_intf, true );
}
248

Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
249
#if 0
Clément Stenac's avatar
Clément Stenac committed
250 251 252 253
/**********************************************************************
 * Visualization selector panel
 **********************************************************************/
VisualSelector::VisualSelector( intf_thread_t *_p_i ) :
254
                                QFrame( NULL ), p_intf( _p_i )
Clément Stenac's avatar
Clément Stenac committed
255 256
{
    QHBoxLayout *layout = new QHBoxLayout( this );
257
    layout->setMargin( 0 );
Clément Stenac's avatar
Clément Stenac committed
258
    QPushButton *prevButton = new QPushButton( "Prev" );
259
    QPushButton *nextButton = new QPushButton( "Next" );
Clément Stenac's avatar
Clément Stenac committed
260 261
    layout->addWidget( prevButton );
    layout->addWidget( nextButton );
Clément Stenac's avatar
Clément Stenac committed
262

263
    layout->addStretch( 10 );
264
    layout->addWidget( new QLabel( qtr( "Current visualization" ) ) );
Clément Stenac's avatar
Clément Stenac committed
265 266 267 268 269 270 271

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

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

Clément Stenac's avatar
Clément Stenac committed
272
    setLayout( layout );
273
    setMaximumHeight( 35 );
Clément Stenac's avatar
Clément Stenac committed
274 275 276
}

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

Clément Stenac's avatar
Clément Stenac committed
279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297
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
298
#endif
Clément Stenac's avatar
Clément Stenac committed
299

300 301 302 303 304
/**********************************************************************
 * Speed control widget
 **********************************************************************/
SpeedControlWidget::SpeedControlWidget( intf_thread_t *_p_i ) :
                             QFrame( NULL ), p_intf( _p_i )
305
{
306
    QSizePolicy sizePolicy( QSizePolicy::Maximum, QSizePolicy::Fixed );
307 308
    sizePolicy.setHorizontalStretch( 0 );
    sizePolicy.setVerticalStretch( 0 );
309

310 311 312 313 314 315
    speedSlider = new QSlider;
    speedSlider->setSizePolicy( sizePolicy );
    speedSlider->setMaximumSize( QSize( 80, 200 ) );
    speedSlider->setOrientation( Qt::Vertical );
    speedSlider->setTickPosition( QSlider::TicksRight );

316 317 318 319
    speedSlider->setRange( -24, 24 );
    speedSlider->setSingleStep( 1 );
    speedSlider->setPageStep( 1 );
    speedSlider->setTickInterval( 12 );
320

321
    CONNECT( speedSlider, valueChanged( int ), this, updateRate( int ) );
322

323
    QToolButton *normalSpeedButton = new QToolButton( this );
324
    normalSpeedButton->setMaximumSize( QSize( 26, 20 ) );
325
    normalSpeedButton->setAutoRaise( true );
326
    normalSpeedButton->setText( "1x" );
327
    normalSpeedButton->setToolTip( qtr( "Revert to normal play speed" ) );
328

329
    CONNECT( normalSpeedButton, clicked(), this, resetRate() );
330

331
    QVBoxLayout *speedControlLayout = new QVBoxLayout;
332 333
    speedControlLayout->setLayoutMargins( 4, 4, 4, 4, 4 );
    speedControlLayout->setSpacing( 4 );
334 335 336
    speedControlLayout->addWidget( speedSlider );
    speedControlLayout->addWidget( normalSpeedButton );
    setLayout( speedControlLayout );
337 338 339
}

SpeedControlWidget::~SpeedControlWidget()
340
{}
341

342 343 344 345 346
void SpeedControlWidget::setEnable( bool b_enable )
{
    speedSlider->setEnabled( b_enable );
}

347 348
void SpeedControlWidget::updateControls( int rate )
{
349 350 351 352 353
    if( speedSlider->isSliderDown() )
    {
        //We don't want to change anything if the user is using the slider
        return;
    }
354

355 356
    double value = 12 * log( (double)INPUT_RATE_DEFAULT / rate ) / log( 2 );
    int sliderValue = (int) ( ( value > 0 ) ? value + .5 : value - .5 );
357

358
    if( sliderValue < speedSlider->minimum() )
359
    {
360
        sliderValue = speedSlider->minimum();
361
    }
362
    else if( sliderValue > speedSlider->maximum() )
363
    {
364
        sliderValue = speedSlider->maximum();
365
    }
366

367 368 369 370
    //Block signals to avoid feedback loop
    speedSlider->blockSignals( true );
    speedSlider->setValue( sliderValue );
    speedSlider->blockSignals( false );
371 372 373 374
}

void SpeedControlWidget::updateRate( int sliderValue )
{
375 376
    double speed = pow( 2, (double)sliderValue / 12 );
    int rate = INPUT_RATE_DEFAULT / speed;
377

378
    THEMIM->getIM()->setRate(rate);
379 380 381 382
}

void SpeedControlWidget::resetRate()
{
383
    THEMIM->getIM()->setRate(INPUT_RATE_DEFAULT);
384
}
385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401



static int downloadCoverCallback( vlc_object_t *p_this,
                                  char const *psz_var,
                                  vlc_value_t oldvar, vlc_value_t newvar,
                                  void *data )
{
    if( !strcmp( psz_var, "item-change" ) )
    {
        CoverArtLabel *art = static_cast< CoverArtLabel* >( data );
        if( art )
            art->requestUpdate();
    }
    return VLC_SUCCESS;
}

402 403 404 405
CoverArtLabel::CoverArtLabel( QWidget *parent,
                              vlc_object_t *_p_this,
                              input_item_t *_p_input )
        : QLabel( parent ), p_this( _p_this), p_input( _p_input ), prevArt()
406 407 408 409
{
    setContextMenuPolicy( Qt::ActionsContextMenu );
    CONNECT( this, updateRequested(), this, doUpdate() );

Antoine Cellerier's avatar
Antoine Cellerier committed
410
    playlist_t *p_playlist = pl_Hold( p_this );
411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427
    var_AddCallback( p_playlist, "item-change",
                     downloadCoverCallback, this );
    pl_Release( p_this );

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

    doUpdate();
}

void CoverArtLabel::downloadCover()
{
    if( p_input )
    {
Antoine Cellerier's avatar
Antoine Cellerier committed
428
        playlist_t *p_playlist = pl_Hold( p_this );
429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451
        playlist_AskForArtEnqueue( p_playlist, p_input );
        pl_Release( p_this );
    }
}

void CoverArtLabel::doUpdate()
{
    if( !p_input )
    {
        setPixmap( QPixmap( ":/noart.png" ) );
        QList< QAction* > artActions = actions();
        if( !artActions.isEmpty() )
            foreach( QAction *act, artActions )
                removeAction( act );
        prevArt = "";
    }
    else
    {
        char *psz_meta = input_item_GetArtURL( p_input );
        if( psz_meta && !strncmp( psz_meta, "file://", 7 ) )
        {
            QString artUrl = qfu( psz_meta ).replace( "file://", "" );
            if( artUrl != prevArt )
452 453 454 455 456 457 458 459 460 461 462
            {
                QPixmap pix;
                if( pix.load( artUrl ) )
                    setPixmap( pix );
                else
                {
                    msg_Dbg( p_this, "Qt could not load image '%s'",
                             qtu( artUrl ) );
                    setPixmap( QPixmap( ":/noart.png" ) );
                }
            }
463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489
            QList< QAction* > artActions = actions();
            if( !artActions.isEmpty() )
            {
                foreach( QAction *act, artActions )
                    removeAction( act );
            }
            prevArt = artUrl;
        }
        else
        {
            if( prevArt != "" )
                setPixmap( QPixmap( ":/noart.png" ) );
            prevArt = "";
            QList< QAction* > artActions = actions();
            if( artActions.isEmpty() )
            {
                QAction *action = new QAction( qtr( "Download cover art" ),
                                               this );
                addAction( action );
                CONNECT( action, triggered(),
                         this, downloadCover() );
            }
        }
        free( psz_meta );
    }
}

490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522
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" ) );


   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;
}

523 524 525 526 527 528 529
bool VolumeClickHandler::eventFilter( QObject *obj, QEvent *e )
{
    if (e->type() == QEvent::MouseButtonPress  )
    {
        aout_VolumeMute( p_intf, NULL );
        audio_volume_t i_volume;
        aout_VolumeGet( p_intf, &i_volume );
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
530
//        m->updateVolume( i_volume *  VOLUME_MAX / (AOUT_VOLUME_MAX/2) );
531 532 533 534
        return true;
    }
    return false;
}
535