Merge lp:~nik90/ubuntu-clock-app/clock-code-restructure into lp:ubuntu-clock-app/saucy

Proposed by Nekhelesh Ramananthan
Status: Merged
Approved by: Nekhelesh Ramananthan
Approved revision: 261
Merged at revision: 247
Proposed branch: lp:~nik90/ubuntu-clock-app/clock-code-restructure
Merge into: lp:ubuntu-clock-app/saucy
Diff against target: 1125 lines (+518/-417)
11 files modified
clock/AnalogClockFace.qml (+33/-6)
clock/CityTimezone.qml (+53/-0)
clock/ClockPage.qml (+11/-30)
clock/EasterEgg.qml (+8/-82)
clock/EasterEggModel.qml (+41/-0)
clock/EasterEggStorage.js (+76/-0)
clock/SearchBox.qml (+65/-0)
clock/WorldCity.qml (+57/-0)
clock/WorldClock.qml (+173/-222)
clock/WorldPage.qml (+0/-76)
tests/autopilot/ubuntu_clock_app/emulators.py (+1/-1)
To merge this branch: bzr merge lp:~nik90/ubuntu-clock-app/clock-code-restructure
Reviewer Review Type Date Requested Status
Ubuntu Phone Apps Jenkins Bot continuous-integration Approve
Ubuntu Clock Developers Pending
Review via email: mp+191912@code.launchpad.net

Commit message

This commit performs some code restructuring and organises elements into separate components.

- Moved world clock search box, worldcity xml model and citytimezone xml model to its own component
- Split EasterEgg.qml into EasterEggModel.qml, EasterEggStorage.js and EasterEgg.qml
- Merged WorldClock.qml and WorldPage.qml
- Removed inverse_mouse area (not required)
- Removed search button
- Added TODO, FIXME comments. Increased code clarity and made it easier for new developers to get involved (clock).

This commit should make it easier to fix any world clock related bugs and is a nice pre-runner to https://bugs.launchpad.net/bugs/1233986

Description of the change

In the rush to 1.0 release, the code dealing with clock alone has become messy and complexy interlinked. This MP performs some code restructuring and organises elements into separate components.

- Moved world clock search box, worldcity xml model and citytimezone xml model to its own component
- Split EasterEgg.qml into EasterEggModel.qml, EasterEggStorage.js and EasterEgg.qml
- Merged WorldClock.qml and WorldPage.qml
- Removed inverse_mouse area (not required)
- Removed search button
- Added TODO, FIXME comments. Increased code clarity and made it easier for new developers to get involved (clock).

This MP should make it easier to fix any world clock related bugs and is a nice pre-runner to https://bugs.launchpad.net/bugs/1233986

To post a comment you must log in.
Revision history for this message
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote :
review: Needs Fixing (continuous-integration)
259. By Nekhelesh Ramananthan

Fixed autopilot test

260. By Nekhelesh Ramananthan

removed username variable

Revision history for this message
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote :
review: Approve (continuous-integration)
261. By Nekhelesh Ramananthan

Merge from master

