]> git.sesse.net Git - vlc/blob - modules/gui/qt4/components/interface_widgets.cpp
806142fd0547fc7282c3bdecc7bad017650cefb3
[vlc] / modules / gui / qt4 / components / interface_widgets.cpp
1 /*****************************************************************************
2  * interface_widgets.cpp : Custom widgets for the main interface
3  ****************************************************************************
4  * Copyright (C) 2006-2010 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  * This program is free software; you can redistribute it and/or modify
13  * it under the terms of the GNU General Public License as published by
14  * the Free Software Foundation; either version 2 of the License, or
15  * ( at your option ) any later version.
16  *
17  * This program is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  * GNU General Public License for more details.
21  *
22  * You should have received a copy of the GNU General Public License
23  * along with this program; if not, write to the Free Software
24  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
25  *****************************************************************************/
26
27 #ifdef HAVE_CONFIG_H
28 # include "config.h"
29 #endif
30
31 #include "components/interface_widgets.hpp"
32 #include "dialogs_provider.hpp"
33 #include "util/customwidgets.hpp"               // qtEventToVLCKey, QVLCStackedWidget
34
35 #include "menus.hpp"             /* Popup menu on bgWidget */
36
37 #include <vlc_vout.h>
38
39 #include <QLabel>
40 #include <QToolButton>
41 #include <QPalette>
42 #include <QEvent>
43 #include <QResizeEvent>
44 #include <QDate>
45 #include <QMenu>
46 #include <QWidgetAction>
47 #include <QDesktopWidget>
48 #include <QPainter>
49 #include <QTimer>
50 #include <QSlider>
51 #include <QBitmap>
52
53 #ifdef Q_WS_X11
54 # include <X11/Xlib.h>
55 # include <qx11info_x11.h>
56 static void videoSync( void )
57 {
58     /* Make sure the X server has processed all requests.
59      * This protects other threads using distinct connections from getting
60      * the video widget window in an inconsistent states. */
61     XSync( QX11Info::display(), False );
62 }
63 #else
64 # define videoSync() (void)0
65 #endif
66
67 #include <math.h>
68 #include <assert.h>
69
70 class ReparentableWidget : public QWidget
71 {
72 private:
73     VideoWidget *owner;
74 public:
75     ReparentableWidget( VideoWidget *owner ) : owner( owner )
76     {}
77 };
78
79 /**********************************************************************
80  * Video Widget. A simple frame on which video is drawn
81  * This class handles resize issues
82  **********************************************************************/
83
84 VideoWidget::VideoWidget( intf_thread_t *_p_i )
85     : QFrame( NULL )
86       , p_intf( _p_i )
87       , reparentable( NULL )
88 {
89     /* Set the policy to expand in both directions */
90     // setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding );
91
92     layout = new QHBoxLayout( this );
93     layout->setContentsMargins( 0, 0, 0, 0 );
94     setLayout( layout );
95 }
96
97 VideoWidget::~VideoWidget()
98 {
99     /* Ensure we are not leaking the video output. This would crash. */
100     assert( reparentable == NULL );
101 }
102
103 /**
104  * Request the video to avoid the conflicts
105  **/
106 WId VideoWidget::request( int *pi_x, int *pi_y,
107                           unsigned int *pi_width, unsigned int *pi_height,
108                           bool b_keep_size )
109 {
110     msg_Dbg( p_intf, "Video was requested %i, %i", *pi_x, *pi_y );
111
112     if( reparentable != NULL )
113     {
114         msg_Dbg( p_intf, "embedded video already in use" );
115         return NULL;
116     }
117     if( b_keep_size )
118     {
119         *pi_width  = size().width();
120         *pi_height = size().height();
121     }
122
123     /* The Qt4 UI needs a fixed a widget ("this"), so that the parent layout is
124      * not messed up when we the video is reparented. Hence, we create an extra
125      * reparentable widget, that will be within the VideoWidget in windowed
126      * mode, and within the root window (NULL parent) in full-screen mode.
127      */
128     reparentable = new ReparentableWidget( this );
129     reparentable->installEventFilter(this );
130     QLayout *innerLayout = new QHBoxLayout( reparentable );
131     innerLayout->setContentsMargins( 0, 0, 0, 0 );
132
133     /* The owner of the video window needs a stable handle (WinId). Reparenting
134      * in Qt4-X11 changes the WinId of the widget, so we need to create another
135      * dummy widget that stays within the reparentable widget. */
136     QWidget *stable = new QWidget();
137     QPalette plt = palette();
138     plt.setColor( QPalette::Window, Qt::black );
139     stable->setPalette( plt );
140     stable->setAutoFillBackground(true);
141     /* Indicates that the widget wants to draw directly onto the screen.
142        Widgets with this attribute set do not participate in composition
143        management */
144     /* This is currently disabled on X11 as it does not seem to improve
145      * performance, but causes the video widget to be transparent... */
146 #ifndef Q_WS_X11
147     stable->setAttribute( Qt::WA_PaintOnScreen, true );
148 #endif
149
150     innerLayout->addWidget( stable );
151
152     layout->addWidget( reparentable );
153
154 #ifdef Q_WS_X11
155     /* HACK: Only one X11 client can subscribe to mouse button press events.
156      * VLC currently handles those in the video display.
157      * Force Qt4 to unsubscribe from mouse press and release events. */
158     Display *dpy = QX11Info::display();
159     Window w = stable->winId();
160     XWindowAttributes attr;
161
162     XGetWindowAttributes( dpy, w, &attr );
163     attr.your_event_mask &= ~(ButtonPressMask|ButtonReleaseMask);
164     XSelectInput( dpy, w, attr.your_event_mask );
165 #endif
166     videoSync();
167 #ifndef NDEBUG
168     msg_Dbg( p_intf, "embedded video ready (handle %p)",
169              (void *)stable->winId() );
170 #endif
171     return stable->winId();
172 }
173
174 /* Set the Widget to the correct Size */
175 /* Function has to be called by the parent
176    Parent has to care about resizing itself */
177 void VideoWidget::SetSizing( unsigned int w, unsigned int h )
178 {
179     if (reparentable->windowState() & Qt::WindowFullScreen )
180         return;
181     if( !isVisible() ) show();
182     resize( w, h );
183     emit sizeChanged( w, h );
184     /* Work-around a bug?misconception? that would happen when vout core resize
185        twice to the same size and would make the vout not centered.
186        This cause a small flicker.
187        See #3621
188      */
189     if( size().width() == w && size().height() == h )
190         updateGeometry();
191     videoSync();
192 }
193
194 void VideoWidget::SetFullScreen( bool b_fs, bool b_ontop )
195 {
196     const Qt::WindowStates curstate = reparentable->windowState();
197     Qt::WindowStates newstate = curstate;
198     Qt::WindowFlags  newflags = reparentable->windowFlags();
199
200
201     if( b_fs )
202     {
203         newstate |= Qt::WindowFullScreen;
204         if( b_ontop )
205             newflags |= Qt::WindowStaysOnTopHint;
206     }
207     else
208     {
209         newstate &= ~Qt::WindowFullScreen;
210         newflags &= ~Qt::WindowStaysOnTopHint;
211     }
212     if( newstate == curstate )
213         return; /* no changes needed */
214
215     if( b_fs )
216     {   /* Go full-screen */
217         int numscreen = var_InheritInteger( p_intf, "qt-fullscreen-screennumber" );
218         /* if user hasn't defined screennumber, or screennumber that is bigger
219          * than current number of screens, take screennumber where current interface
220          * is
221          */
222         if( numscreen == -1 || numscreen > QApplication::desktop()->numScreens() )
223             numscreen = QApplication::desktop()->screenNumber( p_intf->p_sys->p_mi );
224
225         QRect screenres = QApplication::desktop()->screenGeometry( numscreen );
226
227         /* To be sure window is on proper-screen in xinerama */
228         if( !screenres.contains( reparentable->pos() ) )
229         {
230             msg_Dbg( p_intf, "Moving video to correct screen");
231             reparentable->move( QPoint( screenres.x(), screenres.y() ) );
232         }
233         reparentable->setParent( NULL, newflags );
234         reparentable->setWindowState( newstate );
235
236         /* FIXME: inherit from the vout window, not the interface */
237         char *title = var_InheritString( p_intf, "video-title" );
238         reparentable->setWindowTitle( qfu(title ? title : _("Video")) );
239         free( title );
240
241         reparentable->show();
242         reparentable->activateWindow();
243         reparentable->raise();
244     }
245     else
246     {   /* Go windowed */
247         reparentable->setWindowFlags( newflags );
248         reparentable->setWindowState( newstate );
249         layout->addWidget( reparentable );
250     }
251     videoSync();
252 }
253
254 void VideoWidget::release( void )
255 {
256     msg_Dbg( p_intf, "Video is not needed anymore" );
257     //layout->removeWidget( reparentable );
258
259     reparentable->deleteLater();
260     reparentable = NULL;
261     updateGeometry();
262     hide();
263 }
264
265 #undef KeyPress
266 bool VideoWidget::eventFilter(QObject *obj, QEvent *event)
267 {
268     if( obj == reparentable )
269     {
270         if (event->type() == QEvent::Close)
271         {
272             THEDP->quit();
273             return true;
274         }
275         else if( event->type() == QEvent::KeyPress )
276         {
277             emit keyPressed( static_cast<QKeyEvent *>(event) );
278             return true;
279         }
280         else if( event->type() == QEvent::Wheel )
281         {
282             int i_vlckey = qtWheelEventToVLCKey( static_cast<QWheelEvent *>(event) );
283             var_SetInteger( p_intf->p_libvlc, "key-pressed", i_vlckey );
284             return true;
285         }
286     }
287     return false;
288 }
289
290 /**********************************************************************
291  * Background Widget. Show a simple image background. Currently,
292  * it's album art if present or cone.
293  **********************************************************************/
294
295 BackgroundWidget::BackgroundWidget( intf_thread_t *_p_i )
296                  :QWidget( NULL ), p_intf( _p_i ), b_expandPixmap( false )
297 {
298     /* A dark background */
299     setAutoFillBackground( true );
300     QPalette plt = palette();
301     plt.setColor( QPalette::Active, QPalette::Window , Qt::black );
302     plt.setColor( QPalette::Inactive, QPalette::Window , Qt::black );
303     setPalette( plt );
304
305     /* Init the cone art */
306     updateArt( "" );
307
308     CONNECT( THEMIM->getIM(), artChanged( QString ),
309              this, updateArt( const QString& ) );
310 }
311
312 void BackgroundWidget::updateArt( const QString& url )
313 {
314     if ( !url.isEmpty() )
315     {
316         pixmapUrl = url;
317     }
318     else
319     {   /* Xmas joke */
320         if( QDate::currentDate().dayOfYear() >= 354 )
321             pixmapUrl = QString( ":/logo/vlc128-christmas.png" );
322         else
323             pixmapUrl = QString( ":/logo/vlc128.png" );
324     }
325     update();
326 }
327
328 void BackgroundWidget::paintEvent( QPaintEvent *e )
329 {
330     int i_maxwidth, i_maxheight;
331     QPixmap pixmap = QPixmap( pixmapUrl );
332     QPainter painter(this);
333     QBitmap pMask;
334     float f_alpha = 1.0;
335
336     i_maxwidth = std::min( maximumWidth(), width() ) - MARGIN * 2;
337     i_maxheight = std::min( maximumHeight(), height() ) - MARGIN * 2;
338
339     if ( height() > MARGIN * 2 )
340     {
341         /* Scale down the pixmap if the widget is too small */
342         if( pixmap.width() > i_maxwidth || pixmap.height() > i_maxheight )
343         {
344             pixmap = pixmap.scaled( i_maxwidth, i_maxheight,
345                             Qt::KeepAspectRatio, Qt::SmoothTransformation );
346         }
347         else
348         if ( b_expandPixmap &&
349              pixmap.width() < width() && pixmap.height() < height() )
350         {
351             /* Scale up the pixmap to fill widget's size */
352             f_alpha = ( (float) pixmap.height() / (float) height() );
353             pixmap = pixmap.scaled(
354                     width() - MARGIN * 2,
355                     height() - MARGIN * 2,
356                     Qt::KeepAspectRatio,
357                     ( f_alpha < .2 )? /* Don't waste cpu when not visible */
358                         Qt::SmoothTransformation:
359                         Qt::FastTransformation
360                     );
361             /* Non agressive alpha compositing when sizing up */
362             pMask = QBitmap( pixmap.width(), pixmap.height() );
363             pMask.fill( QColor::fromRgbF( 1.0, 1.0, 1.0, f_alpha ) );
364             pixmap.setMask( pMask );
365         }
366
367         painter.drawPixmap(
368                 MARGIN + ( i_maxwidth - pixmap.width() ) /2,
369                 MARGIN + ( i_maxheight - pixmap.height() ) /2,
370                 pixmap);
371     }
372     QWidget::paintEvent( e );
373 }
374
375 void BackgroundWidget::contextMenuEvent( QContextMenuEvent *event )
376 {
377     QVLCMenu::PopupMenu( p_intf, true );
378     event->accept();
379 }
380
381 #if 0
382 #include <QPushButton>
383 #include <QHBoxLayout>
384
385 /**********************************************************************
386  * Visualization selector panel
387  **********************************************************************/
388 VisualSelector::VisualSelector( intf_thread_t *_p_i ) :
389                                 QFrame( NULL ), p_intf( _p_i )
390 {
391     QHBoxLayout *layout = new QHBoxLayout( this );
392     layout->setMargin( 0 );
393     QPushButton *prevButton = new QPushButton( "Prev" );
394     QPushButton *nextButton = new QPushButton( "Next" );
395     layout->addWidget( prevButton );
396     layout->addWidget( nextButton );
397
398     layout->addStretch( 10 );
399     layout->addWidget( new QLabel( qtr( "Current visualization" ) ) );
400
401     current = new QLabel( qtr( "None" ) );
402     layout->addWidget( current );
403
404     BUTTONACT( prevButton, prev() );
405     BUTTONACT( nextButton, next() );
406
407     setLayout( layout );
408     setMaximumHeight( 35 );
409 }
410
411 VisualSelector::~VisualSelector()
412 {}
413
414 void VisualSelector::prev()
415 {
416     char *psz_new = aout_VisualPrev( p_intf );
417     if( psz_new )
418     {
419         current->setText( qfu( psz_new ) );
420         free( psz_new );
421     }
422 }
423
424 void VisualSelector::next()
425 {
426     char *psz_new = aout_VisualNext( p_intf );
427     if( psz_new )
428     {
429         current->setText( qfu( psz_new ) );
430         free( psz_new );
431     }
432 }
433 #endif
434
435 SpeedLabel::SpeedLabel( intf_thread_t *_p_intf, QWidget *parent )
436            : QLabel( parent ), p_intf( _p_intf )
437 {
438     tooltipStringPattern = qtr( "Current playback speed: %1\nClick to adjust" );
439
440     /* Create the Speed Control Widget */
441     speedControl = new SpeedControlWidget( p_intf, this );
442     speedControlMenu = new QMenu( this );
443
444     QWidgetAction *widgetAction = new QWidgetAction( speedControl );
445     widgetAction->setDefaultWidget( speedControl );
446     speedControlMenu->addAction( widgetAction );
447
448     /* Change the SpeedRate in the Status Bar */
449     CONNECT( THEMIM->getIM(), rateChanged( float ), this, setRate( float ) );
450
451     DCONNECT( THEMIM, inputChanged( input_thread_t * ),
452               speedControl, activateOnState() );
453     setRate( var_InheritFloat( p_intf, "rate" ) );
454 }
455
456 SpeedLabel::~SpeedLabel()
457 {
458     delete speedControl;
459     delete speedControlMenu;
460 }
461
462 /****************************************************************************
463  * Small right-click menu for rate control
464  ****************************************************************************/
465
466 void SpeedLabel::showSpeedMenu( QPoint pos )
467 {
468     speedControlMenu->exec( QCursor::pos() - pos
469                           + QPoint( 0, height() ) );
470 }
471
472 void SpeedLabel::setRate( float rate )
473 {
474     QString str;
475     str.setNum( rate, 'f', 2 );
476     str.append( "x" );
477     setText( str );
478     setToolTip( tooltipStringPattern.arg( str ) );
479     speedControl->updateControls( rate );
480 }
481
482 /**********************************************************************
483  * Speed control widget
484  **********************************************************************/
485 SpeedControlWidget::SpeedControlWidget( intf_thread_t *_p_i, QWidget *_parent )
486                     : QFrame( _parent ), p_intf( _p_i )
487 {
488     QSizePolicy sizePolicy( QSizePolicy::Maximum, QSizePolicy::Fixed );
489     sizePolicy.setHorizontalStretch( 0 );
490     sizePolicy.setVerticalStretch( 0 );
491
492     speedSlider = new QSlider( this );
493     speedSlider->setSizePolicy( sizePolicy );
494     speedSlider->setMaximumSize( QSize( 80, 200 ) );
495     speedSlider->setOrientation( Qt::Vertical );
496     speedSlider->setTickPosition( QSlider::TicksRight );
497
498     speedSlider->setRange( -34, 34 );
499     speedSlider->setSingleStep( 1 );
500     speedSlider->setPageStep( 1 );
501     speedSlider->setTickInterval( 17 );
502
503     CONNECT( speedSlider, valueChanged( int ), this, updateRate( int ) );
504
505     QToolButton *normalSpeedButton = new QToolButton( this );
506     normalSpeedButton->setMaximumSize( QSize( 26, 20 ) );
507     normalSpeedButton->setAutoRaise( true );
508     normalSpeedButton->setText( "1x" );
509     normalSpeedButton->setToolTip( qtr( "Revert to normal play speed" ) );
510
511     CONNECT( normalSpeedButton, clicked(), this, resetRate() );
512
513     QVBoxLayout *speedControlLayout = new QVBoxLayout( this );
514     speedControlLayout->setContentsMargins( 4, 4, 4, 4 );
515     speedControlLayout->setSpacing( 4 );
516     speedControlLayout->addWidget( speedSlider );
517     speedControlLayout->addWidget( normalSpeedButton );
518
519     lastValue = 0;
520
521     activateOnState();
522 }
523
524 void SpeedControlWidget::activateOnState()
525 {
526     speedSlider->setEnabled( THEMIM->getIM()->hasInput() );
527 }
528
529 void SpeedControlWidget::updateControls( float rate )
530 {
531     if( speedSlider->isSliderDown() )
532     {
533         //We don't want to change anything if the user is using the slider
534         return;
535     }
536
537     double value = 17 * log( rate ) / log( 2 );
538     int sliderValue = (int) ( ( value > 0 ) ? value + .5 : value - .5 );
539
540     if( sliderValue < speedSlider->minimum() )
541     {
542         sliderValue = speedSlider->minimum();
543     }
544     else if( sliderValue > speedSlider->maximum() )
545     {
546         sliderValue = speedSlider->maximum();
547     }
548     lastValue = sliderValue;
549
550     speedSlider->setValue( sliderValue );
551 }
552
553 void SpeedControlWidget::updateRate( int sliderValue )
554 {
555     if( sliderValue == lastValue )
556         return;
557
558     double speed = pow( 2, (double)sliderValue / 17 );
559     int rate = INPUT_RATE_DEFAULT / speed;
560
561     THEMIM->getIM()->setRate(rate);
562 }
563
564 void SpeedControlWidget::resetRate()
565 {
566     THEMIM->getIM()->setRate( INPUT_RATE_DEFAULT );
567 }
568
569 CoverArtLabel::CoverArtLabel( QWidget *parent, intf_thread_t *_p_i )
570               : QLabel( parent ), p_intf( _p_i )
571 {
572     setContextMenuPolicy( Qt::ActionsContextMenu );
573     CONNECT( this, updateRequested(), this, askForUpdate() );
574
575     setMinimumHeight( 128 );
576     setMinimumWidth( 128 );
577     setMaximumHeight( 128 );
578     setMaximumWidth( 128 );
579     setScaledContents( false );
580     setAlignment( Qt::AlignCenter );
581
582     QList< QAction* > artActions = actions();
583     QAction *action = new QAction( qtr( "Download cover art" ), this );
584     CONNECT( action, triggered(), this, askForUpdate() );
585     addAction( action );
586
587     showArtUpdate( "" );
588 }
589
590 CoverArtLabel::~CoverArtLabel()
591 {
592     QList< QAction* > artActions = actions();
593     foreach( QAction *act, artActions )
594         removeAction( act );
595 }
596
597 void CoverArtLabel::showArtUpdate( const QString& url )
598 {
599     QPixmap pix;
600     if( !url.isEmpty() && pix.load( url ) )
601     {
602         pix = pix.scaled( maximumWidth(), maximumHeight(),
603                           Qt::KeepAspectRatioByExpanding,
604                           Qt::SmoothTransformation );
605     }
606     else
607     {
608         pix = QPixmap( ":/noart.png" );
609     }
610     setPixmap( pix );
611 }
612
613 void CoverArtLabel::askForUpdate()
614 {
615     THEMIM->getIM()->requestArtUpdate();
616 }
617
618 TimeLabel::TimeLabel( intf_thread_t *_p_intf  )
619     : QLabel(), p_intf( _p_intf ), bufTimer( new QTimer(this) ),
620       buffering( false ), showBuffering(false), bufVal( -1 )
621 {
622     b_remainingTime = false;
623     setText( " --:--/--:-- " );
624     setAlignment( Qt::AlignRight | Qt::AlignVCenter );
625     setToolTip( QString( "- " )
626         + qtr( "Click to toggle between elapsed and remaining time" )
627         + QString( "\n- " )
628         + qtr( "Double click to jump to a chosen time position" ) );
629     bufTimer->setSingleShot( true );
630
631     CONNECT( THEMIM->getIM(), positionUpdated( float, int64_t, int ),
632               this, setDisplayPosition( float, int64_t, int ) );
633     CONNECT( THEMIM->getIM(), cachingChanged( float ),
634               this, updateBuffering( float ) );
635     CONNECT( bufTimer, timeout(), this, updateBuffering() );
636 }
637
638 void TimeLabel::setDisplayPosition( float pos, int64_t t, int length )
639 {
640     showBuffering = false;
641     bufTimer->stop();
642
643     if( pos == -1.f )
644     {
645         setText( " --:--/--:-- " );
646         return;
647     }
648
649     int time = t / 1000000;
650
651     secstotimestr( psz_length, length );
652     secstotimestr( psz_time, ( b_remainingTime && length ) ? length - time
653                                                            : time );
654
655     QString timestr = QString( " %1%2/%3 " )
656             .arg( QString( (b_remainingTime && length) ? "-" : "" ) )
657             .arg( QString( psz_time ) )
658             .arg( QString( ( !length && time ) ? "--:--" : psz_length ) );
659
660     setText( timestr );
661
662     cachedLength = length;
663 }
664
665 void TimeLabel::setDisplayPosition( float pos )
666 {
667     if( pos == -1.f || cachedLength == 0 )
668     {
669         setText( " --:--/--:-- " );
670         return;
671     }
672
673     int time = pos * cachedLength;
674     secstotimestr( psz_time,
675                    ( b_remainingTime && cachedLength ?
676                    cachedLength - time : time ) );
677     QString timestr = QString( " %1%2/%3 " )
678         .arg( QString( (b_remainingTime && cachedLength) ? "-" : "" ) )
679         .arg( QString( psz_time ) )
680         .arg( QString( ( !cachedLength && time ) ? "--:--" : psz_length ) );
681
682     setText( timestr );
683 }
684
685
686 void TimeLabel::toggleTimeDisplay()
687 {
688     b_remainingTime = !b_remainingTime;
689 }
690
691
692 void TimeLabel::updateBuffering( float _buffered )
693 {
694     bufVal = _buffered;
695     if( !buffering || bufVal == 0 )
696     {
697         showBuffering = false;
698         buffering = true;
699         bufTimer->start(200);
700     }
701     else if( bufVal == 1 )
702     {
703         showBuffering = buffering = false;
704         bufTimer->stop();
705     }
706     update();
707 }
708
709 void TimeLabel::updateBuffering()
710 {
711     showBuffering = true;
712     update();
713 }
714
715 void TimeLabel::paintEvent( QPaintEvent* event )
716 {
717     if( showBuffering )
718     {
719         QRect r( rect() );
720         r.setLeft( r.width() * bufVal );
721         QPainter p( this );
722         p.setOpacity( 0.4 );
723         p.fillRect( r, palette().color( QPalette::Highlight ) );
724     }
725     QLabel::paintEvent( event );
726 }