• Skip to content
  • Skip to link menu
KDE 4.2 API Reference
  • KDE API Reference
  • API Reference
  • Sitemap
  • Contact Us
 

KNotify

notifybypopup.cpp

Go to the documentation of this file.
00001 /*
00002    Copyright (C) 2005-2006 by Olivier Goffart <ogoffart at kde.org>
00003    Copyright (C) 2008 by Dmitry Suzdalev <dimsuz@gmail.com>
00004 
00005    This program is free software; you can redistribute it and/or modify
00006    it under the terms of the GNU General Public License as published by
00007    the Free Software Foundation; either version 2, or (at your option)
00008    any later version.
00009 
00010    This program is distributed in the hope that it will be useful,
00011    but WITHOUT ANY WARRANTY; without even the implied warranty of
00012    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00013    GNU General Public License for more details.
00014 
00015    You should have received a copy of the GNU General Public License
00016    along with this program; if not, write to the Free Software
00017    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
00018 
00019  */
00020 
00021 #include "notifybypopup.h"
00022 #include "knotifyconfig.h"
00023 
00024 #include <kdebug.h>
00025 #include <kpassivepopup.h>
00026 #include <kiconloader.h>
00027 #include <kdialog.h>
00028 #include <khbox.h>
00029 #include <kvbox.h>
00030 
00031 #include <QLabel>
00032 #include <QTextDocument>
00033 #include <QApplication>
00034 #include <QDesktopWidget>
00035 #include <QDBusConnection>
00036 #include <QDBusConnectionInterface>
00037 #include <kconfiggroup.h>
00038 
00039 static const QString dbusServiceName = "org.kde.VisualNotifications";
00040 static const QString dbusInterfaceName = "org.kde.VisualNotifications";
00041 static const QString dbusPath = "/VisualNotifications";
00042 
00043 NotifyByPopup::NotifyByPopup(QObject *parent) 
00044   : KNotifyPlugin(parent) , m_animationTimer(0), m_dbusServiceExists(false)
00045 {
00046     QRect screen = QApplication::desktop()->availableGeometry();
00047     m_nextPosition = screen.top();
00048 
00049     // check if service already exists on plugin instantiation
00050     QDBusConnectionInterface* interface = QDBusConnection::sessionBus().interface();
00051     m_dbusServiceExists = interface && interface->isServiceRegistered(dbusServiceName);
00052 
00053     if( m_dbusServiceExists )
00054         kDebug(300) << "using" << dbusServiceName << "for popups";
00055 
00056     // to catch register/unregister events from service in runtime
00057     connect(interface, SIGNAL(serviceOwnerChanged(const QString&, const QString&, const QString&)),
00058             SLOT(slotServiceOwnerChanged(const QString&, const QString&, const QString&)));
00059 }
00060 
00061 
00062 NotifyByPopup::~NotifyByPopup()
00063 {
00064     foreach(KPassivePopup *p,m_popups)
00065         p->deleteLater();
00066 }
00067 
00068 void NotifyByPopup::notify( int id, KNotifyConfig * config )
00069 {
00070     kDebug(300) << id;
00071 
00072     // if Notifications DBus service exists on bus,
00073     // it'll be used instead
00074     if(m_dbusServiceExists)
00075     {
00076         sendNotificationDBus(id, 0, config);
00077         return;
00078     }
00079 
00080     if(m_popups.contains(id))
00081     {
00082         //the popup is already shown
00083         finish(id);
00084         return;
00085     }
00086 
00087     KPassivePopup *pop = new KPassivePopup( config->winId );
00088     m_popups[id]=pop;
00089     fillPopup(pop,id,config);
00090     QRect screen = QApplication::desktop()->availableGeometry();
00091     pop->setAutoDelete( true );
00092     connect(pop, SIGNAL(destroyed()) , this, SLOT(slotPopupDestroyed()) );
00093 
00094     // NOTE readEntry here is not KConfigGroup::readEntry - this is a custom class
00095     // It returns QString.
00096     QString timeoutStr = config->readEntry( "Timeout" );
00097     pop->setTimeout( !timeoutStr.isEmpty() ? timeoutStr.toInt() : 0 );
00098 
00099     pop->show(QPoint(screen.left() + screen.width()/2  , m_nextPosition));
00100     m_nextPosition+=pop->height();
00101 }
00102 
00103 void NotifyByPopup::slotPopupDestroyed( )
00104 {
00105     const QObject *s=sender();
00106     if(!s)
00107         return;
00108     QMap<int,KPassivePopup*>::iterator it;
00109     for(it=m_popups.begin() ; it!=m_popups.end(); ++it   )
00110     {
00111         QObject *o=it.value();
00112         if(o && o == s)
00113         {
00114             finish(it.key());
00115             m_popups.remove(it.key());
00116             break;
00117         }
00118     }
00119 
00120     //relocate popup
00121     if(!m_animationTimer)
00122         m_animationTimer = startTimer(10);
00123 }
00124 
00125 void NotifyByPopup::timerEvent(QTimerEvent * event)
00126 {
00127     if(event->timerId() != m_animationTimer)
00128         return KNotifyPlugin::timerEvent(event);
00129     
00130     bool cont=false;
00131     QRect screen = QApplication::desktop()->availableGeometry();
00132     m_nextPosition = screen.top();
00133     foreach(KPassivePopup *pop,m_popups)
00134     {
00135         int posy=pop->pos().y();
00136         if(posy > m_nextPosition)
00137         {
00138             posy=qMax(posy-5,m_nextPosition);
00139             m_nextPosition = posy + pop->height();
00140             cont = cont || posy != m_nextPosition;
00141             pop->move(pop->pos().x(),posy);
00142         }
00143         else
00144             m_nextPosition += pop->height();
00145     }
00146     if(!cont)
00147     {
00148         killTimer(m_animationTimer);
00149         m_animationTimer = 0;
00150     }
00151 }
00152 
00153 void NotifyByPopup::slotLinkClicked( const QString &adr )
00154 {
00155     unsigned int id=adr.section("/" , 0 , 0).toUInt();
00156     unsigned int action=adr.section("/" , 1 , 1).toUInt();
00157 
00158 //  kDebug(300) << id << " " << action;
00159         
00160     if(id==0 || action==0)
00161         return;
00162         
00163     emit actionInvoked(id,action);
00164 }
00165 
00166 void NotifyByPopup::close( int id )
00167 {
00168     // if Notifications DBus service exists on bus,
00169     // it'll be used instead
00170     if( m_dbusServiceExists)
00171     {
00172         closeNotificationDBus(id);
00173         return;
00174     }
00175 
00176     delete m_popups[id];
00177     m_popups.remove(id);
00178 }
00179 
00180 void NotifyByPopup::update(int id, KNotifyConfig * config)
00181 {
00182     // if Notifications DBus service exists on bus,
00183     // it'll be used instead
00184     if( m_dbusServiceExists)
00185     {
00186         sendNotificationDBus(id, id, config);
00187         return;
00188     }
00189 
00190     if(!m_popups.contains(id))
00191         return;
00192     KPassivePopup *p=m_popups[id];
00193     fillPopup(p, id, config);
00194 }
00195 
00196 void NotifyByPopup::fillPopup(KPassivePopup *pop,int id,KNotifyConfig * config)
00197 {
00198     QString appCaption, iconName;
00199     getAppCaptionAndIconName(config, &appCaption, &iconName);
00200 
00201     KIconLoader iconLoader(iconName);
00202     QPixmap appIcon = iconLoader.loadIcon( iconName, KIconLoader::Small );
00203 
00204     KVBox *vb = pop->standardView( appCaption , config->pix.isNull() ? config->text : QString() , appIcon );
00205     KVBox *vb2 = vb;
00206 
00207     if(!config->pix.isNull())
00208     {
00209         const QPixmap &pix=config->pix;
00210         KHBox *hb = new KHBox(vb);
00211         hb->setSpacing(KDialog::spacingHint());
00212         QLabel *pil=new QLabel(hb);
00213         pil->setPixmap( config->pix );
00214         pil->setScaledContents(true);
00215         if(pix.height() > 80 && pix.height() > pix.width() )
00216         {
00217             pil->setMaximumHeight(80);
00218             pil->setMaximumWidth(80*pix.width()/pix.height());
00219         }
00220         else if(pix.width() > 80 && pix.height() <= pix.width())
00221         {
00222             pil->setMaximumWidth(80);
00223             pil->setMaximumHeight(80*pix.height()/pix.width());
00224         }
00225         vb=new KVBox(hb);
00226         QLabel *msg = new QLabel( config->text, vb );
00227         msg->setAlignment( Qt::AlignLeft );
00228     }
00229 
00230 
00231     if ( !config->actions.isEmpty() )
00232     {
00233         QString linkCode=QString::fromLatin1("<p align=\"right\">");
00234         int i=0;
00235         foreach ( const QString & it , config->actions ) 
00236         {
00237             i++;
00238             linkCode+=QString::fromLatin1("&nbsp;<a href=\"%1/%2\">%3</a> ").arg( id ).arg( i ).arg( Qt::escape(it) );
00239         }
00240         linkCode+=QString::fromLatin1("</p>");
00241         QLabel *link = new QLabel(linkCode , vb );
00242         link->setTextInteractionFlags(Qt::LinksAccessibleByMouse);
00243         link->setOpenExternalLinks(false);
00244         //link->setAlignment( AlignRight );
00245         QObject::connect(link, SIGNAL(linkActivated(const QString &)), this, SLOT(slotLinkClicked(const QString& ) ) );
00246         QObject::connect(link, SIGNAL(linkActivated(const QString &)), pop, SLOT(hide()));
00247     }
00248 
00249     pop->setView( vb2 );
00250 }
00251 
00252 void NotifyByPopup::slotServiceOwnerChanged( const QString & serviceName,
00253         const QString & oldOwner, const QString & newOwner )
00254 {
00255     if(serviceName == dbusServiceName)
00256     {
00257         if(oldOwner.isEmpty())
00258         {
00259             // delete existing popups if any
00260             // NOTE: can't use qDeletaAll here, because it can happen that
00261             // some passive popup auto-deletes itself and slotPopupDestroyed
00262             // will get called *while* qDeleteAll will be iterating over QMap
00263             // and that slot will remove corresponding key from QMap which will
00264             // break qDeleteAll.
00265             // This foreach takes this possibility into account:
00266             foreach ( int id, m_popups.keys() )
00267                 delete m_popups.value(id,0);
00268             m_popups.clear();
00269 
00270             m_dbusServiceExists = true;
00271             kDebug(300) << dbusServiceName << " was registered on bus, now using it to show popups";
00272 
00273             // not forgetting to clear old assignments if any
00274             m_idMap.clear();
00275 
00276             // connect to action invocation signals
00277             bool connected = QDBusConnection::sessionBus().connect(QString(), // from any service
00278                     dbusPath,
00279                     dbusInterfaceName,
00280                     "ActionInvoked",
00281                     this,
00282                     SLOT(slotDBusNotificationActionInvoked(uint,const QString&)));
00283             if (!connected) {
00284                 kDebug(300) << "warning: failed to connect to ActionInvoked dbus signal";
00285             }
00286 
00287             connected = QDBusConnection::sessionBus().connect(QString(), // from any service
00288                     dbusPath,
00289                     dbusInterfaceName,
00290                     "NotificationClosed",
00291                     this,
00292                     SLOT(slotDBusNotificationClosed(uint,uint)));
00293             if (!connected) {
00294                 kDebug(300) << "warning: failed to connect to NotificationClosed dbus signal";
00295             }
00296         }
00297         if(newOwner.isEmpty())
00298         {
00299             m_dbusServiceExists = false;
00300             // tell KNotify that all existing notifications which it sent
00301             // to DBus had been closed
00302             foreach (int id, m_idMap.keys()) {
00303                 finished(id);
00304             }
00305             m_idMap.clear();
00306             kDebug(300) << dbusServiceName << " was unregistered from bus, using passive popups from now on";
00307         }
00308     }
00309 }
00310 
00311 void NotifyByPopup::slotDBusNotificationActionInvoked(uint dbus_id, const QString& actKey)
00312 {
00313     // find out knotify id
00314     int id = m_idMap.key(dbus_id, 0);
00315     if (id == 0) {
00316         kDebug(300) << "failed to find knotify id for dbus_id" << dbus_id;
00317         return;
00318     }
00319     kDebug(300) << "action" << actKey << "invoked for notification " << id;
00320     // emulate link clicking
00321     slotLinkClicked( QString("%1/%2").arg(id).arg(actKey) );
00322     // now close notification - similar to popup behaviour
00323     // (popups are hidden after link activation - see 'connects' of linkActivated signal above)
00324     closeNotificationDBus(id);
00325 }
00326 
00327 void NotifyByPopup::slotDBusNotificationClosed(uint dbus_id, uint reason)
00328 {
00329     Q_UNUSED(reason)
00330     // find out knotify id
00331     int id = m_idMap.key(dbus_id, 0);
00332     if (id == 0) {
00333         kDebug(300) << "failed to find knotify id for dbus_id" << dbus_id;
00334         return;
00335     }
00336     // tell KNotify that this notification has been closed
00337     finished(id);
00338 }
00339 
00340 void NotifyByPopup::getAppCaptionAndIconName(KNotifyConfig *config, QString *appCaption, QString *iconName)
00341 {
00342     KConfigGroup globalgroup(&(*config->eventsfile), "Global");
00343     *appCaption = globalgroup.readEntry("Name", globalgroup.readEntry("Comment", config->appname));
00344     *iconName = globalgroup.readEntry("IconName", config->appname);
00345 }
00346 
00347 void NotifyByPopup::sendNotificationDBus(int id, int replacesId, KNotifyConfig* config)
00348 {
00349     QDBusMessage m = QDBusMessage::createMethodCall( dbusServiceName, dbusPath, dbusInterfaceName, "Notify" );
00350 
00351     // NOTE readEntry here is not KConfigGroup::readEntry - this is a custom class
00352     // It returns QString.
00353     QString timeoutStr = config->readEntry( "Timeout" );
00354     int timeout = !timeoutStr.isEmpty() ? timeoutStr.toInt() : 0;
00355 
00356     // if timeout is still zero, try to set it to default knotify timeout
00357     if (timeout == 0) {
00358         // NOTE: this is a little hack. Currently there's no way to easily determine
00359         // if KNotify will close notification after certain timeout or if it's persistent.
00360         // The only thing that comes to mind is to check Persistent option in config and
00361         // if it's absent, use default timeout that KNotify uses for non-persistent popups
00362         bool persistent = (config->readEntry("Persistent") == "true" ||
00363                            config->readEntry("Persistant") == "true");
00364         if (!persistent) {
00365             timeout = 6*1000;
00366         }
00367     }
00368 
00369     QList<QVariant> args;
00370 
00371     // figure out dbus id to replace if needed
00372     uint dbus_replaces_id = 0;
00373     if (replacesId != 0 ) {
00374         dbus_replaces_id = m_idMap.value(replacesId, 0);
00375     }
00376 
00377     QString appCaption, iconName;
00378     getAppCaptionAndIconName(config, &appCaption, &iconName);
00379 
00380     args.append( appCaption ); // app_name
00381     args.append( dbus_replaces_id ); // replaces_id
00382     args.append( config->eventid ); // event_id
00383     args.append( iconName ); // app_icon
00384     args.append( QString()); // summary
00385     args.append( config->text ); // body
00386     // galago spec defines action list to be list like
00387     // (act_id1, action1, act_id2, action2, ...)
00388     //
00389     // assign id's to actions like it's done in fillPopup() method
00390     // (i.e. starting from 1)
00391     QStringList actionList;
00392     int actId = 0;
00393     foreach (const QString& actName, config->actions) {
00394         actId++;
00395         actionList.append(QString::number(actId));
00396         actionList.append(actName);
00397     }
00398 
00399     args.append( actionList ); // actions
00400     args.append( QVariantMap() ); // hints - unused atm
00401     args.append( timeout ); // expire timout
00402 
00403     m.setArguments( args );
00404     QDBusMessage replyMsg = QDBusConnection::sessionBus().call(m);
00405     if(replyMsg.type() == QDBusMessage::ReplyMessage) {
00406         if (!replyMsg.arguments().isEmpty()) {
00407             uint dbus_id = replyMsg.arguments().at(0).toUInt();
00408             m_idMap.insert(id, dbus_id);
00409             kDebug() << "mapping knotify id to dbus id:"<< id << "=>" << dbus_id;
00410         } else {
00411             kDebug() << "error: received reply with no arguments";
00412         }
00413     } else if (replyMsg.type() == QDBusMessage::ErrorMessage) {
00414         kDebug() << "error: failed to send dbus message";
00415     } else {
00416         kDebug() << "unexpected reply type";
00417     }
00418 }
00419 
00420 void NotifyByPopup::closeNotificationDBus(int id)
00421 {
00422     uint dbus_id = m_idMap.value(id, 0);
00423     if (dbus_id == 0) {
00424         kDebug() << "not found dbus id to close";
00425         return;
00426     }
00427 
00428     QDBusMessage m = QDBusMessage::createMethodCall( dbusServiceName, dbusPath, 
00429             dbusInterfaceName, "CloseNotification" );
00430     QList<QVariant> args;
00431     args.append( dbus_id );
00432     m.setArguments( args );
00433     bool queued = QDBusConnection::sessionBus().send(m);
00434     if(!queued)
00435     {
00436         kDebug() << "warning: failed to queue dbus message";
00437     }
00438 }
00439 
00440 #include "notifybypopup.moc"

KNotify

Skip menu "KNotify"
  • Main Page
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Class Members
  • Related Pages

API Reference

Skip menu "API Reference"
  • KCMShell
  • KNotify
  • KStyles
  • Nepomuk Daemons
Generated for API Reference by doxygen 1.5.7
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal