Make plasma libs build.
[amarok.git] / src / systray.cpp
blobce3817a28c98a86f079731714ccb4d82007d1f35
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 "debug.h"
15 #include "enginecontroller.h"
16 #include "playlist/PlaylistModel.h"
17 #include "meta/Meta.h"
18 #include "TheInstances.h"
20 #include <KAction>
21 #include <KApplication>
22 #include <KIconEffect>
23 #include <KLocale>
24 #include <KMenu>
25 #include <KStandardDirs>
27 #include <QEvent>
28 #include <QImage>
29 #include <QMouseEvent>
30 #include <QPixmap>
31 #include <QTimerEvent>
34 namespace Amarok
36 static QImage
37 loadOverlay( const char *iconName )
39 return QImage( KStandardDirs::locate( "data", QString( "amarok/images/b_%1.png" ).arg( iconName ) ), "PNG" )
40 .scaled( 10, 10, Qt::IgnoreAspectRatio, Qt::SmoothTransformation );
45 Amarok::TrayIcon::TrayIcon( QWidget *playerWidget )
46 : KSystemTrayIcon( playerWidget )
47 , EngineObserver( EngineController::instance() )
48 , trackLength( 0 )
49 , mergeLevel( -1 )
50 , overlay( 0 )
51 , blinkTimerID( 0 )
52 , overlayVisible( false )
53 , m_lastFmMode( false )
55 KActionCollection* const ac = Amarok::actionCollection();
57 contextMenu()->addAction( ac->action( "prev" ) );
58 contextMenu()->addAction( ac->action( "play_pause" ) );
59 contextMenu()->addAction( ac->action( "play_pause" ) );
60 contextMenu()->addAction( ac->action( "next" ) );
62 //seems to be necessary
63 QAction *quit = actionCollection()->action( "file_quit" );
64 quit->disconnect();
65 connect( quit, SIGNAL(activated()), kapp, SLOT(quit()) );
67 baseIcon = KSystemTrayIcon::loadIcon( "amarok" );
68 playOverlay = Amarok::loadOverlay( "play" );
69 pauseOverlay = Amarok::loadOverlay( "pause" );
70 overlayVisible = false;
72 //paintIcon();
73 setIcon( baseIcon );
76 bool
77 Amarok::TrayIcon::event( QEvent *e )
79 DEBUG_BLOCK
80 debug() << "Event type: " << e->type();
81 switch( e->type() )
83 case QEvent::DragEnter:
84 debug() << "QEvent::DragEnter";
85 #define e static_cast<QDragEnterEvent*>(e)
87 e->setAccepted( KUrl::List::canDecode( e->mimeData() ) );
88 break;
90 #undef e
92 case QEvent::Drop:
93 debug() << "QEvent::Drop";
94 #define e static_cast<QDropEvent*>(e)
96 KUrl::List list = KUrl::List::fromMimeData( e->mimeData() );
97 if( !list.isEmpty() )
99 KMenu *popup = new KMenu;
100 popup->addAction( KIcon( Amarok::icon( "add_playlist" ) ), i18n( "&Append to Playlist" ), this, SLOT( appendDrops() ) );
101 popup->addAction( KIcon( Amarok::icon( "add_playlist" ) ), i18n( "Append && &Play" ), this, SLOT( appendAndPlayDrops() ) );
102 if( The::playlistModel()->activeRow() >= 0 )
103 popup->addAction( KIcon( Amarok::icon( "queue_track" ) ), i18n( "&Queue Track" ), this, SLOT( queueDrops() ) );
105 popup->addSeparator();
106 popup->addAction( i18n( "&Cancel" ) );
107 popup->exec( e->pos() );
109 break;
111 #undef e
113 case QEvent::Wheel:
114 debug() << "QEvent::Wheel";
115 #define e static_cast<QWheelEvent*>(e)
116 if( e->modifiers() == Qt::ControlModifier )
118 const bool up = e->delta() > 0;
119 if( up ) EngineController::instance()->previous();
120 else EngineController::instance()->next();
121 break;
123 else if( e->modifiers() == Qt::ShiftModifier )
125 EngineController::instance()->seekRelative( (e->delta() / 120) * 5000 ); // 5 seconds for keyboard seeking
126 break;
128 else
129 EngineController::instance()->increaseVolume( e->delta() / Amarok::VOLUME_SENSITIVITY );
131 e->accept();
132 #undef e
133 break;
135 case QEvent::Timer:
136 debug() << "QEvent::Timer";
137 if( static_cast<QTimerEvent*>(e)->timerId() != blinkTimerID )
138 return KSystemTrayIcon::event( e );
140 // if we're playing, blink icon
141 if ( overlay == &playOverlay )
143 overlayVisible = !overlayVisible;
144 paintIcon( mergeLevel, true );
147 break;
149 case QEvent::MouseButtonPress:
150 debug() << "QEvent::MouseButtonPress";
151 if( static_cast<QMouseEvent*>(e)->button() == Qt::MidButton )
153 EngineController::instance()->playPause();
155 return true;
158 //else FALL THROUGH
160 default:
161 debug() << "QEvent:: fall through";
162 return KSystemTrayIcon::event( e );
164 return true;
167 void
168 Amarok::TrayIcon::engineStateChanged( Engine::State state, Engine::State /*oldState*/ )
170 // stop timer
171 if ( blinkTimerID )
173 killTimer( blinkTimerID );
174 blinkTimerID = 0;
176 // draw overlay
177 overlayVisible = true;
179 // draw the right overlay for each state
180 switch( state )
182 case Engine::Paused:
183 overlay = &pauseOverlay;
184 paintIcon( mergeLevel, true );
185 break;
187 case Engine::Playing:
188 overlay = &playOverlay;
189 if( AmarokConfig::animateTrayIcon() )
190 blinkTimerID = startTimer( 1500 ); // start 'blink' timer
192 paintIcon( mergeLevel, true ); // repaint the icon
193 break;
195 case Engine::Empty:
196 overlayVisible = false;
197 paintIcon( -1, true ); // repaint the icon
198 // fall through to default:
199 default:
200 setLastFm( false );
204 void
205 Amarok::TrayIcon::engineNewMetaData( const QHash<qint64, QString> &newMetaData, bool trackChanged )
207 Q_UNUSED( trackChanged )
208 Q_UNUSED( newMetaData )
209 Meta::TrackPtr track = EngineController::instance()->currentTrack();
210 if( !track )
211 return;
212 trackLength = track->length() * 1000;
213 setLastFm( track->type() == "stream/lastfm" );
216 void
217 Amarok::TrayIcon::engineTrackPositionChanged( long position, bool /*userSeek*/ )
220 mergeLevel = trackLength ? ((baseIcon.height() + 1) * position) / trackLength : -1;
221 paintIcon( mergeLevel );
226 void
227 Amarok::TrayIcon::paletteChange( const QPalette & op )
230 if ( palette().active().highlight() == op.active().highlight() || alternateIcon.isNull() )
231 return;
233 alternateIcon.resize( 0, 0 );
234 paintIcon( mergeLevel, true );
238 void
239 Amarok::TrayIcon::paintIcon( int mergePixels, bool force )
241 // skip redrawing the same pixmap
242 static int mergePixelsCache = 0;
243 if ( mergePixels == mergePixelsCache && !force )
244 return;
245 mergePixelsCache = mergePixels;
247 if ( mergePixels < 0 ) {}
248 //return blendOverlay( baseIcon );
250 // make up the grayed icon
251 if ( grayedIcon.isNull() )
253 //QImage tmpTrayIcon = baseIcon.convertToImage();
254 //KIconEffect::semiTransparent( tmpTrayIcon );
255 //grayedIcon = tmpTrayIcon;
258 // make up the alternate icon (use hilight color but more saturated)
259 if ( alternateIcon.isNull() )
261 #if 0
262 QImage tmpTrayIcon = baseIcon.convertToImage();
263 // eros: this looks cool with dark red blue or green but sucks with
264 // other colors (such as kde default's pale pink..). maybe the effect
265 // or the blended color has to be changed..
266 QColor saturatedColor = palette().active().highlight();
267 int hue, sat, value;
268 saturatedColor.getHsv( &hue, &sat, &value );
269 saturatedColor.setHsv( hue, sat > 200 ? 200 : sat, value < 100 ? 100 : value );
270 KIconEffect::colorize( tmpTrayIcon, saturatedColor/* Qt::blue */, 0.9 );
271 alternateIcon = tmpTrayIcon;
272 #endif
275 if ( mergePixels >= alternateIcon.height() ) {}
276 //return blendOverlay( grayedIcon );
277 if ( mergePixels == 0 ) {}
278 //return blendOverlay( alternateIcon );
280 // mix [ grayed <-> colored ] icons
281 QPixmap tmpTrayPixmap = alternateIcon;
282 copyBlt( &tmpTrayPixmap, 0,0, &grayedIcon, 0,0,
283 alternateIcon.width(), mergePixels>0 ? mergePixels-1 : 0 );
284 blendOverlay( tmpTrayPixmap );
287 void
288 Amarok::TrayIcon::blendOverlay( QPixmap &sourcePixmap )
290 #if 0
291 if ( !overlayVisible || !overlay || overlay->isNull() )
292 return setPixmap( sourcePixmap ); // @since 3.2
294 // here comes the tricky part.. no kdefx functions are helping here.. :-(
295 // we have to blend pixmaps with different sizes (blending will be done in
296 // the bottom-left corner of source pixmap with a smaller overlay pixmap)
297 int opW = overlay->width(),
298 opH = overlay->height(),
299 opX = 1,
300 opY = sourcePixmap.height() - opH;
302 // get the rectangle where blending will take place
303 QPixmap sourceCropped( opW, opH, sourcePixmap.depth() );
304 copyBlt( &sourceCropped, 0,0, &sourcePixmap, opX,opY, opW,opH );
306 //speculative fix for a bactrace we received
307 //crash was in covertToImage() somewhere in this function
308 if( sourceCropped.isNull() )
309 return setPixmap( sourcePixmap );
311 // blend the overlay image over the cropped rectangle
312 QImage blendedImage = sourceCropped.convertToImage();
313 QImage overlayImage = overlay->convertToImage();
314 KIconEffect::overlay( blendedImage, overlayImage );
315 sourceCropped.convertFromImage( blendedImage );
317 // put back the blended rectangle to the original image
318 QPixmap sourcePixmapCopy = sourcePixmap;
319 copyBlt( &sourcePixmapCopy, opX,opY, &sourceCropped, 0,0, opW,opH );
321 setPixmap( sourcePixmapCopy ); // @since 3.2
322 #endif
325 void
326 Amarok::TrayIcon::setLastFm( bool lastFmActive )
328 if( lastFmActive == m_lastFmMode ) return;
330 static int separatorId = 0;
332 KActionCollection* const ac = Amarok::actionCollection();
333 if( ac->action( "ban" ) == 0 ) return; //if the LastFm::Controller doesn't exist yet
335 if( lastFmActive )
337 contextMenu()->removeAction( ac->action( "play_pause" ) );
338 // items are inserted in reverse order!
339 contextMenu()->addAction( ac->action( "ban" ) );
340 contextMenu()->addAction( ac->action( "love" ) );
341 contextMenu()->addAction( ac->action( "skip" ) );
342 contextMenu()->addSeparator();
344 m_lastFmMode = true;
346 else
349 contextMenu()->addAction( ac->action( "play_pause" ) );
350 // items are inserted in reverse order!
351 contextMenu()->removeAction( ac->action( "ban" ) );
352 contextMenu()->removeAction( ac->action( "love" ) );
353 contextMenu()->removeAction( ac->action( "skip" ) );
355 //contextMenu()->removeSeparator();
356 m_lastFmMode = false;