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/kopete/systemtray.cpp

436 lines
12 KiB

/*
systemtray.cpp - Kopete Tray Dock Icon
Copyright (c) 2002 by Nick Betcher <nbetcher@kde.org>
Copyright (c) 2002-2003 by Martijn Klingens <klingens@kde.org>
Copyright (c) 2003-2004 by Olivier Goffart <ogoffart @ kde.org>
Kopete (c) 2002-2005 by the Kopete developers <kopete-devel@kde.org>
*************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
*************************************************************************
*/
#include "systemtray.h"
#include <tqtimer.h>
#include <tqtooltip.h>
#include <tqregexp.h>
#include <twin.h>
#include <kaboutdata.h>
#include <tdeactioncollection.h>
#include <kapplication.h>
#include <kdebug.h>
#include <kiconloader.h>
#include "kopeteuiglobal.h"
#include "kopetechatsessionmanager.h"
#include "kopeteballoon.h"
#include "kopeteprefs.h"
#include "kopetemetacontact.h"
#include "kopeteaccount.h"
#include "kopeteaccountmanager.h"
#include "kopetecontact.h"
#include "kopetewindow.h"
KopeteSystemTray* KopeteSystemTray::s_systemTray = 0L;
KopeteSystemTray* KopeteSystemTray::systemTray( TQWidget *parent, const char* name )
{
if( !s_systemTray )
s_systemTray = new KopeteSystemTray( parent, name );
return s_systemTray;
}
KopeteSystemTray::KopeteSystemTray(TQWidget* parent, const char* name)
: KSystemTray(parent,name)
{
// kdDebug(14010) << "Creating KopeteSystemTray" << endl;
TQToolTip::add( this, kapp->aboutData()->shortDescription() );
mIsBlinkIcon = false;
mIsBlinking = false;
mBlinkTimer = new TQTimer(this, "mBlinkTimer");
mKopeteIcon = loadIcon("kopete");
connect(mBlinkTimer, TQT_SIGNAL(timeout()), this, TQT_SLOT(slotBlink()));
connect(Kopete::ChatSessionManager::self() , TQT_SIGNAL(newEvent(Kopete::MessageEvent*)),
this, TQT_SLOT(slotNewEvent(Kopete::MessageEvent*)));
connect(KopetePrefs::prefs(), TQT_SIGNAL(saved()), this, TQT_SLOT(slotConfigChanged()));
connect(Kopete::AccountManager::self(),
TQT_SIGNAL(accountOnlineStatusChanged(Kopete::Account *,
const Kopete::OnlineStatus &, const Kopete::OnlineStatus &)),
this, TQT_SLOT(slotReevaluateAccountStates()));
// the slot called by default by the quit action, KSystemTray::maybeQuit(),
// just closes the parent window, which is hard to distinguish in that window's closeEvent()
// from a click on the window's close widget
// in the quit case, we want to quit the application
// in the close widget click case, we only want to hide the parent window
// so instead, we make it call our general purpose quit slot on the window, which causes a window close and everything else we need
// KDE4 - app will have to listen for quitSelected instead
TDEAction *quit = actionCollection()->action( "file_quit" );
quit->disconnect();
KopeteWindow *myParent = static_cast<KopeteWindow *>( parent );
connect( quit, TQT_SIGNAL( activated() ), myParent, TQT_SLOT( slotQuit() ) );
//setPixmap(mKopeteIcon);
slotReevaluateAccountStates();
slotConfigChanged();
m_balloon=0l;
}
KopeteSystemTray::~KopeteSystemTray()
{
// kdDebug(14010) << "[KopeteSystemTray] ~KopeteSystemTray" << endl;
// delete mBlinkTimer;
Kopete::UI::Global::setSysTrayWId( 0 );
}
void KopeteSystemTray::mousePressEvent( TQMouseEvent *me )
{
if (
(me->button() == Qt::MidButton ||
(me->button() == Qt::LeftButton && KopetePrefs::prefs()->trayflashNotifyLeftClickOpensMessage())) &&
mIsBlinking )
{
mouseDoubleClickEvent( me );
return;
}
KSystemTray::mousePressEvent( me );
}
void KopeteSystemTray::mouseDoubleClickEvent( TQMouseEvent *me )
{
if ( !mIsBlinking )
{
KSystemTray::mousePressEvent( me );
}
else
{
if(!mEventList.isEmpty())
mEventList.first()->apply();
}
}
void KopeteSystemTray::contextMenuAboutToShow( TDEPopupMenu *me )
{
//kdDebug(14010) << k_funcinfo << "Called." << endl;
emit aboutToShowMenu( me );
}
void KopeteSystemTray::startBlink( const TQString &icon )
{
startBlink( TDEGlobal::iconLoader()->loadIcon( icon , KIcon::Panel ) );
}
void KopeteSystemTray::startBlink( const TQPixmap &icon )
{
mBlinkIcon = icon;
if ( mBlinkTimer->isActive() == false )
{
mIsBlinkIcon = true;
mIsBlinking = true;
mBlinkTimer->start( 1000, false );
}
else
{
mBlinkTimer->stop();
mIsBlinkIcon = true;
mIsBlinking = true;
mBlinkTimer->start( 1000, false );
}
}
void KopeteSystemTray::startBlink( const TQMovie &movie )
{
//kdDebug( 14010 ) << k_funcinfo << "starting movie." << endl;
const_cast<TQMovie &>( movie ).unpause();
setMovie( movie );
mIsBlinking = true;
}
void KopeteSystemTray::startBlink()
{
if ( mMovie.isNull() )
mMovie = TDEGlobal::iconLoader()->loadMovie( TQString::fromLatin1( "newmessage" ), KIcon::Panel );
startBlink( mMovie );
}
void KopeteSystemTray::stopBlink()
{
if ( movie() )
kdDebug( 14010 ) << k_funcinfo << "stopping movie." << endl;
else if ( mBlinkTimer->isActive() )
mBlinkTimer->stop();
if ( !mMovie.isNull() )
mMovie.pause();
mIsBlinkIcon = false;
mIsBlinking = false;
//setPixmap( mKopeteIcon );
slotReevaluateAccountStates();
}
void KopeteSystemTray::slotBlink()
{
setPixmap( mIsBlinkIcon ? mKopeteIcon : mBlinkIcon );
mIsBlinkIcon = !mIsBlinkIcon;
}
void KopeteSystemTray::slotNewEvent( Kopete::MessageEvent *event )
{
if( KopetePrefs::prefs()->useStack() )
{
mEventList.prepend( event );
mBalloonEventList.prepend( event );
}
else
{
mEventList.append( event );
mBalloonEventList.append( event );
}
connect(event, TQT_SIGNAL(done(Kopete::MessageEvent*)),
this, TQT_SLOT(slotEventDone(Kopete::MessageEvent*)));
if( event->message().manager() != 0 )
{
if( event->message().manager()->account() )
{
if( !event->message().manager()->account()->isAway() ||
KopetePrefs::prefs()->soundIfAway() )
{
addBalloon();
}
else
{
kdDebug(14000) << k_funcinfo << "Supressing balloon, account is away" << endl;
}
}
}
else
kdDebug(14000) << k_funcinfo << "NULL message().manager()!" << endl;
// tray animation
if ( KopetePrefs::prefs()->trayflashNotify() )
if( mBalloonEventList.count() == mEventList.count() )
startBlink();
else
stopBlink();
}
void KopeteSystemTray::slotEventDone(Kopete::MessageEvent *event)
{
mEventList.remove(event);
removeBalloonEvent(event);
if(mEventList.isEmpty())
stopBlink();
}
void KopeteSystemTray::slotRemoveBalloon()
{
removeBalloonEvent(mBalloonEventList.first());
}
void KopeteSystemTray::removeBalloonEvent(Kopete::MessageEvent *event)
{
bool current= event==mBalloonEventList.first();
mBalloonEventList.remove(event);
if(current && m_balloon)
{
m_balloon->deleteLater();
m_balloon=0l;
if(!mBalloonEventList.isEmpty())
{
//delay the addBalloon to let the time to event be deleted
//in case a contact has been deleted cf Bug 100196
TQTimer::singleShot(0, this, TQT_SLOT(addBalloon()));
}
else
{
if(KopetePrefs::prefs()->trayflashNotify() && !mEventList.isEmpty())
startBlink();
}
}
}
void KopeteSystemTray::addBalloon()
{
/*kdDebug(14010) << k_funcinfo <<
m_balloon << ":" << KopetePrefs::prefs()->showTray() <<
":" << KopetePrefs::prefs()->balloonNotify()
<< ":" << !mBalloonEventList.isEmpty() << endl;*/
if( m_balloon && KopetePrefs::prefs()->useStack() )
{
m_balloon->deleteLater();
m_balloon=0l;
}
if( !m_balloon && KopetePrefs::prefs()->showTray() && KopetePrefs::prefs()->balloonNotify() && !mBalloonEventList.isEmpty() )
{
Kopete::Message msg = mBalloonEventList.first()->message();
if ( msg.from() )
{
TQString msgText = squashMessage( msg );
kdDebug(14010) << k_funcinfo << "msg text=" << msgText << endl;
TQString msgFrom;
if( msg.from()->metaContact() )
msgFrom = msg.from()->metaContact()->displayName();
else
msgFrom = msg.from()->contactId();
m_balloon = new KopeteBalloon(
i18n( "<qt><nobr><b>New Message from %1:</b></nobr><br><nobr>\"%2\"</nobr></qt>" )
.arg( TQStyleSheet::escape( msgFrom ), msgText ), TQString() );
connect(m_balloon, TQT_SIGNAL(signalBalloonClicked()), mBalloonEventList.first() , TQT_SLOT(apply()));
connect(m_balloon, TQT_SIGNAL(signalButtonClicked()), mBalloonEventList.first() , TQT_SLOT(apply()));
connect(m_balloon, TQT_SIGNAL(signalIgnoreButtonClicked()), mBalloonEventList.first() , TQT_SLOT(ignore()));
connect(m_balloon, TQT_SIGNAL(signalTimeout()), this , TQT_SLOT(slotRemoveBalloon()));
m_balloon->setAnchor(mapToGlobal(pos()));
m_balloon->show();
KWin::setOnAllDesktops(m_balloon->winId(), true);
}
}
}
void KopeteSystemTray::slotConfigChanged()
{
// kdDebug(14010) << k_funcinfo << "called." << endl;
if ( KopetePrefs::prefs()->showTray() )
show();
else
hide(); // for users without kicker or a similar docking app
}
void KopeteSystemTray::slotReevaluateAccountStates()
{
// If there is a pending message, we don't need to refresh the system tray now.
// This function will even be called when the animation will stop.
if ( mIsBlinking )
return;
//kdDebug(14010) << k_funcinfo << endl;
bool bOnline = false;
bool bAway = false;
bool bOffline = false;
Kopete::Contact *c = 0;
for (TQPtrListIterator<Kopete::Account> it(Kopete::AccountManager::self()->accounts()); it.current(); ++it)
{
c = it.current()->myself();
if (!c)
continue;
if (c->onlineStatus().status() == Kopete::OnlineStatus::Online)
{
bOnline = true; // at least one contact is online
}
else if (c->onlineStatus().status() == Kopete::OnlineStatus::Away
|| c->onlineStatus().status() == Kopete::OnlineStatus::Invisible)
{
bAway = true; // at least one contact is away or invisible
}
else // this account must be offline (or unknown, which I don't know how to handle)
{
bOffline = true;
}
}
if (!bOnline && !bAway && !bOffline) // special case, no accounts defined (yet)
bOffline = true;
if (bAway)
{
if (!bOnline && !bOffline) // none online and none offline -> all away
setPixmap(loadIcon("kopete_all_away"));
else
setPixmap(loadIcon("kopete_some_away"));
}
else if(bOnline)
{
/*if(bOffline) // at least one offline and at least one online -> some accounts online
setPixmap(loadIcon("kopete_some_online"));
else*/ // none offline and none away -> all online
setPixmap(mKopeteIcon);
}
else // none away and none online -> all offline
{
//kdDebug(14010) << k_funcinfo << "All Accounts offline!" << endl;
setPixmap(loadIcon("kopete_offline"));
}
}
TQString KopeteSystemTray::squashMessage( const Kopete::Message& msg )
{
TQString msgText = msg.parsedBody();
TQRegExp rx( "(<a.*>((http://)?(.+))</a>)" );
rx.setMinimal( true );
if ( rx.search( msgText ) == -1 )
{
// no URLs in text, just pick the first 30 chars of
// the parsed text if necessary. We used parsed text
// so that things like "<knuff>" show correctly
// Escape it after snipping it to not snip entities
msgText =msg.plainBody() ;
if( msgText.length() > 30 )
msgText = msgText.left( 30 ) + TQString::fromLatin1( " ..." );
msgText=Kopete::Message::escape(msgText);
}
else
{
TQString plainText = msg.plainBody();
if ( plainText.length() > 30 )
{
TQString fullUrl = rx.cap( 2 );
TQString shorterUrl;
if ( fullUrl.length() > 30 )
{
TQString urlWithoutProtocol = rx.cap( 4 );
shorterUrl = urlWithoutProtocol.left( 27 )
+ TQString::fromLatin1( "... " );
}
else
{
shorterUrl = fullUrl.left( 27 )
+ TQString::fromLatin1( "... " );
}
// remove message text
msgText = TQString::fromLatin1( "... " ) +
rx.cap( 1 ) +
TQString::fromLatin1( " ..." );
// find last occurrence of URL (the one inside the <a> tag)
int revUrlOffset = msgText.findRev( fullUrl );
msgText.replace( revUrlOffset,
fullUrl.length(), shorterUrl );
}
}
return msgText;
}
#include "systemtray.moc"
// vim: set noet ts=4 sts=4 sw=4: