~ubuntu-branches/ubuntu/utopic/kde-workspace/utopic-proposed

« back to all changes in this revision

Viewing changes to plasma/generic/dataengines/statusnotifieritem/statusnotifieritemsource.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Michał Zając
  • Date: 2011-07-09 08:31:15 UTC
  • Revision ID: james.westby@ubuntu.com-20110709083115-ohyxn6z93mily9fc
Tags: upstream-4.6.90
Import upstream version 4.6.90

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/***************************************************************************
 
2
 *                                                                         *
 
3
 *   Copyright (C) 2009 Marco Martin <notmart@gmail.com>                   *
 
4
 *   Copyright (C) 2009 Matthieu Gallien <matthieu_gallien@yahoo.fr>       *
 
5
 *                                                                         *
 
6
 *   This program is free software; you can redistribute it and/or modify  *
 
7
 *   it under the terms of the GNU General Public License as published by  *
 
8
 *   the Free Software Foundation; either version 2 of the License, or     *
 
9
 *   (at your option) any later version.                                   *
 
10
 *                                                                         *
 
11
 *   This program is distributed in the hope that it will be useful,       *
 
12
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 
13
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 
14
 *   GNU General Public License for more details.                          *
 
15
 *                                                                         *
 
16
 *   You should have received a copy of the GNU General Public License     *
 
17
 *   along with this program; if not, write to the                         *
 
18
 *   Free Software Foundation, Inc.,                                       *
 
19
 *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA .        *
 
20
 ***************************************************************************/
 
21
 
 
22
#include "statusnotifieritemsource.h"
 
23
#include "systemtraytypes.h"
 
24
#include "statusnotifieritemservice.h"
 
25
 
 
26
#include <QApplication>
 
27
#include <QDesktopWidget>
 
28
#include <QIcon>
 
29
#include <KDebug>
 
30
#include <KIcon>
 
31
#include <KIconLoader>
 
32
#include <KStandardDirs>
 
33
#include <QPainter>
 
34
#include <QDBusMessage>
 
35
#include <QDBusPendingCall>
 
36
#include <QDBusPendingReply>
 
37
#include <QVariantMap>
 
38
#include <QImage>
 
39
#include <QMenu>
 
40
#include <QPixmap>
 
41
#include <QSysInfo>
 
42
 
 
43
#include <netinet/in.h>
 
44
 
 
45
#include <dbusmenuimporter.h>
 
46
#ifndef DBUSMENUQT_VERSION
 
47
// DBUSMENUQT_VERSION was introduced in DBusMenuQt 0.4.0
 
48
#define DBUSMENUQT_VERSION 0x000305
 
49
#endif
 
50
 
 
51
class PlasmaDBusMenuImporter : public DBusMenuImporter
 
52
{
 
53
public:
 
54
    PlasmaDBusMenuImporter(const QString &service, const QString &path, KIconLoader *iconLoader, QObject *parent)
 
55
    : DBusMenuImporter(service, path, parent)
 
56
    , m_iconLoader(iconLoader)
 
57
    {}
 
58
 
 
59
protected:
 
60
    virtual QIcon iconForName(const QString &name)
 
61
    {
 
62
        return KIcon(name, m_iconLoader);
 
63
    }
 
64
 
 
65
private:
 
66
    KIconLoader *m_iconLoader;
 
67
};
 
68
 
 
69
StatusNotifierItemSource::StatusNotifierItemSource(const QString &notifierItemId, QObject *parent)
 
70
    : Plasma::DataContainer(parent),
 
71
      m_customIconLoader(0),
 
72
      m_menuImporter(0),
 
73
      m_refreshing(false),
 
74
      m_needsReRefreshing(false),
 
75
      m_titleUpdate(true),
 
76
      m_iconUpdate(true),
 
77
      m_tooltipUpdate(true),
 
78
      m_statusUpdate(true)
 
