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.
436 lines
12 KiB
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 <kactioncollection.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
|
|
KAction *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( KPopupMenu *me )
|
|
{
|
|
//kdDebug(14010) << k_funcinfo << "Called." << endl;
|
|
emit aboutToShowMenu( me );
|
|
}
|
|
|
|
void KopeteSystemTray::startBlink( const TQString &icon )
|
|
{
|
|
startBlink( KGlobal::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 = KGlobal::iconLoader()->loadMovie( TQString::tqfromLatin1( "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>" )
|
|
.tqarg( 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->onlinetqStatus().status() == Kopete::OnlineStatus::Online)
|
|
{
|
|
bOnline = true; // at least one contact is online
|
|
}
|
|
else if (c->onlinetqStatus().status() == Kopete::OnlineStatus::Away
|
|
|| c->onlinetqStatus().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::tqfromLatin1( " ..." );
|
|
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::tqfromLatin1( "... " );
|
|
}
|
|
else
|
|
{
|
|
shorterUrl = fullUrl.left( 27 )
|
|
+ TQString::tqfromLatin1( "... " );
|
|
}
|
|
// remove message text
|
|
msgText = TQString::tqfromLatin1( "... " ) +
|
|
rx.cap( 1 ) +
|
|
TQString::tqfromLatin1( " ..." );
|
|
// 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:
|