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