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