Update text for reset and donate buttons
[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
427     def __init__(self):
428
429         self.pedometer = PedoCounter(self.steps_detected)
430         self.pedometerInterval = PedoIntervalCounter()
431         self.pedometerInterval.set_mode(self.mode)
432         self.repository = PedoRepositoryXML()
433         self.repository.load()
434
435         self.load_values()
436
437         if not self.midnight_set:
438             self.update_at_midnight()
439             self.midnight_set = True
440
441         self.config = Config()
442         self.config.add_observer(self.load_config)
443
444     def update_at_midnight(self):
445         next_day = date.today() + timedelta(days=1)
446         diff = time.mktime(next_day.timetuple()) - time.time()
447         diff = int(diff+5)
448         self.midnight_source_id = gobject.timeout_add_seconds(diff, self.midnight_callback, True)
449
450     def stop_midnight_callback(self):
451         if self.midnight_source_id is not None:
452             gobject.source_remove(self.midnight_source_id)
453
454     def midnight_callback(self, first=False):
455         self.load_values()
456         self.notify()
457         if first:
458             self.midnight_source_id = gobject.timeout_add_seconds(24*3600, self.midnight_callback)
459             return False
460         else:
461             return True
462
463     def load_config(self):
464         self.set_height(self.config.get_height(), self.config.get_step_length())
465         self.set_mode(self.config.get_mode())
466         self.set_unit(self.config.get_unit())
467         self.set_weight(self.config.get_weight())
468         self.set_second_view(self.config.get_secondview())
469         self.set_no_idle_time(self.config.get_noidletime())
470         self.set_sensitivity(self.config.get_sensitivity())
471
472     def load_values(self):
473         if self.second_view == 0:
474             self.v[1] = self.repository.get_alltime_values()
475         elif self.second_view == 1:
476             self.v[1] = self.repository.get_today_values()
477         else:
478             self.v[1] = self.repository.get_this_week_values()
479
480     def save_values(self):
481         logger.info("Saving values to file")
482         self.repository.add_values(self.v[2])
483         self.repository.save()
484         self.load_values()
485
486     def start_pedometer(self):
487         self.v[0] = PedoValues()
488         self.v[2] = PedoValues()
489         self.last_time = time.time()
490         self.is_running = True
491         self.pedometer.start()
492         self.notify(True)
493
494     def reset_all_values(self):
495         self.repository.reset_values()
496         self.v[0] = PedoValues()
497         self.v[1] = PedoValues()
498         self.v[2] = PedoValues()
499         self.notify()
500
501     def stop_pedometer(self):
502         self.is_running = False
503         self.pedometer.request_stop()
504
505     def get_first(self):
506         return self.v[0]
507
508     def get_second(self):
509         if self.is_running:
510             return self.v[2] + self.v[1]
511         else:
512             return self.v[1]
513
514     def update_current(self):
515         """
516         Update distance and calories for current values based on new height, mode values
517         """
518         self.v[0].dist = self.get_distance(self.v[0].steps)
519         self.v[0].calories = self.get_calories(self.v[0].steps)
520
521     def steps_detected(self, cnt, last_steps=False):
522         if not last_steps and cnt == 0 and self.no_idle_time:
523             logger.info("No steps detected, timer is paused")
524         else:
525             self.v[0].steps += cnt
526             self.v[0].dist += self.get_distance(cnt)
527             self.v[0].calories += self.get_calories(self.get_distance(cnt))
528             self.v[0].time += time.time() - self.last_time
529
530             self.v[2].steps += cnt
531             self.v[2].dist += self.get_distance(cnt)
532             self.v[2].calories += self.get_calories(self.get_distance(cnt))
533             self.v[2].time += time.time() - self.last_time
534
535             if not last_steps and self.v[2].steps > self.BUFFER_STEPS_INTERVAL:
536                 self.save_values()
537                 self.notify()
538                 self.v[2] = PedoValues()
539
540             if last_steps:
541                 self.save_values()
542                 self.notify()
543             else:
544                 self.notify(True)
545         self.last_time = time.time()
546
547     def get_calories(self, distance):
548         """calculate lost calories for the distance and weight given as parameters
549         """
550         #different coefficient for running and walking
551         if self.mode == 0:
552             coef = 0.53
553         else:
554             coef = 0.75
555
556         #convert distance from meters to miles
557         distance *= 0.000621371192
558
559         weight = self.weight
560         #convert weight from kg to pounds
561         if self.unit == 0:
562             weight *= 2.20462262
563         return weight * distance * coef
564
565     def set_mode(self, mode):
566         self.mode = mode
567         self.set_height(self.height_interval)
568         self.pedometerInterval.set_mode(self.mode)
569         self.notify()
570
571     def set_unit(self, new_unit):
572         self.unit = new_unit
573         global unit
574         unit = new_unit
575         self.notify()
576
577     def get_str_weight_unit(self, unit=None):
578         if unit is None:
579             unit = self.unit
580         if unit == 0:
581             return "kg"
582         else:
583             return "lb"
584
585     def set_weight(self, value):
586         self.weight = value
587         self.notify()
588
589     def get_weight(self):
590         return self.weight
591
592     def set_sensitivity(self, value):
593         self.sensitivity = value
594         self.pedometerInterval.set_sensitivity(value)
595
596     def get_sensitivity(self):
597         return self.sensitivity
598
599     def set_second_view(self, second_view):
600         self.second_view = second_view
601         self.load_values()
602         self.notify()
603
604     def set_callback_ui(self, func):
605         self.callback_update_ui = func
606
607     def set_height(self, height_interval, step_length=None):
608         self.height_interval = height_interval
609
610         if step_length is None:
611             step_length = self.STEP_LENGTH
612         #set height, will affect the distance
613         if height_interval == 0:
614             self.STEP_LENGTH = 0.59
615         elif height_interval == 1:
616             self.STEP_LENGTH = 0.64
617         elif height_interval == 2:
618             self.STEP_LENGTH = 0.71
619         elif height_interval == 3:
620             self.STEP_LENGTH = 0.77
621         elif height_interval == 4:
622             self.STEP_LENGTH = 0.83
623         elif height_interval == 5:
624             self.STEP_LENGTH = step_length
625         #increase step length if RUNNING
626         if self.mode == 1:
627             self.STEP_LENGTH *= 1.45
628         self.notify()
629
630     def set_no_idle_time(self, value):
631         self.no_idle_time = value
632
633     def get_distance(self, steps=None):
634         if steps == None:
635             steps = self.counter
636         return self.STEP_LENGTH * steps;
637
638     def add_observer(self, func):
639         try:
640             self.observers.index(func)
641         except:
642             self.observers.append(func)
643
644     def remove_observer(self, func):
645         self.observers.remove(func)
646
647     def notify(self, optional=False):
648         if self.callback_update_ui is not None:
649             self.callback_update_ui()
650
651         for func in self.observers:
652             func(optional)
653
654 class AlarmController(Singleton):
655     enable = False
656     fname = "/home/user/MyDocs/.sounds/Ringtones/Bicycle.aac"
657     interval = 5
658     type = 0
659
660     player = None
661     is_playing = False
662     pedo_controller = None
663
664     def __init__(self):
665         self.client = gconf.client_get_default()
666         self.config = Config()
667         self.config.add_observer(self.load_config)
668
669         self.pedo_controller = PedoController()
670         if self.enable:
671             self.init_player()
672             self.pedo_controller.add_observer(self.update)
673             self.start_value = self.pedo_controller.get_first()
674
675     def init_player(self):
676         self.player = gst.element_factory_make("playbin2", "player")
677         fakesink = gst.element_factory_make("fakesink", "fakesink")
678         self.player.set_property("video-sink", fakesink)
679
680         bus = self.player.get_bus()
681         bus.add_signal_watch()
682         bus.connect("message", self.on_message)
683
684     def on_message(self, bus, message):
685         t = message.type
686         if t == gst.MESSAGE_EOS:
687             self.player.set_state(gst.STATE_NULL)
688             self.is_playing = False
689         elif t == gst.MESSAGE_ERROR:
690             self.player.set_state(gst.STATE_NULL)
691             self.is_playing = False
692             err, debug = message.parse_error()
693             logger.error("ERROR: %s, %s" % (err, debug) )
694
695     def update(self, optional):
696         diff = self.pedo_controller.get_first() - self.start_value
697         if self.type == 0 and diff.time >= self.interval * 60 or \
698                    self.type == 1 and diff.steps >= self.interval or \
699                    self.type == 2 and diff.dist >= self.interval or \
700                    self.type == 3 and diff.calories >= self.interval:
701             self.play()
702             #get new instance of current values
703             self.start_value = PedoValues() + self.pedo_controller.get_first()
704             logger.info("Alarm!")
705
706     def play(self):
707         if self.player is None:
708             self.init_player()
709         if self.is_playing:
710             self.player.set_state(gst.STATE_NULL)
711             self.is_playing = False
712         else:
713             self.player.set_property("uri", "file://" + self.fname)
714             self.player.set_state(gst.STATE_PLAYING)
715             self.is_playing = True
716
717     def stop(self):
718         if self.player is not None:
719             self.player.set_state(gst.STATE_NULL)
720
721     def load_config(self):
722         self.enable  = self.config.get_alarm_enable()
723         self.set_alarm_file(self.config.get_alarm_fname())
724         self.set_interval(self.config.get_alarm_interval())
725         self.set_type(self.config.get_alarm_type())
726
727     def set_enable(self, value):
728        self.enable = value
729        if self.enable:
730            self.init_player()
731            self.pedo_controller.add_observer(self.update)
732            self.start_value = self.pedo_controller.get_first()
733        else:
734            self.stop()
735            self.player = None
736            self.pedo_controller.remove_observer(self.update)
737
738     def get_enable(self):
739         return self.enable
740
741     def set_alarm_file(self, fname):
742         self.fname = fname
743
744     def get_alarm_file(self):
745         if self.fname == None:
746             return ""
747         return self.fname
748
749     def set_interval(self, interval):
750         self.interval = interval
751
752     def get_interval(self):
753         return self.interval
754
755     def set_type(self, type):
756         self.type = type
757
758     def get_type(self):
759         return self.type
760
761 class PedoCounter(Singleton):
762     COORD_FNAME = "/sys/class/i2c-adapter/i2c-3/3-001d/coord"
763     COORD_FNAME_SDK = "/home/andrei/pedometer-widget-0.1/date.txt"
764     LOGFILE = "/home/user/log_pedometer"
765     #time in ms between two accelerometer data reads
766     COORD_GET_INTERVAL = 25
767
768     COUNT_INTERVAL = 5
769
770     interval_counter = None
771     stop_requested = False
772     update_function = None
773     logging = False
774     isRunning = False
775
776     def __init__(self, update_function=None):
777         if not os.path.exists(self.COORD_FNAME):
778             self.COORD_FNAME = self.COORD_FNAME_SDK
779
780         self.interval_counter = PedoIntervalCounter()
781         self.update_function = update_function
782
783     def set_logging(self, value):
784         self.logging = value
785
786     def get_rotation(self):
787         f = open(self.COORD_FNAME, 'r')
788         coords = [int(w) for w in f.readline().split()]
789         f.close()
790         return coords
791
792     def start(self):
793         logger.info("Counter started")
794         self.isRunning = True
795         self.stop_requested = False
796         if self.logging:
797             fname = "%d_%d_%d_%d_%d_%d" % time.localtime()[0:6]
798             self.file = open(self.LOGFILE + fname + ".txt", "w")
799         gobject.idle_add(self.run)
800
801     def run(self):
802         self.coords = [[], [], []]
803         self.stime = time.time()
804         self.t = []
805         gobject.timeout_add(self.COORD_GET_INTERVAL, self.read_coords)
806         return False
807
808     def read_coords(self):
809         x, y, z = self.get_rotation()
810         self.coords[0].append(int(x))
811         self.coords[1].append(int(y))
812         self.coords[2].append(int(z))
813         now = time.time() - self.stime
814         if self.logging:
815             self.file.write("%d %d %d %f\n" % (self.coords[0][-1], self.coords[1][-1], self.coords[2][-1], now))
816
817         self.t.append(now)
818         #call stop_interval
819         ret = True
820         if self.t[-1] > self.COUNT_INTERVAL or self.stop_requested:
821             ret = False
822             gobject.idle_add(self.stop_interval)
823         return ret
824
825     def stop_interval(self):
826         self.interval_counter.set_vals(self.coords, self.t)
827         cnt = self.interval_counter.number_steps()
828
829         logger.info("Number of steps detected for last interval %d, number of coords: %d" % (cnt, len(self.t)))
830
831         gobject.idle_add(self.update_function, cnt, self.stop_requested)
832
833         if self.stop_requested:
834             gobject.idle_add(self.stop)
835         else:
836             gobject.idle_add(self.run)
837         return False
838
839     def stop(self):
840         if self.logging:
841             self.file.close()
842         logger.info("Counter has finished")
843
844     def request_stop(self):
845         self.stop_requested = True
846         self.isRunning = False
847
848 class CustomButton(hildon.Button):
849     def __init__(self, icon):
850         hildon.Button.__init__(self, gtk.HILDON_SIZE_AUTO_WIDTH, hildon.BUTTON_ARRANGEMENT_VERTICAL)
851         self.icon = icon
852         self.set_size_request(int(32 * 1.4), int(30 * 1.0))
853         self.retval = self.connect("expose_event", self.expose)
854
855     def set_icon(self, icon):
856         self.icon = icon
857
858     def expose(self, widget, event):
859         self.context = widget.window.cairo_create()
860         self.context.rectangle(event.area.x, event.area.y,
861                             event.area.width, event.area.height)
862
863         self.context.clip()
864         rect = self.get_allocation()
865         self.context.rectangle(rect.x, rect.y, rect.width, rect.height)
866         self.context.set_source_rgba(1, 1, 1, 0)
867
868         style = self.rc_get_style()
869         color = style.lookup_color("DefaultBackgroundColor")
870         if self.state == gtk.STATE_ACTIVE:
871             style = self.rc_get_style()
872             color = style.lookup_color("SelectionColor")
873             self.context.set_source_rgba (color.red / 65535.0, color.green / 65335.0, color.blue / 65535.0, 0.75);
874         self.context.fill()
875
876         #img = cairo.ImageSurface.create_from_png(self.icon)
877
878         #self.context.set_source_surface(img)
879         #self.context.set_source_surface(img, rect.width/2 - img.get_width() /2, 0)
880         img = gtk.Image()
881         img.set_from_file(self.icon)
882         buf = img.get_pixbuf()
883         buf = buf.scale_simple(int(32 * 1.5), int(30 * 1.5), gtk.gdk.INTERP_BILINEAR)
884
885         self.context.set_source_pixbuf(buf, rect.x + (event.area.width / 2 - 15) - 8, rect.y + 1)
886         self.context.scale(200, 200)
887         self.context.paint()
888
889         return self.retval
890
891 class CustomEventBox(gtk.EventBox):
892
893     def __init__(self):
894         gtk.EventBox.__init__(self)
895
896     def do_expose_event(self, event):
897         self.context = self.window.cairo_create()
898         self.context.rectangle(event.area.x, event.area.y,
899                             event.area.width, event.area.height)
900
901         self.context.clip()
902         rect = self.get_allocation()
903         self.context.rectangle(rect.x, rect.y, rect.width, rect.height)
904
905         if self.state == gtk.STATE_ACTIVE:
906             style = self.rc_get_style()
907             color = style.lookup_color("SelectionColor")
908             self.context.set_source_rgba (color.red / 65535.0, color.green / 65335.0, color.blue / 65535.0, 0.75);
909         else:
910             self.context.set_source_rgba(1, 1, 1, 0)
911         self.context.fill()
912
913         gtk.EventBox.do_expose_event(self, event)
914
915 class GraphController(Singleton):
916     ytitles = ["Steps", "Average Speed", "Distance", "Calories"]
917     xtitles = ["Day", "Week"] # "Today"]
918     widget = None
919
920     config = None
921
922     def __init__(self):
923         self.repository = PedoRepositoryXML()
924         self.last_update = 0
925         PedoController().add_observer(self.update_ui)
926         self.config = Config()
927         self.config.add_observer(self.load_config)
928
929     def load_config(self):
930         self.set_current_view(self.config.get_graphview())
931
932     def set_graph(self, widget):
933         self.widget = widget
934         self.update_ui()
935
936     def set_current_view(self, view):
937         """
938         current_view % len(ytitles) - gives the ytitle
939         current_view / len(ytitles) - gives the xtitle
940         """
941         self.x_id = view / len(self.ytitles)
942         self.y_id = view % len(self.ytitles)
943         self.update_ui()
944
945     def next_view(self):
946         current_view = self.config.get_graphview() + 1
947         if current_view == len(self.ytitles) * len(self.xtitles):
948             current_view = 0
949         self.config.set_graphview(current_view)
950
951     def last_weeks_labels(self):
952         d = date.today()
953         delta = timedelta(days=7)
954         ret = []
955         for i in range(7):
956             ret.append(d.strftime("Week %W"))
957             d = d - delta
958         return ret
959
960     def compute_values(self):
961         labels = []
962         if self.x_id == 0:
963             values = self.repository.get_last_7_days()
964             d = date.today()
965             delta = timedelta(days=1)
966             for i in range(7):
967                 labels.append(d.ctime().split()[0])
968                 d = d - delta
969
970         elif self.x_id == 1:
971             values = self.repository.get_last_weeks()
972             d = date.today()
973             for i in range(7):
974                 labels.append(d.strftime("Week %W"))
975                 d = d - timedelta(days=7)
976         else:
977             values = self.repository.get_today()
978             #TODO get labels
979
980         if self.y_id == 0:
981             yvalues = [line.steps for line in values]
982         elif self.y_id == 1:
983             yvalues = [line.get_avg_speed() for line in values]
984         elif self.y_id == 2:
985             yvalues = [line.dist for line in values]
986         else:
987             yvalues = [line.calories for line in values]
988
989         #determine values for y lines in graph
990         diff = self.get_best_interval_value(max(yvalues))
991         ytext = []
992         for i in range(6):
993             ytext.append(str(int(i*diff)))
994
995         if self.widget is not None:
996             yvalues.reverse()
997             labels.reverse()
998             self.widget.values = yvalues
999             self.widget.ytext = ytext
1000             self.widget.xtext = labels
1001             self.widget.max_value = diff * 5
1002             self.widget.text = self.xtitles[self.x_id] + " / " + self.ytitles[self.y_id]
1003             self.widget.queue_draw()
1004         else:
1005             logger.error("Widget not set in GraphController")
1006
1007     def get_best_interval_value(self, max_value):
1008         diff =  1.0 * max_value / 5
1009         l = len(str(int(diff)))
1010         d = math.pow(10, l/2)
1011         val = int(math.ceil(1.0 * diff / d)) * d
1012         if val == 0:
1013             val = 1
1014         return val
1015
1016     def update_ui(self, optional=False):
1017         """update graph values every x seconds"""
1018         if optional and self.last_update - time.time() < 600:
1019             return
1020         if self.widget is None:
1021             return
1022
1023         self.compute_values()
1024         self.last_update = time.time()
1025
1026 class GraphWidget(gtk.DrawingArea):
1027
1028     def __init__(self):
1029         gtk.DrawingArea.__init__(self)
1030         self.set_size_request(-1, 150)
1031         self.yvalues = 5
1032
1033         """sample values"""
1034         self.ytext = ["   0", "1000", "2000", "3000", "4000", "5000"]
1035         self.xtext = ["Monday", "Tuesday", "Wednesday","Thursday", "Friday", "Saturday", "Sunday"]
1036         self.values = [1500, 3400, 4000, 3600, 3200, 0, 4500]
1037         self.max_value = 5000
1038         self.text = "All time steps"
1039
1040     def do_expose_event(self, event):
1041         context = self.window.cairo_create()
1042
1043         # set a clip region for the expose event
1044         context.rectangle(event.area.x, event.area.y,
1045                                event.area.width, event.area.height)
1046         context.clip()
1047
1048         context.save()
1049
1050         context.set_operator(cairo.OPERATOR_SOURCE)
1051         style = self.rc_get_style()
1052
1053         if self.state == gtk.STATE_ACTIVE:
1054             color = style.lookup_color("SelectionColor")
1055         else:
1056              color = style.lookup_color("DefaultBackgroundColor")
1057         context.set_source_rgba (color.red / 65535.0, color.green / 65335.0, color.blue / 65535.0, 0.75)
1058
1059         context.paint()
1060         context.restore();
1061         self.draw(context)
1062
1063     def draw(self, cr):
1064         space_below = 20
1065         space_above = 10
1066         border_right = 10
1067         border_left = 30
1068
1069         rect = self.get_allocation()
1070         x = rect.width
1071         y = rect.height
1072
1073         cr.select_font_face("Purisa", cairo.FONT_SLANT_NORMAL,
1074             cairo.FONT_WEIGHT_NORMAL)
1075         cr.set_font_size(13)
1076
1077         #check space needed to display ylabels
1078         te = cr.text_extents(self.ytext[-1])
1079         border_left = te[2] + 7
1080
1081         cr.set_source_rgb(1, 1, 1)
1082         cr.move_to(border_left, space_above)
1083         cr.line_to(border_left, y-space_below)
1084         cr.set_line_width(2)
1085         cr.stroke()
1086
1087         cr.move_to(border_left, y-space_below)
1088         cr.line_to(x-border_right, y-space_below)
1089         cr.set_line_width(2)
1090         cr.stroke()
1091
1092         ydiff = (y-space_above-space_below) / self.yvalues
1093         for i in range(self.yvalues):
1094             yy = y-space_below-ydiff*(i+1)
1095             cr.move_to(border_left, yy)
1096             cr.line_to(x-border_right, yy)
1097             cr.set_line_width(0.8)
1098             cr.stroke()
1099
1100
1101         for i in range(6):
1102             yy = y - space_below - ydiff*i + 5
1103             te = cr.text_extents(self.ytext[i])
1104
1105             cr.move_to(border_left-te[2]-2, yy)
1106             cr.show_text(self.ytext[i])
1107
1108         cr.set_font_size(15)
1109         te = cr.text_extents(self.text)
1110         cr.move_to((x-te[2])/2, y-5)
1111         cr.show_text(self.text)
1112
1113         graph_x_space = x - border_left - border_right
1114         graph_y_space = y - space_below - space_above
1115         bar_width = graph_x_space*0.75 / len(self.values)
1116         bar_distance = graph_x_space*0.25 / (1+len(self.values))
1117
1118         #set dummy max value to avoid exceptions
1119         if self.max_value == 0:
1120             self.max_value = 100
1121         for i in range(len(self.values)):
1122             xx = border_left + (i+1)*bar_distance + i * bar_width
1123             yy = y-space_below
1124             height = graph_y_space * (1.0 * self.values[i] / self.max_value)
1125             cr.set_source_rgba(1, 1, 1, 0.75)
1126             cr.rectangle(int(xx), int(yy-height), int(bar_width), int(height))
1127             cr.fill()
1128
1129         cr.set_source_rgba(1, 1, 1, 1)
1130         cr.select_font_face("Purisa", cairo.FONT_SLANT_NORMAL,
1131                             cairo.FONT_WEIGHT_NORMAL)
1132         cr.set_font_size(13)
1133
1134         cr.rotate(2*math.pi * (-45) / 180)
1135         for i in range(len(self.values)):
1136             xx = y - space_below - 10
1137             yy = border_left + (i+1)*bar_distance + i * bar_width
1138             cr.move_to(-xx, yy + bar_width*1.25 / 2)
1139             cr.show_text(self.xtext[i])
1140
1141 class Config(Singleton):
1142     mode = 0
1143     height = 0
1144     step_length = 0.7
1145     weight = 70
1146     sensitivity = 100
1147     unit = 0
1148     aspect = 0
1149     sensitivity = 100
1150     second_view = 0
1151     graph_view = 0
1152     no_idle_time = False
1153     logging = False
1154
1155     alarm_enable = False
1156     alarm_fname = "/home/user/MyDocs/.sounds/Ringtones/Bicycle.aac"
1157     alarm_interval = 5
1158     alarm_type = 0
1159
1160     observers = []
1161
1162     def __init__(self):
1163         if self._references > 1:
1164             return
1165         self.client = gconf.client_get_default()
1166         self.client.add_dir('/apps/pedometerhomewidget', gconf.CLIENT_PRELOAD_RECURSIVE)
1167         self.notify_id = self.client.notify_add('/apps/pedometerhomewidget', self.gconf_changed)
1168
1169     def add_observer(self, func):
1170         try:
1171             self.observers.index(func)
1172         except:
1173             self.observers.append(func)
1174             func()
1175
1176     def remove_observer(self, func):
1177         self.observers.remove(func)
1178
1179     def gconf_changed(self, client, *args, **kargs):
1180         self.notify()
1181
1182     def notify(self):
1183         t1 = time.time()
1184         for func in self.observers:
1185             func()
1186         t2 = time.time()
1187         logger.info("Update took: %f seconds" % (t2-t1))
1188
1189     def get_mode(self):
1190         return self.client.get_int(MODE)
1191
1192     def set_mode(self, value):
1193         self.client.set_int(MODE, value)
1194
1195     def get_height(self):
1196         return self.client.get_int(HEIGHT)
1197
1198     def set_height(self, value):
1199         self.client.set_int(HEIGHT, value)
1200
1201     def get_step_length(self):
1202         return self.client.get_float(STEP_LENGTH)
1203
1204     def set_step_length(self, value):
1205         self.client.set_float(STEP_LENGTH, value)
1206
1207     def get_weight(self):
1208         return self.client.get_int(WEIGHT)
1209
1210     def set_weight(self, value):
1211         self.client.set_int(WEIGHT, value)
1212
1213     def get_sensitivity(self):
1214         return self.client.get_int(SENSITIVITY)
1215
1216     def set_sensitivity(self, value):
1217         self.client.set_int(SENSITIVITY, value)
1218
1219     def get_unit(self):
1220         return self.client.get_int(UNIT)
1221
1222     def set_unit(self, value):
1223         self.client.set_int(UNIT, value)
1224
1225     def get_aspect(self):
1226         return self.client.get_int(ASPECT)
1227
1228     def set_aspect(self, value):
1229         self.client.set_int(ASPECT, value)
1230
1231     def get_secondview(self):
1232         value = self.client.get_int(SECONDVIEW)
1233         if value < 0 or value > 2:
1234             value = 0
1235             logger.error("Invalid secondview value read from Gconf. Using default value")
1236
1237         return value
1238
1239     def set_secondview(self, value):
1240         self.client.set_int(SECONDVIEW, value)
1241
1242     def get_graphview(self):
1243         return self.client.get_int(GRAPHVIEW)
1244
1245     def set_graphview(self, value):
1246         self.client.set_int(GRAPHVIEW, value)
1247
1248     def get_noidletime(self):
1249         return self.client.get_bool(NOIDLETIME)
1250
1251     def set_noidletime(self, value):
1252         self.client.set_bool(NOIDLETIME, value)
1253
1254     def get_logging(self):
1255         return self.client.get_bool(LOGGING)
1256
1257     def set_logging(self, value):
1258         self.client.set_bool(LOGGING, value)
1259
1260     def get_alarm_enable(self):
1261         return self.client.get_bool(ALARM_ENABLE)
1262
1263     def set_alarm_enable(self, value):
1264         self.client.set_bool(ALARM_ENABLE, value)
1265
1266     def get_alarm_fname(self):
1267         return self.client.get_string(ALARM_FNAME)
1268
1269     def set_alarm_fname(self, value):
1270         self.client.set_string(ALARM_FNAME, value)
1271
1272     def get_alarm_interval(self):
1273         return self.client.get_int(ALARM_INTERVAL)
1274
1275     def set_alarrm_interval(self, value):
1276         self.client.set_int(ALARM_INTERVAL, value)
1277
1278     def get_alarm_type(self):
1279         return self.client.get_int(ALARM_TYPE)
1280
1281     def set_alarm_type(self, value):
1282         self.client.set_int(ALARM_TYPE, value)
1283
1284 class PedometerHomePlugin(hildondesktop.HomePluginItem):
1285     button = None
1286
1287     #labels to display
1288     labels = ["timer", "count", "dist", "avgSpeed", "calories"]
1289
1290     #current view
1291     labelsC = {}
1292
1293     #second view ( day / week/ alltime)
1294     labelsT = {}
1295
1296     second_view_labels = ["All-time", "Today", "This week"]
1297
1298     controller = None
1299     graph_controller = None
1300
1301     config = None
1302
1303     def __init__(self):
1304         hildondesktop.HomePluginItem.__init__(self)
1305
1306         gobject.type_register(CustomEventBox)
1307         gobject.type_register(GraphWidget)
1308
1309         self.config = Config()
1310
1311         self.button = CustomButton(ICONSPATH + "play.png")
1312         self.button.connect("clicked", self.button_clicked)
1313
1314         self.create_labels(self.labelsC)
1315         self.create_labels(self.labelsT)
1316         self.label_second_view = self.new_label_heading(self.second_view_labels[self.config.get_secondview()])
1317
1318         self.controller = PedoController()
1319         self.controller.set_callback_ui(self.update_values)
1320
1321         self.graph_controller = GraphController()
1322         self.alarm_controller = AlarmController()
1323
1324         self.update_current()
1325         self.update_total()
1326
1327         mainHBox = gtk.HBox(spacing=1)
1328
1329         descVBox = gtk.VBox(spacing=1)
1330         descVBox.add(self.new_label_heading())
1331         descVBox.add(self.new_label_heading("Time:"))
1332         descVBox.add(self.new_label_heading("Steps:"))
1333         descVBox.add(self.new_label_heading("Calories:"))
1334         descVBox.add(self.new_label_heading("Distance:"))
1335         descVBox.add(self.new_label_heading("Avg Speed:"))
1336
1337         currentVBox = gtk.VBox(spacing=1)
1338         currentVBox.add(self.new_label_heading("Current"))
1339         currentVBox.add(self.labelsC["timer"])
1340         currentVBox.add(self.labelsC["count"])
1341         currentVBox.add(self.labelsC["calories"])
1342         currentVBox.add(self.labelsC["dist"])
1343         currentVBox.add(self.labelsC["avgSpeed"])
1344         self.currentBox = currentVBox
1345
1346         totalVBox = gtk.VBox(spacing=1)
1347         totalVBox.add(self.label_second_view)
1348         totalVBox.add(self.labelsT["timer"])
1349         totalVBox.add(self.labelsT["count"])
1350         totalVBox.add(self.labelsT["calories"])
1351         totalVBox.add(self.labelsT["dist"])
1352         totalVBox.add(self.labelsT["avgSpeed"])
1353         self.totalBox = totalVBox
1354
1355         buttonVBox = gtk.VBox(spacing=1)
1356         buttonVBox.add(self.new_label_heading(""))
1357         buttonVBox.add(self.button)
1358         buttonVBox.add(self.new_label_heading(""))
1359
1360         eventBox = CustomEventBox()
1361         eventBox.set_visible_window(False)
1362         eventBox.add(totalVBox)
1363         eventBox.connect("button-press-event", self.eventBox_clicked)
1364         eventBox.connect("button-release-event", self.eventBox_clicked_release)
1365
1366         mainHBox.add(buttonVBox)
1367         mainHBox.add(descVBox)
1368         mainHBox.add(currentVBox)
1369         mainHBox.add(eventBox)
1370         self.mainhbox = mainHBox
1371
1372         graph = GraphWidget()
1373         self.graph_controller.set_graph(graph)
1374
1375         eventBoxGraph = CustomEventBox()
1376         eventBoxGraph.set_visible_window(False)
1377         eventBoxGraph.add(graph)
1378         self.graph = graph
1379         eventBoxGraph.connect("button-press-event", self.eventBoxGraph_clicked)
1380         eventBoxGraph.connect("button-release-event", self.eventBoxGraph_clicked_release)
1381         self.graphBox = eventBoxGraph
1382
1383         self.mainvbox = gtk.VBox()
1384
1385         self.mainvbox.add(mainHBox)
1386         self.mainvbox.add(eventBoxGraph)
1387
1388         self.mainvbox.show_all()
1389         self.add(self.mainvbox)
1390         self.update_aspect()
1391
1392         self.connect("unrealize", self.close_requested)
1393         self.set_settings(True)
1394         self.connect("show-settings", self.show_settings)
1395
1396     def eventBoxGraph_clicked(self, widget, data=None):
1397         widget.set_state(gtk.STATE_ACTIVE)
1398
1399     def eventBoxGraph_clicked_release(self, widget, data=None):
1400         self.graph_controller.next_view()
1401         widget.set_state(gtk.STATE_NORMAL)
1402
1403     def eventBox_clicked(self, widget, data=None):
1404         widget.set_state(gtk.STATE_ACTIVE)
1405
1406     def eventBox_clicked_release(self, widget, data=None):
1407         widget.set_state(gtk.STATE_NORMAL)
1408
1409         second_view = self.config.get_secondview()
1410         second_view = (second_view + 1) % 3
1411         self.config.set_secondview(second_view)
1412
1413     def new_label_heading(self, title=""):
1414         l = gtk.Label(title)
1415         hildon.hildon_helper_set_logical_font(l, "SmallSystemFont")
1416         return l
1417
1418     def create_labels(self, new_labels):
1419         for label in self.labels:
1420             l = gtk.Label()
1421             hildon.hildon_helper_set_logical_font(l, "SmallSystemFont")
1422             hildon.hildon_helper_set_logical_color(l, gtk.RC_FG, gtk.STATE_NORMAL, "ActiveTextColor")
1423             new_labels[label] = l
1424
1425     def update_aspect(self):
1426         aspect = self.config.get_aspect()
1427         if aspect > 0:
1428             self.graphBox.hide_all()
1429         else:
1430             self.graphBox.show_all()
1431
1432         if aspect == 0 or aspect == 1:
1433             self.currentBox.show_all()
1434             self.totalBox.show_all()
1435         elif aspect == 2:
1436             self.currentBox.show_all()
1437             self.totalBox.hide_all()
1438         else:
1439             self.currentBox.hide_all()
1440             self.totalBox.show_all()
1441
1442         x,y = self.size_request()
1443         self.resize(x,y)
1444
1445     def update_ui_values(self, labels, values):
1446         labels["timer"].set_label(values.get_print_time())
1447         labels["count"].set_label(values.get_print_steps())
1448         labels["dist"].set_label(values.get_print_distance())
1449         labels["avgSpeed"].set_label(values.get_print_avg_speed())
1450         labels["calories"].set_label(values.get_print_calories())
1451
1452     def update_current(self):
1453         self.update_ui_values(self.labelsC, self.controller.get_first())
1454
1455     def update_total(self):
1456         self.update_ui_values(self.labelsT, self.controller.get_second())
1457
1458     def show_alarm_settings(self, main_button):
1459         def choose_file(widget):
1460             file = hildon.FileChooserDialog(self, gtk.FILE_CHOOSER_ACTION_OPEN, hildon.FileSystemModel() )
1461             file.show()
1462             if ( file.run() == gtk.RESPONSE_OK):
1463                 fname = file.get_filename()
1464                 widget.set_value(fname)
1465                 self.config.set_alarm_fname(fname)
1466             file.destroy()
1467
1468         def test_sound(button):
1469             try:
1470                 self.alarm_controller.play()
1471             except Exception, e:
1472                 logger.error("Could not play alarm sound: %s" % e)
1473                 hildon.hildon_banner_show_information(self, "None", "Could not play alarm sound")
1474
1475         def enableButton_changed(button):
1476             value = button.get_active()
1477             self.config.set_alarm_enable(value)
1478             if value:
1479                 main_button.set_value("Enabled")
1480             else:
1481                 main_button.set_value("Disabled")
1482
1483         def selectorType_changed(selector, data, labelEntry2):
1484             type = selector.get_active(0)
1485             self.config.set_alarm_type(type)
1486             labelEntry2.set_label(suffix[type])
1487
1488         dialog = gtk.Dialog()
1489         dialog.set_title("Alarm settings")
1490         dialog.add_button("OK", gtk.RESPONSE_OK)
1491
1492         enableButton = hildon.CheckButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT)
1493         enableButton.set_label("Enable alarm")
1494         enableButton.set_active(self.alarm_controller.get_enable())
1495         enableButton.connect("toggled", enableButton_changed)
1496
1497         testButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1498         testButton.set_alignment(0, 0.8, 1, 1)
1499         testButton.set_title("Test sound")
1500         testButton.connect("pressed", test_sound)
1501
1502         fileButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1503         fileButton.set_alignment(0, 0.8, 1, 1)
1504         fileButton.set_title("Alarm sound")
1505         fileButton.set_value(self.alarm_controller.get_alarm_file())
1506         fileButton.connect("pressed", choose_file)
1507
1508         labelEntry = gtk.Label("Notify every:")
1509         suffix = ["mins", "steps", "m/ft", "calories"]
1510         labelEntry2 = gtk.Label(suffix[self.alarm_controller.get_type()])
1511         intervalEntry = hildon.Entry(gtk.HILDON_SIZE_AUTO_WIDTH)
1512         intervalEntry.set_text(str(self.alarm_controller.get_interval()))
1513
1514         selectorType = hildon.TouchSelector(text=True)
1515         selectorType.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
1516         selectorType.append_text("Time")
1517         selectorType.append_text("Steps")
1518         selectorType.append_text("Distance")
1519         selectorType.append_text("Calories")
1520         selectorType.connect("changed", selectorType_changed, labelEntry2)
1521
1522         typePicker = hildon.PickerButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1523         typePicker.set_alignment(0.0, 0.5, 1.0, 1.0)
1524         typePicker.set_title("Alarm type")
1525         typePicker.set_selector(selectorType)
1526         typePicker.set_active(self.alarm_controller.get_type())
1527
1528         hbox = gtk.HBox()
1529         hbox.add(labelEntry)
1530         hbox.add(intervalEntry)
1531         hbox.add(labelEntry2)
1532
1533         dialog.vbox.add(enableButton)
1534         dialog.vbox.add(fileButton)
1535         dialog.vbox.add(testButton)
1536         dialog.vbox.add(typePicker)
1537         dialog.vbox.add(hbox)
1538         dialog.show_all()
1539         while 1:
1540             response = dialog.run()
1541             if response != gtk.RESPONSE_OK:
1542                 break
1543             try:
1544                 value = int(intervalEntry.get_text())
1545                 self.config.set_alarrm_interval(value)
1546                 break
1547             except:
1548                 hildon.hildon_banner_show_information(self, "None", "Invalid interval")
1549
1550         dialog.destroy()
1551
1552     def show_settings(self, widget):
1553         def reset_total_counter(arg):
1554             note = hildon.hildon_note_new_confirmation(self.dialog, "Are you sure you want to delete all your pedometer history?")
1555             ret = note.run()
1556             if ret == gtk.RESPONSE_OK:
1557                 self.controller.reset_all_values()
1558                 hildon.hildon_banner_show_information(self, "None", "All history was deleted")
1559             note.destroy()
1560
1561         def alarmButton_pressed(widget):
1562             self.show_alarm_settings(widget)
1563
1564         def selector_changed(selector, data):
1565             mode = selector.get_active(0)
1566             self.config.set_mode(mode)
1567
1568         def selectorUnit_changed(selector, data):
1569             unit = selector.get_active(0)
1570             self.config.set_unit(unit)
1571
1572             update_weight_button()
1573             stepLengthButton_value_update()
1574
1575         def selectorUI_changed(selector, data):
1576             aspect = selectorUI.get_active(0)
1577             widget.update_aspect()
1578
1579         def logButton_changed(checkButton):
1580             logging = checkButton.get_active()
1581             self.config.set_logging(logging)
1582
1583         def idleButton_changed(idleButton):
1584             no_idle_time = idleButton.get_active()
1585             self.config.set_noidletime(no_idle_time)
1586
1587         def update_weight_button():
1588             weightButton.set_value(str(self.config.get_weight()) + \
1589                                            " " + self.controller.get_str_weight_unit(self.config.get_unit()) )
1590
1591         def weight_dialog(button):
1592             dialog = gtk.Dialog("Weight", self.dialog)
1593             dialog.add_button("OK", gtk.RESPONSE_OK)
1594
1595             label = gtk.Label("Weight:")
1596             entry = gtk.Entry()
1597             entry.set_text(str(self.config.get_weight()))
1598
1599             suffixLabel = gtk.Label(self.controller.get_str_weight_unit(self.config.get_unit()))
1600
1601             hbox = gtk.HBox()
1602             hbox.add(label)
1603             hbox.add(entry)
1604             hbox.add(suffixLabel)
1605
1606             dialog.vbox.add(hbox)
1607             dialog.show_all()
1608             while 1:
1609                 response = dialog.run()
1610                 if response != gtk.RESPONSE_OK:
1611                     break
1612                 try:
1613                     value = int(entry.get_text())
1614                     if value <= 0:
1615                         raise ValueError
1616                     self.config.set_weight(value)
1617                     update_weight_button()
1618                     break
1619                 except:
1620                     hildon.hildon_banner_show_information(self, "None", "Invalid weight")
1621             dialog.destroy()
1622
1623         def sensitivity_dialog(button):
1624             def seekbar_changed(seekbar):
1625                 label.set_text(str(seekbar.get_position()) + " %")
1626
1627             dialog = gtk.Dialog("Sensitivity", self.dialog)
1628             dialog.add_button("OK", gtk.RESPONSE_OK)
1629             seekbar = hildon.Seekbar()
1630             seekbar.set_size_request(400, -1)
1631             seekbar.set_total_time(200)
1632             seekbar.set_position(self.config.get_sensitivity())
1633             seekbar.connect("value-changed", seekbar_changed)
1634
1635             hbox = gtk.HBox()
1636             hbox.add(seekbar)
1637             label = gtk.Label(str(self.config.get_sensitivity()) + " %")
1638             label.set_size_request(30, -1)
1639             hbox.add(label)
1640
1641             dialog.vbox.add(hbox)
1642             dialog.show_all()
1643
1644             if dialog.run() == gtk.RESPONSE_OK:
1645                 value = seekbar.get_position()
1646                 self.config.set_sensitivity(value)
1647                 button.set_value(str(value) + " %")
1648
1649             dialog.destroy()
1650
1651         def stepLengthButton_value_update():
1652             if self.config.get_height() == 5:
1653                 l_unit = ["m", "ft"]
1654                 stepLengthButton.set_value("Custom value: %.2f %s" % (self.config.get_step_length(), l_unit[self.config.get_unit()]))
1655             else:
1656                 h = [ ["< 1.50 m", "1.50 - 1.65 m", "1.66 - 1.80 m", "1.81 - 1.95 m", " > 1.95 m"],
1657                       ["< 5 ft", "5 - 5.5 ft", "5.5 - 6 ft", "6 - 6.5 ft", "> 6.5 ft"]]
1658                 str = "Using predefined value for height: %s" % h[self.config.get_unit()][self.config.get_height()]
1659                 stepLengthButton.set_value(str)
1660
1661         def stepLength_dialog(button):
1662             def selectorH_changed(selector, data, dialog):
1663                 height = selector.get_active(0)
1664                 self.config.set_height(height)
1665                 stepLengthButton_value_update()
1666
1667             def manualButton_clicked(button, dialog):
1668                 dlg = gtk.Dialog()
1669                 dlg.set_title("Custom step length")
1670                 dlg.add_button("OK", gtk.RESPONSE_OK)
1671
1672                 label = gtk.Label("Length")
1673
1674                 entry = hildon.Entry(gtk.HILDON_SIZE_AUTO_WIDTH)
1675                 if self.config.get_height() == 5:
1676                     entry.set_text(str(self.config.get_step_length()))
1677
1678                 labelSuffix = gtk.Label()
1679                 if self.config.get_unit() == 0:
1680                     labelSuffix.set_label("m")
1681                 else:
1682                     labelSuffix.set_label("ft")
1683                 hbox = gtk.HBox()
1684                 hbox.add(label)
1685                 hbox.add(entry)
1686                 hbox.add(labelSuffix)
1687                 dlg.vbox.add(hbox)
1688                 dlg.show_all()
1689
1690                 while 1:
1691                     response = dlg.run()
1692                     if response != gtk.RESPONSE_OK:
1693                         break
1694                     try:
1695                         value = float(entry.get_text())
1696                         if value <= 0:
1697                             raise ValueError
1698                         self.config.set_step_length(value)
1699                         self.config.set_height(5)
1700                         stepLengthButton_value_update()
1701                         break
1702                     except ValueError:
1703                         hildon.hildon_banner_show_information(self, "None", "Invalid length")
1704                 dlg.destroy()
1705                 dialog.destroy()
1706
1707             def heightButton_clicked(button, dialog):
1708                 dialog.destroy()
1709
1710             dialog = gtk.Dialog()
1711             dialog.set_title("Step length")
1712
1713             manualButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1714             manualButton.set_title("Enter custom value")
1715             manualButton.set_alignment(0, 0.8, 1, 1)
1716             manualButton.connect("clicked", manualButton_clicked, dialog)
1717
1718             selectorH = hildon.TouchSelector(text=True)
1719             selectorH.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
1720             selectorH.append_text("< 1.50 m")
1721             selectorH.append_text("1.50 - 1.65 m")
1722             selectorH.append_text("1.66 - 1.80 m")
1723             selectorH.append_text("1.81 - 1.95 m")
1724             selectorH.append_text(" > 1.95 m")
1725
1726             selectorH_English = hildon.TouchSelector(text=True)
1727             selectorH_English.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
1728             selectorH_English.append_text("< 5 ft")
1729             selectorH_English.append_text("5 - 5.5 ft")
1730             selectorH_English.append_text("5.5 - 6 ft")
1731             selectorH_English.append_text("6 - 6.5 ft")
1732             selectorH_English.append_text("> 6.5 ft")
1733
1734             heightPicker = hildon.PickerButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1735             heightPicker.set_alignment(0.0, 0.5, 1.0, 1.0)
1736             heightPicker.set_title("Use predefined values for height")
1737
1738
1739             unit = self.config.get_unit()
1740             if unit == 0:
1741                 heightPicker.set_selector(selectorH)
1742             else:
1743                 heightPicker.set_selector(selectorH_English)
1744
1745             height = self.config.get_height()
1746             if height < 5:
1747                 heightPicker.set_active(height)
1748
1749             heightPicker.get_selector().connect("changed", selectorH_changed, dialog)
1750             heightPicker.connect("value-changed", heightButton_clicked, dialog)
1751
1752             dialog.vbox.add(heightPicker)
1753             dialog.vbox.add(manualButton)
1754             dialog.show_all()
1755
1756             if  dialog.run() == gtk.RESPONSE_DELETE_EVENT:
1757                 dialog.destroy()
1758
1759         def donateButton_clicked(button, dialog):
1760             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"
1761             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
1762             os.system(command)
1763
1764         dialog = gtk.Dialog()
1765         dialog.set_title("Settings")
1766         dialog.add_button("OK", gtk.RESPONSE_OK)
1767         self.dialog = dialog
1768
1769         stepLengthButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1770         stepLengthButton.set_title("Step length")
1771         stepLengthButton.set_alignment(0, 0.8, 1, 1)
1772         stepLengthButton.connect("clicked", stepLength_dialog)
1773         stepLengthButton_value_update()
1774
1775         resetButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1776         resetButton.set_title("Reset")
1777         resetButton.set_value("All the stored values will be erased")
1778         resetButton.set_alignment(0, 0.8, 1, 1)
1779         resetButton.connect("clicked", reset_total_counter)
1780
1781         alarmButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1782         alarmButton.set_title("Alarm")
1783         if self.config.get_alarm_enable():
1784             alarmButton.set_value("Enabled")
1785         else:
1786             alarmButton.set_value("Disabled")
1787         alarmButton.set_alignment(0, 0.8, 1, 1)
1788         alarmButton.connect("clicked", alarmButton_pressed)
1789
1790         selector = hildon.TouchSelector(text=True)
1791         selector.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
1792         selector.append_text("Walk")
1793         selector.append_text("Run")
1794         selector.connect("changed", selector_changed)
1795
1796         modePicker = hildon.PickerButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1797         modePicker.set_alignment(0.0, 0.5, 1.0, 1.0)
1798         modePicker.set_title("Mode")
1799         modePicker.set_selector(selector)
1800         modePicker.set_active(self.config.get_mode())
1801
1802         weightButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1803         weightButton.set_title("Weight")
1804         weightButton.set_alignment(0, 0.8, 1, 1)
1805         update_weight_button()
1806         weightButton.connect("clicked", weight_dialog)
1807
1808         selectorUnit = hildon.TouchSelector(text=True)
1809         selectorUnit.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
1810         selectorUnit.append_text("Metric (km)")
1811         selectorUnit.append_text("English (mi)")
1812         selectorUnit.connect("changed", selectorUnit_changed)
1813
1814         unitPicker = hildon.PickerButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1815         unitPicker.set_alignment(0.0, 0.5, 1.0, 1.0)
1816         unitPicker.set_title("Unit")
1817         unitPicker.set_selector(selectorUnit)
1818         unitPicker.set_active(self.config.get_unit())
1819
1820         selectorUI = hildon.TouchSelector(text=True)
1821         selectorUI = hildon.TouchSelector(text=True)
1822         selectorUI.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
1823         selectorUI.append_text("Show current + total + graph")
1824         selectorUI.append_text("Show current + total")
1825         selectorUI.append_text("Show only current")
1826         selectorUI.append_text("Show only total")
1827         selectorUI.connect("changed", selectorUI_changed)
1828
1829         UIPicker = hildon.PickerButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1830         UIPicker.set_alignment(0.0, 0.5, 1.0, 1.0)
1831         UIPicker.set_title("Widget aspect")
1832         UIPicker.set_selector(selectorUI)
1833         UIPicker.set_active(self.config.get_aspect())
1834
1835         sensitivityButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1836         sensitivityButton.set_title("Sensitivity")
1837         sensitivityButton.set_alignment(0, 0.8, 1, 1)
1838         sensitivityButton.set_value(str(self.config.get_sensitivity()) + " %")
1839         sensitivityButton.connect("clicked", sensitivity_dialog)
1840
1841         donateButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1842         donateButton.set_title("Donate")
1843         donateButton.set_value("Please support the development of this opensource widget!")
1844         donateButton.set_alignment(0, 0.8, 1, 1)
1845         donateButton.connect("clicked", donateButton_clicked, dialog)
1846
1847         logButton = hildon.CheckButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT)
1848         logButton.set_label("Log data")
1849         logButton.set_active(self.config.get_logging())
1850         logButton.connect("toggled", logButton_changed)
1851
1852         idleButton = hildon.CheckButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT)
1853         idleButton.set_label("Pause time when not walking")
1854         idleButton.set_active(self.config.get_noidletime())
1855         idleButton.connect("toggled", idleButton_changed)
1856
1857         pan_area = hildon.PannableArea()
1858         vbox = gtk.VBox()
1859         vbox.add(alarmButton)
1860         vbox.add(modePicker)
1861         vbox.add(stepLengthButton)
1862         vbox.add(weightButton)
1863         vbox.add(unitPicker)
1864         vbox.add(sensitivityButton)
1865         vbox.add(UIPicker)
1866         vbox.add(idleButton)
1867         vbox.add(resetButton)
1868         vbox.add(donateButton)
1869         #vbox.add(logButton)
1870
1871         pan_area.add_with_viewport(vbox)
1872         pan_area.set_size_request(-1, 300)
1873
1874         dialog.vbox.add(pan_area)
1875         dialog.show_all()
1876
1877         response = dialog.run()
1878         dialog.destroy()
1879
1880     def close_requested(self, widget):
1881         if self.controller.is_running:
1882             self.controller.stop_pedometer()
1883         self.controller.stop_midnight_callback()
1884
1885     def update_values(self):
1886         #TODO: do not update if the widget is not on the active desktop
1887         self.label_second_view.set_label(self.second_view_labels[self.config.get_secondview()])
1888         self.update_current()
1889         self.update_total()
1890
1891     def button_clicked(self, button):
1892         if self.controller.is_running:
1893             self.controller.stop_pedometer()
1894             self.button.set_icon(ICONSPATH + "play.png")
1895         else:
1896             self.controller.start_pedometer()
1897             self.button.set_icon(ICONSPATH + "stop.png")
1898             hildon.hildon_banner_show_information(self, "None", "Keep the N900 in a pocket close to your hip for best results")
1899
1900     def do_expose_event(self, event):
1901         cr = self.window.cairo_create()
1902         cr.region(event.window.get_clip_region())
1903         cr.clip()
1904         #cr.set_source_rgba(0.4, 0.64, 0.564, 0.5)
1905         style = self.rc_get_style()
1906         color = style.lookup_color("DefaultBackgroundColor")
1907         cr.set_source_rgba (color.red / 65535.0, color.green / 65335.0, color.blue / 65535.0, 0.75);
1908
1909         radius = 5
1910         width = self.allocation.width
1911         height = self.allocation.height
1912
1913         x = self.allocation.x
1914         y = self.allocation.y
1915
1916         cr.move_to(x + radius, y)
1917         cr.line_to(x + width - radius, y)
1918         cr.curve_to(x + width - radius, y, x + width, y, x + width, y + radius)
1919         cr.line_to(x + width, y + height - radius)
1920         cr.curve_to(x + width, y + height - radius, x + width, y + height, x + width - radius, y + height)
1921         cr.line_to(x + radius, y + height)
1922         cr.curve_to(x + radius, y + height, x, y + height, x, y + height - radius)
1923         cr.line_to(x, y + radius)
1924         cr.curve_to(x, y + radius, x, y, x + radius, y)
1925
1926         cr.set_operator(cairo.OPERATOR_SOURCE)
1927         cr.fill_preserve()
1928
1929         color = style.lookup_color("ActiveTextColor")
1930         cr.set_source_rgba (color.red / 65535.0, color.green / 65335.0, color.blue / 65535.0, 0.5);
1931         cr.set_line_width(1)
1932         cr.stroke()
1933
1934         hildondesktop.HomePluginItem.do_expose_event(self, event)
1935
1936     def do_realize(self):
1937         screen = self.get_screen()
1938         self.set_colormap(screen.get_rgba_colormap())
1939         self.set_app_paintable(True)
1940         hildondesktop.HomePluginItem.do_realize(self)
1941
1942 hd_plugin_type = PedometerHomePlugin
1943
1944 import math
1945 import logging
1946
1947 logger = logging.getLogger("pedometer")
1948 logger.setLevel(logging.INFO)
1949
1950 ch = logging.StreamHandler()
1951 formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
1952 ch.setFormatter(formatter)
1953 logger.addHandler(ch)
1954
1955 # The code below is just for testing purposes.
1956 # It allows to run the widget as a standalone process.
1957 if __name__ == "__main__":
1958     import gobject
1959     gobject.type_register(hd_plugin_type)
1960     obj = gobject.new(hd_plugin_type, plugin_id="plugin_id")
1961     obj.show_all()
1962     gtk.main()