delay a few things on startup, such as setting the visibility mode, which ensures...
[personal-kdebase.git] / runtime / knotify / notifybypopup.cpp
blobb4203d32bbb8d84e9f32e7b171e7747d626c754e
1 /*
2 Copyright (C) 2005-2006 by Olivier Goffart <ogoffart at kde.org>
3 Copyright (C) 2008 by Dmitry Suzdalev <dimsuz@gmail.com>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2, or (at your option)
8 any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 #include "notifybypopup.h"
22 #include "knotifyconfig.h"
24 #include <kdebug.h>
25 #include <kpassivepopup.h>
26 #include <kiconloader.h>
27 #include <kdialog.h>
28 #include <khbox.h>
29 #include <kvbox.h>
31 #include <QLabel>
32 #include <QTextDocument>
33 #include <QApplication>
34 #include <QDesktopWidget>
35 #include <QDBusConnection>
36 #include <QDBusConnectionInterface>
37 #include <kconfiggroup.h>
39 static const QString dbusServiceName = "org.kde.VisualNotifications";
40 static const QString dbusInterfaceName = "org.kde.VisualNotifications";
41 static const QString dbusPath = "/VisualNotifications";
43 NotifyByPopup::NotifyByPopup(QObject *parent)
44 : KNotifyPlugin(parent) , m_animationTimer(0), m_dbusServiceExists(false)
46 QRect screen = QApplication::desktop()->availableGeometry();
47 m_nextPosition = screen.top();
49 // check if service already exists on plugin instantiation
50 QDBusConnectionInterface* interface = QDBusConnection::sessionBus().interface();
51 m_dbusServiceExists = interface && interface->isServiceRegistered(dbusServiceName);
53 if( m_dbusServiceExists )
54 kDebug(300) << "using" << dbusServiceName << "for popups";
56 // to catch register/unregister events from service in runtime
57 connect(interface, SIGNAL(serviceOwnerChanged(const QString&, const QString&, const QString&)),
58 SLOT(slotServiceOwnerChanged(const QString&, const QString&, const QString&)));
62 NotifyByPopup::~NotifyByPopup()
64 foreach(KPassivePopup *p,m_popups)
65 p->deleteLater();
68 void NotifyByPopup::notify( int id, KNotifyConfig * config )
70 kDebug(300) << id;
72 // if Notifications DBus service exists on bus,
73 // it'll be used instead
74 if(m_dbusServiceExists)
76 sendNotificationDBus(id, 0, config);
77 return;
80 if(m_popups.contains(id))
82 //the popup is already shown
83 finish(id);
84 return;
87 KPassivePopup *pop = new KPassivePopup( config->winId );
88 m_popups[id]=pop;
89 fillPopup(pop,id,config);
90 QRect screen = QApplication::desktop()->availableGeometry();
91 pop->setAutoDelete( true );
92 connect(pop, SIGNAL(destroyed()) , this, SLOT(slotPopupDestroyed()) );
94 // NOTE readEntry here is not KConfigGroup::readEntry - this is a custom class
95 // It returns QString.
96 QString timeoutStr = config->readEntry( "Timeout" );
97 pop->setTimeout( !timeoutStr.isEmpty() ? timeoutStr.toInt() : 0 );
99 pop->show(QPoint(screen.left() + screen.width()/2 , m_nextPosition));
100 m_nextPosition+=pop->height();
103 void NotifyByPopup::slotPopupDestroyed( )
105 const QObject *s=sender();
106 if(!s)
107 return;
108 QMap<int,KPassivePopup*>::iterator it;
109 for(it=m_popups.begin() ; it!=m_popups.end(); ++it )
111 QObject *o=it.value();
112 if(o && o == s)
114 finish(it.key());
115 m_popups.remove(it.key());
116 break;
120 //relocate popup
121 if(!m_animationTimer)
122 m_animationTimer = startTimer(10);
125 void NotifyByPopup::timerEvent(QTimerEvent * event)
127 if(event->timerId() != m_animationTimer)
128 return KNotifyPlugin::timerEvent(event);
130 bool cont=false;
131 QRect screen = QApplication::desktop()->availableGeometry();
132 m_nextPosition = screen.top();
133 foreach(KPassivePopup *pop,m_popups)
135 int posy=pop->pos().y();
136 if(posy > m_nextPosition)
138 posy=qMax(posy-5,m_nextPosition);
139 m_nextPosition = posy + pop->height();
140 cont = cont || posy != m_nextPosition;
141 pop->move(pop->pos().x(),posy);
143 else
144 m_nextPosition += pop->height();
146 if(!cont)
148 killTimer(m_animationTimer);
149 m_animationTimer = 0;
153 void NotifyByPopup::slotLinkClicked( const QString &adr )
155 unsigned int id=adr.section("/" , 0 , 0).toUInt();
156 unsigned int action=adr.section("/" , 1 , 1).toUInt();
158 // kDebug(300) << id << " " << action;
160 if(id==0 || action==0)
161 return;
163 emit actionInvoked(id,action);
166 void NotifyByPopup::close( int id )
168 // if Notifications DBus service exists on bus,
169 // it'll be used instead
170 if( m_dbusServiceExists)
172 closeNotificationDBus(id);
173 return;
176 delete m_popups[id];
177 m_popups.remove(id);
180 void NotifyByPopup::update(int id, KNotifyConfig * config)
182 // if Notifications DBus service exists on bus,
183 // it'll be used instead
184 if( m_dbusServiceExists)
186 sendNotificationDBus(id, id, config);
187 return;
190 if(!m_popups.contains(id))
191 return;
192 KPassivePopup *p=m_popups[id];
193 fillPopup(p, id, config);
196 void NotifyByPopup::fillPopup(KPassivePopup *pop,int id,KNotifyConfig * config)
198 QString appCaption, iconName;
199 getAppCaptionAndIconName(config, &appCaption, &iconName);
201 KIconLoader iconLoader(iconName);
202 QPixmap appIcon = iconLoader.loadIcon( iconName, KIconLoader::Small );
204 KVBox *vb = pop->standardView( appCaption , config->pix.isNull() ? config->text : QString() , appIcon );
205 KVBox *vb2 = vb;
207 if(!config->pix.isNull())
209 const QPixmap &pix=config->pix;
210 KHBox *hb = new KHBox(vb);
211 hb->setSpacing(KDialog::spacingHint());
212 QLabel *pil=new QLabel(hb);
213 pil->setPixmap( config->pix );
214 pil->setScaledContents(true);
215 if(pix.height() > 80 && pix.height() > pix.width() )
217 pil->setMaximumHeight(80);
218 pil->setMaximumWidth(80*pix.width()/pix.height());
220 else if(pix.width() > 80 && pix.height() <= pix.width())
222 pil->setMaximumWidth(80);
223 pil->setMaximumHeight(80*pix.height()/pix.width());
225 vb=new KVBox(hb);
226 QLabel *msg = new QLabel( config->text, vb );
227 msg->setAlignment( Qt::AlignLeft );
231 if ( !config->actions.isEmpty() )
233 QString linkCode=QString::fromLatin1("<p align=\"right\">");
234 int i=0;
235 foreach ( const QString & it , config->actions )
237 i++;
238 linkCode+=QString::fromLatin1("&nbsp;<a href=\"%1/%2\">%3</a> ").arg( id ).arg( i ).arg( Qt::escape(it) );
240 linkCode+=QString::fromLatin1("</p>");
241 QLabel *link = new QLabel(linkCode , vb );
242 link->setTextInteractionFlags(Qt::LinksAccessibleByMouse);
243 link->setOpenExternalLinks(false);
244 //link->setAlignment( AlignRight );
245 QObject::connect(link, SIGNAL(linkActivated(const QString &)), this, SLOT(slotLinkClicked(const QString& ) ) );
246 QObject::connect(link, SIGNAL(linkActivated(const QString &)), pop, SLOT(hide()));
249 pop->setView( vb2 );
252 void NotifyByPopup::slotServiceOwnerChanged( const QString & serviceName,
253 const QString & oldOwner, const QString & newOwner )
255 if(serviceName == dbusServiceName)
257 if(oldOwner.isEmpty())
259 // delete existing popups if any
260 // NOTE: can't use qDeletaAll here, because it can happen that
261 // some passive popup auto-deletes itself and slotPopupDestroyed
262 // will get called *while* qDeleteAll will be iterating over QMap
263 // and that slot will remove corresponding key from QMap which will
264 // break qDeleteAll.
265 // This foreach takes this possibility into account:
266 foreach ( int id, m_popups.keys() )
267 delete m_popups.value(id,0);
268 m_popups.clear();
270 m_dbusServiceExists = true;
271 kDebug(300) << dbusServiceName << " was registered on bus, now using it to show popups";
273 // not forgetting to clear old assignments if any
274 m_idMap.clear();
276 // connect to action invocation signals
277 bool connected = QDBusConnection::sessionBus().connect(QString(), // from any service
278 dbusPath,
279 dbusInterfaceName,
280 "ActionInvoked",
281 this,
282 SLOT(slotDBusNotificationActionInvoked(uint,const QString&)));
283 if (!connected) {
284 kDebug(300) << "warning: failed to connect to ActionInvoked dbus signal";
287 connected = QDBusConnection::sessionBus().connect(QString(), // from any service
288 dbusPath,
289 dbusInterfaceName,
290 "NotificationClosed",
291 this,
292 SLOT(slotDBusNotificationClosed(uint,uint)));
293 if (!connected) {
294 kDebug(300) << "warning: failed to connect to NotificationClosed dbus signal";
297 if(newOwner.isEmpty())
299 m_dbusServiceExists = false;
300 // tell KNotify that all existing notifications which it sent
301 // to DBus had been closed
302 foreach (int id, m_idMap.keys()) {
303 finished(id);
305 m_idMap.clear();
306 kDebug(300) << dbusServiceName << " was unregistered from bus, using passive popups from now on";
311 void NotifyByPopup::slotDBusNotificationActionInvoked(uint dbus_id, const QString& actKey)
313 // find out knotify id
314 int id = m_idMap.key(dbus_id, 0);
315 if (id == 0) {
316 kDebug(300) << "failed to find knotify id for dbus_id" << dbus_id;
317 return;
319 kDebug(300) << "action" << actKey << "invoked for notification " << id;
320 // emulate link clicking
321 slotLinkClicked( QString("%1/%2").arg(id).arg(actKey) );
322 // now close notification - similar to popup behaviour
323 // (popups are hidden after link activation - see 'connects' of linkActivated signal above)
324 closeNotificationDBus(id);
327 void NotifyByPopup::slotDBusNotificationClosed(uint dbus_id, uint reason)
329 Q_UNUSED(reason)
330 // find out knotify id
331 int id = m_idMap.key(dbus_id, 0);
332 if (id == 0) {
333 kDebug(300) << "failed to find knotify id for dbus_id" << dbus_id;
334 return;
336 // tell KNotify that this notification has been closed
337 finished(id);
340 void NotifyByPopup::getAppCaptionAndIconName(KNotifyConfig *config, QString *appCaption, QString *iconName)
342 KConfigGroup globalgroup(&(*config->eventsfile), "Global");
343 *appCaption = globalgroup.readEntry("Name", globalgroup.readEntry("Comment", config->appname));
344 *iconName = globalgroup.readEntry("IconName", config->appname);
347 void NotifyByPopup::sendNotificationDBus(int id, int replacesId, KNotifyConfig* config)
349 QDBusMessage m = QDBusMessage::createMethodCall( dbusServiceName, dbusPath, dbusInterfaceName, "Notify" );
351 // NOTE readEntry here is not KConfigGroup::readEntry - this is a custom class
352 // It returns QString.
353 QString timeoutStr = config->readEntry( "Timeout" );
354 int timeout = !timeoutStr.isEmpty() ? timeoutStr.toInt() : 0;
356 // if timeout is still zero, try to set it to default knotify timeout
357 if (timeout == 0) {
358 // NOTE: this is a little hack. Currently there's no way to easily determine
359 // if KNotify will close notification after certain timeout or if it's persistent.
360 // The only thing that comes to mind is to check Persistent option in config and
361 // if it's absent, use default timeout that KNotify uses for non-persistent popups
362 bool persistent = (config->readEntry("Persistent") == "true" ||
363 config->readEntry("Persistant") == "true");
364 if (!persistent) {
365 timeout = 6*1000;
369 QList<QVariant> args;
371 // figure out dbus id to replace if needed
372 uint dbus_replaces_id = 0;
373 if (replacesId != 0 ) {
374 dbus_replaces_id = m_idMap.value(replacesId, 0);
377 QString appCaption, iconName;
378 getAppCaptionAndIconName(config, &appCaption, &iconName);
380 args.append( appCaption ); // app_name
381 args.append( dbus_replaces_id ); // replaces_id
382 args.append( config->eventid ); // event_id
383 args.append( iconName ); // app_icon
384 args.append( QString()); // summary
385 args.append( config->text ); // body
386 // galago spec defines action list to be list like
387 // (act_id1, action1, act_id2, action2, ...)
389 // assign id's to actions like it's done in fillPopup() method
390 // (i.e. starting from 1)
391 QStringList actionList;
392 int actId = 0;
393 foreach (const QString& actName, config->actions) {
394 actId++;
395 actionList.append(QString::number(actId));
396 actionList.append(actName);
399 args.append( actionList ); // actions
400 args.append( QVariantMap() ); // hints - unused atm
401 args.append( timeout ); // expire timout
403 m.setArguments( args );
404 QDBusMessage replyMsg = QDBusConnection::sessionBus().call(m);
405 if(replyMsg.type() == QDBusMessage::ReplyMessage) {
406 if (!replyMsg.arguments().isEmpty()) {
407 uint dbus_id = replyMsg.arguments().at(0).toUInt();
408 m_idMap.insert(id, dbus_id);
409 kDebug() << "mapping knotify id to dbus id:"<< id << "=>" << dbus_id;
410 } else {
411 kDebug() << "error: received reply with no arguments";
413 } else if (replyMsg.type() == QDBusMessage::ErrorMessage) {
414 kDebug() << "error: failed to send dbus message";
415 } else {
416 kDebug() << "unexpected reply type";
420 void NotifyByPopup::closeNotificationDBus(int id)
422 uint dbus_id = m_idMap.value(id, 0);
423 if (dbus_id == 0) {
424 kDebug() << "not found dbus id to close";
425 return;
428 QDBusMessage m = QDBusMessage::createMethodCall( dbusServiceName, dbusPath,
429 dbusInterfaceName, "CloseNotification" );
430 QList<QVariant> args;
431 args.append( dbus_id );
432 m.setArguments( args );
433 bool queued = QDBusConnection::sessionBus().send(m);
434 if(!queued)
436 kDebug() << "warning: failed to queue dbus message";
440 #include "notifybypopup.moc"