4 // Contributors: Stanislav Karchebny <berkus@users.sf.net>, (C) 2003
5 // berkus, mxcl, eros, eean
7 // Copyright: like rest of Amarok
13 #include "amarokconfig.h"
15 #include "enginecontroller.h"
16 #include "playlist/PlaylistModel.h"
17 #include "meta/Meta.h"
18 #include "TheInstances.h"
21 #include <KApplication>
22 #include <KIconEffect>
25 #include <KStandardDirs>
29 #include <QMouseEvent>
31 #include <QTimerEvent>
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() )
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" );
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;
77 Amarok::TrayIcon::event( QEvent
*e
)
80 debug() << "Event type: " << 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() ) );
93 debug() << "QEvent::Drop";
94 #define e static_cast<QDropEvent*>(e)
96 KUrl::List list
= KUrl::List::fromMimeData( e
->mimeData() );
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() );
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();
123 else if( e
->modifiers() == Qt::ShiftModifier
)
125 EngineController::instance()->seekRelative( (e
->delta() / 120) * 5000 ); // 5 seconds for keyboard seeking
129 EngineController::instance()->increaseVolume( e
->delta() / Amarok::VOLUME_SENSITIVITY
);
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 );
149 case QEvent::MouseButtonPress
:
150 debug() << "QEvent::MouseButtonPress";
151 if( static_cast<QMouseEvent
*>(e
)->button() == Qt::MidButton
)
153 EngineController::instance()->playPause();
161 debug() << "QEvent:: fall through";
162 return KSystemTrayIcon::event( e
);
168 Amarok::TrayIcon::engineStateChanged( Engine::State state
, Engine::State
/*oldState*/ )
173 killTimer( blinkTimerID
);
177 overlayVisible
= true;
179 // draw the right overlay for each state
183 overlay
= &pauseOverlay
;
184 paintIcon( mergeLevel
, true );
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
196 overlayVisible
= false;
197 paintIcon( -1, true ); // repaint the icon
198 // fall through to default:
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();
212 trackLength
= track
->length() * 1000;
213 setLastFm( track
->type() == "stream/lastfm" );
217 Amarok::TrayIcon::engineTrackPositionChanged( long position
, bool /*userSeek*/ )
220 mergeLevel = trackLength ? ((baseIcon.height() + 1) * position) / trackLength : -1;
221 paintIcon( mergeLevel );
227 Amarok::TrayIcon::paletteChange( const QPalette
& op
)
230 if ( palette().active().highlight() == op.active().highlight() || alternateIcon.isNull() )
233 alternateIcon.resize( 0, 0 );
234 paintIcon( mergeLevel, true );
239 Amarok::TrayIcon::paintIcon( int mergePixels
, bool force
)
241 // skip redrawing the same pixmap
242 static int mergePixelsCache
= 0;
243 if ( mergePixels
== mergePixelsCache
&& !force
)
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() )
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();
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
;
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
);
288 Amarok::TrayIcon::blendOverlay( QPixmap
&sourcePixmap
)
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(),
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
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
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();
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;