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