Merge lp:~mzanetti/ubuntu-clock-app/add-timezonemodel into lp:~nik90/ubuntu-clock-app/10-world-clocks
- add-timezonemodel
- Merge into 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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Nekhelesh Ramananthan | Pending | ||
Review via email: mp+226611@code.launchpad.net |
Commit message
Add a TimeZoneModel
Description of the change
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 |
Merged into lp:~nik90/ubuntu-clock-app/10-world-clocks