79
{
 
80
    setObjectName(notifierItemId);
 
81
    qDBusRegisterMetaType<KDbusImageStruct>();
 
82
    qDBusRegisterMetaType<KDbusImageVector>();
 
83
    qDBusRegisterMetaType<KDbusToolTipStruct>();
 
84
 
 
85
    m_typeId = notifierItemId;
 
86
    m_name = notifierItemId;
 
87
 
 
88
    int slash = notifierItemId.indexOf('/');
 
89
    if (slash == -1) {
 
90
        kError() << "Invalid notifierItemId:" << notifierItemId;
 
91
        m_valid = false;
 
92
        m_statusNotifierItemInterface = 0;
 
93
        return;
 
94
    }
 
95
    QString service = notifierItemId.left(slash);
 
96
    QString path = notifierItemId.mid(slash);
 
97
 
 
98
    m_statusNotifierItemInterface = new org::kde::StatusNotifierItem(service, path,
 
99
                                                                     QDBusConnection::sessionBus(), this);
 
100
 
 
101
    m_refreshTimer.setSingleShot(true);
 
102
    m_refreshTimer.setInterval(10);
 
103
    connect(&m_refreshTimer, SIGNAL(timeout()), this, SLOT(performRefresh()));
 
104
 
 
105
    m_valid = !service.isEmpty() && m_statusNotifierItemInterface->isValid();
 
106
    if (m_valid) {
 
107
        connect(m_statusNotifierItemInterface, SIGNAL(NewTitle()), this, SLOT(refreshTitle()));
 
108
        connect(m_statusNotifierItemInterface, SIGNAL(NewIcon()), this, SLOT(refreshIcons()));
 
109
        connect(m_statusNotifierItemInterface, SIGNAL(NewAttentionIcon()), this, SLOT(refreshIcons()));
 
110
        connect(m_statusNotifierItemInterface, SIGNAL(NewOverlayIcon()), this, SLOT(refreshIcons()));
 
111
        connect(m_statusNotifierItemInterface, SIGNAL(NewToolTip()), this, SLOT(refreshToolTip()));
 
112
        connect(m_statusNotifierItemInterface, SIGNAL(NewStatus(QString)), this, SLOT(syncStatus(QString)));
 
113
        refresh();
 
114
    }
 
115
}
 
116
 
 
117
StatusNotifierItemSource::~StatusNotifierItemSource()
 
118
{
 
119
    delete m_statusNotifierItemInterface;
 
120
}
 
121
 
 
122
KIconLoader *StatusNotifierItemSource::iconLoader() const
 
123
{
 
124
    return m_customIconLoader ? m_customIconLoader : KIconLoader::global();
 
125
}
 
126
 
 
127
Plasma::Service *StatusNotifierItemSource::createService()
 
128
{
 
129
    return new StatusNotifierItemService(this);
 
130
}
 
131
 
 
132
void StatusNotifierItemSource::syncStatus(QString status)
 
133
{
 
134
    setData("TitleChanged", false);
 
135
    setData("IconsChanged", false);
 
136
    setData("TooltipChanged", false);
 
137
    setData("StatusChanged", true);
 
138
    setData("Status", status);
 
139
    checkForUpdate();
 
140
}
 
141
 
 
142
void StatusNotifierItemSource::refreshTitle()
 
143
{
 
144
    m_titleUpdate = true;
 
145
    refresh();
 
146
}
 
147
 
 
148
void StatusNotifierItemSource::refreshIcons()
 
149
{
 
150
    m_iconUpdate = true;
 
151
    refresh();
 
152
}
 
153
 
 
154
void StatusNotifierItemSource::refreshToolTip()
 
155
{
 
156
    m_tooltipUpdate = true;
 
157
    refresh();
 
158
}
 
159
 
 
160
void StatusNotifierItemSource::refresh()
 
161
{
 
162
    if (!m_refreshTimer.isActive()) {
 
163
        m_refreshTimer.start();
 
164
    }
 
165
}
 
166
 
 
167
void StatusNotifierItemSource::performRefresh()
 
