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