// // AmarokSystray // // Contributors: Stanislav Karchebny , (C) 2003 // berkus, mxcl, eros, eean // Timothy Pearson (c) 2010 // // Copyright: like rest of Amarok // #include "amarok.h" #include "amarokconfig.h" #include "enginecontroller.h" #include "systray.h" #include #include #include #include #include #include #include namespace Amarok { static TQPixmap loadOverlay( const char *iconName, int iconWidth ) { return TQImage( locate( "data", TQString( "amarok/images/b_%1.png" ).tqarg( iconName ) ), "PNG" ).smoothScale( ((iconWidth/2)-(iconWidth/20)), ((iconWidth/2)-(iconWidth/20)) ); } } Amarok::TrayIcon::TrayIcon( TQWidget *playerWidget ) : KSystemTray( playerWidget ) , EngineObserver( EngineController::instance() ) , trackLength( 0 ) , mergeLevel( -1 ) , overlay( 0 ) , blinkTimerID( 0 ) , overlayVisible( false ) , m_lastFmMode( false ) { KActionCollection* const ac = Amarok::actionCollection(); setAcceptDrops( true ); ac->action( "prev" )->plug( contextMenu() ); ac->action( "play_pause" )->plug( contextMenu() ); ac->action( "stop" )->plug( contextMenu() ); ac->action( "next" )->plug( contextMenu() ); //seems to be necessary KAction *quit = actionCollection()->action( "file_quit" ); quit->disconnect(); connect( quit, TQT_SIGNAL(activated()), kapp, TQT_SLOT(quit()) ); baseIcon = KSystemTray::loadSizedIcon( "amarok", width() ); playOverlay = Amarok::loadOverlay( "play", width() ); pauseOverlay = Amarok::loadOverlay( "pause", width() ); overlayVisible = false; //paintIcon(); setPixmap( baseIcon ); } bool Amarok::TrayIcon::event( TQEvent *e ) { switch( e->type() ) { case TQEvent::Drop: case TQEvent::Wheel: case TQEvent::DragEnter: return Amarok::genericEventHandler( this, e ); case TQEvent::Timer: if( TQT_TQTIMEREVENT(e)->timerId() != blinkTimerID ) return KSystemTray::event( e ); // if we're playing, blink icon if ( overlay == &playOverlay ) { overlayVisible = !overlayVisible; paintIcon( mergeLevel, true ); } return true; case TQEvent::MouseButtonPress: if( TQT_TQMOUSEEVENT(e)->button() == Qt::MidButton ) { EngineController::instance()->playPause(); return true; } //else FALL THROUGH default: return KSystemTray::event( e ); } } void Amarok::TrayIcon::resizeEvent ( TQResizeEvent * ) { // Honor Free Desktop specifications that allow for arbitrary system tray icon sizes baseIcon = KSystemTray::loadSizedIcon( "amarok", width() ); if (overlay == &pauseOverlay) { pauseOverlay = Amarok::loadOverlay( "pause", width() ); overlay = &pauseOverlay; } if (overlay == &playOverlay) { playOverlay = Amarok::loadOverlay( "play", width() ); overlay = &playOverlay; } playOverlay = Amarok::loadOverlay( "play", width() ); pauseOverlay = Amarok::loadOverlay( "pause", width() ); grayedIcon = TQPixmap(); alternateIcon = TQPixmap(); paintIcon( -1, true ); } void Amarok::TrayIcon::engineStateChanged( Engine::State state, Engine::State /*oldState*/ ) { // stop timer if ( blinkTimerID ) { killTimer( blinkTimerID ); blinkTimerID = 0; } // draw overlay overlayVisible = true; // draw the right overlay for each state switch( state ) { case Engine::Paused: overlay = &pauseOverlay; paintIcon( mergeLevel, true ); break; case Engine::Playing: overlay = &playOverlay; if( AmarokConfig::animateTrayIcon() ) blinkTimerID = startTimer( 1500 ); // start 'blink' timer paintIcon( mergeLevel, true ); // tqrepaint the icon break; case Engine::Empty: overlayVisible = false; paintIcon( -1, true ); // tqrepaint the icon // fall through to default: default: setLastFm( false ); } } void Amarok::TrayIcon::engineNewMetaData( const MetaBundle &bundle, bool /*trackChanged*/ ) { trackLength = bundle.length() * 1000; setLastFm( bundle.url().protocol() == "lastfm" ); } void Amarok::TrayIcon::engineTrackPositionChanged( long position, bool /*userSeek*/ ) { mergeLevel = trackLength ? ((baseIcon.height() + 1) * position) / trackLength : -1; paintIcon( mergeLevel ); } void Amarok::TrayIcon::paletteChange( const TQPalette & op ) { if ( tqpalette().active().highlight() == op.active().highlight() || alternateIcon.isNull() ) return; alternateIcon.resize( 0, 0 ); paintIcon( mergeLevel, true ); } void Amarok::TrayIcon::paintIcon( int mergePixels, bool force ) { // skip redrawing the same pixmap static int mergePixelsCache = 0; if ( mergePixels == mergePixelsCache && !force ) return; mergePixelsCache = mergePixels; if ( mergePixels < 0 ) return blendOverlay( baseIcon ); // make up the grayed icon if ( grayedIcon.isNull() ) { TQImage tmpTrayIcon = baseIcon.convertToImage(); KIconEffect::semiTransparent( tmpTrayIcon ); grayedIcon = tmpTrayIcon; } // make up the alternate icon (use hilight color but more saturated) if ( alternateIcon.isNull() ) { TQImage tmpTrayIcon = baseIcon.convertToImage(); // eros: this looks cool with dark red blue or green but sucks with // other colors (such as kde default's pale pink..). maybe the effect // or the blended color has to be changed.. TQColor saturatedColor = tqpalette().active().highlight(); int hue, sat, value; saturatedColor.getHsv( &hue, &sat, &value ); saturatedColor.setHsv( hue, sat > 200 ? 200 : sat, value < 100 ? 100 : value ); KIconEffect::colorize( tmpTrayIcon, saturatedColor/* TQt::blue */, 0.9 ); alternateIcon = tmpTrayIcon; } if ( mergePixels >= alternateIcon.height() ) return blendOverlay( grayedIcon ); if ( mergePixels == 0 ) return blendOverlay( alternateIcon ); // mix [ grayed <-> colored ] icons TQPixmap tmpTrayPixmap = alternateIcon; copyBlt( &tmpTrayPixmap, 0,0, &grayedIcon, 0,0, alternateIcon.width(), mergePixels>0 ? mergePixels-1 : 0 ); blendOverlay( tmpTrayPixmap ); } void Amarok::TrayIcon::blendOverlay( TQPixmap &sourcePixmap ) { if ( !overlayVisible || !overlay || overlay->isNull() ) return setPixmap( sourcePixmap ); // @since 3.2 // here comes the tricky part.. no kdefx functions are helping here.. :-( // we have to blend pixmaps with different sizes (blending will be done in // the bottom-left corner of source pixmap with a smaller overlay pixmap) int opW = overlay->width(), opH = overlay->height(), opX = 1, opY = sourcePixmap.height() - opH; // get the rectangle where blending will take place TQPixmap sourceCropped( opW, opH, sourcePixmap.depth() ); copyBlt( &sourceCropped, 0,0, &sourcePixmap, opX,opY, opW,opH ); //speculative fix for a bactrace we received //crash was in covertToImage() somewhere in this function if( sourceCropped.isNull() ) return setPixmap( sourcePixmap ); // blend the overlay image over the cropped rectangle TQImage blendedImage = sourceCropped.convertToImage(); TQImage overlayImage = overlay->convertToImage(); KIconEffect::overlay( blendedImage, overlayImage ); sourceCropped.convertFromImage( blendedImage ); // put back the blended rectangle to the original image TQPixmap sourcePixmapCopy = sourcePixmap; copyBlt( &sourcePixmapCopy, opX,opY, &sourceCropped, 0,0, opW,opH ); setPixmap( sourcePixmapCopy ); // @since 3.2 } void Amarok::TrayIcon::setLastFm( bool lastFmActive ) { if( lastFmActive == m_lastFmMode ) return; static int separatorId = 0; KActionCollection* const ac = Amarok::actionCollection(); if( ac->action( "ban" ) == 0 ) return; //if the LastFm::Controller doesn't exist yet if( lastFmActive ) { ac->action( "play_pause" )->unplug( contextMenu() ); // items are inserted in reverse order! ac->action( "ban" ) ->plug( contextMenu(), 4 ); ac->action( "love" )->plug( contextMenu(), 4 ); ac->action( "skip" )->plug( contextMenu(), 4 ); separatorId = contextMenu()->insertSeparator( 4 ); m_lastFmMode = true; } else { ac->action( "play_pause" )->plug( contextMenu(), 2 ); ac->action( "ban" ) ->unplug( contextMenu() ); ac->action( "love" )->unplug( contextMenu() ); ac->action( "skip" )->unplug( contextMenu() ); if( separatorId != 0 ) contextMenu()->removeItem( separatorId ); // kill separator m_lastFmMode = false; } }