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