Fix widget aspect update bug
[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
1408         self.connect("unrealize", self.close_requested)
1409         self.set_settings(True)
1410         self.connect("show-settings", self.show_settings)
1411
1412         self.config.add_observer(self.update_aspect)
1413
1414     def eventBoxGraph_clicked(self, widget, data=None):
1415         widget.set_state(gtk.STATE_ACTIVE)
1416
1417     def eventBoxGraph_clicked_release(self, widget, data=None):
1418         self.graph_controller.next_view()
1419         widget.set_state(gtk.STATE_NORMAL)
1420
1421     def eventBox_clicked(self, widget, data=None):
1422         widget.set_state(gtk.STATE_ACTIVE)
1423
1424     def eventBox_clicked_release(self, widget, data=None):
1425         widget.set_state(gtk.STATE_NORMAL)
1426
1427         second_view = self.config.get_secondview()
1428         second_view = (second_view + 1) % 3
1429         self.config.set_secondview(second_view)
1430
1431     def new_label_heading(self, title=""):
1432         l = gtk.Label(title)
1433         hildon.hildon_helper_set_logical_font(l, "SmallSystemFont")
1434         return l
1435
1436     def create_labels(self, new_labels):
1437         for label in self.labels:
1438             l = gtk.Label()
1439             hildon.hildon_helper_set_logical_font(l, "SmallSystemFont")
1440             hildon.hildon_helper_set_logical_color(l, gtk.RC_FG, gtk.STATE_NORMAL, "ActiveTextColor")
1441             new_labels[label] = l
1442
1443     def update_aspect(self):
1444         aspect = self.config.get_aspect()
1445         if aspect > 0:
1446             self.graphBox.hide_all()
1447         else:
1448             self.graphBox.show_all()
1449
1450         if aspect == 0 or aspect == 1:
1451             self.currentBox.show_all()
1452             self.totalBox.show_all()
1453         elif aspect == 2:
1454             self.currentBox.show_all()
1455             self.totalBox.hide_all()
1456         else:
1457             self.currentBox.hide_all()
1458             self.totalBox.show_all()
1459
1460         x,y = self.size_request()
1461         self.resize(x,y)
1462
1463     def update_ui_values(self, labels, values):
1464         labels["timer"].set_label(values.get_print_time())
1465         labels["count"].set_label(values.get_print_steps())
1466         labels["dist"].set_label(values.get_print_distance())
1467         labels["avgSpeed"].set_label(values.get_print_avg_speed())
1468         labels["calories"].set_label(values.get_print_calories())
1469
1470     def update_current(self):
1471         self.update_ui_values(self.labelsC, self.controller.get_first())
1472
1473     def update_total(self):
1474         self.update_ui_values(self.labelsT, self.controller.get_second())
1475
1476     def show_alarm_settings(self, main_button):
1477         def choose_file(widget):
1478             file = hildon.FileChooserDialog(self, gtk.FILE_CHOOSER_ACTION_OPEN, hildon.FileSystemModel() )
1479             file.show()
1480             if ( file.run() == gtk.RESPONSE_OK):
1481                 fname = file.get_filename()
1482                 widget.set_value(fname)
1483                 self.config.set_alarm_fname(fname)
1484             file.destroy()
1485
1486         def test_sound(button):
1487             try:
1488                 self.alarm_controller.play()
1489             except Exception, e:
1490                 logger.error("Could not play alarm sound: %s" % e)
1491                 hildon.hildon_banner_show_information(self, "None", "Could not play alarm sound")
1492
1493         def enableButton_changed(button):
1494             value = button.get_active()
1495             self.config.set_alarm_enable(value)
1496             if value:
1497                 main_button.set_value("Enabled")
1498             else:
1499                 main_button.set_value("Disabled")
1500
1501         def selectorType_changed(selector, data, labelEntry2):
1502             type = selector.get_active(0)
1503             self.config.set_alarm_type(type)
1504             labelEntry2.set_label(suffix[type])
1505
1506         dialog = gtk.Dialog()
1507         dialog.set_title("Alarm settings")
1508         dialog.add_button("OK", gtk.RESPONSE_OK)
1509
1510         enableButton = hildon.CheckButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT)
1511         enableButton.set_label("Enable alarm")
1512         enableButton.set_active(self.alarm_controller.get_enable())
1513         enableButton.connect("toggled", enableButton_changed)
1514
1515         testButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1516         testButton.set_alignment(0, 0.8, 1, 1)
1517         testButton.set_title("Test sound")
1518         testButton.connect("pressed", test_sound)
1519
1520         fileButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1521         fileButton.set_alignment(0, 0.8, 1, 1)
1522         fileButton.set_title("Alarm sound")
1523         fileButton.set_value(self.alarm_controller.get_alarm_file())
1524         fileButton.connect("pressed", choose_file)
1525
1526         labelEntry = gtk.Label("Notify every:")
1527         suffix = ["mins", "steps", "m/ft", "calories"]
1528         labelEntry2 = gtk.Label(suffix[self.alarm_controller.get_type()])
1529         intervalEntry = hildon.Entry(gtk.HILDON_SIZE_AUTO_WIDTH)
1530         intervalEntry.set_text(str(self.alarm_controller.get_interval()))
1531
1532         selectorType = hildon.TouchSelector(text=True)
1533         selectorType.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
1534         selectorType.append_text("Time")
1535         selectorType.append_text("Steps")
1536         selectorType.append_text("Distance")
1537         selectorType.append_text("Calories")
1538         selectorType.connect("changed", selectorType_changed, labelEntry2)
1539
1540         typePicker = hildon.PickerButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1541         typePicker.set_alignment(0.0, 0.5, 1.0, 1.0)
1542         typePicker.set_title("Alarm type")
1543         typePicker.set_selector(selectorType)
1544         typePicker.set_active(self.alarm_controller.get_type())
1545
1546         hbox = gtk.HBox()
1547         hbox.add(labelEntry)
1548         hbox.add(intervalEntry)
1549         hbox.add(labelEntry2)
1550
1551         dialog.vbox.add(enableButton)
1552         dialog.vbox.add(fileButton)
1553         dialog.vbox.add(testButton)
1554         dialog.vbox.add(typePicker)
1555         dialog.vbox.add(hbox)
1556         dialog.show_all()
1557         while 1:
1558             response = dialog.run()
1559             if response != gtk.RESPONSE_OK:
1560                 break
1561             try:
1562                 value = int(intervalEntry.get_text())
1563                 self.config.set_alarrm_interval(value)
1564                 break
1565             except:
1566                 hildon.hildon_banner_show_information(self, "None", "Invalid interval")
1567
1568         dialog.destroy()
1569
1570     def show_settings(self, widget):
1571         def reset_total_counter(arg):
1572             note = hildon.hildon_note_new_confirmation(self.dialog, "Are you sure you want to delete all your pedometer history?")
1573             ret = note.run()
1574             if ret == gtk.RESPONSE_OK:
1575                 self.controller.reset_all_values()
1576                 hildon.hildon_banner_show_information(self, "None", "All history was deleted")
1577             note.destroy()
1578
1579         def alarmButton_pressed(widget):
1580             self.show_alarm_settings(widget)
1581
1582         def selector_changed(selector, data):
1583             mode = selector.get_active(0)
1584             self.config.set_mode(mode)
1585
1586         def selectorUnit_changed(selector, data):
1587             unit = selector.get_active(0)
1588             self.config.set_unit(unit)
1589
1590             update_weight_button()
1591             stepLengthButton_value_update()
1592
1593         def selectorUI_changed(selector, data):
1594             aspect = selectorUI.get_active(0)
1595             self.config.set_aspect(aspect)
1596
1597         def logButton_changed(checkButton):
1598             logging = checkButton.get_active()
1599             self.config.set_logging(logging)
1600
1601         def idleButton_changed(idleButton):
1602             no_idle_time = idleButton.get_active()
1603             self.config.set_noidletime(no_idle_time)
1604
1605         def update_weight_button():
1606             weightButton.set_value(str(self.config.get_weight()) + \
1607                                            " " + self.controller.get_str_weight_unit(self.config.get_unit()) )
1608
1609         def weight_dialog(button):
1610             dialog = gtk.Dialog("Weight", self.dialog)
1611             dialog.add_button("OK", gtk.RESPONSE_OK)
1612
1613             label = gtk.Label("Weight:")
1614             entry = gtk.Entry()
1615             entry.set_text(str(self.config.get_weight()))
1616
1617             suffixLabel = gtk.Label(self.controller.get_str_weight_unit(self.config.get_unit()))
1618
1619             hbox = gtk.HBox()
1620             hbox.add(label)
1621             hbox.add(entry)
1622             hbox.add(suffixLabel)
1623
1624             dialog.vbox.add(hbox)
1625             dialog.show_all()
1626             while 1:
1627                 response = dialog.run()
1628                 if response != gtk.RESPONSE_OK:
1629                     break
1630                 try:
1631                     value = int(entry.get_text())
1632                     if value <= 0:
1633                         raise ValueError
1634                     self.config.set_weight(value)
1635                     update_weight_button()
1636                     break
1637                 except:
1638                     hildon.hildon_banner_show_information(self, "None", "Invalid weight")
1639             dialog.destroy()
1640
1641         def sensitivity_dialog(button):
1642             def seekbar_changed(seekbar):
1643                 label.set_text(str(seekbar.get_position()) + " %")
1644
1645             dialog = gtk.Dialog("Sensitivity", self.dialog)
1646             dialog.add_button("OK", gtk.RESPONSE_OK)
1647             seekbar = hildon.Seekbar()
1648             seekbar.set_size_request(400, -1)
1649             seekbar.set_total_time(200)
1650             seekbar.set_position(self.config.get_sensitivity())
1651             seekbar.connect("value-changed", seekbar_changed)
1652
1653             hbox = gtk.HBox()
1654             hbox.add(seekbar)
1655             label = gtk.Label(str(self.config.get_sensitivity()) + " %")
1656             label.set_size_request(30, -1)
1657             hbox.add(label)
1658
1659             dialog.vbox.add(hbox)
1660             dialog.show_all()
1661
1662             if dialog.run() == gtk.RESPONSE_OK:
1663                 value = seekbar.get_position()
1664                 self.config.set_sensitivity(value)
1665                 button.set_value(str(value) + " %")
1666
1667             dialog.destroy()
1668
1669         def stepLengthButton_value_update():
1670             if self.config.get_height() == 5:
1671                 l_unit = ["m", "ft"]
1672                 stepLengthButton.set_value("Custom value: %.2f %s" % (self.config.get_step_length(), l_unit[self.config.get_unit()]))
1673             else:
1674                 h = [ ["< 1.50 m", "1.50 - 1.65 m", "1.66 - 1.80 m", "1.81 - 1.95 m", " > 1.95 m"],
1675                       ["< 5 ft", "5 - 5.5 ft", "5.5 - 6 ft", "6 - 6.5 ft", "> 6.5 ft"]]
1676                 str = "Using predefined value for height: %s" % h[self.config.get_unit()][self.config.get_height()]
1677                 stepLengthButton.set_value(str)
1678
1679         def stepLength_dialog(button):
1680             def selectorH_changed(selector, data, dialog):
1681                 height = selector.get_active(0)
1682                 self.config.set_height(height)
1683                 stepLengthButton_value_update()
1684
1685             def manualButton_clicked(button, dialog):
1686                 dlg = gtk.Dialog()
1687                 dlg.set_title("Custom step length")
1688                 dlg.add_button("OK", gtk.RESPONSE_OK)
1689
1690                 label = gtk.Label("Length")
1691
1692                 entry = hildon.Entry(gtk.HILDON_SIZE_AUTO_WIDTH)
1693                 if self.config.get_height() == 5:
1694                     entry.set_text(str(self.config.get_step_length()))
1695
1696                 labelSuffix = gtk.Label()
1697                 if self.config.get_unit() == 0:
1698                     labelSuffix.set_label("m")
1699                 else:
1700                     labelSuffix.set_label("ft")
1701                 hbox = gtk.HBox()
1702                 hbox.add(label)
1703                 hbox.add(entry)
1704                 hbox.add(labelSuffix)
1705                 dlg.vbox.add(hbox)
1706                 dlg.show_all()
1707
1708                 while 1:
1709                     response = dlg.run()
1710                     if response != gtk.RESPONSE_OK:
1711                         break
1712                     try:
1713                         value = float(entry.get_text())
1714                         if value <= 0:
1715                             raise ValueError
1716                         self.config.set_step_length(value)
1717                         self.config.set_height(5)
1718                         stepLengthButton_value_update()
1719                         break
1720                     except ValueError:
1721                         hildon.hildon_banner_show_information(self, "None", "Invalid length")
1722                 dlg.destroy()
1723                 dialog.destroy()
1724
1725             def heightButton_clicked(button, dialog):
1726                 dialog.destroy()
1727
1728             dialog = gtk.Dialog()
1729             dialog.set_title("Step length")
1730
1731             manualButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1732             manualButton.set_title("Enter custom value")
1733             manualButton.set_alignment(0, 0.8, 1, 1)
1734             manualButton.connect("clicked", manualButton_clicked, dialog)
1735
1736             selectorH = hildon.TouchSelector(text=True)
1737             selectorH.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
1738             selectorH.append_text("< 1.50 m")
1739             selectorH.append_text("1.50 - 1.65 m")
1740             selectorH.append_text("1.66 - 1.80 m")
1741             selectorH.append_text("1.81 - 1.95 m")
1742             selectorH.append_text(" > 1.95 m")
1743
1744             selectorH_English = hildon.TouchSelector(text=True)
1745             selectorH_English.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
1746             selectorH_English.append_text("< 5 ft")
1747             selectorH_English.append_text("5 - 5.5 ft")
1748             selectorH_English.append_text("5.5 - 6 ft")
1749             selectorH_English.append_text("6 - 6.5 ft")
1750             selectorH_English.append_text("> 6.5 ft")
1751
1752             heightPicker = hildon.PickerButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1753             heightPicker.set_alignment(0.0, 0.5, 1.0, 1.0)
1754             heightPicker.set_title("Use predefined values for height")
1755
1756
1757             unit = self.config.get_unit()
1758             if unit == 0:
1759                 heightPicker.set_selector(selectorH)
1760             else:
1761                 heightPicker.set_selector(selectorH_English)
1762
1763             height = self.config.get_height()
1764             if height < 5:
1765                 heightPicker.set_active(height)
1766
1767             heightPicker.get_selector().connect("changed", selectorH_changed, dialog)
1768             heightPicker.connect("value-changed", heightButton_clicked, dialog)
1769
1770             dialog.vbox.add(heightPicker)
1771             dialog.vbox.add(manualButton)
1772             dialog.show_all()
1773
1774             if  dialog.run() == gtk.RESPONSE_DELETE_EVENT:
1775                 dialog.destroy()
1776
1777         def donateButton_clicked(button, dialog):
1778             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"
1779             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
1780             os.system(command)
1781
1782         dialog = gtk.Dialog()
1783         dialog.set_title("Settings")
1784         dialog.add_button("OK", gtk.RESPONSE_OK)
1785         self.dialog = dialog
1786
1787         stepLengthButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1788         stepLengthButton.set_title("Step length")
1789         stepLengthButton.set_alignment(0, 0.8, 1, 1)
1790         stepLengthButton.connect("clicked", stepLength_dialog)
1791         stepLengthButton_value_update()
1792
1793         resetButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1794         resetButton.set_title("Reset")
1795         resetButton.set_value("All the stored values will be erased")
1796         resetButton.set_alignment(0, 0.8, 1, 1)
1797         resetButton.connect("clicked", reset_total_counter)
1798
1799         alarmButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1800         alarmButton.set_title("Alarm")
1801         if self.config.get_alarm_enable():
1802             alarmButton.set_value("Enabled")
1803         else:
1804             alarmButton.set_value("Disabled")
1805         alarmButton.set_alignment(0, 0.8, 1, 1)
1806         alarmButton.connect("clicked", alarmButton_pressed)
1807
1808         selector = hildon.TouchSelector(text=True)
1809         selector.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
1810         selector.append_text("Walk")
1811         selector.append_text("Run")
1812         selector.connect("changed", selector_changed)
1813
1814         modePicker = hildon.PickerButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1815         modePicker.set_alignment(0.0, 0.5, 1.0, 1.0)
1816         modePicker.set_title("Mode")
1817         modePicker.set_selector(selector)
1818         modePicker.set_active(self.config.get_mode())
1819
1820         weightButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1821         weightButton.set_title("Weight")
1822         weightButton.set_alignment(0, 0.8, 1, 1)
1823         update_weight_button()
1824         weightButton.connect("clicked", weight_dialog)
1825
1826         selectorUnit = hildon.TouchSelector(text=True)
1827         selectorUnit.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
1828         selectorUnit.append_text("Metric (km)")
1829         selectorUnit.append_text("English (mi)")
1830         selectorUnit.connect("changed", selectorUnit_changed)
1831
1832         unitPicker = hildon.PickerButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1833         unitPicker.set_alignment(0.0, 0.5, 1.0, 1.0)
1834         unitPicker.set_title("Unit")
1835         unitPicker.set_selector(selectorUnit)
1836         unitPicker.set_active(self.config.get_unit())
1837
1838         selectorUI = hildon.TouchSelector(text=True)
1839         selectorUI = hildon.TouchSelector(text=True)
1840         selectorUI.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
1841         selectorUI.append_text("Show current + total + graph")
1842         selectorUI.append_text("Show current + total")
1843         selectorUI.append_text("Show only current")
1844         selectorUI.append_text("Show only total")
1845         selectorUI.connect("changed", selectorUI_changed)
1846
1847         UIPicker = hildon.PickerButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1848         UIPicker.set_alignment(0.0, 0.5, 1.0, 1.0)
1849         UIPicker.set_title("Widget aspect")
1850         UIPicker.set_selector(selectorUI)
1851         UIPicker.set_active(self.config.get_aspect())
1852
1853         sensitivityButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1854         sensitivityButton.set_title("Sensitivity")
1855         sensitivityButton.set_alignment(0, 0.8, 1, 1)
1856         sensitivityButton.set_value(str(self.config.get_sensitivity()) + " %")
1857         sensitivityButton.connect("clicked", sensitivity_dialog)
1858
1859         donateButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1860         donateButton.set_title("Donate")
1861         donateButton.set_value("Please support the development of this opensource widget!")
1862         donateButton.set_alignment(0, 0.8, 1, 1)
1863         donateButton.connect("clicked", donateButton_clicked, dialog)
1864
1865         logButton = hildon.CheckButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT)
1866         logButton.set_label("Log data")
1867         logButton.set_active(self.config.get_logging())
1868         logButton.connect("toggled", logButton_changed)
1869
1870         idleButton = hildon.CheckButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT)
1871         idleButton.set_label("Pause time when not walking")
1872         idleButton.set_active(self.config.get_noidletime())
1873         idleButton.connect("toggled", idleButton_changed)
1874
1875         pan_area = hildon.PannableArea()
1876         vbox = gtk.VBox()
1877         vbox.add(alarmButton)
1878         vbox.add(modePicker)
1879         vbox.add(stepLengthButton)
1880         vbox.add(weightButton)
1881         vbox.add(unitPicker)
1882         vbox.add(sensitivityButton)
1883         vbox.add(UIPicker)
1884         vbox.add(idleButton)
1885         vbox.add(resetButton)
1886         vbox.add(donateButton)
1887         #vbox.add(logButton)
1888
1889         pan_area.add_with_viewport(vbox)
1890         pan_area.set_size_request(-1, 300)
1891
1892         dialog.vbox.add(pan_area)
1893         dialog.show_all()
1894
1895         response = dialog.run()
1896         dialog.destroy()
1897
1898     def close_requested(self, widget):
1899         if self.controller.is_running:
1900             self.controller.stop_pedometer()
1901         self.controller.stop_midnight_callback()
1902
1903     def update_values(self):
1904         #TODO: do not update if the widget is not on the active desktop
1905         self.label_second_view.set_label(self.second_view_labels[self.config.get_secondview()])
1906         self.update_current()
1907         self.update_total()
1908
1909     def button_clicked(self, button):
1910         if self.controller.is_running:
1911             self.controller.stop_pedometer()
1912             self.button.set_icon(ICONSPATH + "play.png")
1913         else:
1914             self.controller.start_pedometer()
1915             self.button.set_icon(ICONSPATH + "stop.png")
1916             hildon.hildon_banner_show_information(self, "None", "Keep the N900 in a pocket close to your hip for best results")
1917
1918     def do_expose_event(self, event):
1919         cr = self.window.cairo_create()
1920         cr.region(event.window.get_clip_region())
1921         cr.clip()
1922         #cr.set_source_rgba(0.4, 0.64, 0.564, 0.5)
1923         style = self.rc_get_style()
1924         color = style.lookup_color("DefaultBackgroundColor")
1925         cr.set_source_rgba (color.red / 65535.0, color.green / 65335.0, color.blue / 65535.0, 0.75);
1926
1927         radius = 5
1928         width = self.allocation.width
1929         height = self.allocation.height
1930
1931         x = self.allocation.x
1932         y = self.allocation.y
1933
1934         cr.move_to(x + radius, y)
1935         cr.line_to(x + width - radius, y)
1936         cr.curve_to(x + width - radius, y, x + width, y, x + width, y + radius)
1937         cr.line_to(x + width, y + height - radius)
1938         cr.curve_to(x + width, y + height - radius, x + width, y + height, x + width - radius, y + height)
1939         cr.line_to(x + radius, y + height)
1940         cr.curve_to(x + radius, y + height, x, y + height, x, y + height - radius)
1941         cr.line_to(x, y + radius)
1942         cr.curve_to(x, y + radius, x, y, x + radius, y)
1943
1944         cr.set_operator(cairo.OPERATOR_SOURCE)
1945         cr.fill_preserve()
1946
1947         color = style.lookup_color("ActiveTextColor")
1948         cr.set_source_rgba (color.red / 65535.0, color.green / 65335.0, color.blue / 65535.0, 0.5);
1949         cr.set_line_width(1)
1950         cr.stroke()
1951
1952         hildondesktop.HomePluginItem.do_expose_event(self, event)
1953
1954     def do_realize(self):
1955         screen = self.get_screen()
1956         self.set_colormap(screen.get_rgba_colormap())
1957         self.set_app_paintable(True)
1958         hildondesktop.HomePluginItem.do_realize(self)
1959
1960 hd_plugin_type = PedometerHomePlugin
1961
1962 import math
1963 import logging
1964
1965 logger = logging.getLogger("pedometer")
1966 logger.setLevel(logging.INFO)
1967
1968 ch = logging.StreamHandler()
1969 formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
1970 ch.setFormatter(formatter)
1971 logger.addHandler(ch)
1972
1973 # The code below is just for testing purposes.
1974 # It allows to run the widget as a standalone process.
1975 if __name__ == "__main__":
1976     import gobject
1977     gobject.type_register(hd_plugin_type)
1978     obj = gobject.new(hd_plugin_type, plugin_id="plugin_id")
1979     obj.show_all()
1980     gtk.main()