Lots of improvements
[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 hildondesktop
29 import hildon
30
31 PATH = "/apps/pedometerhomewidget"
32 MODE = PATH + "/mode"
33 HEIGHT = PATH + "/height"
34 UNIT = PATH + "/unit"
35 ASPECT = PATH + "/aspect"
36 SECONDVIEW = PATH + "/secondview"
37 GRAPHVIEW = PATH + "/graphview"
38 LOGGING = PATH + "/logging"
39
40 ICONSPATH = "/opt/pedometerhomewidget/"
41
42 unit = 0
43
44 class Singleton(object):
45     _instance = None
46     def __new__(cls, *args, **kwargs):
47         if not cls._instance:
48             cls._instance = super(Singleton, cls).__new__(
49                                 cls, *args, **kwargs)
50         return cls._instance
51
52 class PedoIntervalCounter(Singleton):
53     MIN_THRESHOLD = 500
54     MIN_TIME_STEPS = 0.5
55     x = []
56     y = []
57     z = []
58     t = []
59
60     #TODO: check if last detected step is at the end of the interval
61
62     def set_vals(self, coords, tval):
63         self.x = coords[0]
64         self.y = coords[1]
65         self.z = coords[2]
66         self.t = tval
67
68     def set_mode(self, mode):
69         #runnig, higher threshold to prevent fake steps
70         self.mode = mode
71         if mode == 1:
72             self.MIN_THRESHOLD = 650
73             self.MIN_TIME_STEPS = 0.35
74         #walking
75         else:
76             self.MIN_THRESHOLD = 500
77             self.MIN_TIME_STEPS = 0.5
78
79     def calc_mean(self, vals):
80         sum = 0
81         for i in vals:
82             sum += i
83         if len(vals) > 0:
84             return sum / len(vals)
85         return 0
86
87     def calc_stdev(self, vals):
88         rez = 0
89         mean = self.calc_mean(vals)
90         for i in vals:
91             rez += pow(abs(mean - i), 2)
92         return math.sqrt(rez / len(vals))
93
94     def calc_threshold(self, vals):
95         vmax = max(vals)
96         vmin = min(vals)
97         mean = self.calc_mean(vals)
98         threshold = max (abs(mean - vmax), abs(mean - vmin))
99         return threshold
100
101     def count_steps(self, vals, t):
102         threshold = self.MIN_THRESHOLD
103         mean = self.calc_mean(vals)
104         cnt = 0
105         i = 0
106         while i < len(vals):
107             if abs(vals[i] - mean) > threshold:
108                 cnt += 1
109                 ntime = t[i] + self.MIN_TIME_STEPS
110                 while i < len(vals) and t[i] < ntime:
111                     i += 1
112             i += 1
113         return cnt
114
115     def get_best_values(self, x, y, z):
116         dev1 = self.calc_stdev(x)
117         dev2 = self.calc_stdev(y)
118         dev3 = self.calc_stdev(z)
119         dev_max = max(dev1, dev2, dev3)
120
121         if (abs(dev1 - dev_max) < 0.001):
122             logger.info("X chosen as best axis, stdev %f" % dev1)
123             return x
124         elif (abs(dev2 - dev_max) < 0.001):
125             logger.info("Y chosen as best axis, stdev %f" % dev2)
126             return y
127         else:
128             logger.info("Z chosen as best axis, stdev %f" % dev3)
129             return z
130
131     def number_steps(self):
132         vals = self.get_best_values(self.x, self.y, self.z)
133         return self.count_steps(vals, self.t)
134
135 class PedoValues():
136     def __init__(self, time=0, steps=0, dist=0, calories=0):
137         self.time = time
138         self.steps = steps
139         self.calories = calories
140         self.dist = dist
141         self.unit = unit
142
143     def __add__(self, other):
144         return PedoValues(self.time + other.time,
145                           self.steps + other.steps,
146                           self.dist + other.dist,
147                           self.calories + other.calories)
148
149     def get_print_time(self):
150         tdelta = self.time
151         hours = int(tdelta / 3600)
152         tdelta -= 3600 * hours
153         mins = int(tdelta / 60)
154         tdelta -= 60 * mins
155         secs = int(tdelta)
156         strtime = "%.2d:%.2d:%.2d" % (hours, mins, secs)
157         return strtime
158
159     def get_print_distance(self):
160         if self.dist > 1000:
161             if self.unit == 0:
162                 return "%.2f km" % (self.dist / 1000)
163             else:
164                 return "%.2f mi" % (self.dist / 1609.344)
165         else:
166             if self.unit == 0:
167                 return "%d m" % self.dist
168             else:
169                 return "%d ft" % int(self.dist * 3.2808)
170             
171     def get_avg_speed(self):
172         conv = 0
173         if self.unit:
174             conv = 2.23693629
175         else:
176             conv = 3.6
177
178         if self.time == 0:
179             return 0
180         speed = 1.0 * self.dist / self.time
181         return speed * conv
182     
183     def get_print_avg_speed(self):
184         suffix = ""
185         conv = 0
186         if self.unit:
187             suffix = "mi/h"
188             conv = 2.23693629
189         else:
190             suffix = "km/h"
191             conv = 3.6
192
193         if self.time == 0:
194             return "N/A " + suffix
195         speed = 1.0 * self.dist / self.time
196         #convert from meters per second to km/h or mi/h
197         speed *= conv
198         return "%.2f %s" % (speed, suffix)
199
200     def get_print_steps(self):
201         return str(self.steps)
202
203     def get_print_calories(self):
204         return str(self.calories)
205
206 class PedoRepository(Singleton):
207     values = {}
208
209     def load(self):
210         raise NotImplementedError("Must be implemented by subclass")
211
212     def save(self):
213         raise NotImplementedError("Must be implemented by subclass")
214
215     def reset_values(self):
216         self.values = {}
217         self.save()
218
219     def get_history_count(self):
220         """return the number of days in the log"""
221         return len(values)
222
223     def get_values(self):
224         return self.values
225
226     def add_values(self, values, when=date.today()):
227         """add PedoValues values to repository """
228         try:
229             self.values[when] = self.values[when] + values
230         except KeyError:
231             self.values[when] = values
232
233     def get_last_7_days(self):
234         ret = [] 
235         day = date.today()
236         for i in range(7):
237             try:
238                 ret.append(self.values[day])
239             except KeyError:
240                 ret.append(PedoValues())
241             day = day - timedelta(days=1)
242         return ret
243
244     def get_last_weeks(self):
245         delta = timedelta(days=1)
246         day = date.today()
247         week = int(date.today().strftime("%W"))
248         val = PedoValues()
249         ret = []
250         for i in range(56):
251             try:
252                 val += self.values[day]
253             except KeyError:
254                 pass
255             w = int(day.strftime("%W"))
256             if w != week:
257                 ret.append(val)
258                 val = PedoValues()
259                 week = w
260                 if len(ret) == 7:
261                     break
262             day -= delta
263         return ret
264
265     def get_alltime_values(self):
266         ret = PedoValues()
267         for k, v in self.values.iteritems():
268             ret = ret + v
269         return ret
270     
271     def get_today_values(self):
272         try:
273             return self.values[date.today()]
274         except KeyError:
275             return PedoValues()
276        
277     def get_this_week_values(self):
278         day = date.today()
279         ret = PedoValues()
280         while True: 
281             try:
282                 ret += self.values[day]
283             except:
284                 pass
285             if day.weekday() == 0:
286                 break
287             day = day - timedelta(days=1)
288            
289         return ret 
290             
291 class PedoRepositoryXML(PedoRepository):
292     DIR = os.path.join(os.path.expanduser("~"), ".pedometer")
293     FILE = os.path.join(DIR, "data.xml")
294     FILE2 = os.path.join(DIR, "pickle.log")
295     def __init__(self):
296         if not os.path.exists(self.DIR):
297             os.makedirs(self.DIR)
298         PedoRepository.__init__(self)
299
300     def load(self):
301         try:
302             f = open(self.FILE, "r")
303             dom = parseString(f.read())
304             values = dom.getElementsByTagName("pedometer")[0]
305             for v in values.getElementsByTagName("date"):
306                 d = int(v.getAttribute("ordinal_day"))
307                 steps = int(v.getAttribute("steps"))
308                 calories = float(v.getAttribute("calories"))
309                 dist = float(v.getAttribute("dist"))
310                 time = float(v.getAttribute("time"))
311                 day = date.fromordinal(d)
312                 self.values[day] = PedoValues(time, steps, dist, calories)
313                     
314             f.close()
315         except Exception, e:
316             logger.error("Error while loading data from xml file: %s" % e)
317
318     def save(self):
319         try:
320             f = open(self.FILE, "w")
321
322             impl = getDOMImplementation()
323
324             newdoc = impl.createDocument(None, "pedometer", None)
325             top_element = newdoc.documentElement
326             for k, v in self.values.iteritems():
327                 d = newdoc.createElement('date')
328                 d.setAttribute("day", str(k.isoformat()))
329                 d.setAttribute("ordinal_day", str(k.toordinal()))
330                 d.setAttribute("steps", str(v.steps))
331                 d.setAttribute("time", str(v.time))
332                 d.setAttribute("dist", str(v.dist))
333                 d.setAttribute("calories", str(v.calories))
334                 top_element.appendChild(d)
335
336             newdoc.appendChild(top_element)
337             newdoc.writexml(f)
338             #f.write(newdoc.toprettyxml())
339             f.close()
340         except Exception, e:
341             logger.error("Error while saving data to xml file: %s" % e)
342
343      
344 class PedoRepositoryPickle(PedoRepository):
345     DIR = os.path.join(os.path.expanduser("~"), ".pedometer")
346     FILE = os.path.join(DIR, "pickle.log")
347
348     def __init__(self):
349         if not os.path.exists(self.DIR):
350             os.makedirs(self.DIR)
351         PedoRepository.__init__(self)
352
353     def load(self):
354         try:
355             f = open(self.FILE, "rb")
356             self.values = pickle.load(f)
357             f.close()
358         except Exception, e:
359             logger.error("Error while loading pickle file: %s" % e)
360
361     def save(self):
362         try:
363             f = open(self.FILE, "wb")
364             pickle.dump(self.values, f)
365             f.close()
366         except Exception, e:
367             logger.error("Error while saving data to pickle: %s" % e)
368
369 class PedoController(Singleton):
370     mode = 0
371     unit = 0
372     height_interval = 0
373     #what to display in second view - 0 - alltime, 1 - today, 2 - week
374     second_view = 0
375     callback_update_ui = None
376
377     STEP_LENGTH = 0.7
378     #values for the two views in the widget ( current and day/week/alltime)
379     v = [PedoValues(), PedoValues()]
380
381     startTime = 0
382     is_running = False
383     graph_controller = None
384     
385     def __init__(self):
386         self.pedometer = PedoCounter(self.steps_detected)
387         self.pedometerInterval = PedoIntervalCounter()
388         self.pedometerInterval.set_mode(self.mode)
389         self.repository = PedoRepositoryXML()
390         self.repository.load()
391         
392         self.graph_controller = GraphController()
393         self.load_values()
394
395     def load_values(self):
396         if self.second_view == 0:
397             self.v[1] = self.repository.get_alltime_values()
398         elif self.second_view == 1:
399             self.v[1] = self.repository.get_today_values()
400         else:
401             self.v[1] = self.repository.get_this_week_values()
402
403     def save_values(self):
404         self.repository.add_values(self.v[0])
405         self.repository.save()
406         self.load_values()
407
408     def start_pedometer(self):
409         self.v[0] = PedoValues()
410         self.startTime = time.time()
411         self.is_running = True
412         self.pedometer.start()
413         self.notify_UI(True)
414
415     def stop_pedometer(self):
416         self.is_running = False
417         self.pedometer.request_stop()
418
419     def get_first(self):
420         return self.v[0]
421
422     def get_second(self):
423         if self.is_running:
424             return self.v[0] + self.v[1]
425         else:
426             return self.v[1]
427
428     def update_current(self):
429         """
430         Update distance and calories for current values based on new height, mode values
431         """
432         self.v[0].dist = self.get_distance(self.v[0].steps)
433         self.v[0].calories = self.get_calories(self.v[0].steps)
434
435     def steps_detected(self, cnt, last_steps=False):
436         self.v[0].steps += cnt
437         self.v[0].dist += self.get_distance(cnt)
438         self.v[0].calories += self.get_distance(cnt)
439         self.v[0].time = time.time() - self.startTime
440         if last_steps:
441             self.save_values()
442             self.notify_UI()
443         else:
444             self.notify_UI(True)
445
446     def set_mode(self, mode):
447         self.mode = mode
448         self.set_height(self.height_interval)
449         self.notify_UI()
450
451     def set_unit(self, new_unit):
452         self.unit = new_unit
453         unit = new_unit
454
455     def set_second_view(self, second_view):
456         self.second_view = second_view
457         self.load_values()
458         self.notify_UI()
459
460     def set_callback_ui(self, func):
461         self.callback_update_ui = func
462
463     def set_height(self, height_interval):
464         self.height_inteval = height_interval
465         #set height, will affect the distance
466         if height_interval == 0:
467             self.STEP_LENGTH = 0.59
468         elif height_interval == 1:
469             self.STEP_LENGTH = 0.64
470         elif height_interval == 2:
471             self.STEP_LENGTH = 0.71
472         elif height_interval == 3:
473             self.STEP_LENGTH = 0.77
474         elif height_interval == 4:
475             self.STEP_LENGTH = 0.83
476         #increase step length if RUNNING
477         if self.mode == 1:
478             self.STEP_LENGTH *= 1.45
479         self.notify_UI()
480
481     def get_distance(self, steps=None):
482         if steps == None:
483             steps = self.counter
484         return self.STEP_LENGTH * steps;
485
486     def get_calories(self, steps):
487         return steps
488
489     def notify_UI(self, optional=False):
490         if self.callback_update_ui is not None:
491             self.callback_update_ui()
492         self.graph_controller.update_ui(optional)
493         
494 class PedoCounter(Singleton):
495     COORD_FNAME = "/sys/class/i2c-adapter/i2c-3/3-001d/coord"
496     COORD_FNAME_SDK = "/home/andrei/pedometer-widget-0.1/date.txt"
497     LOGFILE = "/home/user/log_pedometer"
498     #time in ms between two accelerometer data reads
499     COORD_GET_INTERVAL = 10
500
501     COUNT_INTERVAL = 5
502
503     interval_counter = None
504     stop_requested = False
505     update_function = None
506     logging = False
507     isRunning = False
508
509     def __init__(self, update_function=None):
510         if not os.path.exists(self.COORD_FNAME):
511             self.COORD_FNAME = self.COORD_FNAME_SDK
512             
513         self.interval_counter = PedoIntervalCounter()
514         self.update_function = update_function
515
516     def set_logging(self, value):
517         self.logging = value
518
519     def get_rotation(self):
520         f = open(self.COORD_FNAME, 'r')
521         coords = [int(w) for w in f.readline().split()]
522         f.close()
523         return coords
524
525     def start(self):
526         logger.info("Counter started")
527         self.isRunning = True
528         self.stop_requested = False
529         if self.logging:
530             fname = "%d_%d_%d_%d_%d_%d" % time.localtime()[0:6]
531             self.file = open(self.LOGFILE + fname + ".txt", "w")
532         gobject.idle_add(self.run)
533
534     def run(self):
535         self.coords = [[], [], []]
536         self.stime = time.time()
537         self.t = []
538         gobject.timeout_add(self.COORD_GET_INTERVAL, self.read_coords)
539         return False
540
541     def read_coords(self):
542         x, y, z = self.get_rotation()
543         self.coords[0].append(int(x))
544         self.coords[1].append(int(y))
545         self.coords[2].append(int(z))
546         now = time.time() - self.stime
547         if self.logging:
548             self.file.write("%d %d %d %f\n" % (self.coords[0][-1], self.coords[1][-1], self.coords[2][-1], now))
549
550         self.t.append(now)
551         #call stop_interval
552         ret = True
553         if self.t[-1] > self.COUNT_INTERVAL or self.stop_requested:
554             ret = False
555             gobject.idle_add(self.stop_interval)
556         return ret
557
558     def stop_interval(self):
559         self.interval_counter.set_vals(self.coords, self.t)
560         cnt = self.interval_counter.number_steps()
561
562         logger.info("Number of steps detected for last interval %d, number of coords: %d" % (cnt, len(self.t)))
563
564         gobject.idle_add(self.update_function, cnt, self.stop_requested)
565
566         if self.stop_requested:
567             gobject.idle_add(self.stop)
568         else:
569             gobject.idle_add(self.run)
570         return False
571
572     def stop(self):
573         if self.logging:
574             self.file.close()
575         logger.info("Counter has finished")
576
577     def request_stop(self):
578         self.stop_requested = True
579         self.isRunning = False
580
581 class CustomButton(hildon.Button):
582     def __init__(self, icon):
583         hildon.Button.__init__(self, gtk.HILDON_SIZE_AUTO_WIDTH, hildon.BUTTON_ARRANGEMENT_VERTICAL)
584         self.icon = icon
585         self.set_size_request(int(32 * 1.4), int(30 * 1.0))
586         self.retval = self.connect("expose_event", self.expose)
587
588     def set_icon(self, icon):
589         self.icon = icon
590
591     def expose(self, widget, event):
592         self.context = widget.window.cairo_create()
593         self.context.rectangle(event.area.x, event.area.y,
594                             event.area.width, event.area.height)
595
596         self.context.clip()
597         rect = self.get_allocation()
598         self.context.rectangle(rect.x, rect.y, rect.width, rect.height)
599         self.context.set_source_rgba(1, 1, 1, 0)
600
601         style = self.rc_get_style()
602         color = style.lookup_color("DefaultBackgroundColor")
603         if self.state == gtk.STATE_ACTIVE:
604             style = self.rc_get_style()
605             color = style.lookup_color("SelectionColor")
606             self.context.set_source_rgba (color.red / 65535.0, color.green / 65335.0, color.blue / 65535.0, 0.75);
607         self.context.fill()
608
609         #img = cairo.ImageSurface.create_from_png(self.icon)
610
611         #self.context.set_source_surface(img)
612         #self.context.set_source_surface(img, rect.width/2 - img.get_width() /2, 0)
613         img = gtk.Image()
614         img.set_from_file(self.icon)
615         buf = img.get_pixbuf()
616         buf = buf.scale_simple(int(32 * 1.5), int(30 * 1.5), gtk.gdk.INTERP_BILINEAR)
617
618         self.context.set_source_pixbuf(buf, rect.x + (event.area.width / 2 - 15) - 8, rect.y + 1)
619         self.context.scale(200, 200)
620         self.context.paint()
621
622         return self.retval
623
624 class CustomEventBox(gtk.EventBox):
625    
626     def __init__(self):
627         gtk.EventBox.__init__(self)
628     
629     def do_expose_event(self, event):
630         self.context = self.window.cairo_create()
631         self.context.rectangle(event.area.x, event.area.y,
632                             event.area.width, event.area.height)
633
634         self.context.clip()
635         rect = self.get_allocation()
636         self.context.rectangle(rect.x, rect.y, rect.width, rect.height)
637
638         if self.state == gtk.STATE_ACTIVE:
639             style = self.rc_get_style()
640             color = style.lookup_color("SelectionColor")
641             self.context.set_source_rgba (color.red / 65535.0, color.green / 65335.0, color.blue / 65535.0, 0.75);
642         else:
643             self.context.set_source_rgba(1, 1, 1, 0)
644         self.context.fill()
645
646         gtk.EventBox.do_expose_event(self, event)
647
648 class GraphController(Singleton):
649     ytitles = ["Steps", "Average Speed", "Distance", "Calories"]
650     xtitles = ["Day", "Week"] # "Today"]
651     widget = None
652     def __init__(self):
653         self.repository = PedoRepositoryXML()
654         self.last_update = 0
655       
656     def set_graph(self, widget):
657         self.widget = widget 
658         self.update_ui()
659         
660     def set_current_view(self, view):
661         """
662         current_view % len(ytitles) - gives the ytitle
663         current_view / len(ytitles) - gives the xtitle
664         """ 
665         self.current_view = view 
666
667         if self.current_view == len(self.ytitles) * len(self.xtitles):
668             self.current_view = 0
669         self.x_id = self.current_view / len(self.ytitles)
670         self.y_id = self.current_view % len(self.ytitles)
671         
672     def next_view(self):
673         self.set_current_view(self.current_view+1)
674         self.update_ui()
675         return self.current_view
676     
677     def last_weeks_labels(self):
678         d = date.today()
679         delta = timedelta(days=7)
680         ret = []
681         for i in range(7):
682             ret.append(d.strftime("Week %W"))
683             d = d - delta
684         return ret
685     
686     def compute_values(self):
687         labels = []
688         if self.x_id == 0:
689             values = self.repository.get_last_7_days()
690             d = date.today()
691             delta = timedelta(days=1)
692             for i in range(7):
693                 labels.append(d.ctime().split()[0])
694                 d = d - delta
695             
696         elif self.x_id == 1:
697             values = self.repository.get_last_weeks()
698             d = date.today()
699             for i in range(7):
700                 labels.append(d.strftime("Week %W"))
701                 d = d - timedelta(days=7) 
702         else:
703             values = self.repository.get_today() 
704             #TODO get labels
705        
706         if self.y_id == 0:
707             yvalues = [line.steps for line in values]
708         elif self.y_id == 1:
709             yvalues = [line.get_avg_speed() for line in values]
710         elif self.y_id == 2:
711             yvalues = [line.dist for line in values]
712         else:
713             yvalues = [line.calories for line in values]
714             
715         #determine values for y lines in graph
716         diff = self.get_best_interval_value(max(yvalues))
717         ytext = []
718         for i in range(6):
719             s = str(int(i*diff))
720             while len(s) < 5:
721                 s = ' ' + s
722             ytext.append(s)
723             
724         if self.widget is not None:
725             yvalues.reverse()
726             labels.reverse()
727             self.widget.values = yvalues
728             self.widget.ytext = ytext
729             self.widget.xtext = labels
730             self.widget.max_value = diff * 5 
731             self.widget.text = self.xtitles[self.x_id] + " / " + self.ytitles[self.y_id]
732             self.widget.queue_draw()
733         else:
734             logger.error("Widget not set in GraphController")
735         
736     def get_best_interval_value(self, max_value):
737         diff =  1.0 * max_value / 5
738         l = len(str(int(diff)))
739         d = math.pow(10, l/2)
740         val = int(math.ceil(1.0 * diff / d)) * d
741         if val == 0:
742             val = 1
743         return val
744         
745     def update_ui(self, optional=False):
746         """update graph values every x seconds"""
747         if optional and self.last_update - time.time() < 600:
748             return
749         if self.widget is None:
750             return
751         
752         print "update_ui"
753         self.compute_values()
754         self.last_update = time.time()
755         
756 class GraphWidget(gtk.DrawingArea):
757     
758     def __init__(self):
759         gtk.DrawingArea.__init__(self)
760         self.set_size_request(-1, 150)
761         self.yvalues = 5
762         self.ytext = ["   0", "1000", "2000", "3000", "4000", "5000"]
763         self.xtext = ["Monday", "Tuesday", "Wednesday","Thursday", "Friday", "Saturday", "Sunday"]
764         self.values = [1500, 3400, 4000, 3600, 3200, 0, 4500]
765         self.max_value = 5000
766         self.text = "All time steps"
767         
768     def do_expose_event(self, event):
769         context = self.window.cairo_create()
770         
771         # set a clip region for the expose event
772         context.rectangle(event.area.x, event.area.y,
773                                event.area.width, event.area.height)
774         context.clip()
775         
776         context.save()
777         
778         context.set_operator(cairo.OPERATOR_SOURCE)
779         style = self.rc_get_style()
780        
781         if self.state == gtk.STATE_ACTIVE:
782             color = style.lookup_color("SelectionColor")
783         else:
784              color = style.lookup_color("DefaultBackgroundColor")
785         context.set_source_rgba (color.red / 65535.0, color.green / 65335.0, color.blue / 65535.0, 0.75)
786
787         context.paint()
788         context.restore();
789         self.draw(context)
790         
791     def draw(self, cr):
792         print self.max_value
793         space_below = 20 
794         space_above = 10
795         border_right = 10
796         border_left = 30
797         rect = self.get_allocation()
798         x = rect.width
799         y = rect.height
800         
801         cr.set_source_rgb(1, 1, 1)
802         cr.move_to(border_left, space_above)
803         cr.line_to(border_left, y-space_below)
804         cr.set_line_width(2)
805         cr.stroke() 
806         
807         cr.move_to(border_left, y-space_below)
808         cr.line_to(x-border_right, y-space_below)
809         cr.set_line_width(2)
810         cr.stroke() 
811        
812         ydiff = (y-space_above-space_below) / self.yvalues 
813         for i in range(self.yvalues):
814             yy = y-space_below-ydiff*(i+1)
815             cr.move_to(border_left, yy)
816             cr.line_to(x-border_right, yy)
817             cr.set_line_width(0.8)
818             cr.stroke()
819             
820         cr.select_font_face("Purisa", cairo.FONT_SLANT_NORMAL, 
821             cairo.FONT_WEIGHT_NORMAL)
822         cr.set_font_size(13) 
823         for i in range(6):
824             yy = y - space_below - ydiff*i + 5 
825             cr.move_to(1, yy)
826             cr.show_text(self.ytext[i])
827             
828         cr.set_font_size(15)
829         cr.move_to(x/3, y-5)
830         cr.show_text(self.text)
831         
832         graph_x_space = x - border_left - border_right
833         graph_y_space = y - space_below - space_above
834         bar_width = graph_x_space*0.75 / len(self.values)
835         bar_distance = graph_x_space*0.25 / (1+len(self.values))
836        
837         #set dummy max value to avoid exceptions
838         if self.max_value == 0:
839             self.max_value = 100
840             
841         for i in range(len(self.values)):
842             xx = border_left + (i+1)*bar_distance + i * bar_width
843             yy = y-space_below
844             height = graph_y_space * (1.0 * self.values[i] / self.max_value)
845             cr.set_source_rgba(1, 1, 1, 0.75)
846             cr.rectangle(int(xx), int(yy-height), int(bar_width), int(height))
847             cr.fill()
848         
849         cr.set_source_rgba(1, 1, 1, 1)
850         cr.select_font_face("Purisa", cairo.FONT_SLANT_NORMAL, 
851                             cairo.FONT_WEIGHT_NORMAL)
852         cr.set_font_size(13) 
853  
854         cr.rotate(2*math.pi * (-45) / 180)
855         for i in range(len(self.values)):
856             xx = y - space_below - 10
857             yy = border_left + (i+1)*bar_distance + i * bar_width 
858             cr.move_to(-xx, yy + bar_width*1.25 / 2) 
859             cr.show_text(self.xtext[i])
860             
861 class PedometerHomePlugin(hildondesktop.HomePluginItem):
862     button = None
863
864     #labels to display
865     labels = ["timer", "count", "dist", "avgSpeed", "calories"]
866
867     #current view
868     labelsC = {}
869
870     #second view ( day / week/ alltime)
871     labelsT = {}
872     
873     second_view_labels = ["All-time", "Today", "This week"]
874
875     controller = None
876     pedometer = None
877     pedometerInterval = None
878     graph_controller = None
879     startTime = None
880
881     mode = 0
882     height = 0
883     unit = 0
884     aspect = 0
885     second_view = 0
886     logging = False
887
888     def __init__(self):
889         hildondesktop.HomePluginItem.__init__(self)
890
891         gobject.type_register(CustomEventBox)
892         gobject.type_register(GraphWidget)
893         
894         self.client = gconf.client_get_default()
895         try:
896             self.mode = self.client.get_int(MODE)
897             self.height = self.client.get_int(HEIGHT)
898             self.unit = self.client.get_int(UNIT)
899             self.aspect = self.client.get_int(ASPECT)
900             self.second_view = self.client.get_int(SECONDVIEW)
901             self.graph_view = self.client.get_int(GRAPHVIEW)
902             self.logging = self.client.get_bool(LOGGING)
903
904         except:
905             self.client.set_int(MODE, 0)
906             self.client.set_int(HEIGHT, 0)
907             self.client.set_int(UNIT, 0)
908             self.client.set_int(ASPECT, 0)
909             self.client.set_int(SECONDVIEW, 0)
910             self.client.set_int(GRAPHVIEW, 0)
911             self.client.set_bool(LOGGING, False)
912
913         self.controller = PedoController()
914         self.controller.set_height(self.height)
915         self.controller.set_mode(self.mode)
916         self.controller.set_unit(self.unit)
917         self.controller.set_second_view(self.second_view)
918         self.controller.set_callback_ui(self.update_values)
919         
920         self.graph_controller = GraphController()
921         self.graph_controller.set_current_view(self.graph_view)
922
923         self.button = CustomButton(ICONSPATH + "play.png")
924         self.button.connect("clicked", self.button_clicked)
925
926         self.create_labels(self.labelsC)
927         self.create_labels(self.labelsT)
928         self.label_second_view = self.new_label_heading(self.second_view_labels[self.second_view])
929
930         self.update_current()
931         self.update_total()
932
933         mainHBox = gtk.HBox(spacing=1)
934
935         descVBox = gtk.VBox(spacing=1)
936         descVBox.add(self.new_label_heading())
937         descVBox.add(self.new_label_heading("Time:"))
938         descVBox.add(self.new_label_heading("Steps:"))
939         descVBox.add(self.new_label_heading("Calories:"))
940         descVBox.add(self.new_label_heading("Distance:"))
941         descVBox.add(self.new_label_heading("Avg Speed:"))
942
943         currentVBox = gtk.VBox(spacing=1)
944         currentVBox.add(self.new_label_heading("Current"))
945         currentVBox.add(self.labelsC["timer"])
946         currentVBox.add(self.labelsC["count"])
947         currentVBox.add(self.labelsC["calories"])
948         currentVBox.add(self.labelsC["dist"])
949         currentVBox.add(self.labelsC["avgSpeed"])
950         self.currentBox = currentVBox
951
952         totalVBox = gtk.VBox(spacing=1)
953         totalVBox.add(self.label_second_view)
954         totalVBox.add(self.labelsT["timer"])
955         totalVBox.add(self.labelsT["count"])
956         totalVBox.add(self.labelsT["calories"])
957         totalVBox.add(self.labelsT["dist"])
958         totalVBox.add(self.labelsT["avgSpeed"])
959         self.totalBox = totalVBox
960
961         buttonVBox = gtk.VBox(spacing=1)
962         buttonVBox.add(self.new_label_heading(""))
963         buttonVBox.add(self.button)
964         buttonVBox.add(self.new_label_heading(""))
965
966         eventBox = CustomEventBox()
967         eventBox.set_visible_window(False)
968         eventBox.add(totalVBox)
969         eventBox.connect("button-press-event", self.eventBox_clicked)
970         eventBox.connect("button-release-event", self.eventBox_clicked_release)
971         
972         
973         mainHBox.add(buttonVBox)
974         mainHBox.add(descVBox)
975         mainHBox.add(currentVBox)
976         mainHBox.add(eventBox)
977         self.mainhbox = mainHBox
978         
979         graph = GraphWidget()
980         self.graph_controller.set_graph(graph)
981         
982         eventBoxGraph = CustomEventBox()
983         eventBoxGraph.set_visible_window(False)
984         eventBoxGraph.add(graph)
985         self.graph = graph
986         eventBoxGraph.connect("button-press-event", self.eventBoxGraph_clicked)
987         eventBoxGraph.connect("button-release-event", self.eventBoxGraph_clicked_release)
988         
989         self.mainvbox = gtk.VBox()
990         
991         self.mainvbox.add(mainHBox)
992         self.mainvbox.add(eventBoxGraph)
993
994         self.mainvbox.show_all()
995         self.add(self.mainvbox)
996         self.update_aspect()
997
998         self.connect("unrealize", self.close_requested)
999         self.set_settings(True)
1000         self.connect("show-settings", self.show_settings)
1001         
1002     def eventBoxGraph_clicked(self, widget, data=None):
1003         widget.set_state(gtk.STATE_ACTIVE)
1004         
1005     def eventBoxGraph_clicked_release(self, widget, data=None):
1006         self.graph_view = self.graph_controller.next_view()
1007         self.client.set_int(GRAPHVIEW, self.graph_view)
1008         
1009         widget.set_state(gtk.STATE_NORMAL)
1010
1011     def eventBox_clicked(self, widget, data=None):
1012         widget.set_state(gtk.STATE_ACTIVE)
1013         
1014     def eventBox_clicked_release(self, widget, data=None):
1015         widget.set_state(gtk.STATE_NORMAL)
1016         
1017         self.second_view = (self.second_view + 1) % 3
1018         self.controller.set_second_view(self.second_view)
1019         self.client.set_int(SECONDVIEW, self.second_view)
1020   
1021     def new_label_heading(self, title=""):
1022         l = gtk.Label(title)
1023         hildon.hildon_helper_set_logical_font(l, "SmallSystemFont")
1024         return l
1025
1026     def create_labels(self, new_labels):
1027         for label in self.labels:
1028             l = gtk.Label()
1029             hildon.hildon_helper_set_logical_font(l, "SmallSystemFont")
1030             hildon.hildon_helper_set_logical_color(l, gtk.RC_FG, gtk.STATE_NORMAL, "ActiveTextColor")
1031             new_labels[label] = l
1032
1033     def update_aspect(self):
1034         if self.aspect == 0:
1035             self.currentBox.show_all()
1036             self.totalBox.show_all()
1037         elif self.aspect == 1:
1038             self.currentBox.show_all()
1039             self.totalBox.hide_all()
1040         else:
1041             self.currentBox.hide_all()
1042             self.totalBox.show_all()
1043
1044     def update_ui_values(self, labels, values):
1045         labels["timer"].set_label(values.get_print_time())
1046         labels["count"].set_label(values.get_print_steps())
1047         labels["dist"].set_label(values.get_print_distance())
1048         labels["avgSpeed"].set_label(values.get_print_avg_speed())
1049         labels["calories"].set_label(values.get_print_calories())
1050
1051     def update_current(self):
1052         self.update_ui_values(self.labelsC, self.controller.get_first())
1053
1054     def update_total(self):
1055         self.update_ui_values(self.labelsT, self.controller.get_second())
1056
1057     def show_settings(self, widget):
1058         def reset_total_counter(arg):
1059             widget.totalCounter = 0
1060             widget.totalTime = 0
1061             widget.update_total()
1062             hildon.hildon_banner_show_information(self, "None", "Total counter was resetted")
1063
1064         def selector_changed(selector, data):
1065             widget.mode = selector.get_active(0)
1066             widget.client.set_int(MODE, widget.mode)
1067             widget.controller.set_mode(widget.mode)
1068
1069         def selectorH_changed(selector, data):
1070             widget.height = selectorH.get_active(0)
1071             widget.client.set_int(HEIGHT, widget.height)
1072             widget.controller.set_height(widget.height)
1073
1074
1075         def selectorUnit_changed(selector, data):
1076             widget.unit = selectorUnit.get_active(0)
1077             widget.client.set_int(UNIT, widget.unit)
1078             widget.controller.set_unit(widget.unit)
1079
1080         def selectorUI_changed(selector, data):
1081             widget.aspect = selectorUI.get_active(0)
1082             widget.client.set_int(ASPECT, widget.aspect)
1083             widget.update_aspect()
1084
1085         def logButton_changed(checkButton):
1086             widget.logging = checkButton.get_active()
1087             widget.client.set_bool(LOGGING, widget.logging)
1088
1089         dialog = gtk.Dialog()
1090         dialog.set_title("Settings")
1091
1092         dialog.add_button("OK", gtk.RESPONSE_OK)
1093         button = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1094         button.set_title("Reset total counter")
1095         button.set_alignment(0, 0.8, 1, 1)
1096         button.connect("clicked", reset_total_counter)
1097
1098         selector = hildon.TouchSelector(text=True)
1099         selector.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
1100         selector.append_text("Walk")
1101         selector.append_text("Run")
1102         selector.connect("changed", selector_changed)
1103
1104         modePicker = hildon.ButtonPickerButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1105         modePicker.set_alignment(0.0, 0.5, 1.0, 1.0)
1106         modePicker.set_title("Select mode")
1107         modePicker.set_selector(selector)
1108         modePicker.set_active(widget.mode)
1109
1110         selectorH = hildon.TouchSelector(text=True)
1111         selectorH.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
1112         selectorH.append_text("< 1.50 m")
1113         selectorH.append_text("1.50 - 1.65 m")
1114         selectorH.append_text("1.66 - 1.80 m")
1115         selectorH.append_text("1.81 - 1.95 m")
1116         selectorH.append_text(" > 1.95 m")
1117         selectorH.connect("changed", selectorH_changed)
1118
1119         heightPicker = hildon.PickerButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1120         heightPicker.set_alignment(0.0, 0.5, 1.0, 1.0)
1121         heightPicker.set_title("Select height")
1122         heightPicker.set_selector(selectorH)
1123         heightPicker.set_active(widget.height)
1124
1125         selectorUnit = hildon.TouchSelector(text=True)
1126         selectorUnit.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
1127         selectorUnit.append_text("Metric (km)")
1128         selectorUnit.append_text("English (mi)")
1129         selectorUnit.connect("changed", selectorUnit_changed)
1130
1131         unitPicker = hildon.PickerButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1132         unitPicker.set_alignment(0.0, 0.5, 1.0, 1.0)
1133         unitPicker.set_title("Units")
1134         unitPicker.set_selector(selectorUnit)
1135         unitPicker.set_active(widget.unit)
1136
1137         selectorUI = hildon.TouchSelector(text=True)
1138         selectorUI = hildon.TouchSelector(text=True)
1139         selectorUI.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
1140         selectorUI.append_text("Show current + total")
1141         selectorUI.append_text("Show only current")
1142         selectorUI.append_text("Show only total")
1143         selectorUI.connect("changed", selectorUI_changed)
1144
1145         UIPicker = hildon.PickerButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1146         UIPicker.set_alignment(0.0, 0.5, 1.0, 1.0)
1147         UIPicker.set_title("Widget aspect")
1148         UIPicker.set_selector(selectorUI)
1149         UIPicker.set_active(widget.aspect)
1150
1151         logButton = hildon.CheckButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT)
1152         logButton.set_label("Log data")
1153         logButton.set_active(widget.logging)
1154         logButton.connect("toggled", logButton_changed)
1155
1156         pan_area = hildon.PannableArea()
1157         vbox = gtk.VBox()
1158         vbox.add(button)
1159         vbox.add(modePicker)
1160         vbox.add(heightPicker)
1161         vbox.add(unitPicker)
1162         vbox.add(UIPicker)
1163         #vbox.add(logButton)
1164
1165         pan_area.add_with_viewport(vbox)
1166         pan_area.set_size_request(-1, 300)
1167
1168         dialog.vbox.add(pan_area)
1169         dialog.show_all()
1170         response = dialog.run()
1171         #hildon.hildon_banner_show_information(self, "None", "You have to Stop/Start the counter to apply the new settings")
1172         dialog.destroy()
1173
1174     def close_requested(self, widget):
1175         if self.pedometer is None:
1176             return
1177
1178         self.pedometer.request_stop()
1179
1180     def update_values(self):
1181         #TODO: do not update if the widget is not on the active desktop
1182         self.label_second_view.set_label(self.second_view_labels[self.second_view])
1183         self.update_current()
1184         self.update_total()
1185
1186     def button_clicked(self, button):
1187         if self.controller.is_running:
1188             self.controller.stop_pedometer()
1189             self.button.set_icon(ICONSPATH + "play.png")
1190         else:
1191             self.controller.start_pedometer()
1192             self.button.set_icon(ICONSPATH + "stop.png")
1193
1194     def do_expose_event(self, event):
1195         cr = self.window.cairo_create()
1196         cr.region(event.window.get_clip_region())
1197         cr.clip()
1198         #cr.set_source_rgba(0.4, 0.64, 0.564, 0.5)
1199         style = self.rc_get_style()
1200         color = style.lookup_color("DefaultBackgroundColor")
1201         cr.set_source_rgba (color.red / 65535.0, color.green / 65335.0, color.blue / 65535.0, 0.75);
1202
1203         radius = 5
1204         width = self.allocation.width
1205         height = self.allocation.height
1206
1207         x = self.allocation.x
1208         y = self.allocation.y
1209
1210         cr.move_to(x + radius, y)
1211         cr.line_to(x + width - radius, y)
1212         cr.curve_to(x + width - radius, y, x + width, y, x + width, y + radius)
1213         cr.line_to(x + width, y + height - radius)
1214         cr.curve_to(x + width, y + height - radius, x + width, y + height, x + width - radius, y + height)
1215         cr.line_to(x + radius, y + height)
1216         cr.curve_to(x + radius, y + height, x, y + height, x, y + height - radius)
1217         cr.line_to(x, y + radius)
1218         cr.curve_to(x, y + radius, x, y, x + radius, y)
1219
1220         cr.set_operator(cairo.OPERATOR_SOURCE)
1221         cr.fill_preserve()
1222
1223         color = style.lookup_color("ActiveTextColor")
1224         cr.set_source_rgba (color.red / 65535.0, color.green / 65335.0, color.blue / 65535.0, 0.5);
1225         cr.set_line_width(1)
1226         cr.stroke()
1227
1228         hildondesktop.HomePluginItem.do_expose_event(self, event)
1229
1230     def do_realize(self):
1231         screen = self.get_screen()
1232         self.set_colormap(screen.get_rgba_colormap())
1233         self.set_app_paintable(True)
1234         hildondesktop.HomePluginItem.do_realize(self)
1235
1236 hd_plugin_type = PedometerHomePlugin
1237
1238 # The code below is just for testing purposes.
1239 # It allows to run the widget as a standalone process.
1240 if __name__ == "__main__":
1241     import gobject
1242     gobject.type_register(hd_plugin_type)
1243     obj = gobject.new(hd_plugin_type, plugin_id="plugin_id")
1244     obj.show_all()
1245     gtk.main()
1246
1247 ############### old pedometer.py ###
1248 import math
1249 import logging
1250
1251 logger = logging.getLogger("pedometer")
1252 logger.setLevel(logging.INFO)
1253
1254 ch = logging.StreamHandler()
1255 formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
1256 ch.setFormatter(formatter)
1257 logger.addHandler(ch)
1258