/* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 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 <tqvbox.h> #include <dcopclient.h> #include <tqcstring.h> #include <tqguardedptr.h> #include <tqstylesheet.h> #include <tqlabel.h> #include <tqtimer.h> #include <tqtabwidget.h> //TODO, make the KNotification aware of the systemtray. #include "kopeteuiglobal.h" static WId checkWinId( const TQString &/*appName*/, WId senderWinId ) { if(senderWinId==0) senderWinId=Kopete::UI::Global::sysTrayWId(); return senderWinId; } struct KNotification::Private { TQWidget *widget; TQString text; TQStringList actions; int level; }; KNotification::KNotification(TQObject *parent) : TQObject(parent) , d(new Private) { m_linkClicked = false; } KNotification::~KNotification() { delete d; } void KNotification::notifyByExecute(const TQString &command, const TQString& event, const TQString& fromApp, const TQString& text, int winId, int eventId) { if (!command.isEmpty()) { // kdDebug() << "executing command '" << command << "'" << endl; TQMap<TQChar,TQString> subst; subst.insert( 'e', event ); subst.insert( 'a', fromApp ); subst.insert( 's', text ); subst.insert( 'w', TQString::number( winId )); subst.insert( 'i', TQString::number( eventId )); TQString 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; TQString 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; TQGuardedPtr<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(), TQString::null, false ); break; case KNotifyClient::Warning: result = KMessageBox::warningYesNo( d->widget, d->text, i18n( "Warning" ), action, KStdGuiItem::cancel(), TQString::null, false ); break; case KNotifyClient::Error: result = KMessageBox::warningYesNo( d->widget, d->text, i18n( "Error" ), action, KStdGuiItem::cancel(), TQString::null, false ); break; case KNotifyClient::Catastrophe: result = KMessageBox::warningYesNo( d->widget, d->text, i18n( "Fatal" ), action, KStdGuiItem::cancel(), TQString::null, false ); break; } if(result==KMessageBox::Yes && _this) { activate(0); } } } void KNotification::notifyByPassivePopup(const TQPixmap &pix ) { TQString appName = TQString::fromAscii( KNotifyClient::instance()->instanceName() ); KIconLoader iconLoader( appName ); KConfig eventsFile( TQString::fromAscii( KNotifyClient::instance()->instanceName()+"/eventsrc" ), true, false, "data"); KConfigGroup config( &eventsFile, "!Global!" ); TQString iconName = config.readEntry( "IconName", appName ); TQPixmap icon = iconLoader.loadIcon( iconName, KIcon::Small ); TQString 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) ); TQObject::connect(this, TQT_SIGNAL(closed()), pop, TQT_SLOT(deleteLater())); TQVBox *vb = pop->standardView( title, pix.isNull() ? d->text: TQString::null , icon ); TQVBox *vb2=vb; if(!pix.isNull()) { TQHBox *hb = new TQHBox(vb); hb->setSpacing(KDialog::spacingHint()); TQLabel *pil=new TQLabel(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 TQVBox(hb); TQLabel *msg = new TQLabel( d->text, vb, "msg_label" ); msg->setAlignment( AlignLeft ); } if ( !d->actions.isEmpty() ) { TQString linkCode=TQString::fromLatin1("<p align=\"right\">"); int i=0; for ( TQStringList::ConstIterator it = d->actions.begin() ; it != d->actions.end(); ++it ) { i++; linkCode+=TQString::fromLatin1(" <a href=\"%1\">%2</a> ").arg( TQString::number(i) , TQStyleSheet::escape(*it) ); } linkCode+=TQString::fromLatin1("</p>"); KActiveLabel *link = new KActiveLabel(linkCode , vb ); //link->setAlignment( AlignRight ); TQObject::disconnect(link, TQT_SIGNAL(linkClicked(const TQString &)), link, TQT_SLOT(openLink(const TQString &))); TQObject::connect(link, TQT_SIGNAL(linkClicked(const TQString &)), this, TQT_SLOT(slotPopupLinkClicked(const TQString &))); TQObject::connect(link, TQT_SIGNAL(linkClicked(const TQString &)), pop, TQT_SLOT(hide())); } pop->setAutoDelete( true ); //pop->setTimeout(-1); pop->setView( vb2 ); pop->show(); } void KNotification::slotPopupLinkClicked(const TQString &adr) { m_linkClicked = true; unsigned int action=adr.toUInt(); if(action==0) return; activate(action); // since we've hidden the message (KNotification::notifyByPassivePopup(const TQPixmap &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(TQWidget *w) { //TODO this funciton is far from finished. if(w->isTopLevel()) { w->raise(); KWin::activateWindow( w->winId() ); } else { TQWidget *pw=w->parentWidget(); raiseWidget(pw); if( TQTabWidget *tab_widget=dynamic_cast<TQTabWidget*>(pw)) { tab_widget->showPage(w); } } } KNotification *KNotification::event( const TQString& message , const TQString& text, const TQPixmap& pixmap, TQWidget *widget, const TQStringList &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; TQString sound; TQString file; TQString commandline; // get config file KConfig eventsFile( TQString::fromAscii( KNotifyClient::instance()->instanceName()+"/eventsrc" ), true, false, "data"); eventsFile.setGroup(message); KConfig configFile( TQString::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 ) { TQString 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 ) { TQString 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 TQString& text, const TQPixmap& pixmap, TQWidget *widget, TQStringList actions,int present, int level, const TQString &sound, const TQString &file, const TQString &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 ) { TQTimer::singleShot(0,notify,TQT_SLOT(notifyByMessagebox())); } else //not a message box (because closing the event when a message box is there is suicide) if(flags & CloseOnTimeout) { TQTimer::singleShot(6*1000, notify, TQT_SLOT(close())); } if ( present & KNotifyClient::Execute ) { TQString appname = TQString::fromAscii( KNotifyClient::instance()->instanceName() ); notify->notifyByExecute(commandline, TQString::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 <tqimage.h> static KNotification *performCustomNotifications( TQWidget *widget, Kopete::MetaContact * mc, const TQString &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 { TQString sound; TQString 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, TQStringList() , present, 0, sound, TQString::null, TQString::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 TQString& message , const TQString& text, const TQPixmap& pixmap, TQWidget *widget, const TQStringList &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"