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)
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"
25 #include <kpassivepopup.h>
26 #include <kiconloader.h>
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
)
68 void NotifyByPopup::notify( int id
, KNotifyConfig
* config
)
72 // if Notifications DBus service exists on bus,
73 // it'll be used instead
74 if(m_dbusServiceExists
)
76 sendNotificationDBus(id
, 0, config
);
80 if(m_popups
.contains(id
))
82 //the popup is already shown
87 KPassivePopup
*pop
= new KPassivePopup( config
->winId
);
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();
108 QMap
<int,KPassivePopup
*>::iterator it
;
109 for(it
=m_popups
.begin() ; it
!=m_popups
.end(); ++it
)
111 QObject
*o
=it
.value();
115 m_popups
.remove(it
.key());
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
);
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
);
144 m_nextPosition
+= pop
->height();
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)
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
);
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
);
190 if(!m_popups
.contains(id
))
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
);
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());
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\">");
235 foreach ( const QString
& it
, config
->actions
)
238 linkCode
+=QString::fromLatin1(" <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()));
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
265 // This foreach takes this possibility into account:
266 foreach ( int id
, m_popups
.keys() )
267 delete m_popups
.value(id
,0);
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
276 // connect to action invocation signals
277 bool connected
= QDBusConnection::sessionBus().connect(QString(), // from any service
282 SLOT(slotDBusNotificationActionInvoked(uint
,const QString
&)));
284 kDebug(300) << "warning: failed to connect to ActionInvoked dbus signal";
287 connected
= QDBusConnection::sessionBus().connect(QString(), // from any service
290 "NotificationClosed",
292 SLOT(slotDBusNotificationClosed(uint
,uint
)));
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()) {
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);
316 kDebug(300) << "failed to find knotify id for dbus_id" << dbus_id
;
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
)
330 // find out knotify id
331 int id
= m_idMap
.key(dbus_id
, 0);
333 kDebug(300) << "failed to find knotify id for dbus_id" << dbus_id
;
336 // tell KNotify that this notification has been closed
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
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");
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
;
393 foreach (const QString
& actName
, config
->actions
) {
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
;
411 kDebug() << "error: received reply with no arguments";
413 } else if (replyMsg
.type() == QDBusMessage::ErrorMessage
) {
414 kDebug() << "error: failed to send dbus message";
416 kDebug() << "unexpected reply type";
420 void NotifyByPopup::closeNotificationDBus(int id
)
422 uint dbus_id
= m_idMap
.value(id
, 0);
424 kDebug() << "not found dbus id to close";
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
);
436 kDebug() << "warning: failed to queue dbus message";
440 #include "notifybypopup.moc"