10364fb6ea99268cbaae25610a528024e7c21e3a
[pedometerwidget] / src / usr / lib / hildon-desktop / pedometer_widget_home.py
1 #Pedometer Home Widget
2 #Author: Mirestean Andrei < andrei.mirestean at gmail.com >
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, either version 3 of the License, or
7 #(at your option) any later version.
8 #
9 #This program is distributed in the hope that it will be useful,
10 #but WITHOUT ANY WARRANTY; without even the implied warranty of
11 #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 #GNU General Public License for more details.
13 #
14 #You should have received a copy of the GNU General Public License
15 #along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
17 import os
18 import time
19 import pickle
20 from datetime import date, timedelta
21 from xml.dom.minidom import getDOMImplementation, parseString
22
23 import gobject
24 import gconf
25 import gtk
26 import cairo
27
28 import pygst
29 pygst.require("0.10")
30 import gst
31
32 import hildondesktop
33 import hildon
34
35 PATH = "/apps/pedometerhomewidget"
36 MODE = PATH + "/mode"
37 HEIGHT = PATH + "/height"
38 STEP_LENGTH = PATH + "/step_length"
39 WEIGHT = PATH + "/weight"
40 UNIT = PATH + "/unit"
41 SENSITIVITY = PATH + "/sensitivity"
42 ASPECT = PATH + "/aspect"
43 SECONDVIEW = PATH + "/secondview"
44 GRAPHVIEW = PATH + "/graphview"
45 NOIDLETIME = PATH + "/noidletime"
46 LOGGING = PATH + "/logging"
47
48 ALARM_PATH = PATH + "/alarm"
49 ALARM_ENABLE = ALARM_PATH + "/enable"
50 ALARM_FNAME = ALARM_PATH + "/fname"
51 ALARM_TYPE = ALARM_PATH + "/type"
52 ALARM_INTERVAL = ALARM_PATH + "/interval"
53
54 ICONSPATH = "/opt/pedometerhomewidget/"
55
56 unit = 0
57
58 class Singleton(object):
59     _instance = None
60     def __new__(cls, *args, **kwargs):
61         if not cls._instance:
62             cls._instance = super(Singleton, cls).__new__(
63                                 cls, *args, **kwargs)
64         return cls._instance
65
66 class PedoIntervalCounter(Singleton):
67     MIN_THRESHOLD = 500
68     MIN_TIME_STEPS = 0.5
69     sensitivity = 100
70     mode = 0
71     x = []
72     y = []
73     z = []
74     t = []
75
76     #TODO: check if last detected step is at the end of the interval
77
78     def set_vals(self, coords, tval):
79         self.x = coords[0]
80         self.y = coords[1]
81         self.z = coords[2]
82         self.t = tval
83
84     def set_mode(self, mode):
85         #runnig, higher threshold to prevent fake steps
86         self.mode = mode
87         if mode == 1:
88             self.MIN_THRESHOLD = 650.0 * (200 - self.sensitivity) / 100
89             self.MIN_TIME_STEPS = 0.35
90         #walking
91         else:
92             self.MIN_THRESHOLD = 500.0 * (200 - self.sensitivity) / 100
93             self.MIN_TIME_STEPS = 0.5
94
95     def set_sensitivity(self, value):
96         self.sensitivity = value
97         self.set_mode(self.mode)
98
99     def calc_mean(self, vals):
100         sum = 0
101         for i in vals:
102             sum += i
103         if len(vals) > 0:
104             return sum / len(vals)
105         return 0
106
107     def calc_stdev(self, vals):
108         rez = 0
109         mean = self.calc_mean(vals)
110         for i in vals:
111             rez += pow(abs(mean - i), 2)
112         return math.sqrt(rez / len(vals))
113
114     def calc_threshold(self, vals):
115         vmax = max(vals)
116         vmin = min(vals)
117         mean = self.calc_mean(vals)
118         threshold = max (abs(mean - vmax), abs(mean - vmin))
119         return threshold
120
121     def count_steps(self, vals, t):
122         threshold = self.MIN_THRESHOLD
123         mean = self.calc_mean(vals)
124         cnt = 0
125         i = 0
126         while i < len(vals):
127             if abs(vals[i] - mean) > threshold:
128                 cnt += 1
129                 ntime = t[i] + self.MIN_TIME_STEPS
130                 while i < len(vals) and t[i] < ntime:
131                     i += 1
132             i += 1
133         return cnt
134
135     def get_best_values(self, x, y, z):
136         dev1 = self.calc_stdev(x)
137         dev2 = self.calc_stdev(y)
138         dev3 = self.calc_stdev(z)
139         dev_max = max(dev1, dev2, dev3)
140
141         if (abs(dev1 - dev_max) < 0.001):
142             logger.info("X chosen as best axis, stdev %f" % dev1)
143             return x
144         elif (abs(dev2 - dev_max) < 0.001):
145             logger.info("Y chosen as best axis, stdev %f" % dev2)
146             return y
147         else:
148             logger.info("Z chosen as best axis, stdev %f" % dev3)
149             return z
150
151     def number_steps(self):
152         vals = self.get_best_values(self.x, self.y, self.z)
153         return self.count_steps(vals, self.t)
154
155 class PedoValues():
156     def __init__(self, time=0, steps=0, dist=0, calories=0):
157         self.time = time
158         self.steps = steps
159         self.calories = calories
160         self.dist = dist
161
162     def __add__(self, other):
163         return PedoValues(self.time + other.time,
164                           self.steps + other.steps,
165                           self.dist + other.dist,
166                           self.calories + other.calories)
167
168     def __sub__(self, other):
169         return PedoValues(self.time - other.time,
170                           self.steps - other.steps,
171                           self.dist - other.dist,
172                           self.calories - other.calories)
173
174     def get_print_time(self):
175         tdelta = self.time
176         hours = int(tdelta / 3600)
177         tdelta -= 3600 * hours
178         mins = int(tdelta / 60)
179         tdelta -= 60 * mins
180         secs = int(tdelta)
181         strtime = "%.2d:%.2d:%.2d" % (hours, mins, secs)
182         return strtime
183
184     def get_print_distance(self):
185         global unit
186         if self.dist > 1000:
187             if unit == 0:
188                 return "%.2f km" % (self.dist / 1000)
189             else:
190                 return "%.2f mi" % (self.dist / 1609.344)
191         else:
192             if unit == 0:
193                 return "%d m" % self.dist
194             else:
195                 return "%d ft" % int(self.dist * 3.2808)
196
197     def get_avg_speed(self):
198         global unit
199         conv = 0
200         if unit:
201             conv = 2.23693629
202         else:
203             conv = 3.6
204
205         if self.time == 0:
206             return 0
207         speed = 1.0 * self.dist / self.time
208         return speed * conv
209
210     def get_print_avg_speed(self):
211         global unit
212         suffix = ""
213         conv = 0
214         if unit:
215             suffix = "mi/h"
216             conv = 2.23693629
217         else:
218             suffix = "km/h"
219             conv = 3.6
220
221         if self.time == 0:
222             return "N/A " + suffix
223         speed = 1.0 * self.dist / self.time
224         #convert from meters per second to km/h or mi/h
225         speed *= conv
226         return "%.2f %s" % (speed, suffix)
227
228     def get_print_steps(self):
229         return str(self.steps)
230
231     def get_print_calories(self):
232         return "%.2f" % self.calories
233
234 class PedoRepository(Singleton):
235     values = {}
236
237     def load(self):
238         raise NotImplementedError("Must be implemented by subclass")
239
240     def save(self):
241         raise NotImplementedError("Must be implemented by subclass")
242
243     def reset_values(self):
244         self.values = {}
245         self.save()
246
247     def get_history_count(self):
248         """return the number of days in the log"""
249         return len(values)
250
251     def get_values(self):
252         return self.values
253
254     def add_values(self, values, when=None):
255         if when is None:
256             when = date.today()
257         """add PedoValues values to repository """
258         try:
259             self.values[when] = self.values[when] + values
260         except KeyError:
261             self.values[when] = values
262
263     def get_last_7_days(self):
264         ret = []
265         day = date.today()
266         for i in range(7):
267             try:
268                 ret.append(self.values[day])
269             except KeyError:
270                 ret.append(PedoValues())
271             day = day - timedelta(days=1)
272         return ret
273
274     def get_last_weeks(self):
275         delta = timedelta(days=1)
276         day = date.today()
277         week = int(date.today().strftime("%W"))
278         val = PedoValues()
279         ret = []
280         for i in range(56):
281             try:
282                 val += self.values[day]
283             except KeyError:
284                 pass
285             w = int(day.strftime("%W"))
286             if w != week:
287                 ret.append(val)
288                 val = PedoValues()
289                 week = w
290                 if len(ret) == 7:
291                     break
292             day -= delta
293         return ret
294
295     def get_alltime_values(self):
296         ret = PedoValues()
297         for k, v in self.values.iteritems():
298             ret = ret + v
299         return ret
300
301     def get_today_values(self):
302         try:
303             return self.values[date.today()]
304         except KeyError:
305             return PedoValues()
306
307     def get_this_week_values(self):
308         day = date.today()
309         ret = PedoValues()
310         while True:
311             try:
312                 ret += self.values[day]
313             except:
314                 pass
315             if day.weekday() == 0:
316                 break
317             day = day - timedelta(days=1)
318
319         return ret
320
321 class PedoRepositoryXML(PedoRepository):
322     DIR = os.path.join(os.path.expanduser("~"), ".pedometer")
323     FILE = os.path.join(DIR, "data.xml")
324     FILE2 = os.path.join(DIR, "pickle.log")
325     def __init__(self):
326         if not os.path.exists(self.DIR):
327             os.makedirs(self.DIR)
328         PedoRepository.__init__(self)
329
330     def load(self):
331         try:
332             f = open(self.FILE, "r")
333             dom = parseString(f.read())
334             values = dom.getElementsByTagName("pedometer")[0]
335             for v in values.getElementsByTagName("date"):
336                 d = int(v.getAttribute("ordinal_day"))
337                 steps = int(v.getAttribute("steps"))
338                 calories = float(v.getAttribute("calories"))
339                 dist = float(v.getAttribute("dist"))
340                 time = float(v.getAttribute("time"))
341                 day = date.fromordinal(d)
342                 self.values[day] = PedoValues(time, steps, dist, calories)
343
344             f.close()
345         except Exception, e:
346             logger.error("Error while loading data from xml file: %s" % e)
347
348     def save(self):
349         try:
350             f = open(self.FILE, "w")
351
352             impl = getDOMImplementation()
353
354             newdoc = impl.createDocument(None, "pedometer", None)
355             top_element = newdoc.documentElement
356             for k, v in self.values.iteritems():
357                 d = newdoc.createElement('date')
358                 d.setAttribute("day", str(k.isoformat()))
359                 d.setAttribute("ordinal_day", str(k.toordinal()))
360                 d.setAttribute("steps", str(v.steps))
361                 d.setAttribute("time", str(v.time))
362                 d.setAttribute("dist", str(v.dist))
363                 d.setAttribute("calories", str(v.calories))
364                 top_element.appendChild(d)
365
366             newdoc.appendChild(top_element)
367             newdoc.writexml(f)
368             #f.write(newdoc.toprettyxml())
369             f.close()
370         except Exception, e:
371             logger.error("Error while saving data to xml file: %s" % e)
372
373 class PedoRepositoryPickle(PedoRepository):
374     DIR = os.path.join(os.path.expanduser("~"), ".pedometer")
375     FILE = os.path.join(DIR, "pickle.log")
376
377     def __init__(self):
378         if not os.path.exists(self.DIR):
379             os.makedirs(self.DIR)
380         PedoRepository.__init__(self)
381
382     def load(self):
383         try:
384             f = open(self.FILE, "rb")
385             self.values = pickle.load(f)
386             f.close()
387         except Exception, e:
388             logger.error("Error while loading pickle file: %s" % e)
389
390     def save(self):
391         try:
392             f = open(self.FILE, "wb")
393             pickle.dump(self.values, f)
394             f.close()
395         except Exception, e:
396             logger.error("Error while saving data to pickle: %s" % e)
397
398 class PedoController(Singleton):
399     mode = 0
400     unit = 0
401     weight = 70
402     height_interval = 0
403     sensitivity = 100
404     #what to display in second view - 0 - alltime, 1 - today, 2 - week
405     second_view = 0
406     callback_update_ui = None
407     no_idle_time = False
408
409     STEP_LENGTH = 0.7
410     #values for the two views in the widget ( current and day/week/alltime)
411     v = [PedoValues(), PedoValues()]
412
413     last_time = 0
414     is_running = False
415
416     observers = []
417
418     midnight_set = False
419     midnight_source_id = None
420
421     def __init__(self):
422
423         self.pedometer = PedoCounter(self.steps_detected)
424         self.pedometerInterval = PedoIntervalCounter()
425         self.pedometerInterval.set_mode(self.mode)
426         self.repository = PedoRepositoryXML()
427         self.repository.load()
428
429         self.load_values()
430
431         if not self.midnight_set:
432             self.update_at_midnight()
433             self.midnight_set = True
434
435         self.config = Config()
436         self.config.add_observer(self.load_config)
437
438     def update_at_midnight(self):
439         next_day = date.today() + timedelta(days=1)
440         diff = time.mktime(next_day.timetuple()) - time.time()
441         diff = int(diff+5)
442         self.midnight_source_id = gobject.timeout_add_seconds(diff, self.midnight_callback, True)
443
444     def stop_midnight_callback(self):
445         if self.midnight_source_id is not None:
446             gobject.source_remove(self.midnight_source_id)
447
448     def midnight_callback(self, first=False):
449         self.load_values()
450         self.notify()
451         if first:
452             self.midnight_source_id = gobject.timeout_add_seconds(24*3600, self.midnight_callback)
453             return False
454         else:
455             return True
456
457     def load_config(self):
458         self.set_height(self.config.get_height(), self.config.get_step_length())
459         self.set_mode(self.config.get_mode())
460         self.set_unit(self.config.get_unit())
461         self.set_weight(self.config.get_weight())
462         self.set_second_view(self.config.get_secondview())
463         self.set_no_idle_time(self.config.get_noidletime())
464         self.set_sensitivity(self.config.get_sensitivity())
465
466     def load_values(self):
467         if self.second_view == 0:
468             self.v[1] = self.repository.get_alltime_values()
469         elif self.second_view == 1:
470             self.v[1] = self.repository.get_today_values()
471         else:
472             self.v[1] = self.repository.get_this_week_values()
473
474     def save_values(self):
475         self.repository.add_values(self.v[0])
476         self.repository.save()
477         self.load_values()
478
479     def start_pedometer(self):
480         self.v[0] = PedoValues()
481         self.last_time = time.time()
482         self.is_running = True
483         self.pedometer.start()
484         self.notify(True)
485
486     def reset_all_values(self):
487         self.repository.reset_values()
488         self.v[0] = PedoValues()
489         self.v[1] = PedoValues()
490         self.notify()
491
492     def stop_pedometer(self):
493         self.is_running = False
494         self.pedometer.request_stop()
495
496     def get_first(self):
497         return self.v[0]
498
499     def get_second(self):
500         if self.is_running:
501             return self.v[0] + self.v[1]
502         else:
503             return self.v[1]
504
505     def update_current(self):
506         """
507         Update distance and calories for current values based on new height, mode values
508         """
509         self.v[0].dist = self.get_distance(self.v[0].steps)
510         self.v[0].calories = self.get_calories(self.v[0].steps)
511
512     def steps_detected(self, cnt, last_steps=False):
513         if not last_steps and cnt == 0 and self.no_idle_time:
514             logger.info("No steps detected, timer is paused")
515         else:
516             self.v[0].steps += cnt
517             self.v[0].dist += self.get_distance(cnt)
518             self.v[0].calories += self.get_calories(self.get_distance(cnt))
519             self.v[0].time += time.time() - self.last_time
520             if last_steps:
521                 self.save_values()
522                 self.notify()
523             else:
524                 self.notify(True)
525         self.last_time = time.time()
526
527     def get_calories(self, distance):
528         """calculate lost calories for the distance and weight given as parameters
529         """
530         #different coefficient for running and walking
531         if self.mode == 0:
532             coef = 0.53
533         else:
534             coef = 0.75
535
536         #convert distance from meters to miles
537         distance *= 0.000621371192
538
539         weight = self.weight
540         #convert weight from kg to pounds
541         if self.unit == 0:
542             weight *= 2.20462262
543         return weight * distance * coef
544
545     def set_mode(self, mode):
546         self.mode = mode
547         self.set_height(self.height_interval)
548         self.pedometerInterval.set_mode(self.mode)
549         self.notify()
550
551     def set_unit(self, new_unit):
552         self.unit = new_unit
553         global unit
554         unit = new_unit
555         self.notify()
556
557     def get_str_weight_unit(self, unit=None):
558         if unit is None:
559             unit = self.unit
560         if unit == 0:
561             return "kg"
562         else:
563             return "lb"
564
565     def set_weight(self, value):
566         self.weight = value
567         self.notify()
568
569     def get_weight(self):
570         return self.weight
571
572     def set_sensitivity(self, value):
573         self.sensitivity = value
574         self.pedometerInterval.set_sensitivity(value)
575
576     def get_sensitivity(self):
577         return self.sensitivity
578
579     def set_second_view(self, second_view):
580         self.second_view = second_view
581         self.load_values()
582         self.notify()
583
584     def set_callback_ui(self, func):
585         self.callback_update_ui = func
586
587     def set_height(self, height_interval, step_length=None):
588         self.height_interval = height_interval
589
590         if step_length is None:
591             step_length = self.STEP_LENGTH
592         #set height, will affect the distance
593         if height_interval == 0:
594             self.STEP_LENGTH = 0.59
595         elif height_interval == 1:
596             self.STEP_LENGTH = 0.64
597         elif height_interval == 2:
598             self.STEP_LENGTH = 0.71
599         elif height_interval == 3:
600             self.STEP_LENGTH = 0.77
601         elif height_interval == 4:
602             self.STEP_LENGTH = 0.83
603         elif height_interval == 5:
604             self.STEP_LENGTH = step_length
605         #increase step length if RUNNING
606         if self.mode == 1:
607             self.STEP_LENGTH *= 1.45
608         self.notify()
609
610     def set_no_idle_time(self, value):
611         self.no_idle_time = value
612
613     def get_distance(self, steps=None):
614         if steps == None:
615             steps = self.counter
616         return self.STEP_LENGTH * steps;
617
618     def add_observer(self, func):
619         try:
620             self.observers.index(func)
621         except:
622             self.observers.append(func)
623
624     def remove_observer(self, func):
625         self.observers.remove(func)
626
627     def notify(self, optional=False):
628         if self.callback_update_ui is not None:
629             self.callback_update_ui()
630
631         for func in self.observers:
632             func(optional)
633
634 class AlarmController(Singleton):
635     enable = False
636     fname = "/home/user/MyDocs/.sounds/Ringtones/Bicycle.aac"
637     interval = 5
638     type = 0
639
640     player = None
641     is_playing = False
642     pedo_controller = None
643
644     def __init__(self):
645         self.client = gconf.client_get_default()
646         self.config = Config()
647         self.config.add_observer(self.load_config)
648
649         self.pedo_controller = PedoController()
650         if self.enable:
651             self.init_player()
652             self.pedo_controller.add_observer(self.update)
653             self.start_value = self.pedo_controller.get_first()
654
655     def init_player(self):
656         self.player = gst.element_factory_make("playbin2", "player")
657         fakesink = gst.element_factory_make("fakesink", "fakesink")
658         self.player.set_property("video-sink", fakesink)
659
660         bus = self.player.get_bus()
661         bus.add_signal_watch()
662         bus.connect("message", self.on_message)
663
664     def on_message(self, bus, message):
665         t = message.type
666         if t == gst.MESSAGE_EOS:
667             self.player.set_state(gst.STATE_NULL)
668             self.is_playing = False
669         elif t == gst.MESSAGE_ERROR:
670             self.player.set_state(gst.STATE_NULL)
671             self.is_playing = False
672             err, debug = message.parse_error()
673             logger.error("ERROR: %s, %s" % (err, debug) )
674
675     def update(self, optional):
676         diff = self.pedo_controller.get_first() - self.start_value
677         if self.type == 0 and diff.time >= self.interval * 60 or \
678                    self.type == 1 and diff.steps >= self.interval or \
679                    self.type == 2 and diff.dist >= self.interval or \
680                    self.type == 3 and diff.calories >= self.interval:
681             self.play()
682             #get new instance of current values
683             self.start_value = PedoValues() + self.pedo_controller.get_first()
684             logger.info("Alarm!")
685
686     def play(self):
687         if self.player is None:
688             self.init_player()
689         if self.is_playing:
690             self.player.set_state(gst.STATE_NULL)
691             self.is_playing = False
692         else:
693             self.player.set_property("uri", "file://" + self.fname)
694             self.player.set_state(gst.STATE_PLAYING)
695             self.is_playing = True
696
697     def stop(self):
698         if self.player is not None:
699             self.player.set_state(gst.STATE_NULL)
700
701     def load_config(self):
702         self.enable  = self.config.get_alarm_enable()
703         self.set_alarm_file(self.config.get_alarm_fname())
704         self.set_interval(self.config.get_alarm_interval())
705         self.set_type(self.config.get_alarm_type())
706
707     def set_enable(self, value):
708        self.enable = value
709        if self.enable:
710            self.init_player()
711            self.pedo_controller.add_observer(self.update)
712            self.start_value = self.pedo_controller.get_first()
713        else:
714            self.stop()
715            self.player = None
716            self.pedo_controller.remove_observer(self.update)
717
718     def get_enable(self):
719         return self.enable
720
721     def set_alarm_file(self, fname):
722         self.fname = fname
723
724     def get_alarm_file(self):
725         if self.fname == None:
726             return ""
727         return self.fname
728
729     def set_interval(self, interval):
730         self.interval = interval
731
732     def get_interval(self):
733         return self.interval
734
735     def set_type(self, type):
736         self.type = type
737
738     def get_type(self):
739         return self.type
740
741 class PedoCounter(Singleton):
742     COORD_FNAME = "/sys/class/i2c-adapter/i2c-3/3-001d/coord"
743     COORD_FNAME_SDK = "/home/andrei/pedometer-widget-0.1/date.txt"
744     LOGFILE = "/home/user/log_pedometer"
745     #time in ms between two accelerometer data reads
746     COORD_GET_INTERVAL = 25
747
748     COUNT_INTERVAL = 5
749
750     interval_counter = None
751     stop_requested = False
752     update_function = None
753     logging = False
754     isRunning = False
755
756     def __init__(self, update_function=None):
757         if not os.path.exists(self.COORD_FNAME):
758             self.COORD_FNAME = self.COORD_FNAME_SDK
759
760         self.interval_counter = PedoIntervalCounter()
761         self.update_function = update_function
762
763     def set_logging(self, value):
764         self.logging = value
765
766     def get_rotation(self):
767         f = open(self.COORD_FNAME, 'r')
768         coords = [int(w) for w in f.readline().split()]
769         f.close()
770         return coords
771
772     def start(self):
773         logger.info("Counter started")
774         self.isRunning = True
775         self.stop_requested = False
776         if self.logging:
777             fname = "%d_%d_%d_%d_%d_%d" % time.localtime()[0:6]
778             self.file = open(self.LOGFILE + fname + ".txt", "w")
779         gobject.idle_add(self.run)
780
781     def run(self):
782         self.coords = [[], [], []]
783         self.stime = time.time()
784         self.t = []
785         gobject.timeout_add(self.COORD_GET_INTERVAL, self.read_coords)
786         return False
787
788     def read_coords(self):
789         x, y, z = self.get_rotation()
790         self.coords[0].append(int(x))
791         self.coords[1].append(int(y))
792         self.coords[2].append(int(z))
793         now = time.time() - self.stime
794         if self.logging:
795             self.file.write("%d %d %d %f\n" % (self.coords[0][-1], self.coords[1][-1], self.coords[2][-1], now))
796
797         self.t.append(now)
798         #call stop_interval
799         ret = True
800         if self.t[-1] > self.COUNT_INTERVAL or self.stop_requested:
801             ret = False
802             gobject.idle_add(self.stop_interval)
803         return ret
804
805     def stop_interval(self):
806         self.interval_counter.set_vals(self.coords, self.t)
807         cnt = self.interval_counter.number_steps()
808
809         logger.info("Number of steps detected for last interval %d, number of coords: %d" % (cnt, len(self.t)))
810
811         gobject.idle_add(self.update_function, cnt, self.stop_requested)
812
813         if self.stop_requested:
814             gobject.idle_add(self.stop)
815         else:
816             gobject.idle_add(self.run)
817         return False
818
819     def stop(self):
820         if self.logging:
821             self.file.close()
822         logger.info("Counter has finished")
823
824     def request_stop(self):
825         self.stop_requested = True
826         self.isRunning = False
827
828 class CustomButton(hildon.Button):
829     def __init__(self, icon):
830         hildon.Button.__init__(self, gtk.HILDON_SIZE_AUTO_WIDTH, hildon.BUTTON_ARRANGEMENT_VERTICAL)
831         self.icon = icon
832         self.set_size_request(int(32 * 1.4), int(30 * 1.0))
833         self.retval = self.connect("expose_event", self.expose)
834
835     def set_icon(self, icon):
836         self.icon = icon
837
838     def expose(self, widget, event):
839         self.context = widget.window.cairo_create()
840         self.context.rectangle(event.area.x, event.area.y,
841                             event.area.width, event.area.height)
842
843         self.context.clip()
844         rect = self.get_allocation()
845         self.context.rectangle(rect.x, rect.y, rect.width, rect.height)
846         self.context.set_source_rgba(1, 1, 1, 0)
847
848         style = self.rc_get_style()
849         color = style.lookup_color("DefaultBackgroundColor")
850         if self.state == gtk.STATE_ACTIVE:
851             style = self.rc_get_style()
852             color = style.lookup_color("SelectionColor")
853             self.context.set_source_rgba (color.red / 65535.0, color.green / 65335.0, color.blue / 65535.0, 0.75);
854         self.context.fill()
855
856         #img = cairo.ImageSurface.create_from_png(self.icon)
857
858         #self.context.set_source_surface(img)
859         #self.context.set_source_surface(img, rect.width/2 - img.get_width() /2, 0)
860         img = gtk.Image()
861         img.set_from_file(self.icon)
862         buf = img.get_pixbuf()
863         buf = buf.scale_simple(int(32 * 1.5), int(30 * 1.5), gtk.gdk.INTERP_BILINEAR)
864
865         self.context.set_source_pixbuf(buf, rect.x + (event.area.width / 2 - 15) - 8, rect.y + 1)
866         self.context.scale(200, 200)
867         self.context.paint()
868
869         return self.retval
870
871 class CustomEventBox(gtk.EventBox):
872
873     def __init__(self):
874         gtk.EventBox.__init__(self)
875
876     def do_expose_event(self, event):
877         self.context = self.window.cairo_create()
878         self.context.rectangle(event.area.x, event.area.y,
879                             event.area.width, event.area.height)
880
881         self.context.clip()
882         rect = self.get_allocation()
883         self.context.rectangle(rect.x, rect.y, rect.width, rect.height)
884
885         if self.state == gtk.STATE_ACTIVE:
886             style = self.rc_get_style()
887             color = style.lookup_color("SelectionColor")
888             self.context.set_source_rgba (color.red / 65535.0, color.green / 65335.0, color.blue / 65535.0, 0.75);
889         else:
890             self.context.set_source_rgba(1, 1, 1, 0)
891         self.context.fill()
892
893         gtk.EventBox.do_expose_event(self, event)
894
895 class GraphController(Singleton):
896     ytitles = ["Steps", "Average Speed", "Distance", "Calories"]
897     xtitles = ["Day", "Week"] # "Today"]
898     widget = None
899
900     config = None
901
902     def __init__(self):
903         self.repository = PedoRepositoryXML()
904         self.last_update = 0
905         PedoController().add_observer(self.update_ui)
906         self.config = Config()
907         self.config.add_observer(self.load_config)
908
909     def load_config(self):
910         self.set_current_view(self.config.get_graphview())
911
912     def set_graph(self, widget):
913         self.widget = widget
914         self.update_ui()
915
916     def set_current_view(self, view):
917         """
918         current_view % len(ytitles) - gives the ytitle
919         current_view / len(ytitles) - gives the xtitle
920         """
921         self.x_id = view / len(self.ytitles)
922         self.y_id = view % len(self.ytitles)
923         self.update_ui()
924
925     def next_view(self):
926         current_view = self.config.get_graphview() + 1
927         if current_view == len(self.ytitles) * len(self.xtitles):
928             current_view = 0
929         self.config.set_graphview(current_view)
930
931     def last_weeks_labels(self):
932         d = date.today()
933         delta = timedelta(days=7)
934         ret = []
935         for i in range(7):
936             ret.append(d.strftime("Week %W"))
937             d = d - delta
938         return ret
939
940     def compute_values(self):
941         labels = []
942         if self.x_id == 0:
943             values = self.repository.get_last_7_days()
944             d = date.today()
945             delta = timedelta(days=1)
946             for i in range(7):
947                 labels.append(d.ctime().split()[0])
948                 d = d - delta
949
950         elif self.x_id == 1:
951             values = self.repository.get_last_weeks()
952             d = date.today()
953             for i in range(7):
954                 labels.append(d.strftime("Week %W"))
955                 d = d - timedelta(days=7)
956         else:
957             values = self.repository.get_today()
958             #TODO get labels
959
960         if self.y_id == 0:
961             yvalues = [line.steps for line in values]
962         elif self.y_id == 1:
963             yvalues = [line.get_avg_speed() for line in values]
964         elif self.y_id == 2:
965             yvalues = [line.dist for line in values]
966         else:
967             yvalues = [line.calories for line in values]
968
969         #determine values for y lines in graph
970         diff = self.get_best_interval_value(max(yvalues))
971         ytext = []
972         for i in range(6):
973             ytext.append(str(int(i*diff)))
974
975         if self.widget is not None:
976             yvalues.reverse()
977             labels.reverse()
978             self.widget.values = yvalues
979             self.widget.ytext = ytext
980             self.widget.xtext = labels
981             self.widget.max_value = diff * 5
982             self.widget.text = self.xtitles[self.x_id] + " / " + self.ytitles[self.y_id]
983             self.widget.queue_draw()
984         else:
985             logger.error("Widget not set in GraphController")
986
987     def get_best_interval_value(self, max_value):
988         diff =  1.0 * max_value / 5
989         l = len(str(int(diff)))
990         d = math.pow(10, l/2)
991         val = int(math.ceil(1.0 * diff / d)) * d
992         if val == 0:
993             val = 1
994         return val
995
996     def update_ui(self, optional=False):
997         """update graph values every x seconds"""
998         if optional and self.last_update - time.time() < 600:
999             return
1000         if self.widget is None:
1001             return
1002
1003         self.compute_values()
1004         self.last_update = time.time()
1005
1006 class GraphWidget(gtk.DrawingArea):
1007
1008     def __init__(self):
1009         gtk.DrawingArea.__init__(self)
1010         self.set_size_request(-1, 150)
1011         self.yvalues = 5
1012
1013         """sample values"""
1014         self.ytext = ["   0", "1000", "2000", "3000", "4000", "5000"]
1015         self.xtext = ["Monday", "Tuesday", "Wednesday","Thursday", "Friday", "Saturday", "Sunday"]
1016         self.values = [1500, 3400, 4000, 3600, 3200, 0, 4500]
1017         self.max_value = 5000
1018         self.text = "All time steps"
1019
1020     def do_expose_event(self, event):
1021         context = self.window.cairo_create()
1022
1023         # set a clip region for the expose event
1024         context.rectangle(event.area.x, event.area.y,
1025                                event.area.width, event.area.height)
1026         context.clip()
1027
1028         context.save()
1029
1030         context.set_operator(cairo.OPERATOR_SOURCE)
1031         style = self.rc_get_style()
1032
1033         if self.state == gtk.STATE_ACTIVE:
1034             color = style.lookup_color("SelectionColor")
1035         else:
1036              color = style.lookup_color("DefaultBackgroundColor")
1037         context.set_source_rgba (color.red / 65535.0, color.green / 65335.0, color.blue / 65535.0, 0.75)
1038
1039         context.paint()
1040         context.restore();
1041         self.draw(context)
1042
1043     def draw(self, cr):
1044         space_below = 20
1045         space_above = 10
1046         border_right = 10
1047         border_left = 30
1048
1049         rect = self.get_allocation()
1050         x = rect.width
1051         y = rect.height
1052
1053         cr.select_font_face("Purisa", cairo.FONT_SLANT_NORMAL,
1054             cairo.FONT_WEIGHT_NORMAL)
1055         cr.set_font_size(13)
1056
1057         #check space needed to display ylabels
1058         te = cr.text_extents(self.ytext[-1])
1059         border_left = te[2] + 7
1060
1061         cr.set_source_rgb(1, 1, 1)
1062         cr.move_to(border_left, space_above)
1063         cr.line_to(border_left, y-space_below)
1064         cr.set_line_width(2)
1065         cr.stroke()
1066
1067         cr.move_to(border_left, y-space_below)
1068         cr.line_to(x-border_right, y-space_below)
1069         cr.set_line_width(2)
1070         cr.stroke()
1071
1072         ydiff = (y-space_above-space_below) / self.yvalues
1073         for i in range(self.yvalues):
1074             yy = y-space_below-ydiff*(i+1)
1075             cr.move_to(border_left, yy)
1076             cr.line_to(x-border_right, yy)
1077             cr.set_line_width(0.8)
1078             cr.stroke()
1079
1080
1081         for i in range(6):
1082             yy = y - space_below - ydiff*i + 5
1083             te = cr.text_extents(self.ytext[i])
1084
1085             cr.move_to(border_left-te[2]-2, yy)
1086             cr.show_text(self.ytext[i])
1087
1088         cr.set_font_size(15)
1089         te = cr.text_extents(self.text)
1090         cr.move_to((x-te[2])/2, y-5)
1091         cr.show_text(self.text)
1092
1093         graph_x_space = x - border_left - border_right
1094         graph_y_space = y - space_below - space_above
1095         bar_width = graph_x_space*0.75 / len(self.values)
1096         bar_distance = graph_x_space*0.25 / (1+len(self.values))
1097
1098         #set dummy max value to avoid exceptions
1099         if self.max_value == 0:
1100             self.max_value = 100
1101         for i in range(len(self.values)):
1102             xx = border_left + (i+1)*bar_distance + i * bar_width
1103             yy = y-space_below
1104             height = graph_y_space * (1.0 * self.values[i] / self.max_value)
1105             cr.set_source_rgba(1, 1, 1, 0.75)
1106             cr.rectangle(int(xx), int(yy-height), int(bar_width), int(height))
1107             cr.fill()
1108
1109         cr.set_source_rgba(1, 1, 1, 1)
1110         cr.select_font_face("Purisa", cairo.FONT_SLANT_NORMAL,
1111                             cairo.FONT_WEIGHT_NORMAL)
1112         cr.set_font_size(13)
1113
1114         cr.rotate(2*math.pi * (-45) / 180)
1115         for i in range(len(self.values)):
1116             xx = y - space_below - 10
1117             yy = border_left + (i+1)*bar_distance + i * bar_width
1118             cr.move_to(-xx, yy + bar_width*1.25 / 2)
1119             cr.show_text(self.xtext[i])
1120
1121 class Config(Singleton):
1122     mode = 0
1123     height = 0
1124     step_length = 0.7
1125     weight = 70
1126     sensitivity = 100
1127     unit = 0
1128     aspect = 0
1129     sensitivity = 100
1130     second_view = 0
1131     graph_view = 0
1132     no_idle_time = False
1133     logging = False
1134
1135     alarm_enable = False
1136     alarm_fname = "/home/user/MyDocs/.sounds/Ringtones/Bicycle.aac"
1137     alarm_interval = 5
1138     alarm_type = 0
1139
1140     observers = []
1141
1142     def __init__(self):
1143         self.client = gconf.client_get_default()
1144         self.client.add_dir('/apps/pedometerhomewidget', gconf.CLIENT_PRELOAD_NONE)
1145         self.notify_id = self.client.notify_add('/apps/pedometerhomewidget', self.gconf_changed)
1146
1147     def add_observer(self, func):
1148         try:
1149             self.observers.index(func)
1150         except:
1151             self.observers.append(func)
1152             func()
1153
1154     def remove_observer(self, func):
1155         self.observers.remove(func)
1156
1157     def gconf_changed(self, client, *args, **kargs):
1158         self.notify()
1159
1160     def notify(self):
1161         for func in self.observers:
1162             func()
1163
1164     def get_mode(self):
1165         return self.client.get_int(MODE)
1166
1167     def set_mode(self, value):
1168         self.client.set_int(MODE, value)
1169
1170     def get_height(self):
1171         return self.client.get_int(HEIGHT)
1172
1173     def set_height(self, value):
1174         self.client.set_int(HEIGHT, value)
1175
1176     def get_step_length(self):
1177         return self.client.get_float(STEP_LENGTH)
1178
1179     def set_step_length(self, value):
1180         self.client.set_float(STEP_LENGTH, value)
1181
1182     def get_weight(self):
1183         return self.client.get_int(WEIGHT)
1184
1185     def set_weight(self, value):
1186         self.client.set_int(WEIGHT, value)
1187
1188     def get_sensitivity(self):
1189         return self.client.get_int(SENSITIVITY)
1190
1191     def set_sensitivity(self, value):
1192         self.client.set_int(SENSITIVITY, value)
1193
1194     def get_unit(self):
1195         return self.client.get_int(UNIT)
1196
1197     def set_unit(self, value):
1198         self.client.set_int(UNIT, value)
1199
1200     def get_aspect(self):
1201         return self.client.get_int(ASPECT)
1202
1203     def set_aspect(self, value):
1204         self.client.set_int(ASPECT, value)
1205
1206     def get_secondview(self):
1207         value = self.client.get_int(SECONDVIEW)
1208         if value < 0 or value > 2:
1209             value = 0
1210             logger.error("Invalid secondview value read from Gconf. Using default value")
1211
1212         return value
1213
1214     def set_secondview(self, value):
1215         self.client.set_int(SECONDVIEW, value)
1216
1217     def get_graphview(self):
1218         return self.client.get_int(GRAPHVIEW)
1219
1220     def set_graphview(self, value):
1221         self.client.set_int(GRAPHVIEW, value)
1222
1223     def get_noidletime(self):
1224         return self.client.get_bool(NOIDLETIME)
1225
1226     def set_noidletime(self, value):
1227         self.client.set_bool(NOIDLETIME, value)
1228
1229     def get_logging(self):
1230         return self.client.get_bool(LOGGING)
1231
1232     def set_logging(self, value):
1233         self.client.set_bool(LOGGING, value)
1234
1235     def get_alarm_enable(self):
1236         return self.client.get_bool(ALARM_ENABLE)
1237
1238     def set_alarm_enable(self, value):
1239         self.client.set_bool(ALARM_ENABLE, value)
1240
1241     def get_alarm_fname(self):
1242         return self.client.get_string(ALARM_FNAME)
1243
1244     def set_alarm_fname(self, value):
1245         self.client.set_string(ALARM_FNAME, value)
1246
1247     def get_alarm_interval(self):
1248         return self.client.get_int(ALARM_INTERVAL)
1249
1250     def set_alarrm_interval(self, value):
1251         self.client.set_int(ALARM_INTERVAL, value)
1252
1253     def get_alarm_type(self):
1254         return self.client.get_int(ALARM_TYPE)
1255
1256     def set_alarm_type(self, value):
1257         self.client.set_int(ALARM_TYPE, value)
1258
1259 class PedometerHomePlugin(hildondesktop.HomePluginItem):
1260     button = None
1261
1262     #labels to display
1263     labels = ["timer", "count", "dist", "avgSpeed", "calories"]
1264
1265     #current view
1266     labelsC = {}
1267
1268     #second view ( day / week/ alltime)
1269     labelsT = {}
1270
1271     second_view_labels = ["All-time", "Today", "This week"]
1272
1273     controller = None
1274     graph_controller = None
1275
1276     config = None
1277
1278     def __init__(self):
1279         hildondesktop.HomePluginItem.__init__(self)
1280
1281         gobject.type_register(CustomEventBox)
1282         gobject.type_register(GraphWidget)
1283
1284         self.config = Config()
1285
1286         self.button = CustomButton(ICONSPATH + "play.png")
1287         self.button.connect("clicked", self.button_clicked)
1288
1289         self.create_labels(self.labelsC)
1290         self.create_labels(self.labelsT)
1291         self.label_second_view = self.new_label_heading(self.second_view_labels[self.config.get_secondview()])
1292
1293         self.controller = PedoController()
1294         self.controller.set_callback_ui(self.update_values)
1295
1296         self.graph_controller = GraphController()
1297         self.alarm_controller = AlarmController()
1298
1299         self.update_current()
1300         self.update_total()
1301
1302         mainHBox = gtk.HBox(spacing=1)
1303
1304         descVBox = gtk.VBox(spacing=1)
1305         descVBox.add(self.new_label_heading())
1306         descVBox.add(self.new_label_heading("Time:"))
1307         descVBox.add(self.new_label_heading("Steps:"))
1308         descVBox.add(self.new_label_heading("Calories:"))
1309         descVBox.add(self.new_label_heading("Distance:"))
1310         descVBox.add(self.new_label_heading("Avg Speed:"))
1311
1312         currentVBox = gtk.VBox(spacing=1)
1313         currentVBox.add(self.new_label_heading("Current"))
1314         currentVBox.add(self.labelsC["timer"])
1315         currentVBox.add(self.labelsC["count"])
1316         currentVBox.add(self.labelsC["calories"])
1317         currentVBox.add(self.labelsC["dist"])
1318         currentVBox.add(self.labelsC["avgSpeed"])
1319         self.currentBox = currentVBox
1320
1321         totalVBox = gtk.VBox(spacing=1)
1322         totalVBox.add(self.label_second_view)
1323         totalVBox.add(self.labelsT["timer"])
1324         totalVBox.add(self.labelsT["count"])
1325         totalVBox.add(self.labelsT["calories"])
1326         totalVBox.add(self.labelsT["dist"])
1327         totalVBox.add(self.labelsT["avgSpeed"])
1328         self.totalBox = totalVBox
1329
1330         buttonVBox = gtk.VBox(spacing=1)
1331         buttonVBox.add(self.new_label_heading(""))
1332         buttonVBox.add(self.button)
1333         buttonVBox.add(self.new_label_heading(""))
1334
1335         eventBox = CustomEventBox()
1336         eventBox.set_visible_window(False)
1337         eventBox.add(totalVBox)
1338         eventBox.connect("button-press-event", self.eventBox_clicked)
1339         eventBox.connect("button-release-event", self.eventBox_clicked_release)
1340
1341         mainHBox.add(buttonVBox)
1342         mainHBox.add(descVBox)
1343         mainHBox.add(currentVBox)
1344         mainHBox.add(eventBox)
1345         self.mainhbox = mainHBox
1346
1347         graph = GraphWidget()
1348         self.graph_controller.set_graph(graph)
1349
1350         eventBoxGraph = CustomEventBox()
1351         eventBoxGraph.set_visible_window(False)
1352         eventBoxGraph.add(graph)
1353         self.graph = graph
1354         eventBoxGraph.connect("button-press-event", self.eventBoxGraph_clicked)
1355         eventBoxGraph.connect("button-release-event", self.eventBoxGraph_clicked_release)
1356         self.graphBox = eventBoxGraph
1357
1358         self.mainvbox = gtk.VBox()
1359
1360         self.mainvbox.add(mainHBox)
1361         self.mainvbox.add(eventBoxGraph)
1362
1363         self.mainvbox.show_all()
1364         self.add(self.mainvbox)
1365         self.update_aspect()
1366
1367         self.connect("unrealize", self.close_requested)
1368         self.set_settings(True)
1369         self.connect("show-settings", self.show_settings)
1370
1371     def eventBoxGraph_clicked(self, widget, data=None):
1372         widget.set_state(gtk.STATE_ACTIVE)
1373
1374     def eventBoxGraph_clicked_release(self, widget, data=None):
1375         self.graph_controller.next_view()
1376         widget.set_state(gtk.STATE_NORMAL)
1377
1378     def eventBox_clicked(self, widget, data=None):
1379         widget.set_state(gtk.STATE_ACTIVE)
1380
1381     def eventBox_clicked_release(self, widget, data=None):
1382         widget.set_state(gtk.STATE_NORMAL)
1383
1384         second_view = self.config.get_secondview()
1385         second_view = (second_view + 1) % 3
1386         self.config.set_secondview(second_view)
1387
1388     def new_label_heading(self, title=""):
1389         l = gtk.Label(title)
1390         hildon.hildon_helper_set_logical_font(l, "SmallSystemFont")
1391         return l
1392
1393     def create_labels(self, new_labels):
1394         for label in self.labels:
1395             l = gtk.Label()
1396             hildon.hildon_helper_set_logical_font(l, "SmallSystemFont")
1397             hildon.hildon_helper_set_logical_color(l, gtk.RC_FG, gtk.STATE_NORMAL, "ActiveTextColor")
1398             new_labels[label] = l
1399
1400     def update_aspect(self):
1401         aspect = self.config.get_aspect()
1402         if aspect > 0:
1403             self.graphBox.hide_all()
1404         else:
1405             self.graphBox.show_all()
1406
1407         if aspect == 0 or aspect == 1:
1408             self.currentBox.show_all()
1409             self.totalBox.show_all()
1410         elif aspect == 2:
1411             self.currentBox.show_all()
1412             self.totalBox.hide_all()
1413         else:
1414             self.currentBox.hide_all()
1415             self.totalBox.show_all()
1416
1417         x,y = self.size_request()
1418         self.resize(x,y)
1419
1420     def update_ui_values(self, labels, values):
1421         labels["timer"].set_label(values.get_print_time())
1422         labels["count"].set_label(values.get_print_steps())
1423         labels["dist"].set_label(values.get_print_distance())
1424         labels["avgSpeed"].set_label(values.get_print_avg_speed())
1425         labels["calories"].set_label(values.get_print_calories())
1426
1427     def update_current(self):
1428         self.update_ui_values(self.labelsC, self.controller.get_first())
1429
1430     def update_total(self):
1431         self.update_ui_values(self.labelsT, self.controller.get_second())
1432
1433     def show_alarm_settings(self, main_button):
1434         def choose_file(widget):
1435             file = hildon.FileChooserDialog(self, gtk.FILE_CHOOSER_ACTION_OPEN, hildon.FileSystemModel() )
1436             file.show()
1437             if ( file.run() == gtk.RESPONSE_OK):
1438                 fname = file.get_filename()
1439                 widget.set_value(fname)
1440                 self.config.set_alarm_fname(fname)
1441             file.destroy()
1442
1443         def test_sound(button):
1444             try:
1445                 self.alarm_controller.play()
1446             except Exception, e:
1447                 logger.error("Could not play alarm sound: %s" % e)
1448                 hildon.hildon_banner_show_information(self, "None", "Could not play alarm sound")
1449
1450         def enableButton_changed(button):
1451             value = button.get_active()
1452             self.config.set_alarm_enable(value)
1453             if value:
1454                 main_button.set_value("Enabled")
1455             else:
1456                 main_button.set_value("Disabled")
1457
1458         def selectorType_changed(selector, data, labelEntry2):
1459             type = selector.get_active(0)
1460             self.config.set_alarm_type(type)
1461             labelEntry2.set_label(suffix[type])
1462
1463         dialog = gtk.Dialog()
1464         dialog.set_title("Alarm settings")
1465         dialog.add_button("OK", gtk.RESPONSE_OK)
1466
1467         enableButton = hildon.CheckButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT)
1468         enableButton.set_label("Enable alarm")
1469         enableButton.set_active(self.alarm_controller.get_enable())
1470         enableButton.connect("toggled", enableButton_changed)
1471
1472         testButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1473         testButton.set_alignment(0, 0.8, 1, 1)
1474         testButton.set_title("Test sound")
1475         testButton.connect("pressed", test_sound)
1476
1477         fileButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1478         fileButton.set_alignment(0, 0.8, 1, 1)
1479         fileButton.set_title("Alarm sound")
1480         fileButton.set_value(self.alarm_controller.get_alarm_file())
1481         fileButton.connect("pressed", choose_file)
1482
1483         labelEntry = gtk.Label("Notify every:")
1484         suffix = ["mins", "steps", "m/ft", "calories"]
1485         labelEntry2 = gtk.Label(suffix[self.alarm_controller.get_type()])
1486         intervalEntry = hildon.Entry(gtk.HILDON_SIZE_AUTO_WIDTH)
1487         intervalEntry.set_text(str(self.alarm_controller.get_interval()))
1488
1489         selectorType = hildon.TouchSelector(text=True)
1490         selectorType.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
1491         selectorType.append_text("Time")
1492         selectorType.append_text("Steps")
1493         selectorType.append_text("Distance")
1494         selectorType.append_text("Calories")
1495         selectorType.connect("changed", selectorType_changed, labelEntry2)
1496
1497         typePicker = hildon.PickerButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1498         typePicker.set_alignment(0.0, 0.5, 1.0, 1.0)
1499         typePicker.set_title("Alarm type")
1500         typePicker.set_selector(selectorType)
1501         typePicker.set_active(self.alarm_controller.get_type())
1502
1503         hbox = gtk.HBox()
1504         hbox.add(labelEntry)
1505         hbox.add(intervalEntry)
1506         hbox.add(labelEntry2)
1507
1508         dialog.vbox.add(enableButton)
1509         dialog.vbox.add(fileButton)
1510         dialog.vbox.add(testButton)
1511         dialog.vbox.add(typePicker)
1512         dialog.vbox.add(hbox)
1513         dialog.show_all()
1514         while 1:
1515             response = dialog.run()
1516             if response != gtk.RESPONSE_OK:
1517                 break
1518             try:
1519                 value = int(intervalEntry.get_text())
1520                 self.config.set_alarrm_interval(value)
1521                 break
1522             except:
1523                 hildon.hildon_banner_show_information(self, "None", "Invalid interval")
1524
1525         dialog.destroy()
1526
1527     def show_settings(self, widget):
1528         def reset_total_counter(arg):
1529             note = hildon.hildon_note_new_confirmation(self.dialog, "Are you sure you want to delete all your pedometer history?")
1530             ret = note.run()
1531             if ret == gtk.RESPONSE_OK:
1532                 self.controller.reset_all_values()
1533                 hildon.hildon_banner_show_information(self, "None", "All history was deleted")
1534             note.destroy()
1535
1536         def alarmButton_pressed(widget):
1537             self.show_alarm_settings(widget)
1538
1539         def selector_changed(selector, data):
1540             mode = selector.get_active(0)
1541             self.config.set_mode(mode)
1542
1543         def selectorUnit_changed(selector, data):
1544             unit = selector.get_active(0)
1545             self.config.set_unit(unit)
1546
1547             update_weight_button()
1548             stepLengthButton_value_update()
1549
1550         def selectorUI_changed(selector, data):
1551             aspect = selectorUI.get_active(0)
1552             widget.update_aspect()
1553
1554         def logButton_changed(checkButton):
1555             logging = checkButton.get_active()
1556             self.config.set_logging(logging)
1557
1558         def idleButton_changed(idleButton):
1559             no_idle_time = idleButton.get_active()
1560             self.config.set_noidletime(no_idle_time)
1561
1562         def update_weight_button():
1563             weightButton.set_value(str(self.config.get_weight()) + \
1564                                            " " + self.controller.get_str_weight_unit(self.config.get_unit()) )
1565
1566         def weight_dialog(button):
1567             dialog = gtk.Dialog("Weight", self.dialog)
1568             dialog.add_button("OK", gtk.RESPONSE_OK)
1569
1570             label = gtk.Label("Weight:")
1571             entry = gtk.Entry()
1572             entry.set_text(str(self.config.get_weight()))
1573
1574             suffixLabel = gtk.Label(self.controller.get_str_weight_unit(self.config.get_unit()))
1575
1576             hbox = gtk.HBox()
1577             hbox.add(label)
1578             hbox.add(entry)
1579             hbox.add(suffixLabel)
1580
1581             dialog.vbox.add(hbox)
1582             dialog.show_all()
1583             while 1:
1584                 response = dialog.run()
1585                 if response != gtk.RESPONSE_OK:
1586                     break
1587                 try:
1588                     value = int(entry.get_text())
1589                     if value <= 0:
1590                         raise ValueError
1591                     self.config.set_weight(value)
1592                     update_weight_button()
1593                     break
1594                 except:
1595                     hildon.hildon_banner_show_information(self, "None", "Invalid weight")
1596             dialog.destroy()
1597
1598         def sensitivity_dialog(button):
1599             def seekbar_changed(seekbar):
1600                 label.set_text(str(seekbar.get_position()) + " %")
1601
1602             dialog = gtk.Dialog("Sensitivity", self.dialog)
1603             dialog.add_button("OK", gtk.RESPONSE_OK)
1604             seekbar = hildon.Seekbar()
1605             seekbar.set_size_request(400, -1)
1606             seekbar.set_total_time(200)
1607             seekbar.set_position(self.config.get_sensitivity())
1608             seekbar.connect("value-changed", seekbar_changed)
1609
1610             hbox = gtk.HBox()
1611             hbox.add(seekbar)
1612             label = gtk.Label(str(self.config.get_sensitivity()) + " %")
1613             label.set_size_request(30, -1)
1614             hbox.add(label)
1615
1616             dialog.vbox.add(hbox)
1617             dialog.show_all()
1618
1619             if dialog.run() == gtk.RESPONSE_OK:
1620                 value = seekbar.get_position()
1621                 self.config.set_sensitivity(value)
1622                 button.set_value(str(value) + " %")
1623
1624             dialog.destroy()
1625
1626         def stepLengthButton_value_update():
1627             if self.config.get_height() == 5:
1628                 l_unit = ["m", "ft"]
1629                 stepLengthButton.set_value("Custom value: %.2f %s" % (self.config.get_step_length(), l_unit[self.config.get_unit()]))
1630             else:
1631                 h = [ ["< 1.50 m", "1.50 - 1.65 m", "1.66 - 1.80 m", "1.81 - 1.95 m", " > 1.95 m"],
1632                       ["< 5 ft", "5 - 5.5 ft", "5.5 - 6 ft", "6 - 6.5 ft", "> 6.5 ft"]]
1633                 str = "Using predefined value for height: %s" % h[self.config.get_unit()][self.config.get_height()]
1634                 stepLengthButton.set_value(str)
1635
1636         def stepLength_dialog(button):
1637             def selectorH_changed(selector, data, dialog):
1638                 height = selector.get_active(0)
1639                 self.config.set_height(height)
1640                 stepLengthButton_value_update()
1641
1642             def manualButton_clicked(button, dialog):
1643                 dlg = gtk.Dialog()
1644                 dlg.set_title("Custom step length")
1645                 dlg.add_button("OK", gtk.RESPONSE_OK)
1646
1647                 label = gtk.Label("Length")
1648
1649                 entry = hildon.Entry(gtk.HILDON_SIZE_AUTO_WIDTH)
1650                 if self.config.get_height() == 5:
1651                     entry.set_text(str(self.config.get_step_length()))
1652
1653                 labelSuffix = gtk.Label()
1654                 if self.config.get_unit() == 0:
1655                     labelSuffix.set_label("m")
1656                 else:
1657                     labelSuffix.set_label("ft")
1658                 hbox = gtk.HBox()
1659                 hbox.add(label)
1660                 hbox.add(entry)
1661                 hbox.add(labelSuffix)
1662                 dlg.vbox.add(hbox)
1663                 dlg.show_all()
1664
1665                 while 1:
1666                     response = dlg.run()
1667                     if response != gtk.RESPONSE_OK:
1668                         break
1669                     try:
1670                         value = float(entry.get_text())
1671                         if value <= 0:
1672                             raise ValueError
1673                         self.config.set_step_length(value)
1674                         self.config.set_height(5)
1675                         stepLengthButton_value_update()
1676                         break
1677                     except ValueError:
1678                         hildon.hildon_banner_show_information(self, "None", "Invalid length")
1679                 dlg.destroy()
1680                 dialog.destroy()
1681
1682             def heightButton_clicked(button, dialog):
1683                 dialog.destroy()
1684
1685             dialog = gtk.Dialog()
1686             dialog.set_title("Step length")
1687
1688             manualButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1689             manualButton.set_title("Enter custom value")
1690             manualButton.set_alignment(0, 0.8, 1, 1)
1691             manualButton.connect("clicked", manualButton_clicked, dialog)
1692
1693             selectorH = hildon.TouchSelector(text=True)
1694             selectorH.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
1695             selectorH.append_text("< 1.50 m")
1696             selectorH.append_text("1.50 - 1.65 m")
1697             selectorH.append_text("1.66 - 1.80 m")
1698             selectorH.append_text("1.81 - 1.95 m")
1699             selectorH.append_text(" > 1.95 m")
1700
1701             selectorH_English = hildon.TouchSelector(text=True)
1702             selectorH_English.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
1703             selectorH_English.append_text("< 5 ft")
1704             selectorH_English.append_text("5 - 5.5 ft")
1705             selectorH_English.append_text("5.5 - 6 ft")
1706             selectorH_English.append_text("6 - 6.5 ft")
1707             selectorH_English.append_text("> 6.5 ft")
1708
1709             heightPicker = hildon.PickerButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1710             heightPicker.set_alignment(0.0, 0.5, 1.0, 1.0)
1711             heightPicker.set_title("Use predefined values for height")
1712
1713
1714             unit = self.config.get_unit()
1715             if unit == 0:
1716                 heightPicker.set_selector(selectorH)
1717             else:
1718                 heightPicker.set_selector(selectorH_English)
1719
1720             height = self.config.get_height()
1721             if height < 5:
1722                 heightPicker.set_active(height)
1723
1724             heightPicker.get_selector().connect("changed", selectorH_changed, dialog)
1725             heightPicker.connect("value-changed", heightButton_clicked, dialog)
1726
1727             dialog.vbox.add(heightPicker)
1728             dialog.vbox.add(manualButton)
1729             dialog.show_all()
1730
1731             if  dialog.run() == gtk.RESPONSE_DELETE_EVENT:
1732                 dialog.destroy()
1733
1734         def donateButton_clicked(button, dialog):
1735             url = "https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=BKE6E9SLK7NP4&lc=RO&item_name=Pedometer%20Widget&currency_code=EUR&bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted"
1736             command = "dbus-send --system --type=method_call --dest=\"com.nokia.osso_browser\"  --print-reply /com/nokia/osso_browser/request com.nokia.osso_browser.load_url string:\"%s\"" % url
1737             os.system(command)
1738
1739         dialog = gtk.Dialog()
1740         dialog.set_title("Settings")
1741         dialog.add_button("OK", gtk.RESPONSE_OK)
1742         self.dialog = dialog
1743
1744         stepLengthButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1745         stepLengthButton.set_title("Step length")
1746         stepLengthButton.set_alignment(0, 0.8, 1, 1)
1747         stepLengthButton.connect("clicked", stepLength_dialog)
1748         stepLengthButton_value_update()
1749
1750         resetButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1751         resetButton.set_title("Reset total counter")
1752         resetButton.set_alignment(0, 0.8, 1, 1)
1753         resetButton.connect("clicked", reset_total_counter)
1754
1755         alarmButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1756         alarmButton.set_title("Alarm")
1757         if self.config.get_alarm_enable():
1758             alarmButton.set_value("Enabled")
1759         else:
1760             alarmButton.set_value("Disabled")
1761         alarmButton.set_alignment(0, 0.8, 1, 1)
1762         alarmButton.connect("clicked", alarmButton_pressed)
1763
1764         selector = hildon.TouchSelector(text=True)
1765         selector.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
1766         selector.append_text("Walk")
1767         selector.append_text("Run")
1768         selector.connect("changed", selector_changed)
1769
1770         modePicker = hildon.PickerButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1771         modePicker.set_alignment(0.0, 0.5, 1.0, 1.0)
1772         modePicker.set_title("Mode")
1773         modePicker.set_selector(selector)
1774         modePicker.set_active(self.config.get_mode())
1775
1776         weightButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1777         weightButton.set_title("Weight")
1778         weightButton.set_alignment(0, 0.8, 1, 1)
1779         update_weight_button()
1780         weightButton.connect("clicked", weight_dialog)
1781
1782         selectorUnit = hildon.TouchSelector(text=True)
1783         selectorUnit.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
1784         selectorUnit.append_text("Metric (km)")
1785         selectorUnit.append_text("English (mi)")
1786         selectorUnit.connect("changed", selectorUnit_changed)
1787
1788         unitPicker = hildon.PickerButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1789         unitPicker.set_alignment(0.0, 0.5, 1.0, 1.0)
1790         unitPicker.set_title("Unit")
1791         unitPicker.set_selector(selectorUnit)
1792         unitPicker.set_active(self.config.get_unit())
1793
1794         selectorUI = hildon.TouchSelector(text=True)
1795         selectorUI = hildon.TouchSelector(text=True)
1796         selectorUI.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
1797         selectorUI.append_text("Show current + total + graph")
1798         selectorUI.append_text("Show current + total")
1799         selectorUI.append_text("Show only current")
1800         selectorUI.append_text("Show only total")
1801         selectorUI.connect("changed", selectorUI_changed)
1802
1803         UIPicker = hildon.PickerButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1804         UIPicker.set_alignment(0.0, 0.5, 1.0, 1.0)
1805         UIPicker.set_title("Widget aspect")
1806         UIPicker.set_selector(selectorUI)
1807         UIPicker.set_active(self.config.get_aspect())
1808
1809         sensitivityButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1810         sensitivityButton.set_title("Sensitivity")
1811         sensitivityButton.set_alignment(0, 0.8, 1, 1)
1812         sensitivityButton.set_value(str(self.config.get_sensitivity()) + " %")
1813         sensitivityButton.connect("clicked", sensitivity_dialog)
1814
1815         donateButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1816         donateButton.set_title("Donate")
1817         donateButton.set_alignment(0, 0.8, 1, 1)
1818         donateButton.connect("clicked", donateButton_clicked, dialog)
1819
1820         logButton = hildon.CheckButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT)
1821         logButton.set_label("Log data")
1822         logButton.set_active(self.config.get_logging())
1823         logButton.connect("toggled", logButton_changed)
1824
1825         idleButton = hildon.CheckButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT)
1826         idleButton.set_label("Pause time when not walking")
1827         idleButton.set_active(self.config.get_noidletime())
1828         idleButton.connect("toggled", idleButton_changed)
1829
1830         pan_area = hildon.PannableArea()
1831         vbox = gtk.VBox()
1832         vbox.add(alarmButton)
1833         vbox.add(modePicker)
1834         vbox.add(stepLengthButton)
1835         vbox.add(weightButton)
1836         vbox.add(unitPicker)
1837         vbox.add(sensitivityButton)
1838         vbox.add(UIPicker)
1839         vbox.add(idleButton)
1840         vbox.add(resetButton)
1841         vbox.add(donateButton)
1842         #vbox.add(logButton)
1843
1844         pan_area.add_with_viewport(vbox)
1845         pan_area.set_size_request(-1, 300)
1846
1847         dialog.vbox.add(pan_area)
1848         dialog.show_all()
1849
1850         response = dialog.run()
1851         dialog.destroy()
1852
1853     def close_requested(self, widget):
1854         if self.controller.is_running:
1855             self.controller.stop_pedometer()
1856         self.controller.stop_midnight_callback()
1857
1858     def update_values(self):
1859         #TODO: do not update if the widget is not on the active desktop
1860         self.label_second_view.set_label(self.second_view_labels[self.config.get_secondview()])
1861         self.update_current()
1862         self.update_total()
1863
1864     def button_clicked(self, button):
1865         if self.controller.is_running:
1866             self.controller.stop_pedometer()
1867             self.button.set_icon(ICONSPATH + "play.png")
1868         else:
1869             self.controller.start_pedometer()
1870             self.button.set_icon(ICONSPATH + "stop.png")
1871             hildon.hildon_banner_show_information(self, "None", "Keep the N900 in a pocket close to your hip for best results")
1872
1873     def do_expose_event(self, event):
1874         cr = self.window.cairo_create()
1875         cr.region(event.window.get_clip_region())
1876         cr.clip()
1877         #cr.set_source_rgba(0.4, 0.64, 0.564, 0.5)
1878         style = self.rc_get_style()
1879         color = style.lookup_color("DefaultBackgroundColor")
1880         cr.set_source_rgba (color.red / 65535.0, color.green / 65335.0, color.blue / 65535.0, 0.75);
1881
1882         radius = 5
1883         width = self.allocation.width
1884         height = self.allocation.height
1885
1886         x = self.allocation.x
1887         y = self.allocation.y
1888
1889         cr.move_to(x + radius, y)
1890         cr.line_to(x + width - radius, y)
1891         cr.curve_to(x + width - radius, y, x + width, y, x + width, y + radius)
1892         cr.line_to(x + width, y + height - radius)
1893         cr.curve_to(x + width, y + height - radius, x + width, y + height, x + width - radius, y + height)
1894         cr.line_to(x + radius, y + height)
1895         cr.curve_to(x + radius, y + height, x, y + height, x, y + height - radius)
1896         cr.line_to(x, y + radius)
1897         cr.curve_to(x, y + radius, x, y, x + radius, y)
1898
1899         cr.set_operator(cairo.OPERATOR_SOURCE)
1900         cr.fill_preserve()
1901
1902         color = style.lookup_color("ActiveTextColor")
1903         cr.set_source_rgba (color.red / 65535.0, color.green / 65335.0, color.blue / 65535.0, 0.5);
1904         cr.set_line_width(1)
1905         cr.stroke()
1906
1907         hildondesktop.HomePluginItem.do_expose_event(self, event)
1908
1909     def do_realize(self):
1910         screen = self.get_screen()
1911         self.set_colormap(screen.get_rgba_colormap())
1912         self.set_app_paintable(True)
1913         hildondesktop.HomePluginItem.do_realize(self)
1914
1915 hd_plugin_type = PedometerHomePlugin
1916
1917 import math
1918 import logging
1919
1920 logger = logging.getLogger("pedometer")
1921 logger.setLevel(logging.INFO)
1922
1923 ch = logging.StreamHandler()
1924 formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
1925 ch.setFormatter(formatter)
1926 logger.addHandler(ch)
1927
1928 # The code below is just for testing purposes.
1929 # It allows to run the widget as a standalone process.
1930 if __name__ == "__main__":
1931     import gobject
1932     gobject.type_register(hd_plugin_type)
1933     obj = gobject.new(hd_plugin_type, plugin_id="plugin_id")
1934     obj.show_all()
1935     gtk.main()