2 #Author: Mirestean Andrei < andrei.mirestean at gmail.com >
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.
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.
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/>.
20 from datetime import date, timedelta
21 from xml.dom.minidom import getDOMImplementation, parseString
31 PATH = "/apps/pedometerhomewidget"
33 HEIGHT = PATH + "/height"
35 ASPECT = PATH + "/aspect"
36 SECONDVIEW = PATH + "/secondview"
37 GRAPHVIEW = PATH + "/graphview"
38 NOIDLETIME = PATH + "/noidletime"
39 LOGGING = PATH + "/logging"
41 ALARM_PATH = PATH + "/alarm"
42 ALARM_ENABLE = ALARM_PATH + "/enable"
43 ALARM_FNAME = ALARM_PATH + "/fname"
44 ALARM_TYPE = ALARM_PATH + "/type"
45 ALARM_INTERVAL = ALARM_PATH + "/interval"
47 ICONSPATH = "/opt/pedometerhomewidget/"
51 class Singleton(object):
53 def __new__(cls, *args, **kwargs):
55 cls._instance = super(Singleton, cls).__new__(
59 class PedoIntervalCounter(Singleton):
67 #TODO: check if last detected step is at the end of the interval
69 def set_vals(self, coords, tval):
75 def set_mode(self, mode):
76 #runnig, higher threshold to prevent fake steps
79 self.MIN_THRESHOLD = 650
80 self.MIN_TIME_STEPS = 0.35
83 self.MIN_THRESHOLD = 500
84 self.MIN_TIME_STEPS = 0.5
86 def calc_mean(self, vals):
91 return sum / len(vals)
94 def calc_stdev(self, vals):
96 mean = self.calc_mean(vals)
98 rez += pow(abs(mean - i), 2)
99 return math.sqrt(rez / len(vals))
101 def calc_threshold(self, vals):
104 mean = self.calc_mean(vals)
105 threshold = max (abs(mean - vmax), abs(mean - vmin))
108 def count_steps(self, vals, t):
109 threshold = self.MIN_THRESHOLD
110 mean = self.calc_mean(vals)
114 if abs(vals[i] - mean) > threshold:
116 ntime = t[i] + self.MIN_TIME_STEPS
117 while i < len(vals) and t[i] < ntime:
122 def get_best_values(self, x, y, z):
123 dev1 = self.calc_stdev(x)
124 dev2 = self.calc_stdev(y)
125 dev3 = self.calc_stdev(z)
126 dev_max = max(dev1, dev2, dev3)
128 if (abs(dev1 - dev_max) < 0.001):
129 logger.info("X chosen as best axis, stdev %f" % dev1)
131 elif (abs(dev2 - dev_max) < 0.001):
132 logger.info("Y chosen as best axis, stdev %f" % dev2)
135 logger.info("Z chosen as best axis, stdev %f" % dev3)
138 def number_steps(self):
139 vals = self.get_best_values(self.x, self.y, self.z)
140 return self.count_steps(vals, self.t)
143 def __init__(self, time=0, steps=0, dist=0, calories=0):
146 self.calories = calories
150 def __add__(self, other):
151 return PedoValues(self.time + other.time,
152 self.steps + other.steps,
153 self.dist + other.dist,
154 self.calories + other.calories)
156 def get_print_time(self):
158 hours = int(tdelta / 3600)
159 tdelta -= 3600 * hours
160 mins = int(tdelta / 60)
163 strtime = "%.2d:%.2d:%.2d" % (hours, mins, secs)
166 def get_print_distance(self):
169 return "%.2f km" % (self.dist / 1000)
171 return "%.2f mi" % (self.dist / 1609.344)
174 return "%d m" % self.dist
176 return "%d ft" % int(self.dist * 3.2808)
178 def get_avg_speed(self):
187 speed = 1.0 * self.dist / self.time
190 def get_print_avg_speed(self):
201 return "N/A " + suffix
202 speed = 1.0 * self.dist / self.time
203 #convert from meters per second to km/h or mi/h
205 return "%.2f %s" % (speed, suffix)
207 def get_print_steps(self):
208 return str(self.steps)
210 def get_print_calories(self):
211 return str(self.calories)
213 class PedoRepository(Singleton):
217 raise NotImplementedError("Must be implemented by subclass")
220 raise NotImplementedError("Must be implemented by subclass")
222 def reset_values(self):
226 def get_history_count(self):
227 """return the number of days in the log"""
230 def get_values(self):
233 def add_values(self, values, when=date.today()):
234 """add PedoValues values to repository """
236 self.values[when] = self.values[when] + values
238 self.values[when] = values
240 def get_last_7_days(self):
245 ret.append(self.values[day])
247 ret.append(PedoValues())
248 day = day - timedelta(days=1)
251 def get_last_weeks(self):
252 delta = timedelta(days=1)
254 week = int(date.today().strftime("%W"))
259 val += self.values[day]
262 w = int(day.strftime("%W"))
272 def get_alltime_values(self):
274 for k, v in self.values.iteritems():
278 def get_today_values(self):
280 return self.values[date.today()]
284 def get_this_week_values(self):
289 ret += self.values[day]
292 if day.weekday() == 0:
294 day = day - timedelta(days=1)
298 class PedoRepositoryXML(PedoRepository):
299 DIR = os.path.join(os.path.expanduser("~"), ".pedometer")
300 FILE = os.path.join(DIR, "data.xml")
301 FILE2 = os.path.join(DIR, "pickle.log")
303 if not os.path.exists(self.DIR):
304 os.makedirs(self.DIR)
305 PedoRepository.__init__(self)
309 f = open(self.FILE, "r")
310 dom = parseString(f.read())
311 values = dom.getElementsByTagName("pedometer")[0]
312 for v in values.getElementsByTagName("date"):
313 d = int(v.getAttribute("ordinal_day"))
314 steps = int(v.getAttribute("steps"))
315 calories = float(v.getAttribute("calories"))
316 dist = float(v.getAttribute("dist"))
317 time = float(v.getAttribute("time"))
318 day = date.fromordinal(d)
319 self.values[day] = PedoValues(time, steps, dist, calories)
323 logger.error("Error while loading data from xml file: %s" % e)
327 f = open(self.FILE, "w")
329 impl = getDOMImplementation()
331 newdoc = impl.createDocument(None, "pedometer", None)
332 top_element = newdoc.documentElement
333 for k, v in self.values.iteritems():
334 d = newdoc.createElement('date')
335 d.setAttribute("day", str(k.isoformat()))
336 d.setAttribute("ordinal_day", str(k.toordinal()))
337 d.setAttribute("steps", str(v.steps))
338 d.setAttribute("time", str(v.time))
339 d.setAttribute("dist", str(v.dist))
340 d.setAttribute("calories", str(v.calories))
341 top_element.appendChild(d)
343 newdoc.appendChild(top_element)
345 #f.write(newdoc.toprettyxml())
348 logger.error("Error while saving data to xml file: %s" % e)
350 class PedoRepositoryPickle(PedoRepository):
351 DIR = os.path.join(os.path.expanduser("~"), ".pedometer")
352 FILE = os.path.join(DIR, "pickle.log")
355 if not os.path.exists(self.DIR):
356 os.makedirs(self.DIR)
357 PedoRepository.__init__(self)
361 f = open(self.FILE, "rb")
362 self.values = pickle.load(f)
365 logger.error("Error while loading pickle file: %s" % e)
369 f = open(self.FILE, "wb")
370 pickle.dump(self.values, f)
373 logger.error("Error while saving data to pickle: %s" % e)
375 class AlarmController(Singleton):
377 fname = "/home/user/MyDocs/.sounds/Ringtones/Bicycle.aac"
382 self.client = gconf.client_get_default()
384 self.enable = self.client.get_bool(ALARM_ENABLE)
385 self.fname = self.client.get_string(ALARM_FNAME)
386 self.interval = self.client.get_int(ALARM_INTERVAL)
387 self.type = self.client.get_int(ALARM_TYPE)
389 self.client.set_bool(ALARM_ENABLE, self.enable)
390 self.client.set_string(ALARM_FNAME, self.fname)
391 self.client.set_int(ALARM_INTERVAL, self.interval)
392 self.client.set_int(ALARM_TYPE, self.type)
400 def set_enable(self, value):
402 self.client.set_bool(ALARM_ENABLE, value)
404 def get_enable(self):
407 def set_alarm_file(self, fname):
409 self.client.set_string(ALARM_FNAME, fname)
411 def get_alarm_file(self):
412 if self.fname == None:
416 def set_interval(self, interval):
417 self.interval = interval
418 self.client.set_int(ALARM_INTERVAL, interval)
420 def get_interval(self):
423 def set_type(self, type):
425 self.client.set_int(ALARM_TYPE, type)
430 class PedoController(Singleton):
434 #what to display in second view - 0 - alltime, 1 - today, 2 - week
436 callback_update_ui = None
440 #values for the two views in the widget ( current and day/week/alltime)
441 v = [PedoValues(), PedoValues()]
445 graph_controller = None
448 self.pedometer = PedoCounter(self.steps_detected)
449 self.pedometerInterval = PedoIntervalCounter()
450 self.pedometerInterval.set_mode(self.mode)
451 self.repository = PedoRepositoryXML()
452 self.repository.load()
454 self.graph_controller = GraphController()
457 def load_values(self):
458 if self.second_view == 0:
459 self.v[1] = self.repository.get_alltime_values()
460 elif self.second_view == 1:
461 self.v[1] = self.repository.get_today_values()
463 self.v[1] = self.repository.get_this_week_values()
465 def save_values(self):
466 self.repository.add_values(self.v[0])
467 self.repository.save()
470 def start_pedometer(self):
471 self.v[0] = PedoValues()
472 self.last_time = time.time()
473 self.is_running = True
474 self.pedometer.start()
477 def stop_pedometer(self):
478 self.is_running = False
479 self.pedometer.request_stop()
484 def get_second(self):
486 return self.v[0] + self.v[1]
490 def update_current(self):
492 Update distance and calories for current values based on new height, mode values
494 self.v[0].dist = self.get_distance(self.v[0].steps)
495 self.v[0].calories = self.get_calories(self.v[0].steps)
497 def steps_detected(self, cnt, last_steps=False):
498 if not last_steps and cnt == 0 and self.no_idle_time:
499 logger.info("No steps detected, timer is paused")
501 self.v[0].steps += cnt
502 self.v[0].dist += self.get_distance(cnt)
503 self.v[0].calories += self.get_distance(cnt)
504 self.v[0].time += time.time() - self.last_time
510 self.last_time = time.time()
512 def set_mode(self, mode):
514 self.set_height(self.height_interval)
517 def set_unit(self, new_unit):
521 def set_second_view(self, second_view):
522 self.second_view = second_view
526 def set_callback_ui(self, func):
527 self.callback_update_ui = func
529 def set_height(self, height_interval):
530 self.height_inteval = height_interval
531 #set height, will affect the distance
532 if height_interval == 0:
533 self.STEP_LENGTH = 0.59
534 elif height_interval == 1:
535 self.STEP_LENGTH = 0.64
536 elif height_interval == 2:
537 self.STEP_LENGTH = 0.71
538 elif height_interval == 3:
539 self.STEP_LENGTH = 0.77
540 elif height_interval == 4:
541 self.STEP_LENGTH = 0.83
542 #increase step length if RUNNING
544 self.STEP_LENGTH *= 1.45
547 def set_no_idle_time(self, value):
548 self.no_idle_time = value
550 def get_distance(self, steps=None):
553 return self.STEP_LENGTH * steps;
555 def get_calories(self, steps):
558 def notify_UI(self, optional=False):
559 if self.callback_update_ui is not None:
560 self.callback_update_ui()
561 self.graph_controller.update_ui(optional)
563 class PedoCounter(Singleton):
564 COORD_FNAME = "/sys/class/i2c-adapter/i2c-3/3-001d/coord"
565 COORD_FNAME_SDK = "/home/andrei/pedometer-widget-0.1/date.txt"
566 LOGFILE = "/home/user/log_pedometer"
567 #time in ms between two accelerometer data reads
568 COORD_GET_INTERVAL = 10
572 interval_counter = None
573 stop_requested = False
574 update_function = None
578 def __init__(self, update_function=None):
579 if not os.path.exists(self.COORD_FNAME):
580 self.COORD_FNAME = self.COORD_FNAME_SDK
582 self.interval_counter = PedoIntervalCounter()
583 self.update_function = update_function
585 def set_logging(self, value):
588 def get_rotation(self):
589 f = open(self.COORD_FNAME, 'r')
590 coords = [int(w) for w in f.readline().split()]
595 logger.info("Counter started")
596 self.isRunning = True
597 self.stop_requested = False
599 fname = "%d_%d_%d_%d_%d_%d" % time.localtime()[0:6]
600 self.file = open(self.LOGFILE + fname + ".txt", "w")
601 gobject.idle_add(self.run)
604 self.coords = [[], [], []]
605 self.stime = time.time()
607 gobject.timeout_add(self.COORD_GET_INTERVAL, self.read_coords)
610 def read_coords(self):
611 x, y, z = self.get_rotation()
612 self.coords[0].append(int(x))
613 self.coords[1].append(int(y))
614 self.coords[2].append(int(z))
615 now = time.time() - self.stime
617 self.file.write("%d %d %d %f\n" % (self.coords[0][-1], self.coords[1][-1], self.coords[2][-1], now))
622 if self.t[-1] > self.COUNT_INTERVAL or self.stop_requested:
624 gobject.idle_add(self.stop_interval)
627 def stop_interval(self):
628 self.interval_counter.set_vals(self.coords, self.t)
629 cnt = self.interval_counter.number_steps()
631 logger.info("Number of steps detected for last interval %d, number of coords: %d" % (cnt, len(self.t)))
633 gobject.idle_add(self.update_function, cnt, self.stop_requested)
635 if self.stop_requested:
636 gobject.idle_add(self.stop)
638 gobject.idle_add(self.run)
644 logger.info("Counter has finished")
646 def request_stop(self):
647 self.stop_requested = True
648 self.isRunning = False
650 class CustomButton(hildon.Button):
651 def __init__(self, icon):
652 hildon.Button.__init__(self, gtk.HILDON_SIZE_AUTO_WIDTH, hildon.BUTTON_ARRANGEMENT_VERTICAL)
654 self.set_size_request(int(32 * 1.4), int(30 * 1.0))
655 self.retval = self.connect("expose_event", self.expose)
657 def set_icon(self, icon):
660 def expose(self, widget, event):
661 self.context = widget.window.cairo_create()
662 self.context.rectangle(event.area.x, event.area.y,
663 event.area.width, event.area.height)
666 rect = self.get_allocation()
667 self.context.rectangle(rect.x, rect.y, rect.width, rect.height)
668 self.context.set_source_rgba(1, 1, 1, 0)
670 style = self.rc_get_style()
671 color = style.lookup_color("DefaultBackgroundColor")
672 if self.state == gtk.STATE_ACTIVE:
673 style = self.rc_get_style()
674 color = style.lookup_color("SelectionColor")
675 self.context.set_source_rgba (color.red / 65535.0, color.green / 65335.0, color.blue / 65535.0, 0.75);
678 #img = cairo.ImageSurface.create_from_png(self.icon)
680 #self.context.set_source_surface(img)
681 #self.context.set_source_surface(img, rect.width/2 - img.get_width() /2, 0)
683 img.set_from_file(self.icon)
684 buf = img.get_pixbuf()
685 buf = buf.scale_simple(int(32 * 1.5), int(30 * 1.5), gtk.gdk.INTERP_BILINEAR)
687 self.context.set_source_pixbuf(buf, rect.x + (event.area.width / 2 - 15) - 8, rect.y + 1)
688 self.context.scale(200, 200)
693 class CustomEventBox(gtk.EventBox):
696 gtk.EventBox.__init__(self)
698 def do_expose_event(self, event):
699 self.context = self.window.cairo_create()
700 self.context.rectangle(event.area.x, event.area.y,
701 event.area.width, event.area.height)
704 rect = self.get_allocation()
705 self.context.rectangle(rect.x, rect.y, rect.width, rect.height)
707 if self.state == gtk.STATE_ACTIVE:
708 style = self.rc_get_style()
709 color = style.lookup_color("SelectionColor")
710 self.context.set_source_rgba (color.red / 65535.0, color.green / 65335.0, color.blue / 65535.0, 0.75);
712 self.context.set_source_rgba(1, 1, 1, 0)
715 gtk.EventBox.do_expose_event(self, event)
717 class GraphController(Singleton):
718 ytitles = ["Steps", "Average Speed", "Distance", "Calories"]
719 xtitles = ["Day", "Week"] # "Today"]
722 self.repository = PedoRepositoryXML()
725 def set_graph(self, widget):
729 def set_current_view(self, view):
731 current_view % len(ytitles) - gives the ytitle
732 current_view / len(ytitles) - gives the xtitle
734 self.current_view = view
736 if self.current_view == len(self.ytitles) * len(self.xtitles):
737 self.current_view = 0
738 self.x_id = self.current_view / len(self.ytitles)
739 self.y_id = self.current_view % len(self.ytitles)
742 self.set_current_view(self.current_view+1)
744 return self.current_view
746 def last_weeks_labels(self):
748 delta = timedelta(days=7)
751 ret.append(d.strftime("Week %W"))
755 def compute_values(self):
758 values = self.repository.get_last_7_days()
760 delta = timedelta(days=1)
762 labels.append(d.ctime().split()[0])
766 values = self.repository.get_last_weeks()
769 labels.append(d.strftime("Week %W"))
770 d = d - timedelta(days=7)
772 values = self.repository.get_today()
776 yvalues = [line.steps for line in values]
778 yvalues = [line.get_avg_speed() for line in values]
780 yvalues = [line.dist for line in values]
782 yvalues = [line.calories for line in values]
784 #determine values for y lines in graph
785 diff = self.get_best_interval_value(max(yvalues))
788 ytext.append(str(int(i*diff)))
790 if self.widget is not None:
793 self.widget.values = yvalues
794 self.widget.ytext = ytext
795 self.widget.xtext = labels
796 self.widget.max_value = diff * 5
797 self.widget.text = self.xtitles[self.x_id] + " / " + self.ytitles[self.y_id]
798 self.widget.queue_draw()
800 logger.error("Widget not set in GraphController")
802 def get_best_interval_value(self, max_value):
803 diff = 1.0 * max_value / 5
804 l = len(str(int(diff)))
805 d = math.pow(10, l/2)
806 val = int(math.ceil(1.0 * diff / d)) * d
811 def update_ui(self, optional=False):
812 """update graph values every x seconds"""
813 if optional and self.last_update - time.time() < 600:
815 if self.widget is None:
818 self.compute_values()
819 self.last_update = time.time()
821 class GraphWidget(gtk.DrawingArea):
824 gtk.DrawingArea.__init__(self)
825 self.set_size_request(-1, 150)
829 self.ytext = [" 0", "1000", "2000", "3000", "4000", "5000"]
830 self.xtext = ["Monday", "Tuesday", "Wednesday","Thursday", "Friday", "Saturday", "Sunday"]
831 self.values = [1500, 3400, 4000, 3600, 3200, 0, 4500]
832 self.max_value = 5000
833 self.text = "All time steps"
835 def do_expose_event(self, event):
836 context = self.window.cairo_create()
838 # set a clip region for the expose event
839 context.rectangle(event.area.x, event.area.y,
840 event.area.width, event.area.height)
845 context.set_operator(cairo.OPERATOR_SOURCE)
846 style = self.rc_get_style()
848 if self.state == gtk.STATE_ACTIVE:
849 color = style.lookup_color("SelectionColor")
851 color = style.lookup_color("DefaultBackgroundColor")
852 context.set_source_rgba (color.red / 65535.0, color.green / 65335.0, color.blue / 65535.0, 0.75)
864 rect = self.get_allocation()
868 cr.select_font_face("Purisa", cairo.FONT_SLANT_NORMAL,
869 cairo.FONT_WEIGHT_NORMAL)
872 #check space needed to display ylabels
873 te = cr.text_extents(self.ytext[-1])
874 border_left = te[2] + 7
876 cr.set_source_rgb(1, 1, 1)
877 cr.move_to(border_left, space_above)
878 cr.line_to(border_left, y-space_below)
882 cr.move_to(border_left, y-space_below)
883 cr.line_to(x-border_right, y-space_below)
887 ydiff = (y-space_above-space_below) / self.yvalues
888 for i in range(self.yvalues):
889 yy = y-space_below-ydiff*(i+1)
890 cr.move_to(border_left, yy)
891 cr.line_to(x-border_right, yy)
892 cr.set_line_width(0.8)
897 yy = y - space_below - ydiff*i + 5
898 te = cr.text_extents(self.ytext[i])
900 cr.move_to(border_left-te[2]-2, yy)
901 cr.show_text(self.ytext[i])
904 te = cr.text_extents(self.text)
905 cr.move_to((x-te[2])/2, y-5)
906 cr.show_text(self.text)
908 graph_x_space = x - border_left - border_right
909 graph_y_space = y - space_below - space_above
910 bar_width = graph_x_space*0.75 / len(self.values)
911 bar_distance = graph_x_space*0.25 / (1+len(self.values))
913 #set dummy max value to avoid exceptions
914 if self.max_value == 0:
916 for i in range(len(self.values)):
917 xx = border_left + (i+1)*bar_distance + i * bar_width
919 height = graph_y_space * (1.0 * self.values[i] / self.max_value)
920 cr.set_source_rgba(1, 1, 1, 0.75)
921 cr.rectangle(int(xx), int(yy-height), int(bar_width), int(height))
924 cr.set_source_rgba(1, 1, 1, 1)
925 cr.select_font_face("Purisa", cairo.FONT_SLANT_NORMAL,
926 cairo.FONT_WEIGHT_NORMAL)
929 cr.rotate(2*math.pi * (-45) / 180)
930 for i in range(len(self.values)):
931 xx = y - space_below - 10
932 yy = border_left + (i+1)*bar_distance + i * bar_width
933 cr.move_to(-xx, yy + bar_width*1.25 / 2)
934 cr.show_text(self.xtext[i])
936 class PedometerHomePlugin(hildondesktop.HomePluginItem):
940 labels = ["timer", "count", "dist", "avgSpeed", "calories"]
945 #second view ( day / week/ alltime)
948 second_view_labels = ["All-time", "Today", "This week"]
952 pedometerInterval = None
953 graph_controller = None
965 hildondesktop.HomePluginItem.__init__(self)
967 gobject.type_register(CustomEventBox)
968 gobject.type_register(GraphWidget)
970 self.client = gconf.client_get_default()
972 self.mode = self.client.get_int(MODE)
973 self.height = self.client.get_int(HEIGHT)
974 self.unit = self.client.get_int(UNIT)
975 self.aspect = self.client.get_int(ASPECT)
976 self.second_view = self.client.get_int(SECONDVIEW)
977 self.graph_view = self.client.get_int(GRAPHVIEW)
978 self.no_idle_time = self.client.get_bool(NOIDLETIME)
979 self.logging = self.client.get_bool(LOGGING)
982 self.client.set_int(MODE, 0)
983 self.client.set_int(HEIGHT, 0)
984 self.client.set_int(UNIT, 0)
985 self.client.set_int(ASPECT, 0)
986 self.client.set_int(SECONDVIEW, 0)
987 self.client.set_int(GRAPHVIEW, 0)
988 self.client.set_bool(NOIDLETIME, False)
989 self.client.set_bool(LOGGING, False)
991 self.controller = PedoController()
992 self.controller.set_height(self.height)
993 self.controller.set_mode(self.mode)
994 self.controller.set_unit(self.unit)
995 self.controller.set_second_view(self.second_view)
996 self.controller.set_callback_ui(self.update_values)
997 self.controller.set_no_idle_time(self.no_idle_time)
999 self.graph_controller = GraphController()
1000 self.graph_controller.set_current_view(self.graph_view)
1002 self.alarm_controller = AlarmController()
1004 self.button = CustomButton(ICONSPATH + "play.png")
1005 self.button.connect("clicked", self.button_clicked)
1007 self.create_labels(self.labelsC)
1008 self.create_labels(self.labelsT)
1009 self.label_second_view = self.new_label_heading(self.second_view_labels[self.second_view])
1011 self.update_current()
1014 mainHBox = gtk.HBox(spacing=1)
1016 descVBox = gtk.VBox(spacing=1)
1017 descVBox.add(self.new_label_heading())
1018 descVBox.add(self.new_label_heading("Time:"))
1019 descVBox.add(self.new_label_heading("Steps:"))
1020 descVBox.add(self.new_label_heading("Calories:"))
1021 descVBox.add(self.new_label_heading("Distance:"))
1022 descVBox.add(self.new_label_heading("Avg Speed:"))
1024 currentVBox = gtk.VBox(spacing=1)
1025 currentVBox.add(self.new_label_heading("Current"))
1026 currentVBox.add(self.labelsC["timer"])
1027 currentVBox.add(self.labelsC["count"])
1028 currentVBox.add(self.labelsC["calories"])
1029 currentVBox.add(self.labelsC["dist"])
1030 currentVBox.add(self.labelsC["avgSpeed"])
1031 self.currentBox = currentVBox
1033 totalVBox = gtk.VBox(spacing=1)
1034 totalVBox.add(self.label_second_view)
1035 totalVBox.add(self.labelsT["timer"])
1036 totalVBox.add(self.labelsT["count"])
1037 totalVBox.add(self.labelsT["calories"])
1038 totalVBox.add(self.labelsT["dist"])
1039 totalVBox.add(self.labelsT["avgSpeed"])
1040 self.totalBox = totalVBox
1042 buttonVBox = gtk.VBox(spacing=1)
1043 buttonVBox.add(self.new_label_heading(""))
1044 buttonVBox.add(self.button)
1045 buttonVBox.add(self.new_label_heading(""))
1047 eventBox = CustomEventBox()
1048 eventBox.set_visible_window(False)
1049 eventBox.add(totalVBox)
1050 eventBox.connect("button-press-event", self.eventBox_clicked)
1051 eventBox.connect("button-release-event", self.eventBox_clicked_release)
1054 mainHBox.add(buttonVBox)
1055 mainHBox.add(descVBox)
1056 mainHBox.add(currentVBox)
1057 mainHBox.add(eventBox)
1058 self.mainhbox = mainHBox
1060 graph = GraphWidget()
1061 self.graph_controller.set_graph(graph)
1063 eventBoxGraph = CustomEventBox()
1064 eventBoxGraph.set_visible_window(False)
1065 eventBoxGraph.add(graph)
1067 eventBoxGraph.connect("button-press-event", self.eventBoxGraph_clicked)
1068 eventBoxGraph.connect("button-release-event", self.eventBoxGraph_clicked_release)
1070 self.mainvbox = gtk.VBox()
1072 self.mainvbox.add(mainHBox)
1073 self.mainvbox.add(eventBoxGraph)
1075 self.mainvbox.show_all()
1076 self.add(self.mainvbox)
1077 self.update_aspect()
1079 self.connect("unrealize", self.close_requested)
1080 self.set_settings(True)
1081 self.connect("show-settings", self.show_settings)
1083 def eventBoxGraph_clicked(self, widget, data=None):
1084 widget.set_state(gtk.STATE_ACTIVE)
1086 def eventBoxGraph_clicked_release(self, widget, data=None):
1087 self.graph_view = self.graph_controller.next_view()
1088 self.client.set_int(GRAPHVIEW, self.graph_view)
1090 widget.set_state(gtk.STATE_NORMAL)
1092 def eventBox_clicked(self, widget, data=None):
1093 widget.set_state(gtk.STATE_ACTIVE)
1095 def eventBox_clicked_release(self, widget, data=None):
1096 widget.set_state(gtk.STATE_NORMAL)
1098 self.second_view = (self.second_view + 1) % 3
1099 self.controller.set_second_view(self.second_view)
1100 self.client.set_int(SECONDVIEW, self.second_view)
1102 def new_label_heading(self, title=""):
1103 l = gtk.Label(title)
1104 hildon.hildon_helper_set_logical_font(l, "SmallSystemFont")
1107 def create_labels(self, new_labels):
1108 for label in self.labels:
1110 hildon.hildon_helper_set_logical_font(l, "SmallSystemFont")
1111 hildon.hildon_helper_set_logical_color(l, gtk.RC_FG, gtk.STATE_NORMAL, "ActiveTextColor")
1112 new_labels[label] = l
1114 def update_aspect(self):
1115 if self.aspect == 0:
1116 self.currentBox.show_all()
1117 self.totalBox.show_all()
1118 elif self.aspect == 1:
1119 self.currentBox.show_all()
1120 self.totalBox.hide_all()
1122 self.currentBox.hide_all()
1123 self.totalBox.show_all()
1125 def update_ui_values(self, labels, values):
1126 labels["timer"].set_label(values.get_print_time())
1127 labels["count"].set_label(values.get_print_steps())
1128 labels["dist"].set_label(values.get_print_distance())
1129 labels["avgSpeed"].set_label(values.get_print_avg_speed())
1130 labels["calories"].set_label(values.get_print_calories())
1132 def update_current(self):
1133 self.update_ui_values(self.labelsC, self.controller.get_first())
1135 def update_total(self):
1136 self.update_ui_values(self.labelsT, self.controller.get_second())
1138 def show_alarm_settings(self, main_button):
1139 def choose_file(widget):
1140 file = hildon.FileChooserDialog(self, gtk.FILE_CHOOSER_ACTION_OPEN, hildon.FileSystemModel() )
1142 if ( file.run() == gtk.RESPONSE_OK):
1143 fname = file.get_filename()
1144 widget.set_value(fname)
1145 self.alarm_controller.set_alarm_file(fname)
1148 def test_sound(button):
1150 self.alarm_controller.play()
1151 except Exception, e:
1152 logger.error("Could not play alarm sound: %s" % e)
1153 hildon.hildon_banner_show_information(self, "None", "Could not play alarm sound")
1155 def enableButton_changed(button):
1156 value = button.get_active()
1157 self.alarm_controller.set_enable(value)
1159 main_button.set_value("Enabled")
1161 main_button.set_value("Disabled")
1163 def selectorType_changed(selector, data, labelEntry2):
1164 self.alarm_controller.set_type(selector.get_active(0))
1165 labelEntry2.set_label(suffix[self.alarm_controller.get_type()])
1167 dialog = gtk.Dialog()
1168 dialog.set_title("Alarm settings")
1169 dialog.add_button("OK", gtk.RESPONSE_OK)
1171 enableButton = hildon.CheckButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT)
1172 enableButton.set_label("Enable alarm")
1173 enableButton.set_active(self.alarm_controller.get_enable())
1174 enableButton.connect("toggled", enableButton_changed)
1176 testButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1177 testButton.set_alignment(0, 0.8, 1, 1)
1178 testButton.set_title("Test sound")
1179 testButton.connect("pressed", test_sound)
1181 fileButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1182 fileButton.set_alignment(0, 0.8, 1, 1)
1183 fileButton.set_title("Alarm sound")
1184 fileButton.set_value(self.alarm_controller.get_alarm_file())
1185 fileButton.connect("pressed", choose_file)
1187 labelEntry = gtk.Label("Notify every:")
1188 suffix = ["mins", "steps", "m/ft", "calories"]
1189 labelEntry2 = gtk.Label(suffix[self.alarm_controller.get_type()])
1190 intervalEntry = hildon.Entry(gtk.HILDON_SIZE_AUTO_WIDTH)
1191 intervalEntry.set_text(str(self.alarm_controller.get_interval()))
1193 selectorType = hildon.TouchSelector(text=True)
1194 selectorType.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
1195 selectorType.append_text("Time")
1196 selectorType.append_text("Steps")
1197 selectorType.append_text("Distance")
1198 selectorType.append_text("Calories")
1199 selectorType.connect("changed", selectorType_changed, labelEntry2)
1201 typePicker = hildon.PickerButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1202 typePicker.set_alignment(0.0, 0.5, 1.0, 1.0)
1203 typePicker.set_title("Alarm type")
1204 typePicker.set_selector(selectorType)
1205 typePicker.set_active(self.alarm_controller.get_type())
1210 hbox.add(labelEntry)
1211 hbox.add(intervalEntry)
1212 hbox.add(labelEntry2)
1214 dialog.vbox.add(enableButton)
1215 dialog.vbox.add(fileButton)
1216 dialog.vbox.add(typePicker)
1217 dialog.vbox.add(hbox)
1220 response = dialog.run()
1221 if response != gtk.RESPONSE_OK:
1224 value = int(intervalEntry.get_text())
1225 self.alarm_controller.set_interval(value)
1228 hildon.hildon_banner_show_information(self, "None", "Invalid interval")
1232 def show_settings(self, widget):
1233 def reset_total_counter(arg):
1234 widget.totalCounter = 0
1235 widget.totalTime = 0
1236 widget.update_total()
1237 hildon.hildon_banner_show_information(self, "None", "Total counter was resetted")
1239 def alarmButton_pressed(widget):
1240 self.show_alarm_settings(widget)
1242 def selector_changed(selector, data):
1243 widget.mode = selector.get_active(0)
1244 widget.client.set_int(MODE, widget.mode)
1245 widget.controller.set_mode(widget.mode)
1247 def selectorH_changed(selector, data):
1248 widget.height = selectorH.get_active(0)
1249 widget.client.set_int(HEIGHT, widget.height)
1250 widget.controller.set_height(widget.height)
1252 def selectorUnit_changed(selector, data):
1253 widget.unit = selectorUnit.get_active(0)
1254 widget.client.set_int(UNIT, widget.unit)
1255 widget.controller.set_unit(widget.unit)
1257 def selectorUI_changed(selector, data):
1258 widget.aspect = selectorUI.get_active(0)
1259 widget.client.set_int(ASPECT, widget.aspect)
1260 widget.update_aspect()
1262 def logButton_changed(checkButton):
1263 widget.logging = checkButton.get_active()
1264 widget.client.set_bool(LOGGING, widget.logging)
1266 def idleButton_changed(idleButton):
1267 widget.no_idle_time = idleButton.get_active()
1268 widget.client.set_bool(NOIDLETIME, widget.no_idle_time)
1269 widget.controller.set_no_idle_time(widget.no_idle_time)
1271 dialog = gtk.Dialog()
1272 dialog.set_title("Settings")
1273 dialog.add_button("OK", gtk.RESPONSE_OK)
1276 button = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1277 button.set_title("Reset total counter")
1278 button.set_alignment(0, 0.8, 1, 1)
1279 button.connect("clicked", reset_total_counter)
1281 alarmButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1282 alarmButton.set_title("Alarm")
1283 if self.alarm_controller.get_enable():
1284 alarmButton.set_value("Enabled")
1286 alarmButton.set_value("Disabled")
1287 alarmButton.set_alignment(0, 0.8, 1, 1)
1288 alarmButton.connect("clicked", alarmButton_pressed)
1290 selector = hildon.TouchSelector(text=True)
1291 selector.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
1292 selector.append_text("Walk")
1293 selector.append_text("Run")
1294 selector.connect("changed", selector_changed)
1296 modePicker = hildon.PickerButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1297 modePicker.set_alignment(0.0, 0.5, 1.0, 1.0)
1298 modePicker.set_title("Select mode")
1299 modePicker.set_selector(selector)
1300 modePicker.set_active(widget.mode)
1302 selectorH = hildon.TouchSelector(text=True)
1303 selectorH.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
1304 selectorH.append_text("< 1.50 m")
1305 selectorH.append_text("1.50 - 1.65 m")
1306 selectorH.append_text("1.66 - 1.80 m")
1307 selectorH.append_text("1.81 - 1.95 m")
1308 selectorH.append_text(" > 1.95 m")
1309 selectorH.connect("changed", selectorH_changed)
1311 heightPicker = hildon.PickerButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1312 heightPicker.set_alignment(0.0, 0.5, 1.0, 1.0)
1313 heightPicker.set_title("Select height")
1314 heightPicker.set_selector(selectorH)
1315 heightPicker.set_active(widget.height)
1317 selectorUnit = hildon.TouchSelector(text=True)
1318 selectorUnit.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
1319 selectorUnit.append_text("Metric (km)")
1320 selectorUnit.append_text("English (mi)")
1321 selectorUnit.connect("changed", selectorUnit_changed)
1323 unitPicker = hildon.PickerButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1324 unitPicker.set_alignment(0.0, 0.5, 1.0, 1.0)
1325 unitPicker.set_title("Units")
1326 unitPicker.set_selector(selectorUnit)
1327 unitPicker.set_active(widget.unit)
1329 selectorUI = hildon.TouchSelector(text=True)
1330 selectorUI = hildon.TouchSelector(text=True)
1331 selectorUI.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
1332 selectorUI.append_text("Show current + total")
1333 selectorUI.append_text("Show only current")
1334 selectorUI.append_text("Show only total")
1335 selectorUI.connect("changed", selectorUI_changed)
1337 UIPicker = hildon.PickerButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1338 UIPicker.set_alignment(0.0, 0.5, 1.0, 1.0)
1339 UIPicker.set_title("Widget aspect")
1340 UIPicker.set_selector(selectorUI)
1341 UIPicker.set_active(widget.aspect)
1343 logButton = hildon.CheckButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT)
1344 logButton.set_label("Log data")
1345 logButton.set_active(widget.logging)
1346 logButton.connect("toggled", logButton_changed)
1348 idleButton = hildon.CheckButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT)
1349 idleButton.set_label("Pause time when not walking")
1350 idleButton.set_active(widget.no_idle_time)
1351 idleButton.connect("toggled", idleButton_changed)
1353 pan_area = hildon.PannableArea()
1356 vbox.add(alarmButton)
1357 vbox.add(modePicker)
1358 vbox.add(heightPicker)
1359 vbox.add(unitPicker)
1361 vbox.add(idleButton)
1362 #vbox.add(logButton)
1364 pan_area.add_with_viewport(vbox)
1365 pan_area.set_size_request(-1, 300)
1367 dialog.vbox.add(pan_area)
1369 response = dialog.run()
1370 #hildon.hildon_banner_show_information(self, "None", "You have to Stop/Start the counter to apply the new settings")
1373 def close_requested(self, widget):
1374 if self.pedometer is None:
1377 self.pedometer.request_stop()
1379 def update_values(self):
1380 #TODO: do not update if the widget is not on the active desktop
1381 self.label_second_view.set_label(self.second_view_labels[self.second_view])
1382 self.update_current()
1385 def button_clicked(self, button):
1386 if self.controller.is_running:
1387 self.controller.stop_pedometer()
1388 self.button.set_icon(ICONSPATH + "play.png")
1390 self.controller.start_pedometer()
1391 self.button.set_icon(ICONSPATH + "stop.png")
1393 def do_expose_event(self, event):
1394 cr = self.window.cairo_create()
1395 cr.region(event.window.get_clip_region())
1397 #cr.set_source_rgba(0.4, 0.64, 0.564, 0.5)
1398 style = self.rc_get_style()
1399 color = style.lookup_color("DefaultBackgroundColor")
1400 cr.set_source_rgba (color.red / 65535.0, color.green / 65335.0, color.blue / 65535.0, 0.75);
1403 width = self.allocation.width
1404 height = self.allocation.height
1406 x = self.allocation.x
1407 y = self.allocation.y
1409 cr.move_to(x + radius, y)
1410 cr.line_to(x + width - radius, y)
1411 cr.curve_to(x + width - radius, y, x + width, y, x + width, y + radius)
1412 cr.line_to(x + width, y + height - radius)
1413 cr.curve_to(x + width, y + height - radius, x + width, y + height, x + width - radius, y + height)
1414 cr.line_to(x + radius, y + height)
1415 cr.curve_to(x + radius, y + height, x, y + height, x, y + height - radius)
1416 cr.line_to(x, y + radius)
1417 cr.curve_to(x, y + radius, x, y, x + radius, y)
1419 cr.set_operator(cairo.OPERATOR_SOURCE)
1422 color = style.lookup_color("ActiveTextColor")
1423 cr.set_source_rgba (color.red / 65535.0, color.green / 65335.0, color.blue / 65535.0, 0.5);
1424 cr.set_line_width(1)
1427 hildondesktop.HomePluginItem.do_expose_event(self, event)
1429 def do_realize(self):
1430 screen = self.get_screen()
1431 self.set_colormap(screen.get_rgba_colormap())
1432 self.set_app_paintable(True)
1433 hildondesktop.HomePluginItem.do_realize(self)
1435 hd_plugin_type = PedometerHomePlugin
1437 # The code below is just for testing purposes.
1438 # It allows to run the widget as a standalone process.
1439 if __name__ == "__main__":
1441 gobject.type_register(hd_plugin_type)
1442 obj = gobject.new(hd_plugin_type, plugin_id="plugin_id")
1446 ############### old pedometer.py ###
1450 logger = logging.getLogger("pedometer")
1451 logger.setLevel(logging.INFO)
1453 ch = logging.StreamHandler()
1454 formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
1455 ch.setFormatter(formatter)
1456 logger.addHandler(ch)