summaryrefslogtreecommitdiffstats
path: root/kopete/libkopete/kopeteonlinestatusmanager.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'kopete/libkopete/kopeteonlinestatusmanager.cpp')
-rw-r--r--kopete/libkopete/kopeteonlinestatusmanager.cpp436
1 files changed, 436 insertions, 0 deletions
diff --git a/kopete/libkopete/kopeteonlinestatusmanager.cpp b/kopete/libkopete/kopeteonlinestatusmanager.cpp
new file mode 100644
index 00000000..61c41b83
--- /dev/null
+++ b/kopete/libkopete/kopeteonlinestatusmanager.cpp
@@ -0,0 +1,436 @@
+/*
+ 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
+ {
+ QString caption;
+ unsigned int categories;
+ unsigned int options;
+ };
+
+ typedef QMap< OnlineStatus , RegisteredStatusStruct > ProtocolMap ;
+
+ QPixmap *nullPixmap;
+ QMap<Protocol* , ProtocolMap > registeredStatus;
+ QDict< QPixmap > 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, SIGNAL( iconChanged(int) ), this, SLOT( slotIconsChanged() ) );
+}
+
+OnlineStatusManager::~OnlineStatusManager()
+{
+ delete d->nullPixmap;
+ delete d;
+}
+
+void OnlineStatusManager::slotIconsChanged()
+{
+ d->iconCache.clear();
+ emit iconsChanged();
+}
+
+void OnlineStatusManager::registerOnlineStatus( const OnlineStatus &status, const QString & 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();
+}
+
+QString OnlineStatusManager::fingerprint( const OnlineStatus &statusFor, const QString& icon, int size, QColor 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 QString::fromLatin1("%1/%2/%3/%4/%5/%6")
+ .arg( statusFor.description() )
+ .arg( icon )
+ .arg( color.name() )
+ .arg( statusFor.overlayIcons().join( QString::fromLatin1( "," ) ) )
+ .arg( size )
+ .arg( idle ? 'i' : 'a' );
+}
+
+QPixmap OnlineStatusManager::cacheLookupByObject( const OnlineStatus &statusFor, const QString& icon, int size, QColor color, bool idle)
+{
+ QString fp = fingerprint( statusFor, icon, size, color, idle );
+
+ // look it up in the cache
+ QPixmap *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;
+}
+
+QPixmap OnlineStatusManager::cacheLookupByMimeSource( const QString &mimeSource )
+{
+ // look it up in the cache
+ const QPixmap *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 QImage &upper_, QImage &lower, const QPoint &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;
+
+ QImage 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 QRect getBoundingBox( const QImage& image )
+{
+ const int width = image.width();
+ const int height = image.height();
+ if ( width <= 0 || height <= 0 )
+ return QRect();
+
+ // 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 QRect( 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 QPoint getOffsetForCorner( const QImage& upperImage, const QImage& lowerImage, const int i )
+{
+ const int dX = lowerImage.width() - upperImage.width();
+ const int dY = lowerImage.height() - upperImage.height();
+ const int corner = i % 4;
+ QPoint offset;
+ switch( corner ) {
+ case 0:
+ // bottom right
+ offset = QPoint( dX, dY );
+ break;
+ case 1:
+ // bottom left
+ offset = QPoint( 0, dY );
+ break;
+ case 2:
+ // top left
+ offset = QPoint( 0, 0 );
+ break;
+ case 3:
+ // top right
+ offset = QPoint( dX, 0 );
+ break;
+ }
+ return offset;
+}
+
+QPixmap* OnlineStatusManager::renderIcon( const OnlineStatus &statusFor, const QString& baseIcon, int size, QColor 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;
+
+ QPixmap* basis = new QPixmap( 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, QColor() , false );
+ }
+
+ //composite the iconOverlay for this status and the supplied baseIcon
+ QStringList overlays = statusFor.overlayIcons();
+ if ( !( overlays.isEmpty() ) ) // otherwise leave the basis as-is
+ {
+ KIconLoader *loader = KGlobal::instance()->iconLoader();
+
+ int i = 0;
+ for( QStringList::iterator it = overlays.begin(), end = overlays.end(); it != end; ++it )
+ {
+ QPixmap 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.
+ QImage basisImage = basis->convertToImage();
+ QImage overlayImage = overlay.convertToImage();
+ QPoint offset;
+ if ( (*it).endsWith( QString::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 )
+ {
+ QImage scaledImg = basis->convertToImage().smoothScale( size, size );
+ *basis = QPixmap( 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();
+ QString 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
+ QCString 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,
+ SLOT( setOnlineStatus( const Kopete::OnlineStatus&, const QString& ) ),
+ account, actionName );
+ }
+ else
+ {
+ action=new OnlineStatusAction( status, caption, status.iconFor(account) , account, actionName );
+ connect(action,SIGNAL(activated(const Kopete::OnlineStatus&)) ,
+ account, 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 QString &text, const QIconSet &pix, QObject *parent, const char *name)
+ : KAction( text, pix, KShortcut() , parent, name) , m_status(status)
+{
+ connect(this,SIGNAL(activated()),this,SLOT(slotActivated()));
+}
+
+void OnlineStatusAction::slotActivated()
+{
+ emit activated(m_status);
+}
+
+
+} //END namespace Kopete
+
+#include "kopeteonlinestatusmanager.moc"
+
+// vim: set noet ts=4 sts=4 sw=4:
+