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