]> git.sesse.net Git - vlc/blob - modules/gui/qt4/util/input_slider.cpp
Qt: use count() iso size() on Qt Containers
[vlc] / modules / gui / qt4 / util / input_slider.cpp
1 /*****************************************************************************
2  * input_slider.cpp : VolumeSlider and SeekSlider
3  ****************************************************************************
4  * Copyright (C) 2006-2011 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Clément Stenac <zorglub@videolan.org>
8  *          Jean-Baptiste Kempf <jb@videolan.org>
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23  *****************************************************************************/
24
25 #ifdef HAVE_CONFIG_H
26 # include "config.h"
27 #endif
28
29 #include "qt4.hpp"
30
31 #include "util/input_slider.hpp"
32 #include "adapters/seekpoints.hpp"
33 #include <vlc_aout_intf.h>
34
35 #include <stdlib.h>
36
37 #include <QPaintEvent>
38 #include <QPainter>
39 #include <QBitmap>
40 #include <QPainter>
41 #include <QStyleOptionSlider>
42 #include <QLinearGradient>
43 #include <QTimer>
44 #include <QRadialGradient>
45
46 #define MINIMUM 0
47 #define MAXIMUM 1000
48 #define CHAPTERSSPOTSIZE 3
49
50 SeekSlider::SeekSlider( QWidget *_parent ) : QSlider( _parent )
51 {
52     SeekSlider( Qt::Horizontal, _parent );
53 }
54
55 SeekSlider::SeekSlider( Qt::Orientation q, QWidget *_parent )
56           : QSlider( q, _parent )
57 {
58     b_isSliding = false;
59     f_buffering = 1.0;
60     chapters = NULL;
61
62     /* Timer used to fire intermediate updatePos() when sliding */
63     seekLimitTimer = new QTimer( this );
64     seekLimitTimer->setSingleShot( true );
65
66     /* Tooltip bubble */
67     mTimeTooltip = new TimeTooltip( this );
68     mTimeTooltip->setMouseTracking( true );
69
70     /* Properties */
71     setRange( MINIMUM, MAXIMUM );
72     setSingleStep( 2 );
73     setPageStep( 10 );
74     setMouseTracking( true );
75     setTracking( true );
76     setFocusPolicy( Qt::NoFocus );
77
78     /* Init to 0 */
79     setPosition( -1.0, 0, 0 );
80     secstotimestr( psz_length, 0 );
81
82     CONNECT( this, sliderMoved( int ), this, startSeekTimer() );
83     CONNECT( seekLimitTimer, timeout(), this, updatePos() );
84     mTimeTooltip->installEventFilter( this );
85 }
86
87 SeekSlider::~SeekSlider()
88 {
89     delete chapters;
90 }
91
92 /***
93  * \brief Sets the chapters seekpoints adapter
94  *
95  * \params SeekPoints initilized with current intf thread
96 ***/
97 void SeekSlider::setChapters( SeekPoints *chapters_ )
98 {
99     delete chapters;
100     chapters = chapters_;
101     chapters->setParent( this );
102 }
103
104 /***
105  * \brief Main public method, superseeding setValue. Disabling the slider when neeeded
106  *
107  * \param pos Position, between 0 and 1. -1 disables the slider
108  * \param time Elapsed time. Unused
109  * \param legnth Duration time.
110  ***/
111 void SeekSlider::setPosition( float pos, int64_t time, int length )
112 {
113     VLC_UNUSED(time);
114     if( pos == -1.0 )
115     {
116         setEnabled( false );
117         b_isSliding = false;
118     }
119     else
120         setEnabled( true );
121
122     if( !b_isSliding )
123         setValue( (int)( pos * 1000.0 ) );
124
125     inputLength = length;
126 }
127
128 void SeekSlider::startSeekTimer()
129 {
130     /* Only fire one update, when sliding, every 150ms */
131     if( b_isSliding && !seekLimitTimer->isActive() )
132         seekLimitTimer->start( 150 );
133 }
134
135 void SeekSlider::updatePos()
136 {
137     float f_pos = (float)( value() ) / 1000.0;
138     emit sliderDragged( f_pos ); /* Send new position to VLC's core */
139 }
140
141 void SeekSlider::updateBuffering( float f_buffering_ )
142 {
143     f_buffering = f_buffering_;
144     repaint();
145 }
146
147 void SeekSlider::mouseReleaseEvent( QMouseEvent *event )
148 {
149     event->accept();
150     b_isSliding = false;
151     seekLimitTimer->stop(); /* We're not sliding anymore: only last seek on release */
152     if ( b_is_jumping )
153     {
154         b_is_jumping = false;
155         return;
156     }
157     QSlider::mouseReleaseEvent( event );
158     updatePos();
159 }
160
161 void SeekSlider::mousePressEvent( QMouseEvent* event )
162 {
163     /* Right-click */
164     if( event->button() != Qt::LeftButton &&
165         event->button() != Qt::MidButton )
166     {
167         QSlider::mousePressEvent( event );
168         return;
169     }
170
171     b_is_jumping = false;
172     /* handle chapter clicks */
173     int i_width = size().width();
174     if ( chapters && inputLength && i_width)
175     {
176         if ( orientation() == Qt::Horizontal ) /* TODO: vertical */
177         {
178              /* only on chapters zone */
179             if ( event->y() < CHAPTERSSPOTSIZE ||
180                  event->y() > ( size().height() - CHAPTERSSPOTSIZE ) )
181             {
182                 QList<SeekPoint> points = chapters->getPoints();
183                 int i_selected = -1;
184                 int i_min_diff = i_width + 1;
185                 for( int i = 0 ; i < points.count() ; i++ )
186                 {
187                     int x = points.at(i).time / 1000000.0 / inputLength * i_width;
188                     int diff_x = abs( x - event->x() );
189                     if ( diff_x < i_min_diff )
190                     {
191                         i_min_diff = diff_x;
192                         i_selected = i;
193                     } else break;
194                 }
195                 if ( i_selected && i_min_diff < 4 ) // max 4px around mark
196                 {
197                     chapters->jumpTo( i_selected );
198                     event->accept();
199                     b_is_jumping = true;
200                     return;
201                 }
202             }
203         }
204     }
205
206     b_isSliding = true ;
207     setValue( QStyle::sliderValueFromPosition( MINIMUM, MAXIMUM, event->x(), width(), false ) );
208     event->accept();
209 }
210
211 void SeekSlider::mouseMoveEvent( QMouseEvent *event )
212 {
213     if( b_isSliding )
214     {
215         setValue( QStyle::sliderValueFromPosition( MINIMUM, MAXIMUM, event->x(), width(), false) );
216         emit sliderMoved( value() );
217     }
218
219     /* Tooltip */
220     if ( inputLength > 0 )
221     {
222         int posX = qMax( rect().left(), qMin( rect().right(), event->x() ) );
223
224         QString chapterLabel;
225         QPoint p( event->globalX() - ( event->x() - posX ) - ( mTimeTooltip->width() / 2 ),
226                   QWidget::mapToGlobal( pos() ).y() - ( mTimeTooltip->height() + 2 ) );
227
228         if ( orientation() == Qt::Horizontal ) /* TODO: vertical */
229         {
230                 QList<SeekPoint> points = chapters->getPoints();
231                 int i_selected = -1;
232                 for( int i = 0 ; i < points.count() ; i++ )
233                 {
234                     int x = points.at(i).time / 1000000.0 / inputLength * size().width();
235                     if ( event->x() >= x )
236                         i_selected = i;
237                 }
238                 if ( i_selected >= 0 )
239                     chapterLabel = points.at( i_selected ).name;
240         }
241
242         secstotimestr( psz_length, ( posX * inputLength ) / size().width() );
243         mTimeTooltip->setText( psz_length, chapterLabel );
244         mTimeTooltip->move( p );
245     }
246     event->accept();
247 }
248
249 void SeekSlider::wheelEvent( QWheelEvent *event )
250 {
251     /* Don't do anything if we are for somehow reason sliding */
252     if( !b_isSliding )
253     {
254         setValue( value() + event->delta() / 12 ); /* 12 = 8 * 15 / 10
255          Since delta is in 1/8 of ° and mouse have steps of 15 °
256          and that our slider is in 0.1% and we want one step to be a 1%
257          increment of position */
258         emit sliderDragged( value() / 1000.0 );
259     }
260     event->accept();
261 }
262
263 void SeekSlider::enterEvent( QEvent * )
264 {
265     /* Don't show the tooltip if the slider is disabled */
266     if( isEnabled() && inputLength > 0 )
267         mTimeTooltip->show();
268 }
269
270 void SeekSlider::leaveEvent( QEvent * )
271 {
272     if( !rect().contains( mapFromGlobal( QCursor::pos() ) ) )
273         mTimeTooltip->hide();
274 }
275
276 void SeekSlider::hideEvent( QHideEvent * )
277 {
278     mTimeTooltip->hide();
279 }
280
281 bool SeekSlider::eventFilter( QObject *obj, QEvent *event )
282 {
283     if( obj == mTimeTooltip )
284     {
285         if( event->type() == QEvent::Leave ||
286             event->type() == QEvent::MouseMove )
287         {
288             QMouseEvent *e = static_cast<QMouseEvent*>( event );
289             if( !rect().contains( mapFromGlobal( e->globalPos() ) ) )
290                 mTimeTooltip->hide();
291         }
292         return false;
293     }
294     else
295         return QSlider::eventFilter( obj, event );
296 }
297
298 QSize SeekSlider::sizeHint() const
299 {
300     return ( orientation() == Qt::Horizontal ) ? QSize( 100, 18 )
301                                                : QSize( 18, 100 );
302 }
303
304 QSize SeekSlider::handleSize() const
305 {
306     const int size = ( orientation() == Qt::Horizontal ? height() : width() );
307     return QSize( size, size );
308 }
309
310 void SeekSlider::paintEvent( QPaintEvent *event )
311 {
312     Q_UNUSED( event );
313
314     QStyleOptionSlider option;
315     initStyleOption( &option );
316
317     /* */
318     QPainter painter( this );
319     painter.setRenderHints( QPainter::Antialiasing );
320
321     // draw bar
322     const int barCorner = 3;
323     qreal sliderPos     = -1;
324     int range           = MAXIMUM;
325     QRect barRect       = rect();
326
327     // adjust positions based on the current orientation
328     if ( option.sliderPosition != 0 )
329     {
330         switch ( orientation() )
331         {
332             case Qt::Horizontal:
333                 sliderPos = ( ( (qreal)width() ) / (qreal)range )
334                         * (qreal)option.sliderPosition;
335                 break;
336             case Qt::Vertical:
337                 sliderPos = ( ( (qreal)height() ) / (qreal)range )
338                         * (qreal)option.sliderPosition;
339                 break;
340         }
341     }
342
343     switch ( orientation() )
344     {
345         case Qt::Horizontal:
346             barRect.setHeight( handleSize().height() /2 );
347             break;
348         case Qt::Vertical:
349             barRect.setWidth( handleSize().width() /2 );
350             break;
351     }
352
353     barRect.moveCenter( rect().center() );
354
355     // set the background color and gradient
356     QColor backgroundBase( 135, 135, 135 );
357     QLinearGradient backgroundGradient( 0, 0, 0, height() );
358     backgroundGradient.setColorAt( 0.0, backgroundBase );
359     backgroundGradient.setColorAt( 1.0, backgroundBase.lighter( 150 ) );
360
361     // set the foreground color and gradient
362     QColor foregroundBase( 50, 156, 255 );
363     QLinearGradient foregroundGradient( 0, 0, 0, height() );
364     foregroundGradient.setColorAt( 0.0,  foregroundBase );
365     foregroundGradient.setColorAt( 1.0,  foregroundBase.darker( 140 ) );
366
367     // draw a slight 3d effect on the bottom
368     painter.setPen( QColor( 230, 230, 230 ) );
369     painter.setBrush( Qt::NoBrush );
370     painter.drawRoundedRect( barRect.adjusted( 0, 2, 0, 0 ), barCorner, barCorner );
371
372     // draw background
373     painter.setPen( Qt::NoPen );
374     painter.setBrush( backgroundGradient );
375     painter.drawRoundedRect( barRect, barCorner, barCorner );
376
377     // adjusted foreground rectangle
378     QRect valueRect = barRect.adjusted( 1, 1, -1, 0 );
379
380     switch ( orientation() )
381     {
382         case Qt::Horizontal:
383             valueRect.setWidth( qMin( width(), int( sliderPos ) ) );
384             break;
385         case Qt::Vertical:
386             valueRect.setHeight( qMin( height(), int( sliderPos ) ) );
387             valueRect.moveBottom( rect().bottom() );
388             break;
389     }
390
391     if ( option.sliderPosition > minimum() && option.sliderPosition <= maximum() )
392     {
393         // draw foreground
394         painter.setPen( Qt::NoPen );
395         painter.setBrush( foregroundGradient );
396         painter.drawRoundedRect( valueRect, barCorner, barCorner );
397     }
398
399     // draw buffering overlay
400     if ( f_buffering < 1.0 )
401     {
402         QRect innerRect = barRect.adjusted( 1, 1,
403                             barRect.width() * ( -1.0 + f_buffering ) - 1, 0 );
404         QColor overlayColor = QColor( "Orange" );
405         overlayColor.setAlpha( 128 );
406         painter.setBrush( overlayColor );
407         painter.drawRoundedRect( innerRect, barCorner, barCorner );
408     }
409
410     if ( option.state & QStyle::State_MouseOver )
411     {
412         /* draw chapters tickpoints */
413         if ( chapters && inputLength && size().width() )
414         {
415             QColor background = palette().color( QPalette::Active, QPalette::Background );
416             QColor foreground = palette().color( QPalette::Active, QPalette::WindowText );
417             foreground.setHsv( foreground.hue(),
418                             ( background.saturation() + foreground.saturation() ) / 2,
419                             ( background.value() + foreground.value() ) / 2 );
420             if ( orientation() == Qt::Horizontal ) /* TODO: vertical */
421             {
422                 QList<SeekPoint> points = chapters->getPoints();
423                 foreach( SeekPoint point, points )
424                 {
425                     int x = point.time / 1000000.0 / inputLength * size().width();
426                     painter.setPen( foreground );
427                     painter.setBrush( Qt::NoBrush );
428                     painter.drawLine( x, 0, x, CHAPTERSSPOTSIZE );
429                     painter.drawLine( x, height(), x, height() - CHAPTERSSPOTSIZE );
430                 }
431             }
432         }
433
434         // draw handle
435         if ( sliderPos != -1 )
436         {
437             const int margin = 0;
438             QSize hs = handleSize() - QSize( 5, 5 );
439             QPoint pos;
440
441             switch ( orientation() )
442             {
443                 case Qt::Horizontal:
444                     pos = QPoint( sliderPos - ( hs.width() / 2 ), 2 );
445                     pos.rx() = qMax( margin, pos.x() );
446                     pos.rx() = qMin( width() - hs.width() - margin, pos.x() );
447                     break;
448                 case Qt::Vertical:
449                     pos = QPoint( 2, height() - ( sliderPos + ( hs.height() / 2 ) ) );
450                     pos.ry() = qMax( margin, pos.y() );
451                     pos.ry() = qMin( height() - hs.height() - margin, pos.y() );
452                     break;
453             }
454
455             QRadialGradient buttonGradient( pos.x() + ( hs.width() / 2 ) - 2,
456                                             pos.y() + ( hs.height() / 2 ) - 2,
457                                             qMax( hs.width(), hs.height() ) );
458             buttonGradient.setColorAt( 0.0, QColor(  0,  0,  0 ) );
459             buttonGradient.setColorAt( 1.0, QColor( 80, 80, 80 ) );
460
461             painter.setPen( Qt::NoPen );
462             painter.setBrush( buttonGradient );
463             painter.drawEllipse( pos.x(), pos.y(), hs.width(), hs.height() );
464         }
465     }
466 }
467
468
469 /* This work is derived from Amarok's work under GPLv2+
470     - Mark Kretschmann
471     - Gábor Lehel
472    */
473 #define WLENGTH   80 // px
474 #define WHEIGHT   22  // px
475 #define SOUNDMIN  0   // %
476 #define SOUNDMAX  200 // % OR 400 ?
477
478 SoundSlider::SoundSlider( QWidget *_parent, int _i_step, bool b_hard,
479                           char *psz_colors )
480                         : QAbstractSlider( _parent )
481 {
482     f_step = ( _i_step * 100 ) / AOUT_VOLUME_MAX ;
483     setRange( SOUNDMIN, b_hard ? (2 * SOUNDMAX) : SOUNDMAX  );
484     setMouseTracking( true );
485     b_isSliding = false;
486     b_mouseOutside = true;
487     b_isMuted = false;
488
489     pixOutside = QPixmap( ":/toolbar/volslide-outside" );
490
491     const QPixmap temp( ":/toolbar/volslide-inside" );
492     const QBitmap mask( temp.createHeuristicMask() );
493
494     setFixedSize( pixOutside.size() );
495
496     pixGradient = QPixmap( mask.size() );
497     pixGradient2 = QPixmap( mask.size() );
498
499     /* Gradient building from the preferences */
500     QLinearGradient gradient( paddingL, 2, WLENGTH + paddingL , 2 );
501     QLinearGradient gradient2( paddingL, 2, WLENGTH + paddingL , 2 );
502
503     QStringList colorList = qfu( psz_colors ).split( ";" );
504     free( psz_colors );
505
506     /* Fill with 255 if the list is too short */
507     if( colorList.count() < 12 )
508         for( int i = colorList.count(); i < 12; i++)
509             colorList.append( "255" );
510
511     /* Regular colors */
512 #define c(i) colorList.at(i).toInt()
513 #define add_color(gradient, range, c1, c2, c3) \
514     gradient.setColorAt( range, QColor( c(c1), c(c2), c(c3) ) );
515
516     /* Desaturated colors */
517 #define desaturate(c) c->setHsvF( c->hueF(), 0.2 , 0.5, 1.0 )
518 #define add_desaturated_color(gradient, range, c1, c2, c3) \
519     foo = new QColor( c(c1), c(c2), c(c3) );\
520     desaturate( foo ); gradient.setColorAt( range, *foo );\
521     delete foo;
522
523     /* combine the two helpers */
524 #define add_colors( gradient1, gradient2, range, c1, c2, c3 )\
525     add_color( gradient1, range, c1, c2, c3 ); \
526     add_desaturated_color( gradient2, range, c1, c2, c3 );
527
528     QColor * foo;
529     add_colors( gradient, gradient2, 0.0, 0, 1, 2 );
530     add_colors( gradient, gradient2, 0.45, 3, 4, 5 );
531     add_colors( gradient, gradient2, 0.55, 6, 7, 8 );
532     add_colors( gradient, gradient2, 1.0, 9, 10, 11 );
533
534     QPainter painter( &pixGradient );
535     painter.setPen( Qt::NoPen );
536     painter.setBrush( gradient );
537     painter.drawRect( pixGradient.rect() );
538     painter.end();
539
540     painter.begin( &pixGradient2 );
541     painter.setPen( Qt::NoPen );
542     painter.setBrush( gradient2 );
543     painter.drawRect( pixGradient2.rect() );
544     painter.end();
545
546     pixGradient.setMask( mask );
547     pixGradient2.setMask( mask );
548 }
549
550 void SoundSlider::wheelEvent( QWheelEvent *event )
551 {
552     int newvalue = value() + event->delta() / ( 8 * 15 ) * f_step;
553     setValue( __MIN( __MAX( minimum(), newvalue ), maximum() ) );
554
555     emit sliderReleased();
556     emit sliderMoved( value() );
557 }
558
559 void SoundSlider::mousePressEvent( QMouseEvent *event )
560 {
561     if( event->button() != Qt::RightButton )
562     {
563         /* We enter the sliding mode */
564         b_isSliding = true;
565         i_oldvalue = value();
566         emit sliderPressed();
567         changeValue( event->x() - paddingL );
568         emit sliderMoved( value() );
569     }
570 }
571
572 void SoundSlider::mouseReleaseEvent( QMouseEvent *event )
573 {
574     if( event->button() != Qt::RightButton )
575     {
576         if( !b_mouseOutside && value() != i_oldvalue )
577         {
578             emit sliderReleased();
579             setValue( value() );
580             emit sliderMoved( value() );
581         }
582         b_isSliding = false;
583         b_mouseOutside = false;
584     }
585 }
586
587 void SoundSlider::mouseMoveEvent( QMouseEvent *event )
588 {
589     if( b_isSliding )
590     {
591         QRect rect( paddingL - 15,    -1,
592                     WLENGTH + 15 * 2 , WHEIGHT + 5 );
593         if( !rect.contains( event->pos() ) )
594         { /* We are outside */
595             if ( !b_mouseOutside )
596                 setValue( i_oldvalue );
597             b_mouseOutside = true;
598         }
599         else
600         { /* We are inside */
601             b_mouseOutside = false;
602             changeValue( event->x() - paddingL );
603             emit sliderMoved( value() );
604         }
605     }
606     else
607     {
608         int i = ( ( event->x() - paddingL ) * maximum() + 40 ) / WLENGTH;
609         i = __MIN( __MAX( 0, i ), maximum() );
610         setToolTip( QString("%1  \%" ).arg( i ) );
611     }
612 }
613
614 void SoundSlider::changeValue( int x )
615 {
616     setValue( (x * maximum() + 40 ) / WLENGTH );
617 }
618
619 void SoundSlider::setMuted( bool m )
620 {
621     b_isMuted = m;
622     update();
623 }
624
625 void SoundSlider::paintEvent( QPaintEvent *e )
626 {
627     QPainter painter( this );
628     QPixmap *pixGradient;
629     if (b_isMuted)
630         pixGradient = &this->pixGradient2;
631     else
632         pixGradient = &this->pixGradient;
633
634     const int offset = int( ( WLENGTH * value() + 100 ) / maximum() ) + paddingL;
635
636     const QRectF boundsG( 0, 0, offset , pixGradient->height() );
637     painter.drawPixmap( boundsG, *pixGradient, boundsG );
638
639     const QRectF boundsO( 0, 0, pixOutside.width(), pixOutside.height() );
640     painter.drawPixmap( boundsO, pixOutside, boundsO );
641
642     QColor background = palette().color( QPalette::Active, QPalette::Background );
643     QColor foreground = palette().color( QPalette::Active, QPalette::WindowText );
644     foreground.setHsv( foreground.hue(),
645                     ( background.saturation() + foreground.saturation() ) / 2,
646                     ( background.value() + foreground.value() ) / 2 );
647     painter.setPen( foreground );
648     QFont font; font.setPixelSize( 9 );
649     painter.setFont( font );
650     const QRect rect( 0, 0, 34, 15 );
651     painter.drawText( rect, Qt::AlignRight | Qt::AlignVCenter,
652                       QString::number( value() ) + '%' );
653
654     painter.end();
655     e->accept();
656 }
657