168
{
 
169
    if (m_refreshing) {
 
170
        m_needsReRefreshing = true;
 
171
        return;
 
172
    }
 
173
 
 
174
    m_refreshing = true;
 
175
    QDBusMessage message = QDBusMessage::createMethodCall(m_statusNotifierItemInterface->service(),
 
176
                                                          m_statusNotifierItemInterface->path(), "org.freedesktop.DBus.Properties", "GetAll");
 
177
 
 
178
    message << m_statusNotifierItemInterface->interface();
 
179
    QDBusPendingCall call = m_statusNotifierItemInterface->connection().asyncCall(message);
 
180
    QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this);
 
181
    connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher *)), this, SLOT(refreshCallback(QDBusPendingCallWatcher *)));
 
182
}
 
183
 
 
184
/**
 
185
  \todo add a smart pointer to guard call and to automatically delete it at the end of the function
 
186
  */
 
187
void StatusNotifierItemSource::refreshCallback(QDBusPendingCallWatcher *call)
 
188
{
 
189
    m_refreshing = false;
 
190
    if (m_needsReRefreshing) {
 
191
        m_needsReRefreshing = false;
 
192
        performRefresh();
 
193
        call->deleteLater();
 
194
        return;
 
195
    }
 
196
 
 
197
    QDBusPendingReply<QVariantMap> reply = *call;
 
198
    if (reply.isError()) {
 
199
        m_valid = false;
 
200
    } else {
 
201
        // record what has changed
 
202
        setData("TitleChanged", m_titleUpdate);
 
203
        m_titleUpdate = false;
 
204
        setData("IconsChanged", m_iconUpdate);
 
205
        m_iconUpdate = false;
 
206
        setData("ToolTipChanged", m_tooltipUpdate);
 
207
        m_tooltipUpdate = false;
 
208
        setData("StatusChanged", m_statusUpdate);
 
209
        m_statusUpdate = false;
 
210
 
 
211
        //IconThemePath (handle this one first, because it has an impact on
 
212
        //others)
 
213
        QVariantMap properties = reply.argumentAt<0>();
 
214
        if (!m_customIconLoader) {
 
215
            QString path = properties["IconThemePath"].toString();
 
216
            if (!path.isEmpty()) {
 
217
                // FIXME: If last part of path is not "icons", this won't work!
 
218
                QStringList tokens = path.split('/', QString::SkipEmptyParts);
 
219
                if (tokens.length() >= 3 && tokens.takeLast() == "icons") {
 
220
                    QString appName = tokens.takeLast();
 
221
                    QString prefix = '/' + tokens.join("/");
 
222
                    // FIXME: Fix KIconLoader and KIconTheme so that we can use
 
223
                    // our own instance of KStandardDirs
 
224
                    KGlobal::dirs()->addResourceDir("data", prefix);
 
225
                    // We use a separate instance of KIconLoader to avoid
 
226
                    // adding all application dirs to KIconLoader::global(), to
 
227
                    // avoid potential icon name clashes between application
 
228
                    // icons
 
229
                    m_customIconLoader = new KIconLoader(appName, 0 /* dirs */, this);
 
230
                } else {
 
231
                    kWarning() << "Wrong IconThemePath" << path << ": too short or does not end with 'icons'";
 
232
                }
 
233
            }
 
234
        }
 
235
 
 
236
        setData("Category", properties["Category"]);
 
237
        setData("Status", properties["Status"]);
 
238
        setData("Title", properties["Title"]);
 
239
        setData("Id", properties["Id"]);
 
240
        setData("WindowId", properties["WindowId"]);
 
241
        setData("ItemIsMenu", properties["ItemIsMenu"]);
 
242
 
 
243
        //Attention Movie
 
244
        setData("AttentionMovieName", properties["AttentionMovieName"]);
 
245
 
 
246
        QIcon overlay;
 
247
        QStringList overlayNames;
 
248
 
 
249
        //Icon
 
250
        {
 
251
            KDbusImageVector image;
 
252
            QIcon icon;
 
253
 
 
254
            properties["OverlayIconPixmap"].value<QDBusArgument>() >> image;
 
255
            if (image.isEmpty()) {
 
256
                QString iconName = properties["OverlayIconName"].toString();
 
257
                setData("OverlayIconName", iconName);
 
258
                if (!iconName.isEmpty()) {
 
259
                    overlayNames << iconName;
 
260
                    overlay = KIcon(iconName, iconLoader());
 
261
                }
 
262
            } else {
 
263
                overlay = imageVectorToPixmap(image);
 
264
            }
 
265
 
 
266
            properties["IconPixmap"].value<QDBusArgument>() >> image;
 
267
            if (image.isEmpty()) {
 
268
                QString iconName = properties["IconName"].toString();
 
269
                setData("IconName", iconName);
 
270
                if (!iconName.isEmpty()) {
 
271
                    icon = KIcon(iconName, iconLoader(), overlayNames);
 
272
 
 
273
                    if (overlayNames.isEmpty() && !overlay.isNull()) {
 
274
                        overlayIcon(&icon, &overlay);
 
275
                    }
 
276
                }
 
277
            } else {
 
278
                icon = imageVectorToPixmap(image);
 
279
                if (!icon.isNull() && !overlay.isNull()) {
 
280
                    overlayIcon(&icon, &overlay);
 
281
                }
 
282
            }
 
283
            setData("Icon", icon);
 
284
        }
 
285
 
 
286
        //Attention icon
 
287
        {
 
288
            KDbusImageVector image;
 
289
            QIcon attentionIcon;
 
290
 
 
291
            properties["AttentionIconPixmap"].value<QDBusArgument>() >> image;
 
292
            if (image.isEmpty()) {
 
293
                QString iconName = properties["AttentionIconName"].toString();
 
294
                setData("AttentionIconName", iconName);
 
295
                if (!iconName.isEmpty()) {
 
296
                    attentionIcon = KIcon(iconName, iconLoader(), overlayNames);
 
297
 
 
298
                    if (overlayNames.isEmpty() && !overlay.isNull()) {
 
299
                        overlayIcon(&attentionIcon, &overlay);
 
300
                    }
 
301
                }
 
302
            } else {
 
303
                attentionIcon = imageVectorToPixmap(image);
 
304
                if (!attentionIcon.isNull() && !overlay.isNull()) {
 
305
                    overlayIcon(&attentionIcon, &overlay);
 
306
                }
 
307
            }
 
308
            setData("AttentionIcon", attentionIcon);
 
309
        }
 
310
 
 
311
        //ToolTip
 
312
        {
 
313
            KDbusToolTipStruct toolTip;
 
314
            properties["ToolTip"].value<QDBusArgument>() >> toolTip;
 
315
            if (toolTip.title.isEmpty()) {
 
316
                setData("ToolTipTitle", QVariant());
 
317
                setData("ToolTipSubTitle", QVariant());
 
318
                setData("ToolTipIcon", QVariant());
 
319
            } else {
 
320
                QIcon toolTipIcon;
 
321
                if (toolTip.image.size() == 0) {
 
322
                    toolTipIcon = KIcon(toolTip.icon, iconLoader());
 
323
                } else {
 
324
                    toolTipIcon = imageVectorToPixmap(toolTip.image);
 
325
                }
 
326
                setData("ToolTipTitle", toolTip.title);
 
327
                setData("ToolTipSubTitle", toolTip.subTitle);
 
328
                setData("ToolTipIcon", toolTipIcon);
 
329
            }
 
330
        }
 
331
 
 
332
        //Menu
 
333
        if (!m_menuImporter) {
 
334
            QString menuObjectPath = properties["Menu"].value<QDBusObjectPath>().path();
 
335
            if (!menuObjectPath.isEmpty()) {
 
336
                if (menuObjectPath == "/NO_DBUSMENU") {
 
337
                    // This is a hack to make it possible to disable DBusMenu in an
 
338
                    // application. The string "/NO_DBUSMENU" must be the same as in
 
339
                    // KStatusNotifierItem::setContextMenu().
 
340
                    kWarning() << "DBusMenu disabled for this application";
 
341
                } else {
 
342
                    m_menuImporter = new PlasmaDBusMenuImporter(m_statusNotifierItemInterface->service(), menuObjectPath, iconLoader(), this);
 
343
#if DBUSMENUQT_VERSION >= 0x000400
 
344
                    connect(m_menuImporter, SIGNAL(menuUpdated()), this, SLOT(contextMenuReady()));
 
345
#endif
 
346
                }
 
347
            }
 
348
        }
 
349
    }
 
350
 
 
351
    checkForUpdate();
 
352
    call->deleteLater();
 
353
}
 