Revision history for this message
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote :
review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'clock/AnalogClockFace.qml'
2--- clock/AnalogClockFace.qml 2013-07-14 12:33:25 +0000
3+++ clock/AnalogClockFace.qml 2013-10-22 17:11:13 +0000
4@@ -22,13 +22,16 @@
5 import Ubuntu.Components 0.1
6 import "../common"
7
8+// Element which draws the clock face along with the hour, minute, second hand and the clock label
9 AnalogFaceBase {
10 id: clockRoot
11
12 property var currDate: new Date;
13 property int hours: currDate.getHours()
14 property int minutes: currDate.getMinutes()
15- property int seconds: currDate.getUTCSeconds();
16+ property int seconds: currDate.getUTCSeconds();
17+
18+ property alias clockLabel: currentTimeLabel
19
20 signal clicked(var mouse)
21
22@@ -43,7 +46,7 @@
23
24 z: parent.z
25 handHeight: units.gu(12.5); handWidth: units.gu(1);
26- rotation: (clockRoot.hours * 30) + (clockRoot.minutes / 2);
27+ rotation: (hours * 30) + (minutes / 2);
28 }
29
30 AnalogClockHand {
31@@ -51,7 +54,7 @@
32
33 z: parent.z + 1
34 handHeight: units.gu(14.5); handWidth: units.gu(0.5);
35- rotation: clockRoot.minutes * 6;
36+ rotation: minutes * 6;
37 }
38
39 AnalogClockHand {
40@@ -59,9 +62,33 @@
41
42 z: parent.z - 1;
43 handHeight: units.gu(17); handWidth: units.gu(0.5)
44- rotation: clockRoot.seconds * 6;
45- }
46-
47+ rotation: seconds * 6;
48+ }
49+
50+ // Label to show the current time
51+ // TODO: Investigate moving this component into common/AnalogFaceBase.qml since it is required by
52+ // clock, timer, stopwatch and alarm.
53+ Rectangle {
54+ id: labelContainer;
55+
56+ z: innerCircle.z + 1;
57+ width: innerCircle.width; height: width;
58+ anchors.centerIn: parent;
59+ color: "transparent"
60+
61+ Label {
62+ id: currentTimeLabel
63+ objectName: "currentTimeLabel"
64+
65+ anchors.centerIn: parent
66+ horizontalAlignment: Text.AlignHCenter
67+ verticalAlignment: Text.AlignVCenter
68+ color: Theme.palette.normal.baseText
69+ font.pixelSize: units.dp(41)
70+ }
71+ }
72+
73+ // Mouse area to reveal the clock sunrise/sunset on clicking the clock face
74 MouseArea {
75 anchors.fill: parent
76 z: parent.z + 1;
77
78=== added file 'clock/CityTimezone.qml'
79--- clock/CityTimezone.qml 1970-01-01 00:00:00 +0000
80+++ clock/CityTimezone.qml 2013-10-22 17:11:13 +0000
81@@ -0,0 +1,53 @@
82+/*
83+ * Copyright (C) 2013 Canonical Ltd
84+ *
85+ * This program is free software: you can redistribute it and/or modify
86+ * it under the terms of the GNU General Public License version 3 as
87+ * published by the Free Software Foundation.
88+ *
89+ * This program is distributed in the hope that it will be useful,
90+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
91+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
92+ * GNU General Public License for more details.
93+ *
94+ * You should have received a copy of the GNU General Public License
95+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
96+ *
97+ * Authored by: Nekhelesh Ramananthan <krnekhelesh@gmail.com>
98+ */
99+
100+import QtQuick 2.0
101+import Ubuntu.Components 0.1
102+import QtQuick.XmlListModel 2.0
103+
104+// XML Model to retrieve the timezone info of a city searched by the user online.
105+XmlListModel {
106+ id: cityTimezoneModel;
107+
108+ readonly property string timezone_base_url: "http://api.geonames.org/timezone?lat=";
109+
110+ // TODO: Change username to something along the lines of "com.ubuntu.clock"
111+ readonly property string username: "krnekhelesh";
112+
113+ // Function to return the online url for timezone info of a city
114+ function getCityTimezoneUrl (lat, lng) {
115+ return (timezone_base_url + lat + "&lng=" + lng + "&username=" + username);
116+ }
117+
118+ // Function to calculate the time difference w.r.t UTC
119+ function getTimeDifference(data) {
120+ var worldTime = data.split(' ')[1].split(':');
121+ var hoursWT = parseInt(worldTime[0]);
122+ var minutesWT = parseInt(worldTime[1]);
123+ var now = new Date();
124+ var UTCdiff = new Date().getTimezoneOffset()
125+ now.setMinutes(now.getMinutes() + UTCdiff)
126+
127+ // Calculating world city time diff w.r.t UTC
128+ return ((hoursWT - now.getHours()) * 60 + (minutesWT - now.getMinutes()));
129+ }
130+
131+ query: "/geonames/timezone"
132+ XmlRole { name: "time"; query: "time/string()" }
133+ onSourceChanged: reload();
134+}
135
136=== modified file 'clock/ClockPage.qml'
137--- clock/ClockPage.qml 2013-10-06 12:13:35 +0000
138+++ clock/ClockPage.qml 2013-10-22 17:11:13 +0000
139@@ -38,7 +38,7 @@
140
141 Component.onCompleted: {
142 // TODO: Query GPS for location data
143- Utils.log("ClockPage loaded");
144+ Utils.log("ClockPage loaded");
145 if (worldModel.city !== "undefined") {
146 currentCity = worldModel.city;
147 currentLng = worldModel.longitude;
148@@ -58,18 +58,20 @@
149 keywords: i18n.tr("Add;Timezone;Timezones;World;City;Cities;Town;Towns;Place;Places;Location;Locations;Time;Locale;Local;Current")
150 description: "Add a world city"
151 iconSource: Qt.resolvedUrl("../images/add_icon.png")
152- onTriggered: pagestack.push(Qt.resolvedUrl("WorldPage.qml"), {"isWorldCity": true})
153+ onTriggered: pagestack.push(Qt.resolvedUrl("WorldClock.qml"), {"isWorldCity": true})
154 }
155 ]
156
157+ // Function which runs every second to update the clock label
158 function onTimerUpdate(now) {
159- currentTimeFormatted = Qt.formatTime(new Date(), "hh:mm")
160+ currentTimeFormatted = Qt.formatTime(new Date(), "hh:mm")
161 clockFace.timeChanged(now)
162 if (now.getUTCSeconds() == 0) {
163 updateTime();
164 }
165 }
166
167+ // Function which runs every minute to update the world time
168 function updateTime() {
169 var now = new Date();
170 now.setMinutes(now.getMinutes() + diff)
171@@ -111,40 +113,19 @@
172 contentWidth: parent.width
173 contentHeight: clockFace.height + clockFace.anchors.topMargin + savedWorldClock.height + savedWorldClock.anchors.topMargin + units.gu(3)
174
175- // Label to show the current time
176- Rectangle {
177- id: labelContainer;
178-
179- z: clockFace.z + 1;
180- width: clockFace.innerCircle.width; height: width;
181- anchors.centerIn: clockFace;
182- color: "transparent"
183-
184- Label {
185- id: currentTimeLabel
186- objectName: "currentTimeLabel"
187-
188- anchors.centerIn: parent
189- horizontalAlignment: Text.AlignHCenter
190- verticalAlignment: Text.AlignVCenter
191- color: Theme.palette.normal.baseText
192- font.pixelSize: units.dp(41)
193- text: currentTimeFormatted
194- }
195- }
196-
197 // Qml Element to draw the analogue clock face along with its hour, minute and second hands.
198 AnalogClockFace {
199 id: clockFace
200
201 anchors { top: parent.top; topMargin: units.gu(10); horizontalCenter: parent.horizontalCenter }
202+ clockLabel.text: currentTimeFormatted
203
204 onClicked: {
205- if (easterEggCircle.isReady == XmlListModel.Ready && worldModel.city !== "undefined") {
206+ if (easterEggCircle.isReady == XmlListModel.Ready && worldModel.city !== "undefined")
207 clockFace.state == "" ? clockFace.state = "easteregg" : clockFace.state = "";
208- } else {
209+ else
210 Utils.error("Sunrise/Sunset times cannot be loaded without setting the current location or without a internet connection.")
211- }
212+
213 }
214
215 EasterEgg {
216@@ -158,13 +139,13 @@
217 State {
218 name: "easteregg"
219 PropertyChanges { target: easterEggCircle; opacity: 1; }
220- PropertyChanges { target: currentTimeLabel; opacity: 0; }
221+ PropertyChanges { target: clockFace.clockLabel; opacity: 0; }
222 PropertyChanges { target: easterEggCircle; sunriseLabel: easterEggCircle.getSunTime(easterEggCircle.model.get(0).sunriseTime); }
223 PropertyChanges { target: easterEggCircle; sunsetLabel: easterEggCircle.getSunTime(easterEggCircle.model.get(0).sunsetTime); }
224 },
225 State {
226 name: ""
227- PropertyChanges { target: currentTimeLabel; opacity: 1; }
228+ PropertyChanges { target: clockFace.clockLabel; opacity: 1; }
229 PropertyChanges { target: easterEggCircle; opacity: 0; }
230 }
231 ]
232
233=== modified file 'clock/EasterEgg.qml'
234--- clock/EasterEgg.qml 2013-10-08 13:01:27 +0000
235+++ clock/EasterEgg.qml 2013-10-22 17:11:13 +0000
236@@ -21,70 +21,13 @@
237 import Ubuntu.Components 0.1
238 import QtQuick.XmlListModel 2.0
239 import QtQuick.LocalStorage 2.0
240-
241+import "EasterEggStorage.js" as Storage
242 import "../common/ClockUtils.js" as Utils
243
244 // Qml Element to draw the easter egg (sunrise and sunset) on the analogue clock face.
245 Rectangle {
246 id: easterEggCircle;
247
248- property var db: null
249-
250- // Opens the local db if not already open. On the first call creates the table
251- function openDB () {
252- if(db !== null) return;
253-
254- db = LocalStorage.openDatabaseSync("ubuntu-clock-app", "", "Sunrise and Sunset Database", 1000);
255-
256- try {
257- db.transaction(function(tx){
258- tx.executeSql('CREATE TABLE IF NOT EXISTS Easteregg(key INTEGER PRIMARY KEY, date TEXT, riseTime TEXT, setTime TEXT)');
259- var table = tx.executeSql("SELECT * FROM Easteregg");
260- if (table.rows.length == 0) {
261- tx.executeSql('INSERT INTO Easteregg VALUES(?, ?, ?, ?)', [1, '', '', '']);
262- };
263- });
264- } catch (err) {
265- Utils.error("Error creating table in Sunrise/Sunset database: " + err);
266- };
267- }
268-
269- //Read date last checked from DB
270- function getSavedData () {
271-
272- openDB();
273-
274- var result = new Array();
275- try {
276- db.readTransaction(
277- function(tx){
278- var res = tx.executeSql('SELECT * FROM Easteregg WHERE key = 1');
279- if (res.rows.length > 0) {
280- result[0] = new Array();
281- result[0][0] = res.rows.item(0).key;
282- result[0][1] = res.rows.item(0).date;
283- result[0][2] = res.rows.item(0).riseTime;
284- result[0][3] = res.rows.item(0).setTime;
285- };
286- });
287- } catch (err) {
288- Utils.error("Error getting sunrise/sunset last checked date: " + err);
289- }
290- return result;
291- }
292-
293- function saveSunTimes (date, sunRise, sunSet) {
294- openDB();
295- try {
296- db.transaction(function(tx){
297- var res = tx.executeSql('UPDATE Easteregg SET date = ?, riseTime = ?, setTime = ? WHERE key = 1', [date, sunRise, sunSet]);
298- Utils.log("Saving Sunrise/Sunset times to disk");
299- });
300- } catch (err) {
301- Utils.error("Error saving sunrise/sunset times: " + err);
302- };
303- }
304-
305 // FIXME: Replace these constant values with user's location coordinates when automatic location detection is implemented.
306 property real latitude: 200;
307 property real longitude: 200;
308@@ -95,49 +38,32 @@
309
310 // properties to get the sunRiseModel XML model.
311 property alias isReady: sunRiseModel.status;
312- property alias model: sunRiseModel;
313-
314- // Online API properties
315- readonly property string base_url: "http://api.geonames.org/timezone?";
316- readonly property string username: "krnekhelesh";
317+ property alias model: sunRiseModel;
318
319 opacity: 0
320 color: "transparent"
321 z: parent.z + 100
322 visible: opacity == 0 ? false : true
323
324- function reloadSunRiseModel () {
325- sunRiseModel.reload()
326- }
327-
328- // Function to form the url string to make the API call
329- function getUrlString (latitude, longitude) {
330- return (base_url + "lat=" + latitude + "&lng=" + longitude + "&username=" + username);
331- }
332-
333 // Function to convert online data format "2013-04-20 19:40" to just "19:40"
334 function getSunTime (data) {
335 return data.split(' ')[1];
336 }
337
338+ onLatitudeChanged: sunRiseModel.source = sunRiseModel.getUrlString(latitude, longitude)
339+ onLongitudeChanged: sunRiseModel.source = sunRiseModel.getUrlString(latitude, longitude)
340+
341 // TODO 1: Get the latitude and longitude using the platform API automatically instead of using static values.
342- XmlListModel {
343+ EasterEggModel {
344 id: sunRiseModel
345-
346- source: getUrlString(latitude, longitude)
347- query: "/geonames/timezone"
348-
349- XmlRole { name: "sunriseTime"; query: "sunrise/string()"; isKey: true }
350- XmlRole { name: "sunsetTime"; query: "sunset/string()"; isKey: true }
351-
352 onStatusChanged: {
353- var savedData = getSavedData();
354+ var savedData = Storage.getSavedData();
355 var checkedDate = savedData[0][1].split('T')[0]
356 var date = new Date().toISOString();
357 if (status == XmlListModel.Ready && checkedDate != date.split('T')[0] && longitude != 200 && latitude != 200){
358 sunriseTimeLabel.text = getSunTime(sunRiseModel.get(0).sunriseTime);
359 sunsetTimeLabel.text = getSunTime(sunRiseModel.get(0).sunsetTime);
360- saveSunTimes(date, sunriseTimeLabel.text, sunsetTimeLabel.text);
361+ Storage.saveSunTimes(date, sunriseTimeLabel.text, sunsetTimeLabel.text);
362 }
363 else if (status == XmlListModel.Ready) {
364 Utils.log("Retrieving Sunrise/Sunset times from disk");
365
366=== added file 'clock/EasterEggModel.qml'
367--- clock/EasterEggModel.qml 1970-01-01 00:00:00 +0000
368+++ clock/EasterEggModel.qml 2013-10-22 17:11:13 +0000
369@@ -0,0 +1,41 @@
370+/*
371+ * Copyright (C) 2013 Canonical Ltd
372+ *
373+ * This program is free software: you can redistribute it and/or modify
374+ * it under the terms of the GNU General Public License version 3 as
375+ * published by the Free Software Foundation.
376+ *
377+ * This program is distributed in the hope that it will be useful,
378+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
379+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
380+ * GNU General Public License for more details.
381+ *
382+ * You should have received a copy of the GNU General Public License
383+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
384+ *
385+ * Authored by: Nekhelesh Ramananthan <krnekhelesh@gmail.com>
386+ */
387+
388+import QtQuick 2.0
389+import Ubuntu.Components 0.1
390+import QtQuick.XmlListModel 2.0
391+
392+// Qml Element to draw the easter egg (sunrise and sunset) on the analogue clock face.
393+XmlListModel {
394+ id: sunRiseModel
395+
396+ // Online API properties
397+ readonly property string base_url: "http://api.geonames.org/timezone?";
398+ readonly property string username: "krnekhelesh";
399+
400+ // Function to form the url string to make the API call
401+ function getUrlString (latitude, longitude) {
402+ return (base_url + "lat=" + latitude + "&lng=" + longitude + "&username=" + username);
403+ }
404+
405+ query: "/geonames/timezone"
406+ onSourceChanged: reload()
407+
408+ XmlRole { name: "sunriseTime"; query: "sunrise/string()"; isKey: true }
409+ XmlRole { name: "sunsetTime"; query: "sunset/string()"; isKey: true }
410+}
411
412=== added file 'clock/EasterEggStorage.js'
413--- clock/EasterEggStorage.js 1970-01-01 00:00:00 +0000
414+++ clock/EasterEggStorage.js 2013-10-22 17:11:13 +0000
415@@ -0,0 +1,76 @@
416+/*
417+ * Copyright (C) 2013 Canonical Ltd
418+ *
419+ * This program is free software: you can redistribute it and/or modify
420+ * it under the terms of the GNU General Public License version 3 as
421+ * published by the Free Software Foundation.
422+ *
423+ * This program is distributed in the hope that it will be useful,
424+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
425+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
426+ * GNU General Public License for more details.
427+ *
428+ * You should have received a copy of the GNU General Public License
429+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
430+ *
431+ * Authored by: Nekhelesh Ramananthan <krnekhelesh@gmail.com>
432+ */
433+
434+Qt.include("../common/ClockUtils.js")
435+
436+var db = null;
437+
438+// Opens the local db if not already open. On the first call creates the table
439+function openDB () {
440+ if(db !== null) return;
441+
442+ db = LocalStorage.openDatabaseSync("ubuntu-clock-app", "", "Sunrise and Sunset Database", 1000);
443+
444+ try {
445+ db.transaction(function(tx){
446+ tx.executeSql('CREATE TABLE IF NOT EXISTS Easteregg(key INTEGER PRIMARY KEY, date TEXT, riseTime TEXT, setTime TEXT)');
447+ var table = tx.executeSql("SELECT * FROM Easteregg");
448+ if (table.rows.length == 0) {
449+ tx.executeSql('INSERT INTO Easteregg VALUES(?, ?, ?, ?)', [1, '', '', '']);
450+ };
451+ });
452+ } catch (err) {
453+ console.error("Error creating table in Sunrise/Sunset database: " + err);
454+ };
455+}
456+
457+//Read date last checked from DB
458+function getSavedData () {
459+
460+ openDB();
461+
462+ var result = new Array();
463+ try {
464+ db.readTransaction(
465+ function(tx){
466+ var res = tx.executeSql('SELECT * FROM Easteregg WHERE key = 1');
467+ if (res.rows.length > 0) {
468+ result[0] = new Array();
469+ result[0][0] = res.rows.item(0).key;
470+ result[0][1] = res.rows.item(0).date;
471+ result[0][2] = res.rows.item(0).riseTime;
472+ result[0][3] = res.rows.item(0).setTime;
473+ };
474+ });
475+ } catch (err) {
476+ console.error("Error getting sunrise/sunset last checked date: " + err);
477+ }
478+ return result;
479+}
480+
481+function saveSunTimes (date, sunRise, sunSet) {
482+ openDB();
483+ try {
484+ db.transaction(function(tx){
485+ var res = tx.executeSql('UPDATE Easteregg SET date = ?, riseTime = ?, setTime = ? WHERE key = 1', [date, sunRise, sunSet]);
486+ console.log("Saving Sunrise/Sunset times to disk");
487+ });
488+ } catch (err) {
489+ console.error("Error saving sunrise/sunset times: " + err);
490+ };
491+}
492
493=== added file 'clock/SearchBox.qml'
494--- clock/SearchBox.qml 1970-01-01 00:00:00 +0000
495+++ clock/SearchBox.qml 2013-10-22 17:11:13 +0000
496@@ -0,0 +1,65 @@
497+/*
498+ * Copyright (C) 2013 Canonical Ltd
499+ *
500+ * This program is free software: you can redistribute it and/or modify
501+ * it under the terms of the GNU General Public License version 3 as
502+ * published by the Free Software Foundation.
503+ *
504+ * This program is distributed in the hope that it will be useful,
505+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
506+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
507+ * GNU General Public License for more details.
508+ *
509+ * You should have received a copy of the GNU General Public License
510+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
511+ *
512+ * Authored by: Nekhelesh Ramananthan <krnekhelesh@gmail.com>
513+ */
514+
515+import QtQuick 2.0
516+import Ubuntu.Components 0.1
517+import QtQuick.XmlListModel 2.0
518+
519+// Search Box element for world clock. Instant search as you type after a small pause.
520+Item {
521+ id: searchBox;
522+
523+ // Property to hold the string inputted by the user. This is required in other places such as the XML Model for initiating an online query.
524+ property alias search_string: searchLabel.text
525+
526+ // Signal connected to the search_timer triggered signal to expose it to outside objects
527+ signal searchTriggered()
528+
529+ Component.onCompleted: search_timer.triggered.connect(searchTriggered)
530+
531+ // Search Label
532+ TextField {
533+ id: searchLabel
534+
535+ anchors { left: parent.left; leftMargin: units.gu(2); top: parent.top; bottom: parent.bottom; right: parent.right; rightMargin: units.gu(2) }
536+ width: parent.width/1.5
537+ hasClearButton: true
538+ placeholderText: i18n.tr("Search")
539+ primaryItem: Image {
540+ height: parent.height/1.5;
541+ fillMode: Image.PreserveAspectFit
542+ source: Qt.resolvedUrl("../images/search_icon.svg")
543+ }
544+
545+ // Provide a small pause before going online to search
546+ Timer {
547+ id: search_timer
548+ interval: 1100
549+ repeat: false
550+ }
551+
552+ onTextChanged: search_timer.restart()
553+ }
554+
555+ // Indicator to show search activity
556+ ActivityIndicator {
557+ id: searchActivity
558+ anchors { verticalCenter: searchLabel.verticalCenter; right: searchLabel.right; rightMargin: units.gu(1) }
559+ running: searchCityModel.status === XmlListModel.Loading
560+ }
561+}
562
563=== added file 'clock/WorldCity.qml'
564--- clock/WorldCity.qml 1970-01-01 00:00:00 +0000
565+++ clock/WorldCity.qml 2013-10-22 17:11:13 +0000
566@@ -0,0 +1,57 @@
567+/*
568+ * Copyright (C) 2013 Canonical Ltd
569+ *
570+ * This program is free software: you can redistribute it and/or modify
571+ * it under the terms of the GNU General Public License version 3 as
572+ * published by the Free Software Foundation.
573+ *
574+ * This program is distributed in the hope that it will be useful,
575+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
576+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
577+ * GNU General Public License for more details.
578+ *
579+ * You should have received a copy of the GNU General Public License
580+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
581+ *
582+ * Authored by: Nekhelesh Ramananthan <krnekhelesh@gmail.com>
583+ */
584+
585+import QtQuick 2.0
586+import Ubuntu.Components 0.1
587+import QtQuick.XmlListModel 2.0
588+
589+// XML Model to list cities provided locally by clock-app or search online (geoname.org) for more cities and return latitude and longitude information of the city
590+XmlListModel {
591+ id: worldCityModel;
592+
593+ readonly property string search_base_url: "http://api.geonames.org/search?q="
594+ readonly property string local_base_url: "common-cities.xml"
595+ readonly property int no_of_results: 5
596+ property string search_string: ""
597+
598+ // TODO: Change username to something along the lines of "com.ubuntu.clock"
599+ readonly property string username: "krnekhelesh";
600+
601+ // Function to return the online search url for city search
602+ function searchCityUrl(city) {
603+ return (search_base_url + search_string + "&maxRows=" + no_of_results + "&username=" + username + "&style=full&featureClass=P&name_startsWith=" + city);
604+ }
605+
606+ // Function to return the url of the locally provided city list (xml file)
607+ function localCityUrl() {
608+ return Qt.resolvedUrl(local_base_url);
609+ }
610+
611+ // By default list the cities provided locally by clock app
612+ source: localCityUrl()
613+ query: "/geonames/geoname"
614+
615+ XmlRole { name: "city"; query: "toponymName/string()"; isKey: true }
616+ XmlRole { name: "country"; query: "countryName/string()"; isKey: true }
617+ XmlRole { name: "lat"; query: "lat/string()"; isKey: true }
618+ XmlRole { name: "lng"; query: "lng/string()"; isKey: true }
619+ XmlRole { name: "adminName"; query: "adminName1/string()"; isKey: true}
620+ XmlRole { name: "adminName2"; query: "adminName2/string()"; isKey: true}
621+
622+ onSourceChanged: reload();
623+}
624
625=== modified file 'clock/WorldClock.qml'
626--- clock/WorldClock.qml 2013-10-08 22:52:13 +0000
627+++ clock/WorldClock.qml 2013-10-22 17:11:13 +0000
628@@ -1,230 +1,181 @@
629+/*
630+ * Copyright (C) 2013 Canonical Ltd
631+ *
632+ * This program is free software: you can redistribute it and/or modify
633+ * it under the terms of the GNU General Public License version 3 as
634+ * published by the Free Software Foundation.
635+ *
636+ * This program is distributed in the hope that it will be useful,
637+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
638+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
639+ * GNU General Public License for more details.
640+ *
641+ * You should have received a copy of the GNU General Public License
642+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
643+ *
644+ * Authored by: Nekhelesh Ramananthan <krnekhelesh@gmail.com>
645+ */
646+
647 import QtQuick 2.0
648 import Ubuntu.Components 0.1
649 import Ubuntu.Components.ListItems 0.1 as ListItem
650 import QtQuick.XmlListModel 2.0
651
652-Column {
653- id: worldClocks
654-
655- // Properties to store the user searched city details
656- property string city: "null";
657- property real lng: 0;
658- property real lat: 0;
659- property real rawOffSet: 0;
660-
661- // Property to store the city searched by the user
662- property string search_string: "null";
663-
664- // Properties to store the information related to api.geonames.org
665- readonly property string username: "krnekhelesh";
666- readonly property string search_base_url: "http://api.geonames.org/search?q=";
667- readonly property string timezone_base_url: "http://api.geonames.org/timezone?lat=";
668- readonly property string local_base_url: "common-cities.xml";
669- readonly property int no_of_results: 5;
670-
671- property alias searchModel: searchCityModel;
672- property alias index: worldList.currentIndex;
673-
674- signal clicked()
675-
676- function searchCityUrl (city) {
677- return (search_base_url + search_string + "&maxRows=" + no_of_results + "&username=" + username + "&style=full&featureClass=P&name_startsWith=" + city);
678- }
679-
680- function localCityUrl () {
681- return Qt.resolvedUrl(local_base_url);
682- }
683-
684- function getCityTimezoneUrl (lat, lng) {
685- return (timezone_base_url + lat + "&lng=" + lng + "&username=" + username);
686- }
687-
688- function clearUserSearch () {
689- searchCityModel.source = localCityUrl();
690- searchLabel.text = "";
691- worldList.currentIndex = -1;
692- }
693-
694- height: childrenRect.height;
695- spacing: units.gu(2);
696-
697- // Xml model to search city online and retrieve latitude and longitude of searched city
698- XmlListModel {
699- id: searchCityModel;
700-
701- source: localCityUrl()
702- query: "/geonames/geoname"
703-
704- XmlRole { name: "city"; query: "toponymName/string()"; isKey: true }
705- XmlRole { name: "country"; query: "countryName/string()"; isKey: true }
706- XmlRole { name: "lat"; query: "lat/string()"; isKey: true }
707- XmlRole { name: "lng"; query: "lng/string()"; isKey: true }
708- XmlRole { name: "adminName"; query: "adminName1/string()"; isKey: true}
709- XmlRole { name: "adminName2"; query: "adminName2/string()"; isKey: true}
710-
711- onSourceChanged: reload();
712- }
713-
714- Item {
715- id: searchBox;
716-
717- anchors { left: parent.left; right: parent.right }
718- height: units.gu(4)
719-
720- TextField {
721- id: searchLabel
722-
723- anchors { left: parent.left; leftMargin: units.gu(2); top: parent.top; bottom: parent.bottom; right: searchButton.left; rightMargin: -units.gu(1) }
724- width: worldClocks.width/1.5
725- hasClearButton: true
726- placeholderText: i18n.tr("Search")
727- primaryItem: Image {
728- height: parent.height/2
729- width: parent.height/2
730- source: Qt.resolvedUrl("../images/search_icon.svg")
731- }
732-
733- // Provide a small pause before going online to search
734- Timer {
735- id: search_timer
736-
737- interval: 1100
738- repeat: false
739- onTriggered: {
740- if (searchLabel.text != "") {
741- search_string = searchLabel.text;
742- searchCityModel.source = searchCityUrl(search_string);
743- } else {
744- searchCityModel.source = localCityUrl()
745- }
746- }
747- }
748-
749- onTextChanged: search_timer.restart()
750-
751- InverseMouseArea {
752- visible: searchLabel.activeFocus
753- anchors.fill: parent
754- onClicked: {
755- worldClocks.forceActiveFocus()
756- mouse.accepted = false
757- }
758- }
759- }
760-
761- Item {
762- id: searchButton;
763- anchors { right: parent.right; rightMargin: units.gu(6.5); top: parent.top; bottom: parent.bottom }
764- height: searchLabel.height
765- visible: !searchActivity.running
766- Image {
767- id: name
768- source: Qt.resolvedUrl("../images/search_item.svg")
769- }
770-
771- // Make searchButton clickable
772- MouseArea {
773- id: searchArea;
774- enabled: true;
775- anchors.fill: searchButton
776- onClicked: {
777- if (searchLabel.text != "") {
778- search_string = searchLabel.text;
779- searchCityModel.source = searchCityUrl(search_string);
780- }
781- }
782- }
783- }
784-
785-
786- ActivityIndicator {
787- id: searchActivity
788- anchors { verticalCenter: searchButton.verticalCenter; left: searchButton.left; leftMargin: units.gu(1.5) }
789- running: searchCityModel.status === XmlListModel.Loading
790-
791- }
792- }
793-
794- ListView {
795- id: worldList
796- objectName: "worldList"
797-
798- anchors { left: parent.left; right: parent.right }
799- height: units.gu(50)
800- model: searchCityModel
801- currentIndex: -1
802- clip: true;
803-
804- Component {
805- id: sectionHeading
806- ListItem.Header {
807- Label {
808- text: section
809- anchors { verticalCenter: parent.verticalCenter; left: parent.left; leftMargin: units.gu(2) }
810- color: Theme.palette.normal.baseText
811- fontSize: "medium"
812- }
813- }
814- }
815-
816- Label {
817- id: errorMessage
818- width: parent.width
819- fontSize: "large"
820- wrapMode: Text.WordWrap
821- horizontalAlignment: TextInput.AlignHCenter
822- anchors.horizontalCenter: parent.horizontalCenter
823- visible: worldList.count == 0 || searchCityModel.status == XmlListModel.Error || cityDetailsModel.status == XmlListModel.Error
824- text: searchCityModel.status == XmlListModel.Error || cityDetailsModel.status == XmlListModel.Error ? i18n.tr("Unable to search online \nPlease check your internet connection") : i18n.tr("No results found")
825- }
826-
827- section.property: !errorMessage.visible ? "city" : ""
828- section.criteria: ViewSection.FirstCharacter
829- section.delegate: sectionHeading;
830- section.labelPositioning: ViewSection.InlineLabels
831-
832- delegate: ListItem.Base {
833- visible: !errorMessage.visible
834- height: adminName || adminName2 ? units.gu(8) : units.gu(6)
835- Column {
836- anchors { top: parent.top; topMargin: units.gu(0.5); left: parent.left; leftMargin: units.gu(3) }
837- Label {
838- id: cityDelegate
839- text: city
840- color: Theme.palette.normal.baseText
841- fontSize: "large"
842- }
843- Label {
844- id: stateDelegate
845- width: worldClocks.width
846- elide: Text.ElideRight
847- text: {
848- if (adminName && adminName2)
849- return adminName2 + ", " + adminName
850- else if(adminName)
851- return adminName
852- else if(adminName2)
853- return adminName2
854- else
855- return ""
856- }
857- visible: text == "" ? false : true
858- color: Theme.palette.normal.baseText
859- fontSize: "small"
860- }
861- Label {
862- id: countryDelegate
863- text: country
864- color: Theme.palette.normal.baseText
865- fontSize: "small"
866- }
867- }
868-
869- selected: worldList.currentIndex == index;
870-
871- onClicked: {
872- worldList.currentIndex = index;
873- worldClocks.clicked()
874+// Page to to search a world city and add it to the world clocks
875+Page {
876+ id: worldPage
877+
878+ property bool isWorldCity;
879+
880+ visible: false;
881+ title: isWorldCity ? i18n.tr("Add City") : i18n.tr("Edit Current Location")
882+
883+ Column {
884+ id: worldClocks
885+
886+ // Properties to store the user searched city details
887+ property string city: "null";
888+ property real lng: 0;
889+ property real lat: 0;
890+
891+ // Function to clear user search and return to original state
892+ function clearUserSearch () {
893+ searchCityModel.source = searchCityModel.localCityUrl();
894+ searchBox.search_string = "";
895+ worldList.currentIndex = -1;
896+ }
897+
898+ anchors { fill: parent; topMargin: units.gu(2) }
899+ height: childrenRect.height;
900+ spacing: units.gu(2);
901+
902+ // XML Model to list cities provided locally with clock-app or search cities online and retrieve latitude and longitude of searched city
903+ WorldCity {
904+ id: searchCityModel
905+ search_string: searchBox.search_string
906+ }
907+
908+ // XML Model to retrieve timezone of searched city
909+ CityTimezone {
910+ id: cityDetailsModel
911+ onStatusChanged: {
912+ if(status == XmlListModel.Ready && worldClocks.city != "null") {
913+ if (isWorldCity)
914+ worldModel.appendPreset(worldClocks.city, getTimeDifference(cityDetailsModel.get(0).time), worldClocks.lng, worldClocks.lat);
915+ else
916+ worldModel.appendCurrentLocation(worldClocks.city, worldClocks.lng, worldClocks.lat);
917+ worldClocks.clearUserSearch();
918+ pageStack.pop()
919+ }
920+ }
921+ }
922+
923+ // Search Bar to search for cities (online)
924+ // TODO: Add capability to also search cities provided locally with clock-app
925+ SearchBox {
926+ id: searchBox
927+
928+ height: units.gu(4)
929+ anchors { left: parent.left; right: parent.right }
930+
931+ onSearchTriggered: {
932+ if (search_string != "")
933+ searchCityModel.source = searchCityModel.searchCityUrl(search_string);
934+ else
935+ searchCityModel.source = searchCityModel.localCityUrl()
936+ }
937+ }
938+
939+ // List to display the search results
940+ ListView {
941+ id: worldList
942+ objectName: "worldList"
943+
944+ anchors { left: parent.left; right: parent.right }
945+ height: units.gu(50) // TODO: Make list view dynamic rather than the fixed 50 units gu.
946+ model: searchCityModel
947+ currentIndex: -1
948+ clip: true;
949+
950+ Component {
951+ id: sectionHeading
952+ ListItem.Header {
953+ Label {
954+ text: section
955+ anchors { verticalCenter: parent.verticalCenter; left: parent.left; leftMargin: units.gu(2) }
956+ color: Theme.palette.normal.baseText
957+ fontSize: "medium"
958+ }
959+ }
960+ }
961+
962+ Label {
963+ id: errorMessage
964+ width: parent.width
965+ fontSize: "large"
966+ wrapMode: Text.WordWrap
967+ horizontalAlignment: TextInput.AlignHCenter
968+ anchors.horizontalCenter: parent.horizontalCenter
969+ visible: worldList.count == 0 || searchCityModel.status == XmlListModel.Error || cityDetailsModel.status == XmlListModel.Error
970+ text: searchCityModel.status == XmlListModel.Error || cityDetailsModel.status == XmlListModel.Error ? i18n.tr("Unable to search online \nPlease check your internet connection") : i18n.tr("No results found")
971+ }
972+
973+ section.property: !errorMessage.visible ? "city" : ""
974+ section.criteria: ViewSection.FirstCharacter
975+ section.delegate: sectionHeading;
976+ section.labelPositioning: ViewSection.InlineLabels
977+
978+ delegate: ListItem.Base {
979+ visible: !errorMessage.visible
980+ height: adminName || adminName2 ? units.gu(8) : units.gu(6)
981+ Column {
982+ anchors { top: parent.top; topMargin: units.gu(0.5); left: parent.left; leftMargin: units.gu(3) }
983+ Label {
984+ id: cityDelegate
985+ text: city
986+ color: Theme.palette.normal.baseText
987+ fontSize: "large"
988+ }
989+ Label {
990+ id: stateDelegate
991+ width: worldClocks.width
992+ elide: Text.ElideRight
993+ text: {
994+ if (adminName && adminName2)
995+ return adminName2 + ", " + adminName
996+ else if(adminName)
997+ return adminName
998+ else if(adminName2)
999+ return adminName2
1000+ else
1001+ return ""
1002+ }
1003+ visible: text == "" ? false : true
1004+ color: Theme.palette.normal.baseText
1005+ fontSize: "small"
1006+ }
1007+ Label {
1008+ id: countryDelegate
1009+ text: country
1010+ color: Theme.palette.normal.baseText
1011+ fontSize: "small"
1012+ }
1013+ }
1014+
1015+ selected: worldList.currentIndex == index;
1016+
1017+ onClicked: {
1018+ worldList.currentIndex = index;
1019+ worldClocks.lat = searchCityModel.get(index).lat;
1020+ worldClocks.lng = searchCityModel.get(index).lng;
1021+ worldClocks.city = searchCityModel.get(index).city;
1022+ cityDetailsModel.source = cityDetailsModel.getCityTimezoneUrl(worldClocks.lat, worldClocks.lng)
1023+ }
1024+ }
1025+
1026+ Scrollbar {
1027+ flickableItem: worldList;
1028+ align: Qt.AlignTrailing;
1029 }
1030 }
1031 }
1032
1033=== removed file 'clock/WorldPage.qml'
1034--- clock/WorldPage.qml 2013-09-27 08:38:15 +0000
1035+++ clock/WorldPage.qml 1970-01-01 00:00:00 +0000
1036@@ -1,76 +0,0 @@
1037-/*
1038- * Copyright (C) 2013 Canonical Ltd
1039- *
1040- * This program is free software: you can redistribute it and/or modify
1041- * it under the terms of the GNU General Public License version 3 as
1042- * published by the Free Software Foundation.
1043- *
1044- * This program is distributed in the hope that it will be useful,
1045- * but WITHOUT ANY WARRANTY; without even the implied warranty of
1046- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1047- * GNU General Public License for more details.
1048- *
1049- * You should have received a copy of the GNU General Public License
1050- * along with this program. If not, see <http://www.gnu.org/licenses/>.
1051- *
1052- * Authored by: Nekhelesh Ramananthan <krnekhelesh@gmail.com>
1053- */
1054-
1055-import QtQuick 2.0
1056-import Ubuntu.Components 0.1
1057-import QtQuick.XmlListModel 2.0
1058-
1059-Page {
1060- id: worldPage
1061-
1062- property bool isWorldCity;
1063-
1064- visible: false;
1065- title: isWorldCity ? i18n.tr("Add City") : i18n.tr("Edit Current Location")
1066-
1067- function getTimeDifference(data) {
1068- var worldTime = data.split(' ')[1].split(':');
1069- var hoursWT = parseInt(worldTime[0]);
1070- var minutesWT = parseInt(worldTime[1]);
1071- var now = new Date();
1072- var UTCdiff = new Date().getTimezoneOffset()
1073- now.setMinutes(now.getMinutes() + UTCdiff)
1074-
1075- // Calculating world city time diff w.r.t UTC
1076- return ((hoursWT - now.getHours()) * 60 + (minutesWT - now.getMinutes()));
1077- }
1078-
1079- WorldClock {
1080- id: worldClockList;
1081-
1082- anchors { fill: parent; topMargin: units.gu(2) }
1083-
1084- // Xml model to retrieve timezone of searched city
1085- XmlListModel {
1086- id: cityDetailsModel;
1087-
1088- source: worldClockList.getCityTimezoneUrl(worldClockList.lat, worldClockList.lng)
1089- query: "/geonames/timezone"
1090-
1091- onStatusChanged: {
1092- if(status == XmlListModel.Ready && worldClockList.city != "null") {
1093- if (isWorldCity)
1094- worldModel.appendPreset(worldClockList.city, getTimeDifference(cityDetailsModel.get(0).time), worldClockList.lng, worldClockList.lat);
1095- else
1096- worldModel.appendCurrentLocation(worldClockList.city, worldClockList.lng, worldClockList.lat);
1097- worldClockList.clearUserSearch();
1098- pageStack.pop()
1099- }
1100- }
1101-
1102- XmlRole { name: "time"; query: "time/string()" }
1103- }
1104-
1105- onClicked: {
1106- lat = searchModel.get(index).lat;
1107- lng = searchModel.get(index).lng;
1108- city = searchModel.get(index).city;
1109- cityDetailsModel.reload()
1110- }
1111- }
1112-}
1113
1114=== modified file 'tests/autopilot/ubuntu_clock_app/emulators.py'
1115--- tests/autopilot/ubuntu_clock_app/emulators.py 2013-10-21 21:16:41 +0000
1116+++ tests/autopilot/ubuntu_clock_app/emulators.py 2013-10-22 17:11:13 +0000
1117@@ -294,7 +294,7 @@
1118
1119 def get_world_cities_page(self):
1120 """Return the page containing the world cities."""
1121- return self.select_single("WorldPage")
1122+ return self.select_single("WorldClock")
1123
1124 def get_clock_page(self):
1125 """Return the page containing the main clock."""

Subscribers

People subscribed via source and target branches