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>

zorglub's avatar
zorglub committed
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

zorglub's avatar
zorglub committed
41
42
#include <QLabel>
#include <QSpacerItem>
zorglub's avatar
zorglub committed
43
#include <QCursor>
zorglub's avatar
zorglub committed
44
#include <QPushButton>
45
#include <QToolButton>
46
#include <QHBoxLayout>
zorglub's avatar
zorglub 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 */
zorglub's avatar
zorglub 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
}

zorglub's avatar
zorglub 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
zorglub's avatar
zorglub committed
247
248
249
250
/**********************************************************************
 * Visualization selector panel
 **********************************************************************/
VisualSelector::VisualSelector( intf_thread_t *_p_i ) :
251
                                QFrame( NULL ), p_intf( _p_i )
zorglub's avatar
zorglub committed
252
253
{
    QHBoxLayout *layout = new QHBoxLayout( this );
254
    layout->setMargin( 0 );
zorglub's avatar
zorglub committed
255
    QPushButton *prevButton = new QPushButton( "Prev" );
256
    QPushButton *nextButton = new QPushButton( "Next" );
zorglub's avatar
zorglub committed
257
258
    layout->addWidget( prevButton );
    layout->addWidget( nextButton );
zorglub's avatar
zorglub committed
259

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

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

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

zorglub's avatar
zorglub committed
269
    setLayout( layout );
270
    setMaximumHeight( 35 );
zorglub's avatar
zorglub committed
271
272
273
}

VisualSelector::~VisualSelector()
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
274
{}
zorglub's avatar
zorglub committed
275

zorglub's avatar
zorglub 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
zorglub's avatar
zorglub 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() );

dionoea's avatar
dionoea 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 )
    {
dionoea's avatar
dionoea 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