You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
amarok/amarok/src/systray.cpp

307 lines
9.0 KiB

//
// AmarokSystray
//
// Contributors: Stanislav Karchebny <berkus@users.sf.net>, (C) 2003
// berkus, mxcl, eros, eean
// Timothy Pearson <kb9vqf@pearsoncomputing.net> (c) 2010
//
// Copyright: like rest of Amarok
//
#include "amarok.h"
#include "amarokconfig.h"
#include "enginecontroller.h"
#include "systray.h"
#include <tqevent.h>
#include <tqimage.h>
#include <tdeaction.h>
#include <tdeapplication.h>
#include <tdepopupmenu.h>
#include <kiconeffect.h>
#include <kstandarddirs.h>
namespace Amarok
{
static TQPixmap
loadOverlay( const char *iconName, int iconWidth )
{
return TQImage( locate( "data", TQString( "amarok/images/b_%1.png" ).arg( 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 )
{
TDEActionCollection* 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
TDEAction *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 ); // repaint the icon
break;
case Engine::Empty:
overlayVisible = false;
paintIcon( -1, true ); // repaint 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 ( palette().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();
TDEIconEffect::semiTransparent( tmpTrayIcon );
grayedIcon = tmpTrayIcon;
}
// make up the alternate icon (use highlight 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 = palette().active().highlight();
int hue, sat, value;
saturatedColor.getHsv( &hue, &sat, &value );
saturatedColor.setHsv( hue, sat > 200 ? 200 : sat, value < 100 ? 100 : value );
TDEIconEffect::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 tdefx 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 convertToImage() 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();
TDEIconEffect::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;
TDEActionCollection* 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;
}
}