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