Center text for graph name
[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         self.compute_values()
753         self.last_update = time.time()
754         
755 class GraphWidget(gtk.DrawingArea):
756     
757     def __init__(self):
758         gtk.DrawingArea.__init__(self)
759         self.set_size_request(-1, 150)
760         self.yvalues = 5
761         
762         """sample values"""
763         self.ytext = ["   0", "1000", "2000", "3000", "4000", "5000"]
764         self.xtext = ["Monday", "Tuesday", "Wednesday","Thursday", "Friday", "Saturday", "Sunday"]
765         self.values = [1500, 3400, 4000, 3600, 3200, 0, 4500]
766         self.max_value = 5000
767         self.text = "All time steps"
768         
769     def do_expose_event(self, event):
770         context = self.window.cairo_create()
771         
772         # set a clip region for the expose event
773         context.rectangle(event.area.x, event.area.y,
774                                event.area.width, event.area.height)
775         context.clip()
776         
777         context.save()
778         
779         context.set_operator(cairo.OPERATOR_SOURCE)
780         style = self.rc_get_style()
781        
782         if self.state == gtk.STATE_ACTIVE:
783             color = style.lookup_color("SelectionColor")
784         else:
785              color = style.lookup_color("DefaultBackgroundColor")
786         context.set_source_rgba (color.red / 65535.0, color.green / 65335.0, color.blue / 65535.0, 0.75)
787
788         context.paint()
789         context.restore();
790         self.draw(context)
791         
792     def draw(self, cr):
793         space_below = 20 
794         space_above = 10
795         border_right = 10
796         border_left = 30
797         
798         rect = self.get_allocation()
799         x = rect.width
800         y = rect.height
801         
802         cr.set_source_rgb(1, 1, 1)
803         cr.move_to(border_left, space_above)
804         cr.line_to(border_left, y-space_below)
805         cr.set_line_width(2)
806         cr.stroke() 
807         
808         cr.move_to(border_left, y-space_below)
809         cr.line_to(x-border_right, y-space_below)
810         cr.set_line_width(2)
811         cr.stroke() 
812        
813         ydiff = (y-space_above-space_below) / self.yvalues 
814         for i in range(self.yvalues):
815             yy = y-space_below-ydiff*(i+1)
816             cr.move_to(border_left, yy)
817             cr.line_to(x-border_right, yy)
818             cr.set_line_width(0.8)
819             cr.stroke()
820             
821         cr.select_font_face("Purisa", cairo.FONT_SLANT_NORMAL, 
822             cairo.FONT_WEIGHT_NORMAL)
823         cr.set_font_size(13) 
824         for i in range(6):
825             yy = y - space_below - ydiff*i + 5 
826             cr.move_to(1, yy)
827             cr.show_text(self.ytext[i])
828             
829         cr.set_font_size(15)
830         te = cr.text_extents(self.text)
831         cr.move_to((x-te[2])/2, y-5)
832         cr.show_text(self.text)
833         
834         graph_x_space = x - border_left - border_right
835         graph_y_space = y - space_below - space_above
836         bar_width = graph_x_space*0.75 / len(self.values)
837         bar_distance = graph_x_space*0.25 / (1+len(self.values))
838        
839         #set dummy max value to avoid exceptions
840         if self.max_value == 0:
841             self.max_value = 100
842             
843         for i in range(len(self.values)):
844             xx = border_left + (i+1)*bar_distance + i * bar_width
845             yy = y-space_below
846             height = graph_y_space * (1.0 * self.values[i] / self.max_value)
847             cr.set_source_rgba(1, 1, 1, 0.75)
848             cr.rectangle(int(xx), int(yy-height), int(bar_width), int(height))
849             cr.fill()
850         
851         cr.set_source_rgba(1, 1, 1, 1)
852         cr.select_font_face("Purisa", cairo.FONT_SLANT_NORMAL, 
853                             cairo.FONT_WEIGHT_NORMAL)
854         cr.set_font_size(13) 
855  
856         cr.rotate(2*math.pi * (-45) / 180)
857         for i in range(len(self.values)):
858             xx = y - space_below - 10
859             yy = border_left + (i+1)*bar_distance + i * bar_width 
860             cr.move_to(-xx, yy + bar_width*1.25 / 2) 
861             cr.show_text(self.xtext[i])
862             
863 class PedometerHomePlugin(hildondesktop.HomePluginItem):
864     button = None
865
866     #labels to display
867     labels = ["timer", "count", "dist", "avgSpeed", "calories"]
868
869     #current view
870     labelsC = {}
871
872     #second view ( day / week/ alltime)
873     labelsT = {}
874     
875     second_view_labels = ["All-time", "Today", "This week"]
876
877     controller = None
878     pedometer = None
879     pedometerInterval = None
880     graph_controller = None
881     startTime = None
882
883     mode = 0
884     height = 0
885     unit = 0
886     aspect = 0
887     second_view = 0
888     logging = False
889
890     def __init__(self):
891         hildondesktop.HomePluginItem.__init__(self)
892
893         gobject.type_register(CustomEventBox)
894         gobject.type_register(GraphWidget)
895         
896         self.client = gconf.client_get_default()
897         try:
898             self.mode = self.client.get_int(MODE)
899             self.height = self.client.get_int(HEIGHT)
900             self.unit = self.client.get_int(UNIT)
901             self.aspect = self.client.get_int(ASPECT)
902             self.second_view = self.client.get_int(SECONDVIEW)
903             self.graph_view = self.client.get_int(GRAPHVIEW)
904             self.logging = self.client.get_bool(LOGGING)
905
906         except:
907             self.client.set_int(MODE, 0)
908             self.client.set_int(HEIGHT, 0)
909             self.client.set_int(UNIT, 0)
910             self.client.set_int(ASPECT, 0)
911             self.client.set_int(SECONDVIEW, 0)
912             self.client.set_int(GRAPHVIEW, 0)
913             self.client.set_bool(LOGGING, False)
914
915         self.controller = PedoController()
916         self.controller.set_height(self.height)
917         self.controller.set_mode(self.mode)
918         self.controller.set_unit(self.unit)
919         self.controller.set_second_view(self.second_view)
920         self.controller.set_callback_ui(self.update_values)
921         
922         self.graph_controller = GraphController()
923         self.graph_controller.set_current_view(self.graph_view)
924
925         self.button = CustomButton(ICONSPATH + "play.png")
926         self.button.connect("clicked", self.button_clicked)
927
928         self.create_labels(self.labelsC)
929         self.create_labels(self.labelsT)
930         self.label_second_view = self.new_label_heading(self.second_view_labels[self.second_view])
931
932         self.update_current()
933         self.update_total()
934
935         mainHBox = gtk.HBox(spacing=1)
936
937         descVBox = gtk.VBox(spacing=1)
938         descVBox.add(self.new_label_heading())
939         descVBox.add(self.new_label_heading("Time:"))
940         descVBox.add(self.new_label_heading("Steps:"))
941         descVBox.add(self.new_label_heading("Calories:"))
942         descVBox.add(self.new_label_heading("Distance:"))
943         descVBox.add(self.new_label_heading("Avg Speed:"))
944
945         currentVBox = gtk.VBox(spacing=1)
946         currentVBox.add(self.new_label_heading("Current"))
947         currentVBox.add(self.labelsC["timer"])
948         currentVBox.add(self.labelsC["count"])
949         currentVBox.add(self.labelsC["calories"])
950         currentVBox.add(self.labelsC["dist"])
951         currentVBox.add(self.labelsC["avgSpeed"])
952         self.currentBox = currentVBox
953
954         totalVBox = gtk.VBox(spacing=1)
955         totalVBox.add(self.label_second_view)
956         totalVBox.add(self.labelsT["timer"])
957         totalVBox.add(self.labelsT["count"])
958         totalVBox.add(self.labelsT["calories"])
959         totalVBox.add(self.labelsT["dist"])
960         totalVBox.add(self.labelsT["avgSpeed"])
961         self.totalBox = totalVBox
962
963         buttonVBox = gtk.VBox(spacing=1)
964         buttonVBox.add(self.new_label_heading(""))
965         buttonVBox.add(self.button)
966         buttonVBox.add(self.new_label_heading(""))
967
968         eventBox = CustomEventBox()
969         eventBox.set_visible_window(False)
970         eventBox.add(totalVBox)
971         eventBox.connect("button-press-event", self.eventBox_clicked)
972         eventBox.connect("button-release-event", self.eventBox_clicked_release)
973         
974         
975         mainHBox.add(buttonVBox)
976         mainHBox.add(descVBox)
977         mainHBox.add(currentVBox)
978         mainHBox.add(eventBox)
979         self.mainhbox = mainHBox
980         
981         graph = GraphWidget()
982         self.graph_controller.set_graph(graph)
983         
984         eventBoxGraph = CustomEventBox()
985         eventBoxGraph.set_visible_window(False)
986         eventBoxGraph.add(graph)
987         self.graph = graph
988         eventBoxGraph.connect("button-press-event", self.eventBoxGraph_clicked)
989         eventBoxGraph.connect("button-release-event", self.eventBoxGraph_clicked_release)
990         
991         self.mainvbox = gtk.VBox()
992         
993         self.mainvbox.add(mainHBox)
994         self.mainvbox.add(eventBoxGraph)
995
996         self.mainvbox.show_all()
997         self.add(self.mainvbox)
998         self.update_aspect()
999
1000         self.connect("unrealize", self.close_requested)
1001         self.set_settings(True)
1002         self.connect("show-settings", self.show_settings)
1003         
1004     def eventBoxGraph_clicked(self, widget, data=None):
1005         widget.set_state(gtk.STATE_ACTIVE)
1006         
1007     def eventBoxGraph_clicked_release(self, widget, data=None):
1008         self.graph_view = self.graph_controller.next_view()
1009         self.client.set_int(GRAPHVIEW, self.graph_view)
1010         
1011         widget.set_state(gtk.STATE_NORMAL)
1012
1013     def eventBox_clicked(self, widget, data=None):
1014         widget.set_state(gtk.STATE_ACTIVE)
1015         
1016     def eventBox_clicked_release(self, widget, data=None):
1017         widget.set_state(gtk.STATE_NORMAL)
1018         
1019         self.second_view = (self.second_view + 1) % 3
1020         self.controller.set_second_view(self.second_view)
1021         self.client.set_int(SECONDVIEW, self.second_view)
1022   
1023     def new_label_heading(self, title=""):
1024         l = gtk.Label(title)
1025         hildon.hildon_helper_set_logical_font(l, "SmallSystemFont")
1026         return l
1027
1028     def create_labels(self, new_labels):
1029         for label in self.labels:
1030             l = gtk.Label()
1031             hildon.hildon_helper_set_logical_font(l, "SmallSystemFont")
1032             hildon.hildon_helper_set_logical_color(l, gtk.RC_FG, gtk.STATE_NORMAL, "ActiveTextColor")
1033             new_labels[label] = l
1034
1035     def update_aspect(self):
1036         if self.aspect == 0:
1037             self.currentBox.show_all()
1038             self.totalBox.show_all()
1039         elif self.aspect == 1:
1040             self.currentBox.show_all()
1041             self.totalBox.hide_all()
1042         else:
1043             self.currentBox.hide_all()
1044             self.totalBox.show_all()
1045
1046     def update_ui_values(self, labels, values):
1047         labels["timer"].set_label(values.get_print_time())
1048         labels["count"].set_label(values.get_print_steps())
1049         labels["dist"].set_label(values.get_print_distance())
1050         labels["avgSpeed"].set_label(values.get_print_avg_speed())
1051         labels["calories"].set_label(values.get_print_calories())
1052
1053     def update_current(self):
1054         self.update_ui_values(self.labelsC, self.controller.get_first())
1055
1056     def update_total(self):
1057         self.update_ui_values(self.labelsT, self.controller.get_second())
1058
1059     def show_settings(self, widget):
1060         def reset_total_counter(arg):
1061             widget.totalCounter = 0
1062             widget.totalTime = 0
1063             widget.update_total()
1064             hildon.hildon_banner_show_information(self, "None", "Total counter was resetted")
1065
1066         def selector_changed(selector, data):
1067             widget.mode = selector.get_active(0)
1068             widget.client.set_int(MODE, widget.mode)
1069             widget.controller.set_mode(widget.mode)
1070
1071         def selectorH_changed(selector, data):
1072             widget.height = selectorH.get_active(0)
1073             widget.client.set_int(HEIGHT, widget.height)
1074             widget.controller.set_height(widget.height)
1075
1076
1077         def selectorUnit_changed(selector, data):
1078             widget.unit = selectorUnit.get_active(0)
1079             widget.client.set_int(UNIT, widget.unit)
1080             widget.controller.set_unit(widget.unit)
1081
1082         def selectorUI_changed(selector, data):
1083             widget.aspect = selectorUI.get_active(0)
1084             widget.client.set_int(ASPECT, widget.aspect)
1085             widget.update_aspect()
1086
1087         def logButton_changed(checkButton):
1088             widget.logging = checkButton.get_active()
1089             widget.client.set_bool(LOGGING, widget.logging)
1090
1091         dialog = gtk.Dialog()
1092         dialog.set_title("Settings")
1093
1094         dialog.add_button("OK", gtk.RESPONSE_OK)
1095         button = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1096         button.set_title("Reset total counter")
1097         button.set_alignment(0, 0.8, 1, 1)
1098         button.connect("clicked", reset_total_counter)
1099
1100         selector = hildon.TouchSelector(text=True)
1101         selector.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
1102         selector.append_text("Walk")
1103         selector.append_text("Run")
1104         selector.connect("changed", selector_changed)
1105
1106         modePicker = hildon.ButtonPickerButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1107         modePicker.set_alignment(0.0, 0.5, 1.0, 1.0)
1108         modePicker.set_title("Select mode")
1109         modePicker.set_selector(selector)
1110         modePicker.set_active(widget.mode)
1111
1112         selectorH = hildon.TouchSelector(text=True)
1113         selectorH.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
1114         selectorH.append_text("< 1.50 m")
1115         selectorH.append_text("1.50 - 1.65 m")
1116         selectorH.append_text("1.66 - 1.80 m")
1117         selectorH.append_text("1.81 - 1.95 m")
1118         selectorH.append_text(" > 1.95 m")
1119         selectorH.connect("changed", selectorH_changed)
1120
1121         heightPicker = hildon.PickerButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1122         heightPicker.set_alignment(0.0, 0.5, 1.0, 1.0)
1123         heightPicker.set_title("Select height")
1124         heightPicker.set_selector(selectorH)
1125         heightPicker.set_active(widget.height)
1126
1127         selectorUnit = hildon.TouchSelector(text=True)
1128         selectorUnit.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
1129         selectorUnit.append_text("Metric (km)")
1130         selectorUnit.append_text("English (mi)")
1131         selectorUnit.connect("changed", selectorUnit_changed)
1132
1133         unitPicker = hildon.PickerButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1134         unitPicker.set_alignment(0.0, 0.5, 1.0, 1.0)
1135         unitPicker.set_title("Units")
1136         unitPicker.set_selector(selectorUnit)
1137         unitPicker.set_active(widget.unit)
1138
1139         selectorUI = hildon.TouchSelector(text=True)
1140         selectorUI = hildon.TouchSelector(text=True)
1141         selectorUI.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
1142         selectorUI.append_text("Show current + total")
1143         selectorUI.append_text("Show only current")
1144         selectorUI.append_text("Show only total")
1145         selectorUI.connect("changed", selectorUI_changed)
1146
1147         UIPicker = hildon.PickerButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1148         UIPicker.set_alignment(0.0, 0.5, 1.0, 1.0)
1149         UIPicker.set_title("Widget aspect")
1150         UIPicker.set_selector(selectorUI)
1151         UIPicker.set_active(widget.aspect)
1152
1153         logButton = hildon.CheckButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT)
1154         logButton.set_label("Log data")
1155         logButton.set_active(widget.logging)
1156         logButton.connect("toggled", logButton_changed)
1157
1158         pan_area = hildon.PannableArea()
1159         vbox = gtk.VBox()
1160         vbox.add(button)
1161         vbox.add(modePicker)
1162         vbox.add(heightPicker)
1163         vbox.add(unitPicker)
1164         vbox.add(UIPicker)
1165         #vbox.add(logButton)
1166
1167         pan_area.add_with_viewport(vbox)
1168         pan_area.set_size_request(-1, 300)
1169
1170         dialog.vbox.add(pan_area)
1171         dialog.show_all()
1172         response = dialog.run()
1173         #hildon.hildon_banner_show_information(self, "None", "You have to Stop/Start the counter to apply the new settings")
1174         dialog.destroy()
1175
1176     def close_requested(self, widget):
1177         if self.pedometer is None:
1178             return
1179
1180         self.pedometer.request_stop()
1181
1182     def update_values(self):
1183         #TODO: do not update if the widget is not on the active desktop
1184         self.label_second_view.set_label(self.second_view_labels[self.second_view])
1185         self.update_current()
1186         self.update_total()
1187
1188     def button_clicked(self, button):
1189         if self.controller.is_running:
1190             self.controller.stop_pedometer()
1191             self.button.set_icon(ICONSPATH + "play.png")
1192         else:
1193             self.controller.start_pedometer()
1194             self.button.set_icon(ICONSPATH + "stop.png")
1195
1196     def do_expose_event(self, event):
1197         cr = self.window.cairo_create()
1198         cr.region(event.window.get_clip_region())
1199         cr.clip()
1200         #cr.set_source_rgba(0.4, 0.64, 0.564, 0.5)
1201         style = self.rc_get_style()
1202         color = style.lookup_color("DefaultBackgroundColor")
1203         cr.set_source_rgba (color.red / 65535.0, color.green / 65335.0, color.blue / 65535.0, 0.75);
1204
1205         radius = 5
1206         width = self.allocation.width
1207         height = self.allocation.height
1208
1209         x = self.allocation.x
1210         y = self.allocation.y
1211
1212         cr.move_to(x + radius, y)
1213         cr.line_to(x + width - radius, y)
1214         cr.curve_to(x + width - radius, y, x + width, y, x + width, y + radius)
1215         cr.line_to(x + width, y + height - radius)
1216         cr.curve_to(x + width, y + height - radius, x + width, y + height, x + width - radius, y + height)
1217         cr.line_to(x + radius, y + height)
1218         cr.curve_to(x + radius, y + height, x, y + height, x, y + height - radius)
1219         cr.line_to(x, y + radius)
1220         cr.curve_to(x, y + radius, x, y, x + radius, y)
1221
1222         cr.set_operator(cairo.OPERATOR_SOURCE)
1223         cr.fill_preserve()
1224
1225         color = style.lookup_color("ActiveTextColor")
1226         cr.set_source_rgba (color.red / 65535.0, color.green / 65335.0, color.blue / 65535.0, 0.5);
1227         cr.set_line_width(1)
1228         cr.stroke()
1229
1230         hildondesktop.HomePluginItem.do_expose_event(self, event)
1231
1232     def do_realize(self):
1233         screen = self.get_screen()
1234         self.set_colormap(screen.get_rgba_colormap())
1235         self.set_app_paintable(True)
1236         hildondesktop.HomePluginItem.do_realize(self)
1237
1238 hd_plugin_type = PedometerHomePlugin
1239
1240 # The code below is just for testing purposes.
1241 # It allows to run the widget as a standalone process.
1242 if __name__ == "__main__":
1243     import gobject
1244     gobject.type_register(hd_plugin_type)
1245     obj = gobject.new(hd_plugin_type, plugin_id="plugin_id")
1246     obj.show_all()
1247     gtk.main()
1248
1249 ############### old pedometer.py ###
1250 import math
1251 import logging
1252
1253 logger = logging.getLogger("pedometer")
1254 logger.setLevel(logging.INFO)
1255
1256 ch = logging.StreamHandler()
1257 formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
1258 ch.setFormatter(formatter)
1259 logger.addHandler(ch)
1260