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.
tdenetwork/kopete/libkopete/kopeteonlinestatusmanager.cpp

437 lines
13 KiB

/*
kopeteonlinestatusmanager.cpp
Copyright (c) 2004 by Olivier Goffart <ogoffart @ tiscalinet . be>
Copyright (c) 2003 by Will Stephenson <lists@stevello.free-online.co.uk>
Kopete (c) 2003-2004 by the Kopete developers <kopete-devel@kde.org>
*************************************************************************
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Lesser General Public *
* License as published by the Free Software Foundation; either *
* version 2 of the License, or (at your option) any later version. *
* *
*************************************************************************
*/
#include "kopeteonlinestatusmanager.h"
#include "kopeteawayaction.h"
#include "kopeteprotocol.h"
#include "kopeteaccount.h"
#include "kopetecontact.h"
#include <kiconloader.h>
#include <kiconeffect.h>
#include <kdebug.h>
#include <klocale.h>
#include <kstaticdeleter.h>
#include <kapplication.h>
#include <kcpuinfo.h> // for WORDS_BIGENDIAN
#include <algorithm> // for min
namespace Kopete {
class OnlineStatusManager::Private
{public:
struct RegisteredStatusStruct
{
TQString caption;
unsigned int categories;
unsigned int options;
};
typedef TQMap< OnlineStatus , RegisteredStatusStruct > ProtocolMap ;
TQPixmap *nullPixmap;
TQMap<Protocol* , ProtocolMap > registeredStatus;
TQDict< TQPixmap > iconCache;
};
OnlineStatusManager *OnlineStatusManager::s_self=0L;
OnlineStatusManager *OnlineStatusManager::self()
{
static KStaticDeleter<OnlineStatusManager> deleter;
if(!s_self)
deleter.setObject( s_self, new OnlineStatusManager() );
return s_self;
}
OnlineStatusManager::OnlineStatusManager()
: d( new Private )
{
d->iconCache.setAutoDelete( true );
d->nullPixmap = new QPixmap;
connect( kapp, TQT_SIGNAL( iconChanged(int) ), this, TQT_SLOT( slotIconsChanged() ) );
}
OnlineStatusManager::~OnlineStatusManager()
{
delete d->nullPixmap;
delete d;
}
void OnlineStatusManager::slotIconsChanged()
{
d->iconCache.clear();
emit iconsChanged();
}
void OnlineStatusManager::registerOnlineStatus( const OnlineStatus &status, const TQString & caption, unsigned int categories, unsigned int options)
{
Private::RegisteredStatusStruct s;
s.caption=caption;
s.categories=categories;
s.options=options;
d->registeredStatus[status.protocol()].insert(status, s );
}
OnlineStatus OnlineStatusManager::onlineStatus(Protocol * protocol, Categories category) const
{
/* Each category has a number which is a power of two, so it is possible to have several categories per online status
* the logaritm in base two if this number, which represent the bit which is equal to 1 in the number is chosen to be in a tree
* 1 (0 is reserved for Offline)
* / \
* 2 3
* / \ / \
* 4 5 6 7
* /\ / \ / \ / \
* 8 9 10 11 12 13 14 15
* To get the parent of a key, one just divide per two the number
*/
Private::ProtocolMap protocolMap=d->registeredStatus[protocol];
int categ_nb=-1; //the logaritm of category
uint category_=category;
while(category_)
{
category_ >>= 1;
categ_nb++;
} //that code will give the log +1
do
{
Private::ProtocolMap::Iterator it;
for ( it = protocolMap.begin(); it != protocolMap.end(); it++ )
{
unsigned int catgs=it.data().categories;
if(catgs & (1<<(categ_nb)))
return it.key();
}
//no status found in this category, try the previous one.
categ_nb=(int)(categ_nb/2);
} while (categ_nb > 0);
kdWarning() << "No status in the category " << category << " for the protocol " << protocol->displayName() <<endl;
return OnlineStatus();
}
TQString OnlineStatusManager::fingerprint( const OnlineStatus &statusFor, const TQString& icon, int size, TQColor color, bool idle)
{
// create a 'fingerprint' to use as a hash key
// fingerprint consists of description/icon name/color/overlay name/size/idle state
return TQString::fromLatin1("%1/%2/%3/%4/%5/%6")
.arg( statusFor.description() )
.arg( icon )
.arg( color.name() )
.arg( statusFor.overlayIcons().join( TQString::fromLatin1( "," ) ) )
.arg( size )
.arg( idle ? 'i' : 'a' );
}
TQPixmap OnlineStatusManager::cacheLookupByObject( const OnlineStatus &statusFor, const TQString& icon, int size, TQColor color, bool idle)
{
TQString fp = fingerprint( statusFor, icon, size, color, idle );
// look it up in the cache
TQPixmap *theIcon= d->iconCache.find( fp );
if ( !theIcon )
{
// cache miss
// kdDebug(14010) << k_funcinfo << "Missed " << fingerprint << " in icon cache!" << endl;
theIcon = renderIcon( statusFor, icon, size, color, idle);
d->iconCache.insert( fp, theIcon );
}
return *theIcon;
}
TQPixmap OnlineStatusManager::cacheLookupByMimeSource( const TQString &mimeSource )
{
// look it up in the cache
const TQPixmap *theIcon= d->iconCache.find( mimeSource );
if ( !theIcon )
{
// need to return an invalid pixmap
theIcon = d->nullPixmap;
}
return *theIcon;
}
// This code was forked from the broken KImageEffect::blendOnLower, but it's
// been so heavily fixed and rearranged it's hard to recognise that now.
static void blendOnLower( const TQImage &upper_, TQImage &lower, const TQPoint &offset )
{
if ( upper_.width() <= 0 || upper_.height() <= 0 )
return;
if ( lower.width() <= 0 || lower.height() <= 0 )
return;
if ( offset.x() < 0 || offset.x() >= lower.width() )
return;
if ( offset.y() < 0 || offset.y() >= lower.height() )
return;
TQImage upper = upper_;
if ( upper.depth() != 32 )
upper = upper.convertDepth( 32 );
if ( lower.depth() != 32 )
lower = lower.convertDepth( 32 );
const int cx = offset.x();
const int cy = offset.y();
const int cw = std::min( upper.width() + cx, lower.width() );
const int ch = std::min( upper.height() + cy, lower.height() );
const int m = 255;
for ( int j = cy; j < ch; ++j )
{
QRgb *u = (QRgb*)upper.scanLine(j - cy);
QRgb *l = (QRgb*)lower.scanLine(j) + cx;
for( int k = cx; k < cw; ++u, ++l, ++k )
{
int ua = qAlpha(*u);
if ( !ua )
continue;
int la = qAlpha(*l);
int d = ua * m + la * (m - ua);
uchar r = uchar( ( qRed(*u) * ua * m + qRed(*l) * la * (m - ua) ) / d );
uchar g = uchar( ( qGreen(*u) * ua * m + qGreen(*l) * la * (m - ua) ) / d );
uchar b = uchar( ( qBlue(*u) * ua * m + qBlue(*l) * la * (m - ua) ) / d );
uchar a = uchar( ( ua * ua * m + la * la * (m - ua) ) / d );
*l = qRgba( r, g, b, a );
}
}
}
// Get bounding box of image via alpha channel
static TQRect getBoundingBox( const TQImage& image )
{
const int width = image.width();
const int height = image.height();
if ( width <= 0 || height <= 0 )
return TQRect();
// scan image from left to right and top to bottom
// to get upper left corner of bounding box
int x1 = width - 1;
int y1 = height - 1;
for ( int j = 0; j < height; ++j )
{
QRgb *i = (QRgb*)image.scanLine(j);
for( int k = 0; k < width; ++i, ++k )
{
if ( qAlpha(*i) )
{
x1 = std::min( x1, k );
y1 = std::min( y1, j );
break;
}
}
}
// scan image from right to left and bottom to top
// to get lower right corner of bounding box
int x2 = 0;
int y2 = 0;
for ( int j = height-1; j >= 0; --j )
{
QRgb *i = (QRgb*)image.scanLine(j) + width-1;
for( int k = width-1; k >= 0; --i, --k )
{
if ( qAlpha(*i) )
{
x2 = std::max( x2, k );
y2 = std::max( y2, j );
break;
}
}
}
return TQRect( x1, y1, std::max( 0, x2-x1+1 ), std::max( 0, y2-y1+1 ) );
}
// Get offset for upperImage to blend it in the i%4-th corner of lowerImage:
// bottom right, bottom left, top left, top right
static TQPoint getOffsetForCorner( const TQImage& upperImage, const TQImage& lowerImage, const int i )
{
const int dX = lowerImage.width() - upperImage.width();
const int dY = lowerImage.height() - upperImage.height();
const int corner = i % 4;
TQPoint offset;
switch( corner ) {
case 0:
// bottom right
offset = TQPoint( dX, dY );
break;
case 1:
// bottom left
offset = TQPoint( 0, dY );
break;
case 2:
// top left
offset = TQPoint( 0, 0 );
break;
case 3:
// top right
offset = TQPoint( dX, 0 );
break;
}
return offset;
}
TQPixmap* OnlineStatusManager::renderIcon( const OnlineStatus &statusFor, const TQString& baseIcon, int size, TQColor color, bool idle) const
{
// create an icon suiting the status from the base icon
// use reasonable defaults if not provided or protocol not set
if ( baseIcon == statusFor.overlayIcons().first() )
kdWarning( 14010 ) << "Base and overlay icons are the same - icon effects will not be visible." << endl;
TQPixmap* basis = new TQPixmap( SmallIcon( baseIcon ) );
// Colorize
if ( color.isValid() )
*basis = KIconEffect().apply( *basis, KIconEffect::Colorize, 1, color, 0);
// Note that we do this before compositing the overlay, since we want
// that to be colored in this case.
if ( statusFor.internalStatus() == Kopete::OnlineStatus::AccountOffline || statusFor.status() == Kopete::OnlineStatus::Offline )
{
*basis = KIconEffect().apply( *basis, KIconEffect::ToGray , 0.85, TQColor() , false );
}
//composite the iconOverlay for this status and the supplied baseIcon
TQStringList overlays = statusFor.overlayIcons();
if ( !( overlays.isEmpty() ) ) // otherwise leave the basis as-is
{
KIconLoader *loader = KGlobal::instance()->iconLoader();
int i = 0;
for( TQStringList::iterator it = overlays.begin(), end = overlays.end(); it != end; ++it )
{
TQPixmap overlay = loader->loadIcon(*it, KIcon::Small, 0 ,
KIcon::DefaultState, 0L, /*canReturnNull=*/ true );
if ( !overlay.isNull() )
{
// we want to preserve the alpha channels of both basis and overlay.
// there's no way to do this in Qt. In fact, there's no way to do this
// in KDE since KImageEffect is so badly broken.
TQImage basisImage = basis->convertToImage();
TQImage overlayImage = overlay.convertToImage();
TQPoint offset;
if ( (*it).endsWith( TQString::fromLatin1( "_overlay" ) ) )
{
// it is possible to have more than one overlay icon
// to avoid overlapping we place them in different corners
overlayImage = overlayImage.copy( getBoundingBox( overlayImage ) );
offset = getOffsetForCorner( overlayImage, basisImage, i );
++i;
}
blendOnLower( overlayImage, basisImage, offset );
basis->convertFromImage( basisImage );
}
}
}
// no need to scale if the icon is already of the required size (assuming height == width!)
if ( basis->width() != size )
{
TQImage scaledImg = basis->convertToImage().smoothScale( size, size );
*basis = TQPixmap( scaledImg );
}
// if idle, apply effects
if ( idle )
KIconEffect::semiTransparent( *basis );
return basis;
}
void OnlineStatusManager::createAccountStatusActions( Account *account , KActionMenu *parent)
{
Private::ProtocolMap protocolMap=d->registeredStatus[account->protocol()];
Private::ProtocolMap::Iterator it;
for ( it = --protocolMap.end(); it != protocolMap.end(); --it )
{
unsigned int options=it.data().options;
if(options & OnlineStatusManager::HideFromMenu)
continue;
OnlineStatus status=it.key();
TQString caption=it.data().caption;
KAction *action;
// Any existing actions owned by the account are reused by recovering them
// from the parent's child list.
// The description of the onlinestatus is used as the qobject name
// This is safe as long as OnlineStatus are immutable
TQCString actionName = status.description().ascii();
if ( !( action = static_cast<KAction*>( account->child( actionName ) ) ) )
{
if(options & OnlineStatusManager::HasAwayMessage)
{
action = new AwayAction( status, caption, status.iconFor(account), 0, account,
TQT_SLOT( setOnlineStatus( const Kopete::OnlineStatus&, const TQString& ) ),
account, actionName );
}
else
{
action=new OnlineStatusAction( status, caption, status.iconFor(account) , account, actionName );
connect(action,TQT_SIGNAL(activated(const Kopete::OnlineStatus&)) ,
account, TQT_SLOT(setOnlineStatus(const Kopete::OnlineStatus&)));
}
}
#if 0
//disabled because since action are reused, they are not enabled back if the account is online.
if(options & OnlineStatusManager::DisabledIfOffline && !account->isConnected())
action->setEnabled(false);
#endif
if(parent)
parent->insert(action);
}
}
OnlineStatusAction::OnlineStatusAction( const OnlineStatus& status, const TQString &text, const TQIconSet &pix, TQObject *parent, const char *name)
: KAction( text, pix, KShortcut() , parent, name) , m_status(status)
{
connect(this,TQT_SIGNAL(activated()),this,TQT_SLOT(slotActivated()));
}
void OnlineStatusAction::slotActivated()
{
emit activated(m_status);
}
} //END namespace Kopete
#include "kopeteonlinestatusmanager.moc"
// vim: set noet ts=4 sts=4 sw=4: