Merge lp:~mzanetti/ubuntu-clock-app/add-timezonemodel into lp:~nik90/ubuntu-clock-app/10-world-clocks

Proposed by Michael Zanetti
Status: Merged
Merged at revision: 24
Proposed branch: lp:~mzanetti/ubuntu-clock-app/add-timezonemodel
Merge into: lp:~nik90/ubuntu-clock-app/10-world-clocks
Diff against target: 493 lines (+364/-29)
8 files modified
app/worldclock/WorldCityList.qml (+6/-15)
backend/CMakeLists.txt (+1/-0)
backend/modules/Timezone/backend.cpp (+2/-0)
backend/modules/Timezone/timezonemodel.cpp (+214/-0)
backend/modules/Timezone/timezonemodel.h (+84/-0)
backend/modules/Timezone/zone.cpp (+29/-11)
backend/modules/Timezone/zone.h (+27/-2)
ubuntu-clock-app.json (+1/-1)
To merge this branch: bzr merge lp:~mzanetti/ubuntu-clock-app/add-timezonemodel
Reviewer Review Type Date Requested Status
Nekhelesh Ramananthan Pending
Review via email: mp+226611@code.launchpad.net

Commit message

Add a TimeZoneModel

To post a comment you must log in.
26. By Michael Zanetti

add missing endResetModel

Revision history for this message
Nekhelesh Ramananthan (nik90) wrote :

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'app/worldclock/WorldCityList.qml'
2--- app/worldclock/WorldCityList.qml 2014-07-13 11:25:46 +0000
3+++ app/worldclock/WorldCityList.qml 2014-07-14 09:09:10 +0000
4@@ -10,33 +10,24 @@
5 title: i18n.tr("Select a city")
6 visible: false
7
8- XmlListModel {
9- id: worldCityModel;
10-
11+ TimeZoneModel {
12+ id: timeZoneModel
13 source: Qt.resolvedUrl("world-city-list.xml")
14- query: "/Cities/City"
15-
16- XmlRole { name: "city"; query: "cityName/string()"; isKey: true }
17- XmlRole { name: "country"; query: "countryName/string()"; isKey: true }
18- XmlRole { name: "timezoneID"; query: "timezoneID/string()"; isKey: true }
19- }
20-
21- Zone {
22- id: cityTimezone
23+ updateInterval: 1000
24 }
25
26 ListView {
27 id: cityList
28
29 anchors.fill: parent
30- model: worldCityModel
31+ model: timeZoneModel
32
33 delegate: ListItem.Subtitled {
34 text: city + "," + country
35- subText: timezoneID
36+ subText: timezoneID + "(" + localTime + ")"
37
38 onClicked: {
39- console.log(cityTimezone.getLocalTime(timezoneID))
40+ print(model.timezoneID, model.localTime)
41 }
42 }
43 }
44
45=== modified file 'backend/CMakeLists.txt'
46--- backend/CMakeLists.txt 2014-07-13 11:18:31 +0000
47+++ backend/CMakeLists.txt 2014-07-14 09:09:10 +0000
48@@ -6,6 +6,7 @@
49 ubuntu-clock-appbackend_SRCS
50 modules/Timezone/backend.cpp
51 modules/Timezone/zone.cpp
52+ modules/Timezone/timezonemodel.cpp
53 )
54
55 add_library(ubuntu-clock-appbackend MODULE
56
57=== modified file 'backend/modules/Timezone/backend.cpp'
58--- backend/modules/Timezone/backend.cpp 2014-06-06 19:43:43 +0000
59+++ backend/modules/Timezone/backend.cpp 2014-07-14 09:09:10 +0000
60@@ -2,6 +2,7 @@
61 #include <QtQml/QQmlContext>
62 #include "backend.h"
63 #include "zone.h"
64+#include "timezonemodel.h"
65
66
67 void BackendPlugin::registerTypes(const char *uri)
68@@ -9,6 +10,7 @@
69 Q_ASSERT(uri == QLatin1String("Timezone"));
70
71 qmlRegisterType<Zone>(uri, 1, 0, "Zone");
72+ qmlRegisterType<TimeZoneModel>(uri, 1, 0, "TimeZoneModel");
73 }
74
75 void BackendPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
76
77=== added file 'backend/modules/Timezone/timezonemodel.cpp'
78--- backend/modules/Timezone/timezonemodel.cpp 1970-01-01 00:00:00 +0000
79+++ backend/modules/Timezone/timezonemodel.cpp 2014-07-14 09:09:10 +0000
80@@ -0,0 +1,214 @@
81+
82+#include "timezonemodel.h"
83+
84+#include <QTimeZone>
85+#include <QFile>
86+#include <QXmlStreamReader>
87+#include <QTimeZone>
88+#include <QDebug>
89+
90+TimeZoneModel::TimeZoneModel(QObject *parent):
91+ QAbstractListModel(parent)
92+{
93+ m_updateTimer.setInterval(0);
94+ connect(&m_updateTimer, &QTimer::timeout, this, &TimeZoneModel::update);
95+}
96+
97+// In order for Qt/QML to know how many entries our model has, we
98+// need to implement rowCount() and return the amount of rows.
99+int TimeZoneModel::rowCount(const QModelIndex &parent) const
100+{
101+ // Qt's models can also handle tables and tree views, so the index
102+ // is not just a integer, but consists of a parent, a row and acolumn.
103+ // We're just using the siple list mode, so let's ignore the parent.
104+ // Using Q_UNUSED(parent) gets rid of the compile warning about the
105+ // unused variable.
106+ Q_UNUSED(parent)
107+
108+ return m_timeZones.count();
109+}
110+
111+// This is the main function to get the data out of the model.
112+// We need to return our stuff here.
113+QVariant TimeZoneModel::data(const QModelIndex &index, int role) const
114+{
115+ // Again, ignore everything from the index except row. Were' not into tables and trees.
116+ int row = index.row();
117+
118+ // Now, each cell can have multiple values, e.g. a text value, a color value,
119+ // an icon value and what not. Those things are selected by using "roles".
120+ // We have defined Roles in our .h file. Lets use them here.
121+
122+ switch (role) {
123+ case RoleCityName:
124+ return m_timeZones.at(row).cityName;
125+ case RoleCountyName:
126+ return m_timeZones.at(row).country;
127+ case RoleTimeZoneId:
128+ return m_timeZones.at(row).timeZoneId;
129+ case RoleTimeString:
130+ QTimeZone zone(m_timeZones.at(row).timeZoneId.toLatin1());
131+ // TODO: pass formatting options as parameter to toString().
132+ // see: http://qt-project.org/doc/qt-5/qdate.html#toString
133+ return QDateTime::currentDateTime().toTimeZone(zone).toString();
134+ }
135+
136+ // In case the method was called with an invalid index or role, let's
137+ // return an empty QVariant
138+ return QVariant();
139+}
140+
141+QHash<int, QByteArray> TimeZoneModel::roleNames() const
142+{
143+ QHash<int, QByteArray> roles;
144+ roles.insert(RoleCityName, "city");
145+ roles.insert(RoleCountyName, "country");
146+ roles.insert(RoleTimeZoneId, "timezoneID");
147+ roles.insert(RoleTimeString, "localTime");
148+ return roles;
149+}
150+
151+QUrl TimeZoneModel::source() const
152+{
153+ return m_source;
154+}
155+
156+void TimeZoneModel::setSource(const QUrl &source)
157+{
158+ if (m_source == source) {
159+ // Don't parse the file again if the source is the same already
160+ return;
161+ }
162+
163+ // Change the property and let people know by emitting the changed signal
164+ m_source = source;
165+ emit sourceChanged();
166+
167+ // Ultimately load the file
168+ loadTimeZonesFromXml();
169+}
170+
171+int TimeZoneModel::updateInterval() const
172+{
173+ return m_updateTimer.interval();
174+}
175+
176+void TimeZoneModel::setUpdateInterval(int updateInterval)
177+{
178+ if (m_updateTimer.interval() != updateInterval) {
179+ m_updateTimer.setInterval(updateInterval);
180+ emit updateIntervalChanged();
181+
182+ if (m_updateTimer.interval() > 0) {
183+ m_updateTimer.start();
184+ } else {
185+ m_updateTimer.stop();
186+ }
187+ }
188+}
189+
190+void TimeZoneModel::loadTimeZonesFromXml()
191+{
192+ // Let qml know that the model will be cleared and rebuilt
193+ beginResetModel();
194+
195+ m_timeZones.clear();
196+
197+ QFile file(m_source.path());
198+ if (!file.open(QFile::ReadOnly)) {
199+ qWarning() << "Can't open" << m_source << ". Model will be empty.";
200+ endResetModel();
201+ return;
202+ }
203+ QXmlStreamReader reader(&file);
204+ bool haveCities = false;
205+ bool isCityName = false;
206+ bool isCountryName = false;
207+ bool isTzId = false;
208+
209+ TimeZone tz;
210+ while (!reader.atEnd() && !reader.hasError()) {
211+ QXmlStreamReader::TokenType token = reader.readNext();
212+
213+ // Skip any header
214+ if(token == QXmlStreamReader::StartDocument) {
215+ continue;
216+ }
217+
218+ if (token == QXmlStreamReader::StartElement) {
219+
220+ // skip anything outside the Cities tag
221+ if (!haveCities) {
222+ if (reader.name() == "Cities") {
223+ haveCities = true;
224+ }
225+ continue;
226+ }
227+
228+ if (reader.name() == "City") {
229+ // A new time zone begins. clear tz
230+ tz = TimeZone();
231+ }
232+ if (reader.name() == "cityName") {
233+ isCityName = true;
234+ }
235+ if (reader.name() == "countryName") {
236+ isCountryName = true;
237+ }
238+ if (reader.name() == "timezoneID") {
239+ isTzId = true;
240+ }
241+ }
242+
243+ if (token == QXmlStreamReader::Characters) {
244+
245+ if (isCityName) {
246+ tz.cityName = reader.text().toString();
247+ }
248+ if (isCountryName) {
249+ tz.country = reader.text().toString();
250+ }
251+ if (isTzId) {
252+ tz.timeZoneId = reader.text().toString();
253+ }
254+ }
255+
256+ if (token == QXmlStreamReader::EndElement) {
257+ if (reader.name() == "Cities") {
258+ haveCities = false;
259+ }
260+ if (reader.name() == "City") {
261+ // A time zone has ended. insert it into list
262+ m_timeZones.append(tz);
263+ qDebug() << "appended tz:" << tz.cityName << tz.country << tz.timeZoneId;
264+ }
265+ if (reader.name() == "cityName") {
266+ isCityName = false;
267+ }
268+ if (reader.name() == "countryName") {
269+ isCountryName = false;
270+ }
271+ if (reader.name() == "timezoneID") {
272+ isTzId = false;
273+ }
274+ }
275+ }
276+
277+ // Let QML know that the model is usable again.
278+ endResetModel();
279+}
280+
281+void TimeZoneModel::update()
282+{
283+ // All we have to do is to emit notifications for the view's to re-request
284+ // the data for role timezoneID because the time will be calculated on the
285+ // fly anyways
286+ // For that we emit dataChanged with a startIndex of 0, an endIndex for the
287+ // last item and the RoleTimeString as the changed roles.
288+
289+ QModelIndex startIndex = index(0);
290+ QModelIndex endIndex = index(m_timeZones.count() - 1);
291+ QVector<int> roles;
292+ roles << RoleTimeString;
293+ emit dataChanged(startIndex, endIndex, roles);
294+}
295
296=== added file 'backend/modules/Timezone/timezonemodel.h'
297--- backend/modules/Timezone/timezonemodel.h 1970-01-01 00:00:00 +0000
298+++ backend/modules/Timezone/timezonemodel.h 2014-07-14 09:09:10 +0000
299@@ -0,0 +1,84 @@
300+#ifndef TIMEZONEMODEL_H
301+#define TIMEZONEMODEL_H
302+
303+#include <QAbstractListModel>
304+#include <QDateTime>
305+#include <QUrl>
306+#include <QTimer>
307+
308+// Create a simple container class to hold our information
309+class TimeZone
310+{
311+public:
312+ QString cityName;
313+ QString country;
314+ QString timeZoneId;
315+};
316+
317+
318+
319+// We're going to use QAbstractListModel as the base class
320+// That makes it compatible to QML's ListView.
321+class TimeZoneModel: public QAbstractListModel
322+{
323+ Q_OBJECT
324+
325+ // Let's have a source property for the xml file, just like the XmlListModel
326+ Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged)
327+
328+ // A this property determines the interval for updating time. The default, 0, doesn't update at all
329+ Q_PROPERTY(int updateInterval READ updateInterval WRITE setUpdateInterval NOTIFY updateIntervalChanged)
330+
331+public:
332+ enum Roles {
333+ RoleCityName,
334+ RoleCountyName,
335+ RoleTimeZoneId,
336+ RoleTimeString
337+ };
338+
339+ // A simple constructor. Add the standard QObject *parent parameter.
340+ // That helps Qt with "garbage collection"
341+ TimeZoneModel(QObject *parent = 0);
342+
343+ // Let's override the pure virtual functions (the ones marked as
344+ // "virtual" and have "= 0" in the end.
345+ int rowCount(const QModelIndex &parent) const override;
346+ QVariant data(const QModelIndex &index, int role) const override;
347+
348+ // As QML can't really deal with the Roles enum above, we need a mapping
349+ // between the enum and strings
350+ QHash<int, QByteArray> roleNames() const override;
351+
352+ // We need to implement the READ and WRITE methods of the properties
353+ QUrl source() const;
354+ void setSource(const QUrl &source);
355+
356+ int updateInterval() const;
357+ void setUpdateInterval(int updateInterval);
358+
359+signals:
360+ // and we need a signal for the NOTIFY when the properties change
361+ void sourceChanged();
362+ void updateIntervalChanged();
363+
364+private:
365+ // Lets do the xml parsing in a separate function for less messy code
366+ void loadTimeZonesFromXml();
367+
368+private slots:
369+ // A private slot that gets called by the updateTimer
370+ void update();
371+
372+private:
373+ // Keep a list of TimeZone objects, holding all our timeZones.
374+ QList<TimeZone> m_timeZones;
375+
376+ // And keel a store of the source property
377+ QUrl m_source;
378+
379+ // Have a timer to update stuff
380+ QTimer m_updateTimer;
381+};
382+
383+#endif
384
385=== modified file 'backend/modules/Timezone/zone.cpp'
386--- backend/modules/Timezone/zone.cpp 2014-07-13 11:25:46 +0000
387+++ backend/modules/Timezone/zone.cpp 2014-07-14 09:09:10 +0000
388@@ -1,8 +1,12 @@
389 #include "zone.h"
390
391+// This is the constructor
392+// It will only be called once, when a new "Zone" object is created.
393+// If you read currentDateTime in here, it'll stick to the time when the
394+// Object is created and not reflect the current time when getLocalTime is called.
395 Zone::Zone(QObject *parent) :
396 QObject(parent),
397- m_time(QDateTime::currentDateTime())
398+ m_time(QDateTime::currentDateTime()) // delete this
399 {
400
401 }
402@@ -11,14 +15,28 @@
403
404 }
405
406-QDateTime Zone::getLocalTime(QByteArray *timezoneID) {
407- // Create a QTimeZone object and initiate it with the timezone ID provided
408- QTimeZone zone = QTimeZone(*timezoneID);
409-
410- // Convert the QDateTime object to the timezone provided
411- m_time.toTimeZone(zone);
412-
413- qDebug() << m_time;
414-
415- return m_time;
416+//QDateTime Zone::getLocalTime(QByteArray *timezoneID) {
417+// // Create a QTimeZone object and initiate it with the timezone ID provided
418+// QTimeZone zone = QTimeZone(*timezoneID);
419+
420+// // Convert the QDateTime object to the timezone provided
421+// m_time.toTimeZone(zone);
422+
423+// qDebug() << m_time;
424+
425+// return m_time;
426+//}
427+
428+// In my opinion this should work but it doesn't will ask some other people
429+// and report a bug if m suspicion turns out to be true.
430+QDateTime Zone::getLocalTime(const QByteArray &timeZoneId) const
431+{
432+ QTimeZone zone(timeZoneId);
433+ return QDateTime::currentDateTime().toTimeZone(zone);
434+}
435+
436+// So. let's use this instead:
437+QString Zone::getCurrentTimeString(const QByteArray &timeZoneId) const
438+{
439+ return getLocalTime(timeZoneId).toString();
440 }
441
442=== modified file 'backend/modules/Timezone/zone.h'
443--- backend/modules/Timezone/zone.h 2014-07-13 11:25:46 +0000
444+++ backend/modules/Timezone/zone.h 2014-07-14 09:09:10 +0000
445@@ -22,10 +22,35 @@
446 * Public function to receive a Olson Timezone ID as argument and
447 * output that timezone's QDateTime object.
448 */
449- QDateTime getLocalTime(QByteArray *timezoneID);
450+// QDateTime getLocalTime(QByteArray *timezoneID);
451+
452+
453+// You shouldn't use slots with return values... Instead, make a public
454+// function and mark it as Q_INVOKABLE
455+public:
456+ // You shouldn't use a pointer (*) to QByteArray. There's only very few cases
457+ // where that is desirable. Until you understand the difference, better
458+ // stick to references (&) like this:
459+ Q_INVOKABLE QDateTime getLocalTime(const QByteArray &timeZoneId) const;
460+
461+ // Marking the parameter and the method "const" is good practice and helps
462+ // with keeping the code easier to maintain, but it's not required. Don't
463+ // get hung up on those if you're not understanding those yet.
464+
465+ // You could also just use this:
466+ // QDateTime getLocalTime(QByteArray timeZoneId);
467+ // That, however, makes the code slower, because it creates a copy of
468+ // timeZoneId before passing it into the function. Using & doesn't
469+ // do that and refers to the original value. We're using const to make
470+ // sure to not accidentally change the original value.
471+
472+
473+ // Workaround for QDateTime losing timezone information
474+ Q_INVOKABLE QString getCurrentTimeString(const QByteArray &timeZoneId) const;
475+
476
477 private:
478- QDateTime m_time;
479+ QDateTime m_time; // delete this
480 };
481
482 #endif // ZONE_H
483
484=== modified file 'ubuntu-clock-app.json'
485--- ubuntu-clock-app.json 2014-07-11 03:01:10 +0000
486+++ ubuntu-clock-app.json 2014-07-14 09:09:10 +0000
487@@ -5,4 +5,4 @@
488 "networking"
489 ],
490 "policy_version": 1.2
491-}
492+}
493\ No newline at end of file

Subscribers

People subscribed via source and target branches