1 | /* |
2 | Copyright (C) 2005-2009 by Olivier Goffart <ogoffart at kde.org> |
3 | Copyright (C) 2008 by Dmitry Suzdalev <dimsuz@gmail.com> |
4 | |
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. |
9 | |
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. |
14 | |
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. |
18 | |
19 | */ |
20 | |
21 | #include "notifybypopup.h" |
22 | #include "imageconverter.h" |
23 | #include "notifybypopupgrowl.h" |
24 | |
25 | #include <kdebug.h> |
26 | #include <kpassivepopup.h> |
27 | #include <kiconloader.h> |
28 | #include <kdialog.h> |
29 | #include <khbox.h> |
30 | #include <kvbox.h> |
31 | #include <kcharsets.h> |
32 | #include <knotifyconfig.h> |
33 | |
34 | #include <QBuffer> |
35 | #include <QImage> |
36 | #include <QLabel> |
37 | #include <QTextDocument> |
38 | #include <QApplication> |
39 | #include <QDesktopWidget> |
40 | #include <QDBusConnection> |
41 | #include <QDBusConnectionInterface> |
42 | #include <QDBusServiceWatcher> |
43 | #include <QXmlStreamReader> |
44 | #include <kconfiggroup.h> |
45 | |
46 | static const char dbusServiceName[] = "org.freedesktop.Notifications" ; |
47 | static const char dbusInterfaceName[] = "org.freedesktop.Notifications" ; |
48 | static const char dbusPath[] = "/org/freedesktop/Notifications" ; |
49 | |
50 | NotifyByPopup::(QObject *parent) |
51 | : KNotifyPlugin(parent) , m_animationTimer(0), m_dbusServiceExists(false), |
52 | m_dbusServiceCapCacheDirty(true) |
53 | { |
54 | QRect screen = QApplication::desktop()->availableGeometry(); |
55 | m_nextPosition = screen.top(); |
56 | |
57 | // check if service already exists on plugin instantiation |
58 | QDBusConnectionInterface* interface = QDBusConnection::sessionBus().interface(); |
59 | m_dbusServiceExists = interface && interface->isServiceRegistered(dbusServiceName); |
60 | |
61 | if( m_dbusServiceExists ) |
62 | slotServiceOwnerChanged(dbusServiceName, QString(), "_" ); //connect signals |
63 | |
64 | // to catch register/unregister events from service in runtime |
65 | QDBusServiceWatcher *watcher = new QDBusServiceWatcher(this); |
66 | watcher->setConnection(QDBusConnection::sessionBus()); |
67 | watcher->setWatchMode(QDBusServiceWatcher::WatchForOwnerChange); |
68 | watcher->addWatchedService(dbusServiceName); |
69 | connect(watcher, SIGNAL(serviceOwnerChanged(const QString&, const QString&, const QString&)), |
70 | SLOT(slotServiceOwnerChanged(const QString&, const QString&, const QString&))); |
71 | if(!m_dbusServiceExists) |
72 | { |
73 | bool startfdo = false; |
74 | #ifdef Q_WS_WIN |
75 | startfdo = true; |
76 | #else |
77 | if (qgetenv("KDE_FULL_SESSION" ).isEmpty()) |
78 | { |
79 | QDBusMessage message = QDBusMessage::createMethodCall("org.freedesktop.DBus" , |
80 | "/org/freedesktop/DBus" , |
81 | "org.freedesktop.DBus" , |
82 | "ListActivatableNames" ); |
83 | QDBusReply<QStringList> reply = QDBusConnection::sessionBus().call(message); |
84 | if (reply.isValid() && reply.value().contains(dbusServiceName)) { |
85 | startfdo = true; |
86 | // We need to set m_dbusServiceExists to true because dbus might be too slow |
87 | // starting the service and the first call to NotifyByPopup::notify |
88 | // might not have had the service up, by setting this to true we |
89 | // guarantee it will still go through dbus and dbus will do the correct |
90 | // thing and wait for the service to go up |
91 | m_dbusServiceExists = true; |
92 | } |
93 | } |
94 | #endif |
95 | if (startfdo) |
96 | QDBusConnection::sessionBus().interface()->startService(dbusServiceName); |
97 | } |
98 | } |
99 | |
100 | |
101 | NotifyByPopup::() |
102 | { |
103 | foreach(KPassivePopup *p,m_popups) |
104 | p->deleteLater(); |
105 | } |
106 | |
107 | void NotifyByPopup::( int id, KNotifyConfig * config ) |
108 | { |
109 | kDebug() << id << " active notifications:" << m_popups.keys() << m_idMap.keys(); |
110 | |
111 | if(m_popups.contains(id) || m_idMap.contains(id)) |
112 | { |
113 | kDebug() << "the popup is already shown" ; |
114 | finish(id); |
115 | return; |
116 | } |
117 | |
118 | // if Notifications DBus service exists on bus, |
119 | // it'll be used instead |
120 | if(m_dbusServiceExists) |
121 | { |
122 | if(!sendNotificationDBus(id, 0, config)) |
123 | finish(id); //an error ocurred. |
124 | return; |
125 | } |
126 | |
127 | // Default to 6 seconds if no timeout has been defined |
128 | int timeout = config->timeout == -1 ? 6000 : config->timeout; |
129 | |
130 | // if Growl can display our popups, use that instead |
131 | if(NotifyByPopupGrowl::canPopup()) |
132 | { |
133 | KNotifyConfig *c = ensurePopupCompatibility( config ); |
134 | |
135 | QString appCaption, iconName; |
136 | getAppCaptionAndIconName(c, &appCaption, &iconName); |
137 | |
138 | KIconLoader iconLoader(iconName); |
139 | QPixmap appIcon = iconLoader.loadIcon( iconName, KIconLoader::Small ); |
140 | |
141 | NotifyByPopupGrowl::popup( &appIcon, timeout, appCaption, c->text ); |
142 | |
143 | // Finish immediately, because current NotifyByPopupGrowl can't callback |
144 | finish(id); |
145 | delete c; |
146 | return; |
147 | } |
148 | |
149 | KPassivePopup *pop = new KPassivePopup( config->winId ); |
150 | m_popups[id]=pop; |
151 | fillPopup(pop,id,config); |
152 | QRect screen = QApplication::desktop()->availableGeometry(); |
153 | pop->setAutoDelete( true ); |
154 | connect(pop, SIGNAL(destroyed()) , this, SLOT(slotPopupDestroyed()) ); |
155 | |
156 | pop->setTimeout(timeout); |
157 | pop->adjustSize(); |
158 | pop->show(QPoint(screen.left() + screen.width()/2 - pop->width()/2 , m_nextPosition)); |
159 | m_nextPosition+=pop->height(); |
160 | } |
161 | |
162 | void NotifyByPopup::( ) |
163 | { |
164 | const QObject *s=sender(); |
165 | if(!s) |
166 | return; |
167 | QMap<int,KPassivePopup*>::iterator it; |
168 | for(it=m_popups.begin() ; it!=m_popups.end(); ++it ) |
169 | { |
170 | QObject *o=it.value(); |
171 | if(o && o == s) |
172 | { |
173 | finish(it.key()); |
174 | m_popups.remove(it.key()); |
175 | break; |
176 | } |
177 | } |
178 | |
179 | //relocate popup |
180 | if(!m_animationTimer) |
181 | m_animationTimer = startTimer(10); |
182 | } |
183 | |
184 | void NotifyByPopup::(QTimerEvent * event) |
185 | { |
186 | if(event->timerId() != m_animationTimer) |
187 | return KNotifyPlugin::timerEvent(event); |
188 | |
189 | bool cont=false; |
190 | QRect screen = QApplication::desktop()->availableGeometry(); |
191 | m_nextPosition = screen.top(); |
192 | foreach(KPassivePopup *pop,m_popups) |
193 | { |
194 | int posy=pop->pos().y(); |
195 | if(posy > m_nextPosition) |
196 | { |
197 | posy=qMax(posy-5,m_nextPosition); |
198 | m_nextPosition = posy + pop->height(); |
199 | cont = cont || posy != m_nextPosition; |
200 | pop->move(pop->pos().x(),posy); |
201 | } |
202 | else |
203 | m_nextPosition += pop->height(); |
204 | } |
205 | if(!cont) |
206 | { |
207 | killTimer(m_animationTimer); |
208 | m_animationTimer = 0; |
209 | } |
210 | } |
211 | |
212 | void NotifyByPopup::( const QString &adr ) |
213 | { |
214 | unsigned int id=adr.section('/' , 0 , 0).toUInt(); |
215 | unsigned int action=adr.section('/' , 1 , 1).toUInt(); |
216 | |
217 | // kDebug() << id << " " << action; |
218 | |
219 | if(id==0 || action==0) |
220 | return; |
221 | |
222 | emit actionInvoked(id,action); |
223 | } |
224 | |
225 | void NotifyByPopup::( int id ) |
226 | { |
227 | delete m_popups.take(id); |
228 | |
229 | if( m_dbusServiceExists) |
230 | { |
231 | closeNotificationDBus(id); |
232 | } |
233 | } |
234 | |
235 | void NotifyByPopup::(int id, KNotifyConfig * config) |
236 | { |
237 | if (m_popups.contains(id)) |
238 | { |
239 | KPassivePopup *p=m_popups[id]; |
240 | fillPopup(p, id, config); |
241 | return; |
242 | } |
243 | |
244 | // if Notifications DBus service exists on bus, |
245 | // it'll be used instead |
246 | if( m_dbusServiceExists) |
247 | { |
248 | sendNotificationDBus(id, id, config); |
249 | return; |
250 | } |
251 | |
252 | // otherwise, just display a new Growl notification |
253 | if(NotifyByPopupGrowl::canPopup()) |
254 | { |
255 | notify( id, config ); |
256 | } |
257 | } |
258 | |
259 | void NotifyByPopup::(KPassivePopup *pop,int id,KNotifyConfig * config) |
260 | { |
261 | QString appCaption, iconName; |
262 | getAppCaptionAndIconName(config, &appCaption, &iconName); |
263 | |
264 | KIconLoader iconLoader(iconName); |
265 | QPixmap appIcon = iconLoader.loadIcon( iconName, KIconLoader::Small ); |
266 | |
267 | KVBox *vb = pop->standardView( config->title.isEmpty() ? appCaption : config->title , config->image.isNull() ? config->text : QString() , appIcon ); |
268 | KVBox *vb2 = vb; |
269 | |
270 | if(!config->image.isNull()) |
271 | { |
272 | QPixmap pix = QPixmap::fromImage(config->image.toImage()); |
273 | KHBox *hb = new KHBox(vb); |
274 | hb->setSpacing(KDialog::spacingHint()); |
275 | QLabel *pil=new QLabel(hb); |
276 | pil->setPixmap( pix ); |
277 | pil->setScaledContents(true); |
278 | if(pix.height() > 80 && pix.height() > pix.width() ) |
279 | { |
280 | pil->setMaximumHeight(80); |
281 | pil->setMaximumWidth(80*pix.width()/pix.height()); |
282 | } |
283 | else if(pix.width() > 80 && pix.height() <= pix.width()) |
284 | { |
285 | pil->setMaximumWidth(80); |
286 | pil->setMaximumHeight(80*pix.height()/pix.width()); |
287 | } |
288 | vb=new KVBox(hb); |
289 | QLabel *msg = new QLabel( config->text, vb ); |
290 | msg->setAlignment( Qt::AlignLeft ); |
291 | } |
292 | |
293 | |
294 | if ( !config->actions.isEmpty() ) |
295 | { |
296 | QString linkCode=QString::fromLatin1("<p align=\"right\">" ); |
297 | int i=0; |
298 | foreach ( const QString & it , config->actions ) |
299 | { |
300 | i++; |
301 | linkCode+=QString::fromLatin1(" <a href=\"%1/%2\">%3</a> " ).arg( id ).arg( i ).arg( Qt::escape(it) ); |
302 | } |
303 | linkCode+=QString::fromLatin1("</p>" ); |
304 | QLabel *link = new QLabel(linkCode , vb ); |
305 | link->setTextInteractionFlags(Qt::LinksAccessibleByMouse); |
306 | link->setOpenExternalLinks(false); |
307 | //link->setAlignment( AlignRight ); |
308 | QObject::connect(link, SIGNAL(linkActivated(const QString &)), this, SLOT(slotLinkClicked(const QString& ) ) ); |
309 | QObject::connect(link, SIGNAL(linkActivated(const QString &)), pop, SLOT(hide())); |
310 | } |
311 | |
312 | pop->setView( vb2 ); |
313 | } |
314 | |
315 | void NotifyByPopup::( const QString & serviceName, |
316 | const QString & oldOwner, const QString & newOwner ) |
317 | { |
318 | kDebug() << serviceName << oldOwner << newOwner; |
319 | // tell KNotify that all existing notifications which it sent |
320 | // to DBus had been closed |
321 | foreach (int id, m_idMap.keys()) |
322 | finished(id); |
323 | m_idMap.clear(); |
324 | |
325 | m_dbusServiceCapCacheDirty = true; |
326 | m_dbusServiceCapabilities.clear(); |
327 | |
328 | if(newOwner.isEmpty()) |
329 | { |
330 | m_dbusServiceExists = false; |
331 | } |
332 | else if(oldOwner.isEmpty()) |
333 | { |
334 | m_dbusServiceExists = true; |
335 | |
336 | // connect to action invocation signals |
337 | bool connected = QDBusConnection::sessionBus().connect(QString(), // from any service |
338 | dbusPath, |
339 | dbusInterfaceName, |
340 | "ActionInvoked" , |
341 | this, |
342 | SLOT(slotDBusNotificationActionInvoked(uint,const QString&))); |
343 | if (!connected) { |
344 | kWarning() << "warning: failed to connect to ActionInvoked dbus signal" ; |
345 | } |
346 | |
347 | connected = QDBusConnection::sessionBus().connect(QString(), // from any service |
348 | dbusPath, |
349 | dbusInterfaceName, |
350 | "NotificationClosed" , |
351 | this, |
352 | SLOT(slotDBusNotificationClosed(uint,uint))); |
353 | if (!connected) { |
354 | kWarning() << "warning: failed to connect to NotificationClosed dbus signal" ; |
355 | } |
356 | } |
357 | } |
358 | |
359 | |
360 | void NotifyByPopup::(uint dbus_id, const QString& actKey) |
361 | { |
362 | // find out knotify id |
363 | int id = m_idMap.key(dbus_id, 0); |
364 | if (id == 0) { |
365 | kDebug() << "failed to find knotify id for dbus_id" << dbus_id; |
366 | return; |
367 | } |
368 | kDebug() << "action" << actKey << "invoked for notification " << id; |
369 | // emulate link clicking |
370 | slotLinkClicked( QString("%1/%2" ).arg(id).arg(actKey) ); |
371 | // now close notification - similar to popup behaviour |
372 | // (popups are hidden after link activation - see 'connects' of linkActivated signal above) |
373 | closeNotificationDBus(id); |
374 | } |
375 | |
376 | void NotifyByPopup::(uint dbus_id, uint reason) |
377 | { |
378 | Q_UNUSED(reason) |
379 | // find out knotify id |
380 | int id = m_idMap.key(dbus_id, 0); |
381 | kDebug() << dbus_id << " -> " << id; |
382 | if (id == 0) { |
383 | kDebug() << "failed to find knotify id for dbus_id" << dbus_id; |
384 | return; |
385 | } |
386 | // tell KNotify that this notification has been closed |
387 | m_idMap.remove(id); |
388 | finished(id); |
389 | } |
390 | |
391 | void NotifyByPopup::getAppCaptionAndIconName(KNotifyConfig *config, QString *appCaption, QString *iconName) |
392 | { |
393 | KConfigGroup globalgroup(&(*config->eventsfile), "Global" ); |
394 | *appCaption = globalgroup.readEntry("Name" , globalgroup.readEntry("Comment" , config->appname)); |
395 | |
396 | KConfigGroup eventGroup(&(*config->eventsfile), QString("Event/%1" ).arg(config->eventid)); |
397 | if (eventGroup.hasKey("IconName" )) { |
398 | *iconName = eventGroup.readEntry("IconName" , config->appname); |
399 | } else { |
400 | *iconName = globalgroup.readEntry("IconName" , config->appname); |
401 | } |
402 | } |
403 | |
404 | bool NotifyByPopup::(int id, int replacesId, KNotifyConfig* config_nocheck) |
405 | { |
406 | // figure out dbus id to replace if needed |
407 | uint dbus_replaces_id = 0; |
408 | if (replacesId != 0 ) { |
409 | dbus_replaces_id = m_idMap.value(replacesId, 0); |
410 | if (!dbus_replaces_id) |
411 | return false; //the popup has been closed, there is nothing to replace. |
412 | } |
413 | |
414 | QDBusMessage m = QDBusMessage::createMethodCall( dbusServiceName, dbusPath, dbusInterfaceName, "Notify" ); |
415 | |
416 | QList<QVariant> args; |
417 | |
418 | QString appCaption, iconName; |
419 | getAppCaptionAndIconName(config_nocheck, &appCaption, &iconName); |
420 | |
421 | KNotifyConfig *config = ensurePopupCompatibility( config_nocheck ); |
422 | |
423 | args.append( appCaption ); // app_name |
424 | args.append( dbus_replaces_id ); // replaces_id |
425 | args.append( iconName ); // app_icon |
426 | args.append( config->title.isEmpty() ? appCaption : config->title ); // summary |
427 | args.append( config->text ); // body |
428 | // galago spec defines action list to be list like |
429 | // (act_id1, action1, act_id2, action2, ...) |
430 | // |
431 | // assign id's to actions like it's done in fillPopup() method |
432 | // (i.e. starting from 1) |
433 | QStringList actionList; |
434 | int actId = 0; |
435 | foreach (const QString& actName, config->actions) { |
436 | actId++; |
437 | actionList.append(QString::number(actId)); |
438 | actionList.append(actName); |
439 | } |
440 | |
441 | args.append( actionList ); // actions |
442 | |
443 | QVariantMap map; |
444 | // Add the application name to the hints. |
445 | // According to fdo spec, the app_name is supposed to be the applicaton's "pretty name" |
446 | // but in some places it's handy to know the application name itself |
447 | if (!config->appname.isEmpty()) { |
448 | map["x-kde-appname" ] = config->appname; |
449 | } |
450 | |
451 | // let's see if we've got an image, and store the image in the hints map |
452 | if (!config->image.isNull()) { |
453 | QImage image = config->image.toImage(); |
454 | map["image_data" ] = ImageConverter::variantForImage(image); |
455 | } |
456 | |
457 | args.append( map ); // hints |
458 | args.append( config->timeout ); // expire timout |
459 | |
460 | m.setArguments( args ); |
461 | QDBusMessage replyMsg = QDBusConnection::sessionBus().call(m); |
462 | |
463 | delete config; |
464 | |
465 | if(replyMsg.type() == QDBusMessage::ReplyMessage) { |
466 | if (!replyMsg.arguments().isEmpty()) { |
467 | uint dbus_id = replyMsg.arguments().at(0).toUInt(); |
468 | if (dbus_id == 0) |
469 | { |
470 | kDebug() << "error: dbus_id is null" ; |
471 | return false; |
472 | } |
473 | if (dbus_replaces_id && dbus_id == dbus_replaces_id) |
474 | return true; |
475 | #if 1 |
476 | int oldId = m_idMap.key(dbus_id, 0); |
477 | if (oldId != 0) { |
478 | kWarning() << "Received twice the same id " << dbus_id << "( previous notification: " << oldId << ")" ; |
479 | m_idMap.remove(oldId); |
480 | finish(oldId); |
481 | } |
482 | #endif |
483 | m_idMap.insert(id, dbus_id); |
484 | kDebug() << "mapping knotify id to dbus id:" << id << "=>" << dbus_id; |
485 | |
486 | return true; |
487 | } else { |
488 | kDebug() << "error: received reply with no arguments" ; |
489 | } |
490 | } else if (replyMsg.type() == QDBusMessage::ErrorMessage) { |
491 | kDebug() << "error: failed to send dbus message" ; |
492 | } else { |
493 | kDebug() << "unexpected reply type" ; |
494 | } |
495 | return false; |
496 | } |
497 | |
498 | void NotifyByPopup::(int id) |
499 | { |
500 | uint dbus_id = m_idMap.take(id); |
501 | if (dbus_id == 0) { |
502 | kDebug() << "not found dbus id to close" << id; |
503 | return; |
504 | } |
505 | |
506 | QDBusMessage m = QDBusMessage::createMethodCall( dbusServiceName, dbusPath, |
507 | dbusInterfaceName, "CloseNotification" ); |
508 | QList<QVariant> args; |
509 | args.append( dbus_id ); |
510 | m.setArguments( args ); |
511 | bool queued = QDBusConnection::sessionBus().send(m); |
512 | if(!queued) |
513 | { |
514 | kDebug() << "warning: failed to queue dbus message" ; |
515 | } |
516 | |
517 | } |
518 | |
519 | QStringList NotifyByPopup::() |
520 | { |
521 | if (!m_dbusServiceExists) { |
522 | if( NotifyByPopupGrowl::canPopup() ) { |
523 | return NotifyByPopupGrowl::capabilities(); |
524 | } else { |
525 | // Return capabilities of the KPassivePopup implementation |
526 | return QStringList() << "actions" << "body" << "body-hyperlinks" |
527 | << "body-markup" << "icon-static" ; |
528 | } |
529 | } |
530 | |
531 | if(m_dbusServiceCapCacheDirty) { |
532 | QDBusMessage m = QDBusMessage::createMethodCall( dbusServiceName, dbusPath, |
533 | dbusInterfaceName, "GetCapabilities" ); |
534 | QDBusMessage replyMsg = QDBusConnection::sessionBus().call(m); |
535 | if (replyMsg.type() != QDBusMessage::ReplyMessage) { |
536 | kWarning(300) << "Error while calling popup server GetCapabilities()" ; |
537 | return QStringList(); |
538 | } |
539 | |
540 | if (replyMsg.arguments().isEmpty()) { |
541 | kWarning(300) << "popup server GetCapabilities() returned an empty reply" ; |
542 | return QStringList(); |
543 | } |
544 | |
545 | m_dbusServiceCapabilities = replyMsg.arguments().at(0).toStringList(); |
546 | m_dbusServiceCapCacheDirty = false; |
547 | } |
548 | |
549 | return m_dbusServiceCapabilities; |
550 | } |
551 | |
552 | |
553 | KNotifyConfig *NotifyByPopup::( const KNotifyConfig *config ) |
554 | { |
555 | KNotifyConfig *c = config->copy(); |
556 | QStringList cap = popupServerCapabilities(); |
557 | |
558 | if( !cap.contains( "actions" ) ) |
559 | { |
560 | c->actions.clear(); |
561 | } |
562 | |
563 | if( !cap.contains( "body-markup" ) ) |
564 | { |
565 | if( c->title.startsWith( "<html>" ) ) |
566 | c->title = stripHtml( config->title ); |
567 | if( c->text.startsWith( "<html>" ) ) |
568 | c->text = stripHtml( config->text ); |
569 | } |
570 | |
571 | return c; |
572 | } |
573 | |
574 | QString NotifyByPopup::( const QString &text ) |
575 | { |
576 | QXmlStreamReader r( "<elem>" + text + "</elem>" ); |
577 | HtmlEntityResolver resolver; |
578 | r.setEntityResolver( &resolver ); |
579 | QString result; |
580 | while( !r.atEnd() ) { |
581 | r.readNext(); |
582 | if( r.tokenType() == QXmlStreamReader::Characters ) |
583 | { |
584 | result.append( r.text() ); |
585 | } |
586 | else if( r.tokenType() == QXmlStreamReader::StartElement |
587 | && r.name() == "br" ) |
588 | { |
589 | result.append( "\n" ); |
590 | } |
591 | } |
592 | |
593 | if(r.hasError()) |
594 | { |
595 | // XML error in the given text, just return the original string |
596 | kWarning(300) << "Notification to send to backend which does " |
597 | "not support HTML, contains invalid XML:" |
598 | << r.errorString() << "line" << r.lineNumber() |
599 | << "col" << r.columnNumber(); |
600 | return text; |
601 | } |
602 | |
603 | return result; |
604 | } |
605 | |
606 | QString NotifyByPopup::HtmlEntityResolver::( |
607 | const QString &name ) |
608 | { |
609 | QString result = |
610 | QXmlStreamEntityResolver::resolveUndeclaredEntity(name); |
611 | if( !result.isEmpty() ) |
612 | return result; |
613 | |
614 | QChar ent = KCharsets::fromEntity( '&' + name ); |
615 | if( ent.isNull() ) { |
616 | kWarning(300) << "Notification to send to backend which does " |
617 | "not support HTML, contains invalid entity: " |
618 | << name; |
619 | ent = ' '; |
620 | } |
621 | return QString(ent); |
622 | } |
623 | |
624 | #include "notifybypopup.moc" |
625 | |