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 110
VideoWidget::~VideoWidget()
{
111 112
    /* Ensure we are not leaking the video output. This would crash. */
    assert( !p_vout );
113 114
}

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

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

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

157 158 159 160
QSize VideoWidget::sizeHint() const
{
    return videoSize;
}
161

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

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

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

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

195 196 197 198
    QGridLayout *backgroundLayout = new QGridLayout( this );
    backgroundLayout->addWidget( label, 0, 1 );
    backgroundLayout->setColumnStretch( 0, 1 );
    backgroundLayout->setColumnStretch( 2, 1 );
199

200 201
    CONNECT( THEMIM->getIM(), artChanged( input_item_t* ),
             this, updateArt( input_item_t* ) );
202 203
}

Clément Stenac's avatar
Clément Stenac committed
204
BackgroundWidget::~BackgroundWidget()
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
205
{}
Ilkka Ollakka's avatar
 
Ilkka Ollakka committed
206

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

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

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

241 242 243 244
void BackgroundWidget::contextMenuEvent( QContextMenuEvent *event )
{
    QVLCMenu::PopupMenu( p_intf, true );
}
245

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

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

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

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

Clément Stenac's avatar
Clément Stenac committed
269
    setLayout( layout );
270
    setMaximumHeight( 35 );
Clément Stenac's avatar
Clément Stenac committed
271 272 273
}

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

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

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

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

313 314 315 316
    speedSlider->setRange( -24, 24 );
    speedSlider->setSingleStep( 1 );
    speedSlider->setPageStep( 1 );
    speedSlider->setTickInterval( 12 );
317

318
    CONNECT( speedSlider, valueChanged( int ), this, updateRate( int ) );
319

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

326
    CONNECT( normalSpeedButton, clicked(), this, resetRate() );
327

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

SpeedControlWidget::~SpeedControlWidget()
337
{}
338

339 340 341 342 343
void SpeedControlWidget::setEnable( bool b_enable )
{
    speedSlider->setEnabled( b_enable );
}

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

352 353
    double value = 12 * log( (double)INPUT_RATE_DEFAULT / rate ) / log( 2 );
    int sliderValue = (int) ( ( value > 0 ) ? value + .5 : value - .5 );
354

355
    if( sliderValue < speedSlider->minimum() )
356
    {
357
        sliderValue = speedSlider->minimum();
358
    }
359
    else if( sliderValue > speedSlider->maximum() )
360
    {
361
        sliderValue = speedSlider->maximum();
362
    }
363

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

void SpeedControlWidget::updateRate( int sliderValue )
{
372 373
    double speed = pow( 2, (double)sliderValue / 12 );
    int rate = INPUT_RATE_DEFAULT / speed;
374

375
    THEMIM->getIM()->setRate(rate);
376 377 378 379
}

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



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

399 400 401 402
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()
403 404 405 406
{
    setContextMenuPolicy( Qt::ActionsContextMenu );
    CONNECT( this, updateRequested(), this, doUpdate() );

Antoine Cellerier's avatar
Antoine Cellerier committed
407
    playlist_t *p_playlist = pl_Hold( p_this );
408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424
    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
425
        playlist_t *p_playlist = pl_Hold( p_this );
426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448
        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 )
449 450 451 452 453 454 455 456 457 458 459
            {
                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" ) );
                }
            }
460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486
            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 );
    }
}

487 488 489 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
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;
}

520 521 522 523 524 525 526
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
527
//        m->updateVolume( i_volume *  VOLUME_MAX / (AOUT_VOLUME_MAX/2) );
528 529 530 531
        return true;
    }
    return false;
}
532