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