Lots of work on album grouping in the playlist. Now handles ragging around stuff...
[amarok.git] / src / systray.cpp
blob9e38fd84ddc3793b7c70d71526dc7d9ca64123a6
1 //
2 // AmarokSystray
3 //
4 // Contributors: Stanislav Karchebny <berkus@users.sf.net>, (C) 2003
5 // berkus, mxcl, eros, eean
6 //
7 // Copyright: like rest of Amarok
8 //
10 #include "systray.h"
12 #include "amarok.h"
13 #include "amarokconfig.h"
14 #include "enginecontroller.h"
15 #include "meta/meta.h"
17 #include <KAction>
18 #include <KApplication>
19 #include <KIconEffect>
20 #include <KMenu>
21 #include <KStandardDirs>
23 #include <QEvent>
24 #include <QImage>
25 #include <QMouseEvent>
26 #include <QPixmap>
27 #include <QTimerEvent>
30 namespace Amarok
32 static QImage
33 loadOverlay( const char *iconName )
35 return QImage( KStandardDirs::locate( "data", QString( "amarok/images/b_%1.png" ).arg( iconName ) ), "PNG" )
36 .scaled( 10, 10, Qt::IgnoreAspectRatio, Qt::SmoothTransformation );
41 Amarok::TrayIcon::TrayIcon( QWidget *playerWidget )
42 : KSystemTrayIcon( playerWidget )
43 , EngineObserver( EngineController::instance() )
44 , trackLength( 0 )
45 , mergeLevel( -1 )
46 , overlay( 0 )
47 , blinkTimerID( 0 )
48 , overlayVisible( false )
49 , m_lastFmMode( false )
51 KActionCollection* const ac = Amarok::actionCollection();
53 //setAcceptDrops( true );
55 contextMenu()->addAction( ac->action( "prev" ) );
56 contextMenu()->addAction( ac->action( "play_pause" ) );
57 contextMenu()->addAction( ac->action( "play_pause" ) );
58 contextMenu()->addAction( ac->action( "next" ) );
60 //seems to be necessary
61 QAction *quit = actionCollection()->action( "file_quit" );
62 quit->disconnect();
63 connect( quit, SIGNAL(activated()), kapp, SLOT(quit()) );
65 baseIcon = KSystemTrayIcon::loadIcon( "amarok" );
66 playOverlay = Amarok::loadOverlay( "play" );
67 pauseOverlay = Amarok::loadOverlay( "pause" );
68 overlayVisible = false;
70 //paintIcon();
71 setIcon( baseIcon );
74 bool
75 Amarok::TrayIcon::event( QEvent *e )
77 switch( e->type() )
79 case QEvent::Drop:
80 case QEvent::Wheel:
81 case QEvent::DragEnter:
82 //return Amarok::genericEventHandler( this, e );
84 case QEvent::Timer:
85 if( static_cast<QTimerEvent*>(e)->timerId() != blinkTimerID )
86 return KSystemTrayIcon::event( e );
88 // if we're playing, blink icon
89 if ( overlay == &playOverlay )
91 overlayVisible = !overlayVisible;
92 paintIcon( mergeLevel, true );
95 return true;
97 case QEvent::MouseButtonPress:
98 if( static_cast<QMouseEvent*>(e)->button() == Qt::MidButton )
100 EngineController::instance()->playPause();
102 return true;
105 //else FALL THROUGH
107 default:
108 return KSystemTrayIcon::event( e );
112 void
113 Amarok::TrayIcon::engineStateChanged( Engine::State state, Engine::State /*oldState*/ )
115 // stop timer
116 if ( blinkTimerID )
118 killTimer( blinkTimerID );
119 blinkTimerID = 0;
121 // draw overlay
122 overlayVisible = true;
124 // draw the right overlay for each state
125 switch( state )
127 case Engine::Paused:
128 overlay = &pauseOverlay;
129 paintIcon( mergeLevel, true );
130 break;
132 case Engine::Playing:
133 overlay = &playOverlay;
134 if( AmarokConfig::animateTrayIcon() )
135 blinkTimerID = startTimer( 1500 ); // start 'blink' timer
137 paintIcon( mergeLevel, true ); // repaint the icon
138 break;
140 case Engine::Empty:
141 overlayVisible = false;
142 paintIcon( -1, true ); // repaint the icon
143 // fall through to default:
144 default:
145 setLastFm( false );
149 void
150 Amarok::TrayIcon::engineNewMetaData( const QHash<qint64, QString> &newMetaData, bool trackChanged )
152 Q_UNUSED( trackChanged )
153 Q_UNUSED( newMetaData )
154 Meta::TrackPtr track = EngineController::instance()->currentTrack();
155 if( !track )
156 return;
157 trackLength = track->length() * 1000;
158 setLastFm( track->type() == "stream/lastfm" );
161 void
162 Amarok::TrayIcon::engineTrackPositionChanged( long position, bool /*userSeek*/ )
165 mergeLevel = trackLength ? ((baseIcon.height() + 1) * position) / trackLength : -1;
166 paintIcon( mergeLevel );
171 void
172 Amarok::TrayIcon::paletteChange( const QPalette & op )
175 if ( palette().active().highlight() == op.active().highlight() || alternateIcon.isNull() )
176 return;
178 alternateIcon.resize( 0, 0 );
179 paintIcon( mergeLevel, true );
183 void
184 Amarok::TrayIcon::paintIcon( int mergePixels, bool force )
186 // skip redrawing the same pixmap
187 static int mergePixelsCache = 0;
188 if ( mergePixels == mergePixelsCache && !force )
189 return;
190 mergePixelsCache = mergePixels;
192 if ( mergePixels < 0 ) {}
193 //return blendOverlay( baseIcon );
195 // make up the grayed icon
196 if ( grayedIcon.isNull() )
198 //QImage tmpTrayIcon = baseIcon.convertToImage();
199 //KIconEffect::semiTransparent( tmpTrayIcon );
200 //grayedIcon = tmpTrayIcon;
203 // make up the alternate icon (use hilight color but more saturated)
204 if ( alternateIcon.isNull() )
206 #if 0
207 QImage tmpTrayIcon = baseIcon.convertToImage();
208 // eros: this looks cool with dark red blue or green but sucks with
209 // other colors (such as kde default's pale pink..). maybe the effect
210 // or the blended color has to be changed..
211 QColor saturatedColor = palette().active().highlight();
212 int hue, sat, value;
213 saturatedColor.getHsv( &hue, &sat, &value );
214 saturatedColor.setHsv( hue, sat > 200 ? 200 : sat, value < 100 ? 100 : value );
215 KIconEffect::colorize( tmpTrayIcon, saturatedColor/* Qt::blue */, 0.9 );
216 alternateIcon = tmpTrayIcon;
217 #endif
220 if ( mergePixels >= alternateIcon.height() ) {}
221 //return blendOverlay( grayedIcon );
222 if ( mergePixels == 0 ) {}
223 //return blendOverlay( alternateIcon );
225 // mix [ grayed <-> colored ] icons
226 QPixmap tmpTrayPixmap = alternateIcon;
227 copyBlt( &tmpTrayPixmap, 0,0, &grayedIcon, 0,0,
228 alternateIcon.width(), mergePixels>0 ? mergePixels-1 : 0 );
229 blendOverlay( tmpTrayPixmap );
232 void
233 Amarok::TrayIcon::blendOverlay( QPixmap &sourcePixmap )
235 #if 0
236 if ( !overlayVisible || !overlay || overlay->isNull() )
237 return setPixmap( sourcePixmap ); // @since 3.2
239 // here comes the tricky part.. no kdefx functions are helping here.. :-(
240 // we have to blend pixmaps with different sizes (blending will be done in
241 // the bottom-left corner of source pixmap with a smaller overlay pixmap)
242 int opW = overlay->width(),
243 opH = overlay->height(),
244 opX = 1,
245 opY = sourcePixmap.height() - opH;
247 // get the rectangle where blending will take place
248 QPixmap sourceCropped( opW, opH, sourcePixmap.depth() );
249 copyBlt( &sourceCropped, 0,0, &sourcePixmap, opX,opY, opW,opH );
251 //speculative fix for a bactrace we received
252 //crash was in covertToImage() somewhere in this function
253 if( sourceCropped.isNull() )
254 return setPixmap( sourcePixmap );
256 // blend the overlay image over the cropped rectangle
257 QImage blendedImage = sourceCropped.convertToImage();
258 QImage overlayImage = overlay->convertToImage();
259 KIconEffect::overlay( blendedImage, overlayImage );
260 sourceCropped.convertFromImage( blendedImage );
262 // put back the blended rectangle to the original image
263 QPixmap sourcePixmapCopy = sourcePixmap;
264 copyBlt( &sourcePixmapCopy, opX,opY, &sourceCropped, 0,0, opW,opH );
266 setPixmap( sourcePixmapCopy ); // @since 3.2
267 #endif
270 void
271 Amarok::TrayIcon::setLastFm( bool lastFmActive )
273 if( lastFmActive == m_lastFmMode ) return;
275 static int separatorId = 0;
277 KActionCollection* const ac = Amarok::actionCollection();
278 if( ac->action( "ban" ) == 0 ) return; //if the LastFm::Controller doesn't exist yet
280 if( lastFmActive )
282 contextMenu()->removeAction( ac->action( "play_pause" ) );
283 // items are inserted in reverse order!
284 contextMenu()->addAction( ac->action( "ban" ) );
285 contextMenu()->addAction( ac->action( "love" ) );
286 contextMenu()->addAction( ac->action( "skip" ) );
287 contextMenu()->addSeparator();
289 m_lastFmMode = true;
291 else
294 contextMenu()->addAction( ac->action( "play_pause" ) );
295 // items are inserted in reverse order!
296 contextMenu()->removeAction( ac->action( "ban" ) );
297 contextMenu()->removeAction( ac->action( "love" ) );
298 contextMenu()->removeAction( ac->action( "skip" ) );
300 //contextMenu()->removeSeparator();
301 m_lastFmMode = false;