diff --git a/src/plasma/CMakeLists.txt b/src/plasma/CMakeLists.txt index 3801857df..7cc2fe3b9 100644 --- a/src/plasma/CMakeLists.txt +++ b/src/plasma/CMakeLists.txt @@ -50,6 +50,7 @@ set(Plasma_LIB_SRCS private/applet_p.cpp private/associatedapplicationmanager.cpp private/containment_p.cpp + private/timetracker.cpp #Dataengines, services datacontainer.cpp diff --git a/src/plasma/applet.h b/src/plasma/applet.h index 6effcc7cb..06b2722e7 100644 --- a/src/plasma/applet.h +++ b/src/plasma/applet.h @@ -65,6 +65,12 @@ class Package; class PLASMA_EXPORT Applet : public QObject { Q_OBJECT + Q_PROPERTY(Types::ItemStatus status READ status WRITE setStatus NOTIFY statusChanged) + Q_PROPERTY(Types::ImmutabilityType immutability READ immutability WRITE setImmutability NOTIFY immutabilityChanged) + Q_PROPERTY(Types::FormFactor formFactor READ formFactor NOTIFY formFactorChanged) + Q_PROPERTY(Types::Location location READ location NOTIFY locationChanged) + Q_PROPERTY(QString title READ title WRITE setTitle FINAL) + Q_PROPERTY(QString icon READ icon WRITE setIcon FINAL) public: //CONSTRUCTORS diff --git a/src/plasma/containment.h b/src/plasma/containment.h index c3907b0a2..968b3e2a1 100644 --- a/src/plasma/containment.h +++ b/src/plasma/containment.h @@ -59,6 +59,7 @@ class PLASMA_EXPORT Containment : public Applet { Q_OBJECT Q_PROPERTY(QString wallpaper READ wallpaper WRITE setWallpaper NOTIFY wallpaperChanged) + Q_PROPERTY(bool isUiReady READ isUiReady NOTIFY uiReadyChanged) public: /** diff --git a/src/plasma/private/applet_p.cpp b/src/plasma/private/applet_p.cpp index dcf53ce33..4fe27c979 100644 --- a/src/plasma/private/applet_p.cpp +++ b/src/plasma/private/applet_p.cpp @@ -41,6 +41,7 @@ #include "scripting/scriptengine.h" #include "scripting/appletscript.h" #include "private/containment_p.h" +#include "timetracker.h" namespace Plasma { @@ -76,6 +77,9 @@ AppletPrivate::AppletPrivate(KService::Ptr service, const KPluginInfo *info, int } QObject::connect(actions->action("configure"), SIGNAL(triggered()), q, SLOT(requestConfiguration())); +#ifndef NDEBUG + new TimeTracker(q); +#endif } AppletPrivate::~AppletPrivate() diff --git a/src/plasma/private/containment_p.cpp b/src/plasma/private/containment_p.cpp index 6a3b09cf2..f8b457828 100644 --- a/src/plasma/private/containment_p.cpp +++ b/src/plasma/private/containment_p.cpp @@ -34,6 +34,7 @@ #include "pluginloader.h" #include "private/applet_p.h" +#include "timetracker.h" namespace Plasma { @@ -53,6 +54,10 @@ ContainmentPrivate::ContainmentPrivate(Containment *c): if (appletParent) { QObject::connect(appletParent->containment(), &Containment::screenChanged, c, &Containment::screenChanged); } + +#ifndef NDEBUG + new TimeTracker(q); +#endif } Plasma::ContainmentPrivate::~ContainmentPrivate() diff --git a/src/plasma/private/timetracker.cpp b/src/plasma/private/timetracker.cpp new file mode 100644 index 000000000..bb809a597 --- /dev/null +++ b/src/plasma/private/timetracker.cpp @@ -0,0 +1,142 @@ +/* + * Copyright 2014 by Aleix Pol Gonzalez + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "timetracker.h" +#include +#include +#include +#include +#include +#include +#include + +using namespace Plasma; + +struct TimeTrackerWriter : QObject { + Q_OBJECT +public: + TimeTrackerWriter() { + QObject::connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, static_cast(&TimeTrackerWriter::print)); + } + + void print() { + QJsonArray array; + + Q_FOREACH(const ObjectHistory& history, m_data) { + QVariantMap map; + map["events"] = serializeEvents(history.events); + map["initial"] = history.initial; + + array.append(QJsonValue::fromVariant(map)); + } + Q_ASSERT(array.count() == m_data.count()); + QJsonDocument doc; + doc.setArray(array); + + QFile f(QStringLiteral("/tmp/debug-")+qgetenv("USER")+".json"); + bool b = f.open(QFile::WriteOnly); + Q_ASSERT(b); + f.write(doc.toJson()); + } + + void feed(QObject* obj, const ObjectHistory& tracker) + { + m_data[obj] = tracker; + } + + QHash m_data; + +private: + QVariantList serializeEvents(const QVector& events) const { + QVariantList ret; + Q_ASSERT(!events.isEmpty()); + foreach(const TimeEvent& ev, events) { + QVariantMap map; + map["comment"] = ev.comment; + map["time"] = ev.moment.toMSecsSinceEpoch(); + ret.append(map); + } + Q_ASSERT(ret.count() == events.count()); + return ret; + } +}; +Q_GLOBAL_STATIC(TimeTrackerWriter, s_writer); + +TimeTracker::TimeTracker(QObject* o) + : QObject(o) +{ + m_history.events.append(TimeEvent { QDateTime::currentDateTime(), QString("constructed %1 %2").arg(o->metaObject()->className()).arg(o->objectName()) }); + + QTimer* t = new QTimer(this); + t->setInterval(2000); + t->setSingleShot(false); + connect(t, SIGNAL(timeout()), this, SLOT(sync())); + t->start(); + + QMetaObject::invokeMethod(this, "init", Qt::QueuedConnection); +} + +void TimeTracker::init() +{ + QMetaMethod propChange = metaObject()->method(metaObject()->indexOfSlot("propertyChanged()")); + Q_ASSERT(propChange.isValid() && metaObject()->indexOfSlot("propertyChanged()")>=0); + + QObject* o = parent(); + for (int i = 0, pc = o->metaObject()->propertyCount(); imetaObject()->property(i); + if (prop.isFinal() || prop.isConstant()) + m_history.initial[prop.name()] = prop.read(o); + + if (prop.hasNotifySignal()) + connect(o, prop.notifySignal(), this, propChange); + } +} + +TimeTracker::~TimeTracker() +{ + sync(); +} + +void TimeTracker::sync() +{ + s_writer->feed(parent(), m_history); +} + +void TimeTracker::propertyChanged() +{ + Q_ASSERT(sender() == parent()); + + const QMetaObject* mo = parent()->metaObject(); + for (int i = 0, pc = mo->propertyCount(); iproperty(i); + if (prop.notifySignalIndex() == senderSignalIndex()) { + QString val; + QVariant var = prop.read(parent()); + if(var.canConvert()) { + val = var.toString(); + } else { + val = QString("").arg(var.typeName()); + } + m_history.events.append(TimeEvent { QDateTime::currentDateTime(), QString("property %1 changed to %2").arg(prop.name()).arg(val)}); + break; + } + } +} + +#include "timetracker.moc" diff --git a/src/plasma/private/timetracker.h b/src/plasma/private/timetracker.h new file mode 100644 index 000000000..b8aacabfb --- /dev/null +++ b/src/plasma/private/timetracker.h @@ -0,0 +1,77 @@ +/* + * Copyright 2014 by Aleix Pol Gonzalez + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef TIMETRACKER_H +#define TIMETRACKER_H + +#include +#include +#include +#include + +#include "plasma/plasma_export.h" + +namespace Plasma +{ +class Containment; +class Applet; + +struct TimeEvent +{ + QDateTime moment; + QString comment; +}; + +struct ObjectHistory +{ + QVariantMap initial; + QVector events; +}; + +/** + * This debugging class is meant to provide an overview of how the objects change + * over time and hopefully provide the information required to detect buggy initialization. + * + * To use it, you'll pass the object you want to track to the constructor and the TimeTracker + * will use Qt introspection to read the properties and check what values they have and how they + * change. + * + * To analyze the results one can read the generated json file /tmp/debug-$USER, as soon + * as the process has quit. + */ + +class PLASMA_EXPORT TimeTracker : QObject +{ +Q_OBJECT +public: + TimeTracker(QObject* applet); + virtual ~TimeTracker(); + +private Q_SLOTS: + void init(); + void sync(); + void propertyChanged(); + +private: + ObjectHistory m_history; +}; + +} + +#endif // TIMETRACKER_H