354
 
 
355
void StatusNotifierItemSource::contextMenuReady()
 
356
{
 
357
#if DBUSMENUQT_VERSION < 0x000400
 
358
    // Work around to avoid infinite recursion because menuReadyToBeShown() is emitted
 
359
    // by DBusMenuImporter at the end of its slot connected to aboutToShow()
 
360
    // (dbusmenu-qt 0.3.5)
 
361
    disconnect(m_menuImporter, SIGNAL(menuReadyToBeShown()), this, SLOT(contextMenuReady()));
 
362
#endif
 
363
    emit contextMenuReady(m_menuImporter->menu());
 
364
}
 
365
 
 
366
QPixmap StatusNotifierItemSource::KDbusImageStructToPixmap(const KDbusImageStruct &image) const
 
367
{
 
368
    //swap from network byte order if we are little endian
 
369
    if (QSysInfo::ByteOrder == QSysInfo::LittleEndian) {
 
370
        uint *uintBuf = (uint *) image.data.data();
 
371
        for (uint i = 0; i < image.data.size()/sizeof(uint); ++i) {
 
372
            *uintBuf = ntohl(*uintBuf);
 
373
            ++uintBuf;
 
374
        }
 
375
    }
 
376
    QImage iconImage(image.width, image.height, QImage::Format_ARGB32 );
 
377
    memcpy(iconImage.bits(), (uchar*)image.data.data(), iconImage.numBytes());
 
378
 
 
379
    return QPixmap::fromImage(iconImage);
 
380
}
 
