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