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.
534 lines
14 KiB
534 lines
14 KiB
/* This file is part of the KDE libraries
|
|
Copyright (C) 2005 Olivier Goffart <ogoffart @ kde.org>
|
|
|
|
This library is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU Library General Public
|
|
License version 2 as published by the Free Software Foundation.
|
|
|
|
This library is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
Library General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Library General Public License
|
|
along with this library; see the file COPYING.LIB. If not, write to
|
|
the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
|
Boston, MA 02111-1307, USA.
|
|
*/
|
|
|
|
#include "knotification.h"
|
|
|
|
#include <kdebug.h>
|
|
#include <kapplication.h>
|
|
#include <knotifyclient.h>
|
|
#include <kmessagebox.h>
|
|
#include <klocale.h>
|
|
#include <kiconloader.h>
|
|
#include <kconfig.h>
|
|
#include <kpassivepopup.h>
|
|
#include <kactivelabel.h>
|
|
#include <kprocess.h>
|
|
#include <kdialog.h>
|
|
#include <kmacroexpander.h>
|
|
#include <kwin.h>
|
|
|
|
|
|
#include <qvbox.h>
|
|
#include <dcopclient.h>
|
|
#include <qcstring.h>
|
|
#include <qguardedptr.h>
|
|
#include <qstylesheet.h>
|
|
#include <qlabel.h>
|
|
#include <qtimer.h>
|
|
#include <qtabwidget.h>
|
|
|
|
|
|
|
|
//TODO, make the KNotification aware of the systemtray.
|
|
#include "kopeteuiglobal.h"
|
|
static WId checkWinId( const QString &/*appName*/, WId senderWinId )
|
|
{
|
|
if(senderWinId==0)
|
|
senderWinId=Kopete::UI::Global::sysTrayWId();
|
|
|
|
return senderWinId;
|
|
}
|
|
|
|
|
|
struct KNotification::Private
|
|
{
|
|
QWidget *widget;
|
|
QString text;
|
|
QStringList actions;
|
|
int level;
|
|
};
|
|
|
|
KNotification::KNotification(QObject *parent) :
|
|
QObject(parent) , d(new Private)
|
|
{
|
|
m_linkClicked = false;
|
|
}
|
|
|
|
KNotification::~KNotification()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
|
|
void KNotification::notifyByExecute(const QString &command, const QString& event,
|
|
const QString& fromApp, const QString& text,
|
|
int winId, int eventId)
|
|
{
|
|
if (!command.isEmpty())
|
|
{
|
|
// kdDebug() << "executing command '" << command << "'" << endl;
|
|
QMap<QChar,QString> subst;
|
|
subst.insert( 'e', event );
|
|
subst.insert( 'a', fromApp );
|
|
subst.insert( 's', text );
|
|
subst.insert( 'w', QString::number( winId ));
|
|
subst.insert( 'i', QString::number( eventId ));
|
|
QString execLine = KMacroExpander::expandMacrosShellQuote( command, subst );
|
|
if ( execLine.isEmpty() )
|
|
execLine = command; // fallback
|
|
|
|
KProcess p;
|
|
p.setUseShell(true);
|
|
p << execLine;
|
|
p.start(KProcess::DontCare);
|
|
// return true;
|
|
}
|
|
//return false;
|
|
}
|
|
|
|
|
|
void KNotification::notifyByMessagebox()
|
|
{
|
|
// ignore empty messages
|
|
if ( d->text.isEmpty() )
|
|
return;
|
|
|
|
QString action=d->actions[0];
|
|
WId winId=d->widget ? d->widget->topLevelWidget()->winId() : 0;
|
|
|
|
if( action.isEmpty())
|
|
{
|
|
// display message box for specified event level
|
|
switch( d->level )
|
|
{
|
|
default:
|
|
case KNotifyClient::Notification:
|
|
KMessageBox::informationWId( winId, d->text, i18n( "Notification" ) );
|
|
break;
|
|
case KNotifyClient::Warning:
|
|
KMessageBox::sorryWId( winId, d->text, i18n( "Warning" ) );
|
|
break;
|
|
case KNotifyClient::Error:
|
|
KMessageBox::errorWId( winId, d->text, i18n( "Error" ) );
|
|
break;
|
|
case KNotifyClient::Catastrophe:
|
|
KMessageBox::errorWId( winId, d->text, i18n( "Fatal" ) );
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{ //we may show the specific action button
|
|
int result=0;
|
|
QGuardedPtr<KNotification> _this=this; //this can be deleted
|
|
switch( d->level )
|
|
{
|
|
default:
|
|
case KNotifyClient::Notification:
|
|
result = KMessageBox::questionYesNo(d->widget, d->text, i18n( "Notification" ), action, KStdGuiItem::cancel(), QString::null, false );
|
|
break;
|
|
case KNotifyClient::Warning:
|
|
result = KMessageBox::warningYesNo( d->widget, d->text, i18n( "Warning" ), action, KStdGuiItem::cancel(), QString::null, false );
|
|
break;
|
|
case KNotifyClient::Error:
|
|
result = KMessageBox::warningYesNo( d->widget, d->text, i18n( "Error" ), action, KStdGuiItem::cancel(), QString::null, false );
|
|
break;
|
|
case KNotifyClient::Catastrophe:
|
|
result = KMessageBox::warningYesNo( d->widget, d->text, i18n( "Fatal" ), action, KStdGuiItem::cancel(), QString::null, false );
|
|
break;
|
|
}
|
|
if(result==KMessageBox::Yes && _this)
|
|
{
|
|
activate(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void KNotification::notifyByPassivePopup(const QPixmap &pix )
|
|
{
|
|
QString appName = QString::fromAscii( KNotifyClient::instance()->instanceName() );
|
|
KIconLoader iconLoader( appName );
|
|
KConfig eventsFile( QString::fromAscii( KNotifyClient::instance()->instanceName()+"/eventsrc" ), true, false, "data");
|
|
KConfigGroup config( &eventsFile, "!Global!" );
|
|
QString iconName = config.readEntry( "IconName", appName );
|
|
QPixmap icon = iconLoader.loadIcon( iconName, KIcon::Small );
|
|
QString title = config.readEntry( "Comment", appName );
|
|
//KPassivePopup::message(title, text, icon, senderWinId);
|
|
|
|
WId winId=d->widget ? d->widget->topLevelWidget()->winId() : 0;
|
|
|
|
KPassivePopup *pop = new KPassivePopup( checkWinId(appName, winId) );
|
|
QObject::connect(this, SIGNAL(closed()), pop, SLOT(deleteLater()));
|
|
|
|
QVBox *vb = pop->standardView( title, pix.isNull() ? d->text: QString::null , icon );
|
|
QVBox *vb2=vb;
|
|
|
|
if(!pix.isNull())
|
|
{
|
|
QHBox *hb = new QHBox(vb);
|
|
hb->setSpacing(KDialog::spacingHint());
|
|
QLabel *pil=new QLabel(hb);
|
|
pil->setPixmap(pix);
|
|
pil->setScaledContents(true);
|
|
if(pix.height() > 80 && pix.height() > pix.width() )
|
|
{
|
|
pil->setMaximumHeight(80);
|
|
pil->setMaximumWidth(80*pix.width()/pix.height());
|
|
}
|
|
else if(pix.width() > 80 && pix.height() <= pix.width())
|
|
{
|
|
pil->setMaximumWidth(80);
|
|
pil->setMaximumHeight(80*pix.height()/pix.width());
|
|
}
|
|
vb=new QVBox(hb);
|
|
QLabel *msg = new QLabel( d->text, vb, "msg_label" );
|
|
msg->setAlignment( AlignLeft );
|
|
}
|
|
|
|
|
|
if ( !d->actions.isEmpty() )
|
|
{
|
|
QString linkCode=QString::fromLatin1("<p align=\"right\">");
|
|
int i=0;
|
|
for ( QStringList::ConstIterator it = d->actions.begin() ; it != d->actions.end(); ++it )
|
|
{
|
|
i++;
|
|
linkCode+=QString::fromLatin1(" <a href=\"%1\">%2</a> ").arg( QString::number(i) , QStyleSheet::escape(*it) );
|
|
}
|
|
linkCode+=QString::fromLatin1("</p>");
|
|
KActiveLabel *link = new KActiveLabel(linkCode , vb );
|
|
//link->setAlignment( AlignRight );
|
|
QObject::disconnect(link, SIGNAL(linkClicked(const QString &)), link, SLOT(openLink(const QString &)));
|
|
QObject::connect(link, SIGNAL(linkClicked(const QString &)), this, SLOT(slotPopupLinkClicked(const QString &)));
|
|
QObject::connect(link, SIGNAL(linkClicked(const QString &)), pop, SLOT(hide()));
|
|
}
|
|
|
|
pop->setAutoDelete( true );
|
|
//pop->setTimeout(-1);
|
|
|
|
pop->setView( vb2 );
|
|
pop->show();
|
|
|
|
}
|
|
|
|
void KNotification::slotPopupLinkClicked(const QString &adr)
|
|
{
|
|
m_linkClicked = true;
|
|
unsigned int action=adr.toUInt();
|
|
if(action==0)
|
|
return;
|
|
|
|
activate(action);
|
|
|
|
// since we've hidden the message (KNotification::notifyByPassivePopup(const QPixmap &pix ))
|
|
// we must now schedule overselves for deletion
|
|
close();
|
|
}
|
|
|
|
void KNotification::activate(unsigned int action)
|
|
{
|
|
if(action==0)
|
|
emit activated();
|
|
|
|
emit activated(action);
|
|
deleteLater();
|
|
}
|
|
|
|
|
|
void KNotification::close()
|
|
{
|
|
// if the user hasn't clicked the link, and if we got here, it means the dialog closed
|
|
// and we were ignored
|
|
if (!m_linkClicked)
|
|
{
|
|
emit ignored();
|
|
}
|
|
|
|
emit closed();
|
|
deleteLater();
|
|
}
|
|
|
|
|
|
void KNotification::raiseWidget()
|
|
{
|
|
if(!d->widget)
|
|
return;
|
|
|
|
raiseWidget(d->widget);
|
|
}
|
|
|
|
|
|
void KNotification::raiseWidget(QWidget *w)
|
|
{
|
|
//TODO this funciton is far from finished.
|
|
if(w->isTopLevel())
|
|
{
|
|
w->raise();
|
|
KWin::activateWindow( w->winId() );
|
|
}
|
|
else
|
|
{
|
|
QWidget *pw=w->parentWidget();
|
|
raiseWidget(pw);
|
|
|
|
if( QTabWidget *tab_widget=dynamic_cast<QTabWidget*>(pw))
|
|
{
|
|
tab_widget->showPage(w);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
KNotification *KNotification::event( const QString& message , const QString& text,
|
|
const QPixmap& pixmap, QWidget *widget,
|
|
const QStringList &actions, unsigned int flags)
|
|
{
|
|
/* NOTE: this function still use the KNotifyClient,
|
|
* in the future (KDE4) all the function of the knotifyclient will be moved there.
|
|
* Some code here is derived from the old KNotify deamon
|
|
*/
|
|
|
|
int level=KNotifyClient::Default;
|
|
QString sound;
|
|
QString file;
|
|
QString commandline;
|
|
|
|
// get config file
|
|
KConfig eventsFile( QString::fromAscii( KNotifyClient::instance()->instanceName()+"/eventsrc" ), true, false, "data");
|
|
eventsFile.setGroup(message);
|
|
|
|
KConfig configFile( QString::fromAscii( KNotifyClient::instance()->instanceName()+".eventsrc" ), true, false);
|
|
configFile.setGroup(message);
|
|
|
|
int present=KNotifyClient::getPresentation(message);
|
|
if(present==-1)
|
|
present=KNotifyClient::getDefaultPresentation(message);
|
|
if(present==-1)
|
|
present=0;
|
|
|
|
// get sound file name
|
|
if( present & KNotifyClient::Sound ) {
|
|
QString theSound = configFile.readPathEntry( "soundfile" );
|
|
if ( theSound.isEmpty() )
|
|
theSound = eventsFile.readPathEntry( "default_sound" );
|
|
if ( !theSound.isEmpty() )
|
|
sound = theSound;
|
|
}
|
|
|
|
// get log file name
|
|
if( present & KNotifyClient::Logfile ) {
|
|
QString theFile = configFile.readPathEntry( "logfile" );
|
|
if ( theFile.isEmpty() )
|
|
theFile = eventsFile.readPathEntry( "default_logfile" );
|
|
if ( !theFile.isEmpty() )
|
|
file = theFile;
|
|
}
|
|
|
|
// get default event level
|
|
if( present & KNotifyClient::Messagebox )
|
|
level = eventsFile.readNumEntry( "level", 0 );
|
|
|
|
// get command line
|
|
if (present & KNotifyClient::Execute ) {
|
|
commandline = configFile.readPathEntry( "commandline" );
|
|
if ( commandline.isEmpty() )
|
|
commandline = eventsFile.readPathEntry( "default_commandline" );
|
|
}
|
|
|
|
return userEvent( text, pixmap, widget, actions, present , level, sound, file, commandline, flags );
|
|
}
|
|
|
|
KNotification *KNotification::userEvent( const QString& text, const QPixmap& pixmap, QWidget *widget,
|
|
QStringList actions,int present, int level, const QString &sound, const QString &file,
|
|
const QString &commandline, unsigned int flags)
|
|
{
|
|
|
|
/* NOTE: this function still use the KNotifyClient,
|
|
* in the futur (KDE4) all the function of the knotifyclient will be moved there.
|
|
* Some code of this function fome from the old KNotify deamon
|
|
*/
|
|
|
|
|
|
KNotification *notify=new KNotification(widget);
|
|
notify->d->widget=widget;
|
|
notify->d->text=text;
|
|
notify->d->actions=actions;
|
|
notify->d->level=level;
|
|
WId winId=widget ? widget->topLevelWidget()->winId() : 0;
|
|
|
|
|
|
//we will catch some event that will not be fired by the old deamon
|
|
|
|
|
|
//we remove presentation that has been already be played, and we fire the event in the old way
|
|
|
|
|
|
KNotifyClient::userEvent(winId,text,present & ~( KNotifyClient::PassivePopup|KNotifyClient::Messagebox|KNotifyClient::Execute),level,sound,file);
|
|
|
|
|
|
if ( present & KNotifyClient::PassivePopup )
|
|
{
|
|
notify->notifyByPassivePopup( pixmap );
|
|
}
|
|
if ( present & KNotifyClient::Messagebox )
|
|
{
|
|
QTimer::singleShot(0,notify,SLOT(notifyByMessagebox()));
|
|
}
|
|
else //not a message box (because closing the event when a message box is there is suicide)
|
|
if(flags & CloseOnTimeout)
|
|
{
|
|
QTimer::singleShot(6*1000, notify, SLOT(close()));
|
|
}
|
|
if ( present & KNotifyClient::Execute )
|
|
{
|
|
QString appname = QString::fromAscii( KNotifyClient::instance()->instanceName() );
|
|
notify->notifyByExecute(commandline, QString::null,appname,text, winId, 0 );
|
|
}
|
|
|
|
return notify;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* This code is there before i find a great way to perform context-dependent notifications
|
|
* in a way independent of kopete.
|
|
* i'm in fact still using the Will's old code.
|
|
*/
|
|
|
|
|
|
#include "kopeteeventpresentation.h"
|
|
#include "kopetegroup.h"
|
|
#include "kopetenotifydataobject.h"
|
|
#include "kopetenotifyevent.h"
|
|
#include "kopetemetacontact.h"
|
|
#include "kopeteuiglobal.h"
|
|
#include <qimage.h>
|
|
|
|
|
|
static KNotification *performCustomNotifications( QWidget *widget, Kopete::MetaContact * mc, const QString &message, bool& suppress)
|
|
{
|
|
KNotification *n=0L;
|
|
//kdDebug( 14010 ) << k_funcinfo << endl;
|
|
if ( suppress )
|
|
return n;
|
|
|
|
// Anything, including the MC itself, may set suppress and prevent further notifications
|
|
|
|
/* This is a really ugly piece of logic now. The idea is to check for notifications
|
|
* first on the metacontact, then on each of its groups, until something suppresses
|
|
* any further notifications.
|
|
* So on the first run round this loop, dataObj points to the metacontact, and on subsequent
|
|
* iterations it points to one of the contact's groups. The metacontact pointer is maintained
|
|
* so that if a group has a chat notification set for this event, we can call execute() on the MC.
|
|
*/
|
|
|
|
bool checkingMetaContact = true;
|
|
Kopete::NotifyDataObject * dataObj = mc;
|
|
do {
|
|
QString sound;
|
|
QString text;
|
|
|
|
if ( dataObj )
|
|
{
|
|
Kopete::NotifyEvent *evt = dataObj->notifyEvent( message );
|
|
if ( evt )
|
|
{
|
|
suppress = evt->suppressCommon();
|
|
int present = 0;
|
|
// sound
|
|
Kopete::EventPresentation *pres = evt->presentation( Kopete::EventPresentation::Sound );
|
|
if ( pres && pres->enabled() )
|
|
{
|
|
present = present | KNotifyClient::Sound;
|
|
sound = pres->content();
|
|
evt->firePresentation( Kopete::EventPresentation::Sound );
|
|
}
|
|
// message
|
|
if ( ( pres = evt->presentation( Kopete::EventPresentation::Message ) )
|
|
&& pres->enabled() )
|
|
{
|
|
present = present | KNotifyClient::PassivePopup;
|
|
text = pres->content();
|
|
evt->firePresentation( Kopete::EventPresentation::Message );
|
|
}
|
|
// chat
|
|
if ( ( pres = evt->presentation( Kopete::EventPresentation::Chat ) )
|
|
&& pres->enabled() )
|
|
{
|
|
mc->execute();
|
|
evt->firePresentation( Kopete::EventPresentation::Chat );
|
|
}
|
|
// fire the event
|
|
n=KNotification::userEvent( text, mc->photo(), widget, QStringList() , present, 0, sound, QString::null, QString::null , KNotification::CloseOnTimeout);
|
|
}
|
|
}
|
|
|
|
if ( mc )
|
|
{
|
|
if ( checkingMetaContact )
|
|
{
|
|
// only executed on first iteration
|
|
checkingMetaContact = false;
|
|
dataObj = mc->groups().first();
|
|
}
|
|
else
|
|
dataObj = mc->groups().next();
|
|
}
|
|
}
|
|
while ( dataObj && !suppress );
|
|
return n;
|
|
}
|
|
|
|
|
|
|
|
|
|
KNotification *KNotification::event( Kopete::MetaContact *mc, const QString& message ,
|
|
const QString& text, const QPixmap& pixmap, QWidget *widget,
|
|
const QStringList &actions, unsigned int flags)
|
|
{
|
|
if (message.isEmpty()) return 0;
|
|
|
|
bool suppress = false;
|
|
KNotification *n=performCustomNotifications( widget, mc, message, suppress);
|
|
|
|
if ( suppress )
|
|
{
|
|
//kdDebug( 14000 ) << "suppressing common notifications" << endl;
|
|
return n; // custom notifications don't create a single unique id
|
|
}
|
|
else
|
|
{
|
|
//kdDebug( 14000 ) << "carrying out common notifications" << endl;
|
|
return event( message, text, pixmap, widget , actions, flags);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#include "knotification.moc"
|
|
|
|
|
|
|