381
 
 
382
QIcon StatusNotifierItemSource::imageVectorToPixmap(const KDbusImageVector &vector) const
 
383
{
 
384
    QIcon icon;
 
385
 
 
386
    for (int i = 0; i<vector.size(); ++i) {
 
387
        icon.addPixmap(KDbusImageStructToPixmap(vector[i]));
 
388
    }
 
389
 
 
390
    return icon;
 
391
}
 
392
 
 
393
void StatusNotifierItemSource::overlayIcon(QIcon *icon, QIcon *overlay)
 
394
{
 
395
    QIcon tmp;
 
396
    QPixmap m_iconPixmap = icon->pixmap(KIconLoader::SizeSmall, KIconLoader::SizeSmall);
 
397
 
 
398
    QPainter p(&m_iconPixmap);
 
399
 
 
400
    const int size = KIconLoader::SizeSmall/2;
 
401
    p.drawPixmap(QRect(size, size, size, size), overlay->pixmap(size, size), QRect(0,0,size,size));
 
402
    p.end();
 
403
    tmp.addPixmap(m_iconPixmap);
 
404
 
 
405
    //if an m_icon exactly that size wasn't found don't add it to the vector
 
406
    m_iconPixmap = icon->pixmap(KIconLoader::SizeSmallMedium, KIconLoader::SizeSmallMedium);
 
407
    if (m_iconPixmap.width() == KIconLoader::SizeSmallMedium) {
 
408
        const int size = KIconLoader::SizeSmall/2;
 
409
        QPainter p(&m_iconPixmap);
 
410
        p.drawPixmap(QRect(m_iconPixmap.width()-size, m_iconPixmap.height()-size, size, size), overlay->pixmap(size, size), QRect(0,0,size,size));
 
411
        p.end();
 
412
        tmp.addPixmap(m_iconPixmap);
 
413
    }
 
414
 
 
415
    m_iconPixmap = icon->pixmap(KIconLoader::SizeMedium, KIconLoader::SizeMedium);
 
416
    if (m_iconPixmap.width() == KIconLoader::SizeMedium) {
 
417
        const int size = KIconLoader::SizeSmall/2;
 
418
        QPainter p(&m_iconPixmap);
 
419
        p.drawPixmap(QRect(m_iconPixmap.width()-size, m_iconPixmap.height()-size, size, size), overlay->pixmap(size, size), QRect(0,0,size,size));
 
420
        p.end();
 
421
        tmp.addPixmap(m_iconPixmap);
 
422
    }
 
423
 
 
424
    m_iconPixmap = icon->pixmap(KIconLoader::SizeLarge, KIconLoader::SizeLarge);
 
425
    if (m_iconPixmap.width() == KIconLoader::SizeLarge) {
 
426
        const int size = KIconLoader::SizeSmall;
 
427
        QPainter p(&m_iconPixmap);
 
428
        p.drawPixmap(QRect(m_iconPixmap.width()-size, m_iconPixmap.height()-size, size, size), overlay->pixmap(size, size), QRect(0,0,size,size));
 
429
        p.end();
 
430
        tmp.addPixmap(m_iconPixmap);
 
431
    }
 
432
 
 
433
    // We can't do 'm_icon->addPixmap()' because if 'm_icon' uses KIconEngine,
 
434
    // it will ignore the added pixmaps. This is not a bug in KIconEngine,
 
435
    // QIcon::addPixmap() doc says: "Custom m_icon engines are free to ignore
 
436
    // additionally added pixmaps".
 
437
    *icon = tmp;
 
438
    //hopefully huge and enormous not necessary right now, since it's quite costly
 
439
}
 
