File indexing completed on 2024-05-05 04:49:29

0001 /****************************************************************************************
0002  * Copyright (c) 2003-2009 Mark Kretschmann <kretschmann@kde.org>                       *
0003  * Copyright (c) 2005 Gabor Lehel <illissius@gmail.com>                                 *
0004  * Copyright (c) 2008 Dan Meltzer <parallelgrapefruit@gmail.com>                        *
0005  *                                                                                      *
0006  * This program is free software; you can redistribute it and/or modify it under        *
0007  * the terms of the GNU General Public License as published by the Free Software        *
0008  * Foundation; either version 2 of the License, or (at your option) any later           *
0009  * version.                                                                             *
0010  *                                                                                      *
0011  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
0012  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
0013  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
0014  *                                                                                      *
0015  * You should have received a copy of the GNU General Public License along with         *
0016  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
0017  ****************************************************************************************/
0018 
0019 #include "SliderWidget.h"
0020 
0021 #include <config.h>
0022 
0023 #include "core/support/Amarok.h"
0024 #include "amarokurls/AmarokUrlHandler.h"
0025 #include "amarokconfig.h"
0026 #include "App.h"
0027 #include "BookmarkTriangle.h"
0028 #include "core/support/Debug.h"
0029 #include "EngineController.h"
0030 #include "core/meta/support/MetaUtility.h"
0031 #include "SvgHandler.h"
0032 #include "ProgressWidget.h"
0033 
0034 #include <QIcon>
0035 #include <KLocalizedString>
0036 #include <QStandardPaths>
0037 
0038 #include <QAction>
0039 #include <QContextMenuEvent>
0040 #include <QFontMetrics>
0041 #include <QMenu>
0042 #include <QStyle>
0043 #include <QStyleOption>
0044 #include <QPainter>
0045 
0046 Amarok::Slider::Slider( Qt::Orientation orientation, uint max, QWidget *parent )
0047     : QSlider( orientation, parent )
0048     , m_sliding( false )
0049     , m_outside( false )
0050     , m_prevValue( 0 )
0051     , m_needsResize( true )
0052 {
0053     setMouseTracking( true );
0054     setRange( 0, max );
0055     setAttribute( Qt::WA_NoMousePropagation, true );
0056     setAttribute( Qt::WA_Hover, true );
0057     if ( orientation == Qt::Vertical )
0058     {
0059         setInvertedAppearance( true );
0060         setInvertedControls( true );
0061     }
0062 }
0063 
0064 QRect
0065 Amarok::Slider::sliderHandleRect( const QRect &slider, qreal percent ) const
0066 {
0067     QRect rect;
0068     const bool inverse = ( orientation() == Qt::Horizontal ) ?
0069                          ( invertedAppearance() != (layoutDirection() == Qt::RightToLeft) ) :
0070                          ( !invertedAppearance() );
0071 
0072     if( m_usingCustomStyle)
0073         rect = The::svgHandler()->sliderKnobRect( slider, percent, inverse );
0074     else
0075     {
0076         if ( inverse )
0077             percent = 1.0 - percent;
0078         const int handleSize = style()->pixelMetric( QStyle::PM_SliderControlThickness );
0079         rect = QRect( 0, 0, handleSize, handleSize );
0080         rect.moveTo( slider.x() + qRound( ( slider.width() - handleSize ) * percent ), slider.y() + 1 );
0081     }
0082 
0083     return rect;
0084 }
0085 
0086 void
0087 Amarok::Slider::wheelEvent( QWheelEvent *e )
0088 {
0089     DEBUG_BLOCK
0090 
0091     if( orientation() == Qt::Vertical )
0092     {
0093         // Will be handled by the parent widget
0094         e->ignore();
0095         return;
0096     }
0097 
0098     // Position Slider (horizontal)
0099     // only used for progress slider now!
0100     int step = e->angleDelta().y() * 24; //FIXME: check if .x() must be used
0101     int nval = value() + step;
0102     nval = qMax(nval, minimum());
0103     nval = qMin(nval, maximum());
0104 
0105     QSlider::setValue( nval );
0106 
0107     Q_EMIT sliderReleased( value() );
0108 }
0109 
0110 void
0111 Amarok::Slider::mouseMoveEvent( QMouseEvent *e )
0112 {
0113     if ( m_sliding )
0114     {
0115         //feels better, but using set value of 20 is bad of course
0116         QRect rect( -20, -20, width()+40, height()+40 );
0117 
0118         if ( orientation() == Qt::Horizontal && !rect.contains( e->pos() ) )
0119         {
0120             if ( !m_outside )
0121             {
0122                 QSlider::setValue( m_prevValue );
0123                 //if mouse released outside of slider, Q_EMIT sliderMoved to previous value
0124                 Q_EMIT sliderMoved( m_prevValue );
0125             }
0126             m_outside = true;
0127         }
0128         else
0129         {
0130             m_outside = false;
0131             slideEvent( e );
0132             Q_EMIT sliderMoved( value() );
0133         }
0134     }
0135     else
0136         QSlider::mouseMoveEvent( e );
0137 }
0138 
0139 void
0140 Amarok::Slider::slideEvent( QMouseEvent *e )
0141 {
0142     QRect knob;
0143     if ( maximum() > minimum() )
0144         knob = sliderHandleRect( rect(), ((qreal)value()) / ( maximum() - minimum() ) );
0145 
0146     int position;
0147     int span;
0148 
0149     if( orientation() == Qt::Horizontal )
0150     {
0151         position = e->pos().x() - knob.width() / 2;
0152         span = width() - knob.width();
0153     }
0154     else
0155     {
0156         position = e->pos().y() - knob.height() / 2;
0157         span = height() - knob.height();
0158     }
0159 
0160     const bool inverse = ( orientation() == Qt::Horizontal ) ?
0161                          ( invertedAppearance() != (layoutDirection() == Qt::RightToLeft) ) :
0162                          ( !invertedAppearance() );
0163     const int val = QStyle::sliderValueFromPosition( minimum(), maximum(), position, span, inverse );
0164     QSlider::setValue( val );
0165 }
0166 
0167 void
0168 Amarok::Slider::mousePressEvent( QMouseEvent *e )
0169 {
0170     m_sliding   = true;
0171     m_prevValue = value();
0172 
0173     QRect knob;
0174     if ( maximum() > minimum() )
0175         knob = sliderHandleRect( rect(), ((qreal)value()) / ( maximum() - minimum() ) );
0176     if ( !knob.contains( e->pos() ) )
0177         mouseMoveEvent( e );
0178 }
0179 
0180 void
0181 Amarok::Slider::mouseReleaseEvent( QMouseEvent* )
0182 {
0183     if( !m_outside && value() != m_prevValue )
0184        Q_EMIT sliderReleased( value() );
0185 
0186     m_sliding = false;
0187     m_outside = false;
0188 }
0189 
0190 void
0191 Amarok::Slider::setValue( int newValue )
0192 {
0193     //don't adjust the slider while the user is dragging it!
0194     if ( !m_sliding || m_outside )
0195         QSlider::setValue( newValue );
0196     else
0197         m_prevValue = newValue;
0198 }
0199 
0200 void Amarok::Slider::paintCustomSlider( QPainter *p, bool paintMoodbar )
0201 {
0202     qreal percent = 0.0;
0203     if ( maximum() > minimum() )
0204         percent = ((qreal)value()) / ( maximum() - minimum() );
0205     QStyleOptionSlider opt;
0206     initStyleOption( &opt );
0207     if ( m_sliding ||
0208         ( underMouse() && sliderHandleRect( rect(), percent ).contains( mapFromGlobal(QCursor::pos()) ) ) )
0209     {
0210         opt.activeSubControls |= QStyle::SC_SliderHandle;
0211     }
0212     The::svgHandler()->paintCustomSlider( p, &opt, percent, paintMoodbar );
0213 }
0214 
0215 
0216 //////////////////////////////////////////////////////////////////////////////////////////
0217 /// CLASS VolumeSlider
0218 //////////////////////////////////////////////////////////////////////////////////////////
0219 
0220 Amarok::VolumeSlider::VolumeSlider( uint max, QWidget *parent, bool customStyle )
0221     : Amarok::Slider( customStyle ? Qt::Horizontal : Qt::Vertical, max, parent )
0222 {
0223     m_usingCustomStyle = customStyle;
0224     setFocusPolicy( Qt::NoFocus );
0225     setInvertedAppearance( false );
0226     setInvertedControls( false );
0227 }
0228 
0229 void
0230 Amarok::VolumeSlider::mousePressEvent( QMouseEvent *e )
0231 {
0232     if( e->button() != Qt::RightButton )
0233     {
0234         Amarok::Slider::mousePressEvent( e );
0235         slideEvent( e );
0236     }
0237 }
0238 
0239 void
0240 Amarok::VolumeSlider::contextMenuEvent( QContextMenuEvent *e )
0241 {
0242     QMenu menu;
0243     menu.setTitle(   i18n( "Volume" ) );
0244     menu.addAction(  i18n(   "100%" ) )->setData( 100 );
0245     menu.addAction(  i18n(    "80%" ) )->setData(  80 );
0246     menu.addAction(  i18n(    "60%" ) )->setData(  60 );
0247     menu.addAction(  i18n(    "40%" ) )->setData(  40 );
0248     menu.addAction(  i18n(    "20%" ) )->setData(  20 );
0249     menu.addAction(  i18n(     "0%" ) )->setData(   0 );
0250 
0251     /*
0252     // TODO: Phonon
0253     menu.addSeparator();
0254     menu.addAction( QIcon::fromTheme( "view-media-equalizer-amarok" ), i18n( "&Equalizer" ), qApp, &QCoreApplication::slotConfigEqualizer()) )->setData( -1 );
0255     */
0256 
0257     QAction* a = menu.exec( mapToGlobal( e->pos() ) );
0258     if( a )
0259     {
0260         const int n = a->data().toInt();
0261         if( n >= 0 )
0262         {
0263             QSlider::setValue( n );
0264             Q_EMIT sliderReleased( n );
0265         }
0266     }
0267 }
0268 
0269 void
0270 Amarok::VolumeSlider::wheelEvent( QWheelEvent *e )
0271 {
0272     const uint step = e->angleDelta().y() / Amarok::VOLUME_SENSITIVITY; //FIXME: check if .x() must be used
0273     QSlider::setValue( QSlider::value() + step );
0274 
0275     Q_EMIT sliderReleased( value() );
0276 }
0277 
0278 void
0279 Amarok::VolumeSlider::paintEvent( QPaintEvent *event )
0280 {
0281     if( m_usingCustomStyle )
0282     {
0283         QPainter p( this );
0284         paintCustomSlider( &p );
0285         p.end();
0286         return;
0287     }
0288 
0289     QSlider::paintEvent( event );
0290 }
0291 
0292 
0293 //////////////////////////////////////////////////////////////////////////////////////////
0294 ////////////////////////////////// TIMESLIDER ////////////////////////////////////////////
0295 //////////////////////////////////////////////////////////////////////////////////////////
0296 
0297 Amarok::TimeSlider::TimeSlider( QWidget *parent )
0298     : Amarok::Slider( Qt::Horizontal, 0, parent )
0299     , m_triangles()
0300     , m_knobX( 0.0 )
0301 {
0302     m_usingCustomStyle = true;
0303     setFocusPolicy( Qt::NoFocus );
0304 }
0305 
0306 void
0307 Amarok::TimeSlider::setSliderValue( int value )
0308 {
0309     Amarok::Slider::setValue( value );
0310 }
0311 
0312 void
0313 Amarok::TimeSlider::paintEvent( QPaintEvent *pe )
0314 {
0315     QPainter p( this );
0316     p.setClipRegion( pe->region() );
0317     paintCustomSlider( &p, AmarokConfig::showMoodbarInSlider() );
0318     p.end();
0319 
0320 }
0321 
0322 void Amarok::TimeSlider::resizeEvent(QResizeEvent * event)
0323 {
0324     Amarok::Slider::resizeEvent( event );
0325     The::amarokUrlHandler()->updateTimecodes();
0326 }
0327 
0328 void Amarok::TimeSlider::sliderChange( SliderChange change )
0329 {
0330     if ( change == SliderValueChange || change == SliderRangeChange )
0331     {
0332         int oldKnobX = m_knobX;
0333         qreal percent = 0.0;
0334         if ( maximum() > minimum() )
0335             percent = ((qreal)value()) / ( maximum() - minimum() );
0336         QRect knob = sliderHandleRect( rect(), percent );
0337         m_knobX = knob.x();
0338 
0339         if (oldKnobX < m_knobX)
0340             update( oldKnobX, knob.y(), knob.right() + 1 - oldKnobX, knob.height() );
0341         else if (oldKnobX > m_knobX)
0342             update( m_knobX, knob.y(), oldKnobX + knob.width(), knob.height() );
0343     }
0344     else
0345         Amarok::Slider::sliderChange( change ); // calls update()
0346 }
0347 
0348 void Amarok::TimeSlider::drawTriangle( const QString& name, int milliSeconds, bool showPopup )
0349 {
0350     DEBUG_BLOCK
0351     int sliderHeight = height() - ( s_sliderInsertY * 2 );
0352     int sliderLeftWidth = sliderHeight / 3;
0353 
0354     // This mess converts the # of seconds into the pixel width value where the triangle should be drawn
0355     int x_pos = ( ( ( double ) milliSeconds - ( double ) minimum() ) / ( maximum() - minimum() ) ) * ( width() - ( sliderLeftWidth + sliderLeftWidth + s_sliderInsertX * 2 ) );
0356     debug() << "drawing triangle at " << x_pos;
0357     BookmarkTriangle * tri = new BookmarkTriangle( this, milliSeconds, name, width(), showPopup );
0358     connect( tri, &BookmarkTriangle::clicked, this, &TimeSlider::slotTriangleClicked );
0359     connect( tri, &BookmarkTriangle::focused, this, &TimeSlider::slotTriangleFocused );
0360     m_triangles << tri;
0361     tri->setGeometry( x_pos + 6 /* to center the point */, 1 /*y*/, 11, 11 ); // 6 = hard coded border width
0362     tri->show();
0363 }
0364 
0365 void Amarok::TimeSlider::slotTriangleClicked( int seconds )
0366 {
0367     Q_EMIT sliderReleased( seconds );
0368 }
0369 
0370 void Amarok::TimeSlider::slotTriangleFocused( int seconds )
0371 {
0372     QList<BookmarkTriangle *>::iterator i;
0373     for( i = m_triangles.begin(); i != m_triangles.end(); ++i ){
0374          if( (*i)->getTimeValue() != seconds )
0375              (*i)->hidePopup();
0376     }
0377 }
0378 
0379 void Amarok::TimeSlider::clearTriangles()
0380 {
0381     QList<BookmarkTriangle *>::iterator i;
0382     for( i = m_triangles.begin(); i != m_triangles.end(); ++i ){
0383       (*i)->deleteLater();
0384     }
0385     m_triangles.clear();
0386 }
0387 
0388 void Amarok::TimeSlider::mousePressEvent( QMouseEvent *event )
0389 {
0390     if( !The::engineController()->isSeekable() )
0391         return; // Eat the event,, it's not possible to seek
0392     Amarok::Slider::mousePressEvent( event );
0393 }
0394 
0395 bool Amarok::TimeSlider::event( QEvent * event )
0396 {
0397     if( event->type() == QEvent::ToolTip )
0398     {
0399         // Make a QHelpEvent out of this
0400         QHelpEvent * helpEvent = dynamic_cast<QHelpEvent *>( event );
0401         if( helpEvent )
0402         {
0403 
0404             //figure out "percentage" of the track length that the mouse is hovering over the slider
0405             qreal percentage = (qreal) helpEvent->x() / (qreal) width();
0406             long trackLength = The::engineController()->trackLength();
0407             int trackPosition = trackLength * percentage;
0408 
0409             // Update tooltip to show the track position under the cursor
0410             setToolTip( i18nc( "Tooltip shown when the mouse is over the progress slider, representing the position in the currently playing track that Amarok will seek to if you click the mouse. Keep it concise.", "Jump to: %1", Meta::msToPrettyTime( trackPosition ) ) );
0411         }
0412     }
0413 
0414     return QWidget::event( event );
0415 }
0416 
0417