Not crap after all...
[amarok.git] / src / sliderwidget.cpp
blobe71b67919246bf285061de0d1f7379943c056515
1 /***************************************************************************
2 amarokslider.cpp - description
3 -------------------
4 begin : Dec 15 2003
5 copyright : (C) 2003 by Mark Kretschmann
6 email : markey@web.de
7 copyright : (C) 2005 by Gábor Lehel
8 email : illissius@gmail.com
9 ***************************************************************************/
11 /***************************************************************************
12 * *
13 * This program is free software; you can redistribute it and/or modify *
14 * it under the terms of the GNU General Public License as published by *
15 * the Free Software Foundation; either version 2 of the License, or *
16 * (at your option) any later version. *
17 * *
18 ***************************************************************************/
20 #include "config-amarok.h"
22 #include "amarok.h"
23 #include "amarokconfig.h"
24 #include "app.h"
25 #include "debug.h"
26 #include "enginecontroller.h"
27 #include "sliderwidget.h"
29 #include <QBitmap>
30 #include <QBrush>
31 #include <QContextMenuEvent>
32 #include <QImage>
33 #include <QMenu>
34 #include <QPainter>
35 #include <QStyle>
36 #include <QStyleOptionComplex>
37 #include <QTimer>
39 #include <kicon.h>
40 #include <kimageeffect.h>
41 #include <klocale.h>
42 #include <kmenu.h>
43 #include <kpixmapeffect.h>
44 #include <kstandarddirs.h>
46 Amarok::Slider::Slider( Qt::Orientation orientation, QWidget *parent, uint max )
47 : QSlider( orientation, parent )
48 , m_sliding( false )
49 , m_outside( false )
50 , m_prevValue( 0 )
52 setRange( 0, max );
55 void
56 Amarok::Slider::wheelEvent( QWheelEvent *e )
58 if( orientation() == Qt::Vertical ) {
59 // Will be handled by the parent widget
60 e->ignore();
61 return;
64 // Position Slider (horizontal)
65 int step = e->delta() * 1500 / 18;
66 int nval = QSlider::value() + step;
67 nval = qMax(nval, minimum());
68 nval = qMin(nval, maximum());
70 QSlider::setValue( nval );
72 emit sliderReleased( value() );
75 void
76 Amarok::Slider::mouseMoveEvent( QMouseEvent *e )
78 if ( m_sliding )
80 //feels better, but using set value of 20 is bad of course
81 QRect rect( -20, -20, width()+40, height()+40 );
83 if ( orientation() == Qt::Horizontal && !rect.contains( e->pos() ) ) {
84 if ( !m_outside )
85 QSlider::setValue( m_prevValue );
86 m_outside = true;
87 } else {
88 m_outside = false;
89 slideEvent( e );
90 emit sliderMoved( value() );
93 else QSlider::mouseMoveEvent( e );
96 void
97 Amarok::Slider::slideEvent( QMouseEvent *e )
99 QStyleOptionComplex complex;
100 QRect rect = style()->subControlRect( QStyle::CC_Slider, &complex , QStyle::SC_SliderHandle, this );
102 QSlider::setValue( orientation() == Qt::Horizontal
103 ? ((QApplication::isRightToLeft())
104 ? QStyle::sliderValueFromPosition( minimum(), maximum(), width() - (e->pos().x() - rect.width()/2), width() + rect.width() )
105 : QStyle::sliderValueFromPosition( minimum(), maximum(), e->pos().x() - rect.width()/2, width() - rect.width() ) )
106 : QStyle::sliderValueFromPosition( minimum(), maximum(), e->pos().y() - rect.height()/2, height() - rect.height() ) );
109 void
110 Amarok::Slider::mousePressEvent( QMouseEvent *e )
112 QStyleOptionComplex complex;
113 QRect rect = style()->subControlRect( QStyle::CC_Slider, &complex , QStyle::SC_SliderHandle, this );
114 m_sliding = true;
115 m_prevValue = QSlider::value();
117 if ( !rect.contains( e->pos() ) )
118 mouseMoveEvent( e );
121 void
122 Amarok::Slider::mouseReleaseEvent( QMouseEvent* )
124 if( !m_outside && QSlider::value() != m_prevValue )
125 emit sliderReleased( value() );
127 m_sliding = false;
128 m_outside = false;
131 void
132 Amarok::Slider::setValue( int newValue )
134 //don't adjust the slider while the user is dragging it!
136 if ( !m_sliding || m_outside )
137 QSlider::setValue( adjustValue( newValue ) );
138 else
139 m_prevValue = newValue;
143 //////////////////////////////////////////////////////////////////////////////////////////
144 /// CLASS PrettySlider
145 //////////////////////////////////////////////////////////////////////////////////////////
147 #define THICKNESS 7
148 #define MARGIN 3
150 Amarok::PrettySlider::PrettySlider( Qt::Orientation orientation, SliderMode mode,
151 QWidget *parent, uint max )
152 : Amarok::Slider( orientation, parent, max )
153 , m_mode( mode )
154 , m_showingMoodbar( false )
156 if( m_mode == Pretty)
158 setWindowFlags( Qt::WNoAutoErase );
159 setFocusPolicy( Qt::NoFocus );
162 // We only have to connect this *once*, since our MetaBundle
163 // doesn't get destroyed until we do.
164 connect( &m_bundle.moodbar(), SIGNAL( jobEvent( int ) ),
165 SLOT( moodbarJobEvent( int ) ) );
167 // We want to know if we should reset our moodbar data
168 connect( App::instance(), SIGNAL( moodbarPrefs( bool, bool, int, bool ) ),
169 SLOT( slotMoodbarPrefs( bool, bool, int, bool ) ) );
173 void
174 Amarok::PrettySlider::mousePressEvent( QMouseEvent *e )
176 Amarok::Slider::mousePressEvent( e );
178 slideEvent( e );
181 void
182 Amarok::PrettySlider::slideEvent( QMouseEvent *e )
184 if( m_mode == Pretty || m_showingMoodbar )
185 QSlider::setValue( orientation() == Qt::Horizontal
186 ? QStyle::sliderValueFromPosition( minimum(), maximum(), e->pos().x(), width()-2 )
187 : QStyle::sliderValueFromPosition( minimum(), maximum(), e->pos().y(), height()-2 ) );
188 else
189 Amarok::Slider::slideEvent( e );
192 namespace Amarok {
193 namespace ColorScheme {
194 extern QColor Background;
195 extern QColor Foreground;
199 void
200 Amarok::PrettySlider::paintEvent( QPaintEvent *e )
202 const int w = orientation() == Qt::Horizontal ? width() : height();
203 const int pos = int( double( w-2 ) / maximum() * Slider::value() );
204 int h = THICKNESS;
206 m_showingMoodbar = ( !m_bundle.url().isEmpty() &&
207 m_bundle.moodbar().dataExists() &&
208 AmarokConfig::showMoodbar() );
209 QPixmap mood;
210 if( m_showingMoodbar )
212 if( m_mode == Normal )
213 h = (orientation() == Qt::Vertical ? width() : height()) - 2*MARGIN;
214 mood = m_bundle.moodbar().draw( w, h );
216 // If we're a Normal PrettySlider and we have no moodbar,
217 // emulate the behavior of Slider
218 else if( m_mode == Normal )
220 Amarok::Slider::paintEvent( e );
221 return;
224 QPainter p( this );
226 if ( orientation() == Qt::Vertical )
228 p.translate( 0, height()-1 );
229 p.rotate( -90 ); //90 degrees clockwise
232 if( !m_showingMoodbar )
234 p.translate( 0, MARGIN );
235 p.setPen( Amarok::ColorScheme::Foreground );
236 p.fillRect( 0, 0, pos, h, QColor( Amarok::ColorScheme::Background ) );
237 p.drawRect( 0, 0, w, h );
238 p.translate( 0, -MARGIN );
240 else
242 p.translate( 0, MARGIN );
243 p.drawPixmap( 0, 0, mood );
244 p.setPen( Amarok::ColorScheme::Foreground );
245 p.drawRect( 0, 0, w, h );
246 p.translate( 0, -MARGIN );
248 // Larger triangle for the moodbar
251 //<Triangle Marker>
252 if( m_mode == Pretty )
254 QPolygon pa( 3 );
255 pa.setPoint( 0, pos - 3, 1 );
256 pa.setPoint( 1, pos + 3, 1 );
257 pa.setPoint( 2, pos, 5 );
258 p.setBrush( paletteForegroundColor() );
259 p.drawConvexPolygon( pa );
262 else if( m_mode == Normal )
264 QPolygon pa( 3 );
265 pa.setPoint( 0, pos - 5, 1 );
266 pa.setPoint( 1, pos + 5, 1 );
267 pa.setPoint( 2, pos, 9 );
268 p.setBrush( paletteForegroundColor() );
269 p.drawConvexPolygon( pa );
271 //</Triangle Marker>
275 // This gets called when the moodbar job starts or finishes
276 void
277 Amarok::PrettySlider::moodbarJobEvent( int newState )
279 if( newState == Moodbar::JobStateSucceeded )
281 debug() << "moodbarJobEvent: new moodbar data" << endl;
282 update();
286 // This gets called when the user presses "Ok" or "Apply" in the
287 // config dialog. Reload our moodbar data, in case it was
288 // permanently disabled before because the moodbar was disabled.
289 void
290 Amarok::PrettySlider::slotMoodbarPrefs( bool show, bool moodier, int alter, bool withMusic )
292 (void) moodier; (void) alter; (void) withMusic;
294 if( show )
296 m_bundle.moodbar().reset();
297 if( !m_bundle.moodbar().dataExists() )
298 m_bundle.moodbar().load();
299 update();
306 // This is called when the track changes / stops / starts
307 void
308 Amarok::PrettySlider::newBundle( const MetaBundle &bundle )
310 m_bundle = bundle;
312 // This is the easiest way to tell if the bundle refers
313 // to a real track, or if we're STOP'd.
314 if( m_bundle.url().isEmpty() )
315 return;
317 // It's a real track; get the moodbar data if it's not there
318 if( !m_bundle.moodbar().dataExists() )
319 m_bundle.moodbar().load();
320 else
321 update();
326 #if 0
327 /** these functions aren't required in our fixed size world,
328 but they may become useful one day **/
330 QSize
331 Amarok::PrettySlider::minimumSizeHint() const
333 return sizeHint();
336 QSize
337 Amarok::PrettySlider::sizeHint() const
339 constPolish();
341 return (orientation() == Qt::Horizontal
342 ? QSize( maximum(), THICKNESS + MARGIN )
343 : QSize( THICKNESS + MARGIN, maximum() )).expandedTo( QApplit ication::globalStrut() );
345 #endif
347 //////////////////////////////////////////////////////////////////////////////////////////
348 /// CLASS VolumeSlider
349 //////////////////////////////////////////////////////////////////////////////////////////
351 Amarok::VolumeSlider::VolumeSlider( QWidget *parent, uint max )
352 : Amarok::Slider( Qt::Horizontal, parent, max )
353 , m_animCount( 0 )
354 , m_animTimer( new QTimer( this ) )
355 , m_pixmapInset( QPixmap( KStandardDirs::locate( "data","amarok/images/volumeslider-inset.png" ) ) )
357 setFocusPolicy( Qt::NoFocus );
359 // BEGIN Calculate handle animation pixmaps for mouse-over effect
360 QImage pixmapHandle ( KStandardDirs::locate( "data","amarok/images/volumeslider-handle.png" ) );
361 QImage pixmapHandleGlow( KStandardDirs::locate( "data","amarok/images/volumeslider-handle_glow.png" ) );
363 float opacity = 0.0;
364 const float step = 1.0 / ANIM_MAX;
365 QImage dst;
366 for ( int i = 0; i < ANIM_MAX; ++i ) {
367 dst = pixmapHandle;
368 KImageEffect::blend( pixmapHandleGlow, dst, opacity );
369 m_handlePixmaps.append( QPixmap( dst ) );
370 opacity += step;
372 // END
374 generateGradient();
376 setMinimumWidth( m_pixmapInset.width() );
377 setMinimumHeight( m_pixmapInset.height() );
379 connect( m_animTimer, SIGNAL( timeout() ), this, SLOT( slotAnimTimer() ) );
382 void
383 Amarok::VolumeSlider::generateGradient()
385 //QImage temp( KStandardDirs::locate( "data","amarok/images/volumeslider-gradient.png" ) );
386 //KIconEffect::colorize( temp, colorGroup().highlight(), 1.0 );
388 const QPixmap temp( KStandardDirs::locate( "data","amarok/images/volumeslider-gradient.png" ) );
389 const QBitmap mask( temp.createHeuristicMask() );
391 m_pixmapGradient = QPixmap( m_pixmapInset.size() );
392 KPixmapEffect::gradient( m_pixmapGradient, colorGroup().alternateBase(), colorGroup().highlight(),
393 KPixmapEffect::HorizontalGradient );
394 m_pixmapGradient.setMask( mask );
397 void
398 Amarok::VolumeSlider::slotAnimTimer() //SLOT
400 if ( m_animEnter ) {
401 m_animCount++;
402 repaint( );
403 if ( m_animCount == ANIM_MAX - 1 )
404 m_animTimer->stop();
405 } else {
406 m_animCount--;
407 repaint();
408 if ( m_animCount == 0 )
409 m_animTimer->stop();
413 void
414 Amarok::VolumeSlider::mousePressEvent( QMouseEvent *e )
416 if( e->button() != Qt::RightButton ) {
417 Amarok::Slider::mousePressEvent( e );
418 slideEvent( e );
422 void
423 Amarok::VolumeSlider::contextMenuEvent( QContextMenuEvent *e )
425 QMenu menu;
426 menu.setTitle( i18n( "Volume" ) );
427 menu.addAction( i18n( "100%" ) )->setData( 100 );
428 menu.addAction( i18n( "80%" ) )->setData( 80 );
429 menu.addAction( i18n( "60%" ) )->setData( 60 );
430 menu.addAction( i18n( "40%" ) )->setData( 40 );
431 menu.addAction( i18n( "20%" ) )->setData( 20 );
432 menu.addAction( i18n( "0%" ) )->setData( 0 );
434 if( EngineController::hasEngineProperty( "HasEqualizer" ) )
436 menu.addSeparator();
437 menu.addAction( KIcon( "equalizer" ), i18n( "&Equalizer" ), kapp, SLOT( slotConfigEqualizer() ) )
438 ->setData( -1 );
441 QAction* a = menu.exec( mapToGlobal( e->pos() ) );
442 if( a ) {
443 const int n = a->data().toInt();
444 if( n >= 0 )
446 QSlider::setValue( n );
447 emit sliderReleased( n );
452 void
453 Amarok::VolumeSlider::slideEvent( QMouseEvent *e )
455 QSlider::setValue( QStyle::sliderValueFromPosition( minimum(), maximum(), e->pos().x(), width()-2 ) );
458 void
459 Amarok::VolumeSlider::wheelEvent( QWheelEvent *e )
461 const uint step = e->delta() / Amarok::VOLUME_SENSITIVITY;
462 QSlider::setValue( QSlider::value() + step );
464 emit sliderReleased( value() );
467 void
468 Amarok::VolumeSlider::paintEvent( QPaintEvent * )
470 QPainter p( this );
472 const int padding = 7;
473 const int offset = int( double( ( width() - 2 * padding ) * value() ) / maximum() );
475 const QRectF boundsG( 0, 0, offset + padding, m_pixmapGradient.height() );
476 p.drawPixmap( boundsG, m_pixmapGradient, boundsG );
478 const QRectF boundsI( 0, 0, m_pixmapInset.width(), m_pixmapInset.height() );
479 p.drawPixmap( boundsI, m_pixmapInset, boundsI );
481 const QRectF targetBounds( offset - m_handlePixmaps[0].width() / 2 + padding, 0, m_handlePixmaps[m_animCount].width(), m_handlePixmaps[m_animCount].height() );
482 const QRectF srcBounds( 0, 0, m_handlePixmaps[m_animCount].width(), m_handlePixmaps[m_animCount].height() );
483 p.drawPixmap( targetBounds, m_handlePixmaps[m_animCount], srcBounds );
485 // Draw percentage number
486 p.setPen( palette().color( QPalette::Disabled, QColorGroup::Text ).dark() );
487 QFont font;
488 font.setPixelSize( 9 );
489 p.setFont( font );
490 const QRect rect( 0, 0, 34, 15 );
491 p.drawText( rect, Qt::AlignRight | Qt::AlignVCenter, QString::number( value() ) + '%' );
494 void
495 Amarok::VolumeSlider::enterEvent( QEvent* )
497 m_animEnter = true;
498 m_animCount = 0;
500 m_animTimer->start( ANIM_INTERVAL );
503 void
504 Amarok::VolumeSlider::leaveEvent( QEvent* )
506 // This can happen if you enter and leave the widget quickly
507 if ( m_animCount == 0 )
508 m_animCount = 1;
510 m_animEnter = false;
511 m_animTimer->start( ANIM_INTERVAL );
514 void
515 Amarok::VolumeSlider::paletteChange( const QPalette& )
517 generateGradient();
520 #include "sliderwidget.moc"