440
 
 
441
void StatusNotifierItemSource::activate(int x, int y)
 
442
{
 
443
    if (m_statusNotifierItemInterface && m_statusNotifierItemInterface->isValid()) {
 
444
        m_statusNotifierItemInterface->call(QDBus::NoBlock, "Activate", x, y);
 
445
    }
 
446
}
 
447
 
 
448
void StatusNotifierItemSource::secondaryActivate(int x, int y)
 
449
{
 
450
    if (m_statusNotifierItemInterface && m_statusNotifierItemInterface->isValid()) {
 
451
        m_statusNotifierItemInterface->call(QDBus::NoBlock, "SecondaryActivate", x, y);
 
452
    }
 
453
}
 
454
 
 
455
void StatusNotifierItemSource::scroll(int delta, const QString &direction)
 
456
{
 
457
    if (m_statusNotifierItemInterface && m_statusNotifierItemInterface->isValid()) {
 
458
        m_statusNotifierItemInterface->call(QDBus::NoBlock, "Scroll", delta, direction);
 
459
    }
 
460
}
 
461
 
 
462
void StatusNotifierItemSource::contextMenu(int x, int y)
 
463
{
 
464
    if (m_menuImporter) {
 
465
    #if DBUSMENUQT_VERSION >= 0x000400
 
466
        m_menuImporter->updateMenu();
 
467
    #else
 
468
        QMenu *menu = m_menuImporter->menu();
 
469
        // Simulate an "aboutToShow" so that menu->sizeHint() is correct. Otherwise
 
470
        // the menu may show up over the applet if new actions are added on the
 
471
        // fly.
 
472
        connect(m_menuImporter, SIGNAL(menuReadyToBeShown()), this, SLOT(contextMenuReady()));
 
473
        QMetaObject::invokeMethod(menu, "aboutToShow");
 
474
    #endif
 
475
    } else {
 
476
        kWarning() << "Could not find DBusMenu interface, falling back to calling ContextMenu()";
 
477
        if (m_statusNotifierItemInterface && m_statusNotifierItemInterface->isValid()) {
 
478
            m_statusNotifierItemInterface->call(QDBus::NoBlock, "ContextMenu", x, y);
 
479
        }
 
480
    }
 
481
}
 
482
 
 
483
#include "statusnotifieritemsource.moc"