/* kopeteonlinestatusmanager.cpp Copyright (c) 2004 by Olivier Goffart Copyright (c) 2003 by Will Stephenson Kopete (c) 2003-2004 by the Kopete developers ************************************************************************* * * * 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 #include #include #include #include #include #include // for WORDS_BIGENDIAN #include // 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 registeredStatus; TQDict< TQPixmap > iconCache; }; OnlineStatusManager *OnlineStatusManager::s_self=0L; OnlineStatusManager *OnlineStatusManager::self() { static KStaticDeleter 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() <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( 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: