@@ -0,0 +1,465 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ *
16+ * Author: Pete Woods <pete.woods@canonical.com>
17+ */
18+
19+#include "UserMetrics.h"
20+#include "plugins/Utils/qvariantlistmodel.h"
21+
22+#include <QtCore/QDir>
23+#include <QtCore/QString>
24+#include <QtGui/QIcon>
25+#include <QMultiMap>
26+
27+using namespace UserMetricsOutput;
28+
29+namespace UserMetricsOutput
30+{
31+class UserMetricsDataPrivate;
32+class UserMetricsPrivate;
33+
34+class Q_DECL_EXPORT UserMetricsData: public QObject
35+{
36+public:
37+ explicit UserMetricsData(QObject *parent);
38+
39+ UserMetricsData(const QString &label,
40+ const ColorTheme &firstColor,
41+ const QVariantList &firstMonth,
42+ const ColorTheme &secondColor,
43+ const QVariantList &secondMonth, QObject* parent);
44+
45+ ~UserMetricsData();
46+
47+protected:
48+ UserMetricsDataPrivate * const d_ptr;
49+
50+public:
51+ const QString & label() const;
52+ const ColorTheme & firstColor() const;
53+ const QVariantList & firstMonth() const;
54+ const ColorTheme & secondColor() const;
55+ const QVariantList & secondMonth() const;
56+ int length() const;
57+
58+private:
59+ Q_DECLARE_PRIVATE(UserMetricsData)
60+};
61+
62+class UserMetricsPrivate: QObject
63+{
64+public:
65+ explicit UserMetricsPrivate(UserMetrics *parent);
66+
67+ ~UserMetricsPrivate();
68+
69+public:
70+ typedef QSharedPointer<UserMetricsData> UserMetricsDataPtr;
71+ typedef QMultiMap<QString, UserMetricsDataPtr> FakeDataMap;
72+
73+ UserMetrics * const q_ptr;
74+ QString m_label;
75+ ColorTheme m_firstColor;
76+ QVariantListModel m_firstMonth;
77+ ColorTheme m_secondColor;
78+ QVariantListModel m_secondMonth;
79+ int m_currentDay;
80+ QString m_username;
81+ FakeDataMap::const_iterator m_dataIndex;
82+ UserMetricsDataPtr m_newData;
83+ FakeDataMap m_fakeData;
84+
85+ void setUsername(const QString &username);
86+
87+protected:
88+ void nextFakeData();
89+
90+ void generateFakeData();
91+
92+ void loadFakeData();
93+
94+ void finishSetFakeData();
95+
96+private:
97+ Q_DECLARE_PUBLIC(UserMetrics)
98+};
99+
100+class UserMetricsDataPrivate: QObject
101+{
102+public:
103+ explicit UserMetricsDataPrivate(UserMetricsData *parent);
104+
105+ UserMetricsDataPrivate(const QString &label,
106+ const ColorTheme &firstColor,
107+ const QVariantList &firstMonth,
108+ const ColorTheme &secondColor,
109+ const QVariantList &secondMonth, UserMetricsData *parent);
110+
111+ ~UserMetricsDataPrivate();
112+
113+ UserMetricsData * const q_ptr;
114+ QString m_label;
115+ ColorTheme m_firstColor;
116+ QVariantList m_firstMonth;
117+ ColorTheme m_secondColor;
118+ QVariantList m_secondMonth;
119+ int m_length;
120+
121+protected:
122+ int calculateLength();
123+
124+private:
125+ Q_DECLARE_PUBLIC(UserMetricsData)
126+};
127+
128+}
129+
130+UserMetricsDataPrivate::UserMetricsDataPrivate(UserMetricsData *parent) :
131+ q_ptr(parent), m_firstColor(this), m_secondColor(this)
132+{
133+ m_length = calculateLength();
134+}
135+
136+UserMetricsDataPrivate::UserMetricsDataPrivate(const QString &label,
137+ const ColorTheme &firstColor, const QVariantList &firstMonth,
138+ const ColorTheme &secondColor,
139+ const QVariantList &secondMonth, UserMetricsData *parent) :
140+ q_ptr(parent), m_label(label), m_firstColor(this), m_firstMonth(
141+ firstMonth), m_secondColor(this), m_secondMonth(secondMonth)
142+{
143+ m_length = calculateLength();
144+ m_firstColor = firstColor;
145+ m_secondColor = secondColor;
146+}
147+
148+UserMetricsDataPrivate::~UserMetricsDataPrivate()
149+{
150+}
151+
152+int UserMetricsDataPrivate::calculateLength()
153+{
154+ int day(m_firstMonth.size());
155+ auto it = m_firstMonth.end(), end = m_firstMonth.begin();
156+ while (it != end)
157+ {
158+ --it;
159+ --day;
160+ if (!it->isNull())
161+ {
162+ return day;
163+ }
164+ }
165+
166+ return -1;
167+}
168+
169+UserMetricsData::UserMetricsData(QObject *parent) :
170+ QObject(parent), d_ptr(new UserMetricsDataPrivate(this))
171+{
172+}
173+
174+UserMetricsData::UserMetricsData(const QString &label,
175+ const ColorTheme &firstColor, const QVariantList &firstMonth,
176+ const ColorTheme &secondColor,
177+ const QVariantList &secondMonth, QObject* parent) :
178+ QObject(parent), d_ptr(
179+ new UserMetricsDataPrivate(label, firstColor, firstMonth,
180+ secondColor, secondMonth, this))
181+{
182+}
183+
184+UserMetricsData::~UserMetricsData()
185+{
186+ delete d_ptr;
187+}
188+
189+const QString & UserMetricsData::label() const
190+{
191+ return d_ptr->m_label;
192+}
193+
194+const ColorTheme & UserMetricsData::firstColor() const
195+{
196+ return d_ptr->m_firstColor;
197+}
198+
199+const QVariantList & UserMetricsData::firstMonth() const
200+{
201+ return d_ptr->m_firstMonth;
202+}
203+
204+const ColorTheme & UserMetricsData::secondColor() const
205+{
206+ return d_ptr->m_secondColor;
207+}
208+
209+const QVariantList & UserMetricsData::secondMonth() const
210+{
211+ return d_ptr->m_secondMonth;
212+}
213+
214+int UserMetricsData::length() const
215+{
216+ return d_ptr->m_length;
217+}
218+
219+UserMetricsPrivate::UserMetricsPrivate(UserMetrics *parent) :
220+ q_ptr(parent), m_firstColor(this), m_secondColor(this)
221+{
222+ m_fakeData.insert("", UserMetricsDataPtr(new UserMetricsData(this)));
223+}
224+
225+UserMetricsPrivate::~UserMetricsPrivate()
226+{
227+}
228+
229+void UserMetricsPrivate::setUsername(const QString &username)
230+{
231+ if (m_username == username) {
232+ return;
233+ }
234+
235+ m_username = username;
236+
237+ m_dataIndex = m_fakeData.constFind(m_username);
238+ if (m_dataIndex == m_fakeData.end())
239+ {
240+ m_dataIndex = m_fakeData.constFind("");
241+ }
242+
243+ loadFakeData();
244+
245+ q_ptr->usernameChanged(m_username);
246+}
247+
248+void UserMetricsPrivate::generateFakeData()
249+{
250+ std::default_random_engine generator;
251+ std::normal_distribution<qreal> distribution(0.5, 0.2);
252+ auto rand = std::bind(distribution, generator);
253+
254+ QVector<QColor> colours;
255+ colours.push_back(QColor::fromRgbF(0.3, 0.27, 0.32));
256+ colours.push_back(QColor::fromRgbF(0.83, 0.49, 0.58));
257+ colours.push_back(QColor::fromRgbF(0.63, 0.51, 0.59));
258+
259+ colours.push_back(QColor::fromRgbF(0.28, 0.26, 0.4));
260+ colours.push_back(QColor::fromRgbF(0.47, 0.38, 0.56));
261+ colours.push_back(QColor::fromRgbF(0.69, 0.65, 0.78));
262+
263+ colours.push_back(QColor::fromRgbF(0.32, 0.21, 0.16));
264+ colours.push_back(QColor::fromRgbF(0.55, 0.45, 0.32));
265+ colours.push_back(QColor::fromRgbF(0.85, 0.74, 0.53));
266+
267+ colours.push_back(QColor::fromRgbF(0.25, 0.31, 0.19));
268+ colours.push_back(QColor::fromRgbF(0.63, 0.53, 0.3));
269+ colours.push_back(QColor::fromRgbF(0.89, 0.56, 0.31));
270+
271+ ColorTheme first(colours[0], colours[1], colours[2]);
272+ ColorTheme second(colours[3], colours[4], colours[5]);
273+ ColorTheme eighth(colours[6], colours[7], colours[8]);
274+ ColorTheme ninth(colours[9], colours[10], colours[11]);
275+
276+ {
277+ QVariantList firstMonth;
278+ while (firstMonth.size() < 17)
279+ firstMonth.push_back(QVariant(rand()));
280+ while (firstMonth.size() < 31)
281+ firstMonth.push_back(QVariant());
282+ QVariantList secondMonth;
283+ while (secondMonth.size() < 31)
284+ secondMonth.push_back(QVariant(rand()));
285+ QSharedPointer<UserMetricsData> data(
286+ new UserMetricsData("<b>52km</b> travelled", first, firstMonth,
287+ ninth, secondMonth, this));
288+ m_fakeData.insert("single", data);
289+ }
290+
291+ {
292+ QVariantList firstMonth;
293+ while (firstMonth.size() < 17)
294+ firstMonth.push_back(QVariant(rand()));
295+ while (firstMonth.size() < 31)
296+ firstMonth.push_back(QVariant());
297+ QVariantList secondMonth;
298+ while (secondMonth.size() < 31)
299+ secondMonth.push_back(QVariant(rand()));
300+ QSharedPointer<UserMetricsData> data(
301+ new UserMetricsData("<b>33</b> messages today", second,
302+ firstMonth, eighth, secondMonth, this));
303+ m_fakeData.insert("single", data);
304+ }
305+
306+ {
307+ QVariantList firstMonth;
308+ while (firstMonth.size() < 17)
309+ firstMonth.push_back(QVariant(rand()));
310+ while (firstMonth.size() < 31)
311+ firstMonth.push_back(QVariant());
312+ QVariantList secondMonth;
313+ while (secondMonth.size() < 31)
314+ secondMonth.push_back(QVariant(rand()));
315+ QSharedPointer<UserMetricsData> data(
316+ new UserMetricsData("<b>19</b> minutes talk time", eighth,
317+ firstMonth, second, secondMonth, this));
318+ m_fakeData.insert("single", data);
319+ // Also use same data for some tablet users
320+ m_fakeData.insert("has-password", data);
321+ m_fakeData.insert("no-password", data);
322+ m_fakeData.insert("empty-name", data);
323+ }
324+}
325+
326+void UserMetricsPrivate::loadFakeData()
327+{
328+ m_newData = *m_dataIndex;
329+
330+ bool oldLabelEmpty = m_label.isEmpty();
331+ bool newLabelEmpty = m_newData->label().isEmpty();
332+
333+ if (oldLabelEmpty && !newLabelEmpty)
334+ {
335+ q_ptr->dataAboutToAppear();
336+ finishSetFakeData();
337+ } else if (!oldLabelEmpty && newLabelEmpty)
338+ {
339+ q_ptr->dataAboutToDisappear();
340+ } else if (!oldLabelEmpty && !newLabelEmpty)
341+ {
342+ q_ptr->dataAboutToChange();
343+ }
344+ // we emit no signal if the data has stayed empty
345+}
346+
347+void UserMetricsPrivate::finishSetFakeData()
348+{
349+ bool oldLabelEmpty = m_label.isEmpty();
350+ bool newLabelEmpty = m_newData->label().isEmpty();
351+
352+ m_label = m_newData->label();
353+ m_firstColor = m_newData->firstColor();
354+ m_firstMonth.setVariantList(m_newData->firstMonth());
355+ m_secondColor = m_newData->secondColor();
356+ m_secondMonth.setVariantList(m_newData->secondMonth());
357+
358+ bool currentDayChanged = m_currentDay != m_newData->length();
359+ m_currentDay = m_newData->length();
360+
361+ q_ptr->labelChanged(m_label);
362+ if (currentDayChanged)
363+ {
364+ q_ptr->currentDayChanged(m_currentDay);
365+ }
366+
367+ if (oldLabelEmpty && !newLabelEmpty)
368+ {
369+ q_ptr->dataAppeared();
370+ } else if (!oldLabelEmpty && newLabelEmpty)
371+ {
372+ q_ptr->dataDisappeared();
373+ } else if (!oldLabelEmpty && !newLabelEmpty)
374+ {
375+ q_ptr->dataChanged();
376+ }
377+ // we emit no signal if the data has stayed empty
378+}
379+
380+void UserMetricsPrivate::nextFakeData()
381+{
382+ ++m_dataIndex;
383+ if (m_dataIndex == m_fakeData.end() || m_dataIndex.key() != m_username)
384+ {
385+ m_dataIndex = m_fakeData.constFind(m_username);
386+ }
387+
388+ loadFakeData();
389+}
390+
391+UserMetrics::UserMetrics(QObject *parent) :
392+ QObject(parent), d_ptr(new UserMetricsPrivate(this))
393+{
394+ d_ptr->generateFakeData();
395+ setUsername("");
396+
397+ connect(this, SIGNAL(nextDataSource()), this, SLOT(nextDataSourceSlot()),
398+ Qt::QueuedConnection);
399+ connect(this, SIGNAL(readyForDataChange()), this,
400+ SLOT(readyForDataChangeSlot()), Qt::QueuedConnection);
401+}
402+
403+UserMetrics::~UserMetrics()
404+{
405+ delete d_ptr;
406+}
407+
408+QString UserMetrics::label() const
409+{
410+ return d_ptr->m_label;
411+}
412+
413+QString UserMetrics::username() const
414+{
415+ return d_ptr->m_username;
416+}
417+
418+void UserMetrics::setUsername(const QString &username)
419+{
420+ d_ptr->setUsername(username);
421+}
422+
423+ColorTheme *UserMetrics::firstColor() const
424+{
425+ return &d_ptr->m_firstColor;
426+}
427+
428+ColorTheme *UserMetrics::secondColor() const
429+{
430+ return &d_ptr->m_secondColor;
431+}
432+
433+QAbstractItemModel *UserMetrics::firstMonth() const
434+{
435+ return &d_ptr->m_firstMonth;
436+}
437+
438+QAbstractItemModel *UserMetrics::secondMonth() const
439+{
440+ return &d_ptr->m_secondMonth;
441+}
442+
443+int UserMetrics::currentDay() const
444+{
445+ return d_ptr->m_currentDay;
446+}
447+
448+void UserMetrics::nextDataSourceSlot()
449+{
450+ d_ptr->nextFakeData();
451+}
452+
453+void UserMetrics::readyForDataChangeSlot()
454+{
455+ d_ptr->finishSetFakeData();
456+}
457+
458+/**
459+ * Factory methods
460+ */
461+
462+UserMetrics * UserMetrics::getInstance()
463+{
464+ return new UserMetrics();
465+}