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
35 PATH = "/apps/pedometerhomewidget"
37 HEIGHT = PATH + "/height"
38 WEIGHT = PATH + "/weight"
40 ASPECT = PATH + "/aspect"
41 SECONDVIEW = PATH + "/secondview"
42 GRAPHVIEW = PATH + "/graphview"
43 NOIDLETIME = PATH + "/noidletime"
44 LOGGING = PATH + "/logging"
46 ALARM_PATH = PATH + "/alarm"
47 ALARM_ENABLE = ALARM_PATH + "/enable"
48 ALARM_FNAME = ALARM_PATH + "/fname"
49 ALARM_TYPE = ALARM_PATH + "/type"
50 ALARM_INTERVAL = ALARM_PATH + "/interval"
52 ICONSPATH = "/opt/pedometerhomewidget/"
56 class Singleton(object):
58 def __new__(cls, *args, **kwargs):
60 cls._instance = super(Singleton, cls).__new__(
64 class PedoIntervalCounter(Singleton):
72 #TODO: check if last detected step is at the end of the interval
74 def set_vals(self, coords, tval):
80 def set_mode(self, mode):
81 #runnig, higher threshold to prevent fake steps
84 self.MIN_THRESHOLD = 650
85 self.MIN_TIME_STEPS = 0.35
88 self.MIN_THRESHOLD = 500
89 self.MIN_TIME_STEPS = 0.5
91 def calc_mean(self, vals):
96 return sum / len(vals)
99 def calc_stdev(self, vals):
101 mean = self.calc_mean(vals)
103 rez += pow(abs(mean - i), 2)
104 return math.sqrt(rez / len(vals))
106 def calc_threshold(self, vals):
109 mean = self.calc_mean(vals)
110 threshold = max (abs(mean - vmax), abs(mean - vmin))
113 def count_steps(self, vals, t):
114 threshold = self.MIN_THRESHOLD
115 mean = self.calc_mean(vals)
119 if abs(vals[i] - mean) > threshold:
121 ntime = t[i] + self.MIN_TIME_STEPS
122 while i < len(vals) and t[i] < ntime:
127 def get_best_values(self, x, y, z):
128 dev1 = self.calc_stdev(x)
129 dev2 = self.calc_stdev(y)
130 dev3 = self.calc_stdev(z)
131 dev_max = max(dev1, dev2, dev3)
133 if (abs(dev1 - dev_max) < 0.001):
134 logger.info("X chosen as best axis, stdev %f" % dev1)
136 elif (abs(dev2 - dev_max) < 0.001):
137 logger.info("Y chosen as best axis, stdev %f" % dev2)
140 logger.info("Z chosen as best axis, stdev %f" % dev3)
143 def number_steps(self):
144 vals = self.get_best_values(self.x, self.y, self.z)
145 return self.count_steps(vals, self.t)
148 def __init__(self, time=0, steps=0, dist=0, calories=0):
151 self.calories = calories
155 def __add__(self, other):
156 return PedoValues(self.time + other.time,
157 self.steps + other.steps,
158 self.dist + other.dist,
159 self.calories + other.calories)
161 def __sub__(self, other):
162 return PedoValues(self.time - other.time,
163 self.steps - other.steps,
164 self.dist - other.dist,
165 self.calories - other.calories)
167 def get_print_time(self):
169 hours = int(tdelta / 3600)
170 tdelta -= 3600 * hours
171 mins = int(tdelta / 60)
174 strtime = "%.2d:%.2d:%.2d" % (hours, mins, secs)
177 def get_print_distance(self):
180 return "%.2f km" % (self.dist / 1000)
182 return "%.2f mi" % (self.dist / 1609.344)
185 return "%d m" % self.dist
187 return "%d ft" % int(self.dist * 3.2808)
189 def get_avg_speed(self):
198 speed = 1.0 * self.dist / self.time
201 def get_print_avg_speed(self):
212 return "N/A " + suffix
213 speed = 1.0 * self.dist / self.time
214 #convert from meters per second to km/h or mi/h
216 return "%.2f %s" % (speed, suffix)
218 def get_print_steps(self):
219 return str(self.steps)
221 def get_print_calories(self):
222 return str(self.calories)
224 class PedoRepository(Singleton):
228 raise NotImplementedError("Must be implemented by subclass")
231 raise NotImplementedError("Must be implemented by subclass")
233 def reset_values(self):
237 def get_history_count(self):
238 """return the number of days in the log"""
241 def get_values(self):
244 def add_values(self, values, when=date.today()):
245 """add PedoValues values to repository """
247 self.values[when] = self.values[when] + values
249 self.values[when] = values
251 def get_last_7_days(self):
256 ret.append(self.values[day])
258 ret.append(PedoValues())
259 day = day - timedelta(days=1)
262 def get_last_weeks(self):
263 delta = timedelta(days=1)
265 week = int(date.today().strftime("%W"))
270 val += self.values[day]
273 w = int(day.strftime("%W"))
283 def get_alltime_values(self):
285 for k, v in self.values.iteritems():
289 def get_today_values(self):
291 return self.values[date.today()]
295 def get_this_week_values(self):
300 ret += self.values[day]
303 if day.weekday() == 0:
305 day = day - timedelta(days=1)
309 class PedoRepositoryXML(PedoRepository):
310 DIR = os.path.join(os.path.expanduser("~"), ".pedometer")
311 FILE = os.path.join(DIR, "data.xml")
312 FILE2 = os.path.join(DIR, "pickle.log")
314 if not os.path.exists(self.DIR):
315 os.makedirs(self.DIR)
316 PedoRepository.__init__(self)
320 f = open(self.FILE, "r")
321 dom = parseString(f.read())
322 values = dom.getElementsByTagName("pedometer")[0]
323 for v in values.getElementsByTagName("date"):
324 d = int(v.getAttribute("ordinal_day"))
325 steps = int(v.getAttribute("steps"))
326 calories = float(v.getAttribute("calories"))
327 dist = float(v.getAttribute("dist"))
328 time = float(v.getAttribute("time"))
329 day = date.fromordinal(d)
330 self.values[day] = PedoValues(time, steps, dist, calories)
334 logger.error("Error while loading data from xml file: %s" % e)
338 f = open(self.FILE, "w")
340 impl = getDOMImplementation()
342 newdoc = impl.createDocument(None, "pedometer", None)
343 top_element = newdoc.documentElement
344 for k, v in self.values.iteritems():
345 d = newdoc.createElement('date')
346 d.setAttribute("day", str(k.isoformat()))
347 d.setAttribute("ordinal_day", str(k.toordinal()))
348 d.setAttribute("steps", str(v.steps))
349 d.setAttribute("time", str(v.time))
350 d.setAttribute("dist", str(v.dist))
351 d.setAttribute("calories", str(v.calories))
352 top_element.appendChild(d)
354 newdoc.appendChild(top_element)
356 #f.write(newdoc.toprettyxml())
359 logger.error("Error while saving data to xml file: %s" % e)
361 class PedoRepositoryPickle(PedoRepository):
362 DIR = os.path.join(os.path.expanduser("~"), ".pedometer")
363 FILE = os.path.join(DIR, "pickle.log")
366 if not os.path.exists(self.DIR):
367 os.makedirs(self.DIR)
368 PedoRepository.__init__(self)
372 f = open(self.FILE, "rb")
373 self.values = pickle.load(f)
376 logger.error("Error while loading pickle file: %s" % e)
380 f = open(self.FILE, "wb")
381 pickle.dump(self.values, f)
384 logger.error("Error while saving data to pickle: %s" % e)
386 class PedoController(Singleton):
390 #what to display in second view - 0 - alltime, 1 - today, 2 - week
392 callback_update_ui = None
396 #values for the two views in the widget ( current and day/week/alltime)
397 v = [PedoValues(), PedoValues()]
405 self.pedometer = PedoCounter(self.steps_detected)
406 self.pedometerInterval = PedoIntervalCounter()
407 self.pedometerInterval.set_mode(self.mode)
408 self.repository = PedoRepositoryXML()
409 self.repository.load()
413 def load_values(self):
414 if self.second_view == 0:
415 self.v[1] = self.repository.get_alltime_values()
416 elif self.second_view == 1:
417 self.v[1] = self.repository.get_today_values()
419 self.v[1] = self.repository.get_this_week_values()
421 def save_values(self):
422 self.repository.add_values(self.v[0])
423 self.repository.save()
426 def start_pedometer(self):
427 self.v[0] = PedoValues()
428 self.last_time = time.time()
429 self.is_running = True
430 self.pedometer.start()
433 def reset_all_values(self):
434 self.repository.reset_values()
437 def stop_pedometer(self):
438 self.is_running = False
439 self.pedometer.request_stop()
444 def get_second(self):
446 return self.v[0] + self.v[1]
450 def update_current(self):
452 Update distance and calories for current values based on new height, mode values
454 self.v[0].dist = self.get_distance(self.v[0].steps)
455 self.v[0].calories = self.get_calories(self.v[0].steps)
457 def steps_detected(self, cnt, last_steps=False):
458 if not last_steps and cnt == 0 and self.no_idle_time:
459 logger.info("No steps detected, timer is paused")
461 self.v[0].steps += cnt
462 self.v[0].dist += self.get_distance(cnt)
463 self.v[0].calories += self.get_distance(cnt)
464 self.v[0].time += time.time() - self.last_time
470 self.last_time = time.time()
472 def set_mode(self, mode):
474 self.set_height(self.height_interval)
477 def set_unit(self, new_unit):
481 def get_str_weight_unit(self):
487 def set_weight(self, value):
491 def get_weight(self):
494 def set_second_view(self, second_view):
495 self.second_view = second_view
499 def set_callback_ui(self, func):
500 self.callback_update_ui = func
502 def set_height(self, height_interval):
503 self.height_inteval = height_interval
504 #set height, will affect the distance
505 if height_interval == 0:
506 self.STEP_LENGTH = 0.59
507 elif height_interval == 1:
508 self.STEP_LENGTH = 0.64
509 elif height_interval == 2:
510 self.STEP_LENGTH = 0.71
511 elif height_interval == 3:
512 self.STEP_LENGTH = 0.77
513 elif height_interval == 4:
514 self.STEP_LENGTH = 0.83
515 #increase step length if RUNNING
517 self.STEP_LENGTH *= 1.45
520 def set_no_idle_time(self, value):
521 self.no_idle_time = value
523 def get_distance(self, steps=None):
526 return self.STEP_LENGTH * steps;
528 def get_calories(self, steps):
531 def add_observer(self, func):
533 self.observers.index(func)
535 self.observers.append(func)
537 def remove_observer(self, func):
538 self.observers.remove(func)
540 def notify(self, optional=False):
541 if self.callback_update_ui is not None:
542 self.callback_update_ui()
544 for func in self.observers:
547 class AlarmController(Singleton):
549 fname = "/home/user/MyDocs/.sounds/Ringtones/Bicycle.aac"
555 pedo_controller = None
558 self.client = gconf.client_get_default()
560 self.enable = self.client.get_bool(ALARM_ENABLE)
561 self.fname = self.client.get_string(ALARM_FNAME)
562 self.interval = self.client.get_int(ALARM_INTERVAL)
563 self.type = self.client.get_int(ALARM_TYPE)
565 self.client.set_bool(ALARM_ENABLE, self.enable)
566 self.client.set_string(ALARM_FNAME, self.fname)
567 self.client.set_int(ALARM_INTERVAL, self.interval)
568 self.client.set_int(ALARM_TYPE, self.type)
570 self.pedo_controller = PedoController()
573 self.pedo_controller.add_observer(self.update)
574 self.start_value = self.pedo_controller.get_first()
576 def init_player(self):
577 self.player = gst.element_factory_make("playbin2", "player")
578 fakesink = gst.element_factory_make("fakesink", "fakesink")
579 self.player.set_property("video-sink", fakesink)
581 bus = self.player.get_bus()
582 bus.add_signal_watch()
583 bus.connect("message", self.on_message)
585 def on_message(self, bus, message):
587 if t == gst.MESSAGE_EOS:
588 self.player.set_state(gst.STATE_NULL)
589 self.is_playing = False
590 elif t == gst.MESSAGE_ERROR:
591 self.player.set_state(gst.STATE_NULL)
592 self.is_playing = False
593 err, debug = message.parse_error()
594 logger.error("ERROR: %s, %s" % (err, debug) )
596 def update(self, optional):
597 diff = self.pedo_controller.get_first() - self.start_value
598 if self.type == 0 and diff.time >= self.interval * 60 or \
599 self.type == 1 and diff.steps >= self.interval or \
600 self.type == 2 and diff.dist >= self.interval or \
601 self.type == 3 and diff.calories >= self.interval:
603 #get new instance of current values
604 self.start_value = PedoValues() + self.pedo_controller.get_first()
605 logger.info("Alarm!")
608 if self.player is None:
611 self.player.set_state(gst.STATE_NULL)
612 self.is_playing = False
614 self.player.set_property("uri", "file://" + self.fname)
615 self.player.set_state(gst.STATE_PLAYING)
616 self.is_playing = True
619 self.player.set_state(gst.STATE_NULL)
621 def set_enable(self, value):
623 self.client.set_bool(ALARM_ENABLE, value)
626 self.pedo_controller.add_observer(self.update)
627 self.start_value = self.pedo_controller.get_first()
631 self.pedo_controller.remove_observer(self.update)
633 def get_enable(self):
636 def set_alarm_file(self, fname):
638 self.client.set_string(ALARM_FNAME, fname)
640 def get_alarm_file(self):
641 if self.fname == None:
645 def set_interval(self, interval):
646 self.interval = interval
647 self.client.set_int(ALARM_INTERVAL, interval)
649 def get_interval(self):
652 def set_type(self, type):
654 self.client.set_int(ALARM_TYPE, type)
659 class PedoCounter(Singleton):
660 COORD_FNAME = "/sys/class/i2c-adapter/i2c-3/3-001d/coord"
661 COORD_FNAME_SDK = "/home/andrei/pedometer-widget-0.1/date.txt"
662 LOGFILE = "/home/user/log_pedometer"
663 #time in ms between two accelerometer data reads
664 COORD_GET_INTERVAL = 10
668 interval_counter = None
669 stop_requested = False
670 update_function = None
674 def __init__(self, update_function=None):
675 if not os.path.exists(self.COORD_FNAME):
676 self.COORD_FNAME = self.COORD_FNAME_SDK
678 self.interval_counter = PedoIntervalCounter()
679 self.update_function = update_function
681 def set_logging(self, value):
684 def get_rotation(self):
685 f = open(self.COORD_FNAME, 'r')
686 coords = [int(w) for w in f.readline().split()]
691 logger.info("Counter started")
692 self.isRunning = True
693 self.stop_requested = False
695 fname = "%d_%d_%d_%d_%d_%d" % time.localtime()[0:6]
696 self.file = open(self.LOGFILE + fname + ".txt", "w")
697 gobject.idle_add(self.run)
700 self.coords = [[], [], []]
701 self.stime = time.time()
703 gobject.timeout_add(self.COORD_GET_INTERVAL, self.read_coords)
706 def read_coords(self):
707 x, y, z = self.get_rotation()
708 self.coords[0].append(int(x))
709 self.coords[1].append(int(y))
710 self.coords[2].append(int(z))
711 now = time.time() - self.stime
713 self.file.write("%d %d %d %f\n" % (self.coords[0][-1], self.coords[1][-1], self.coords[2][-1], now))
718 if self.t[-1] > self.COUNT_INTERVAL or self.stop_requested:
720 gobject.idle_add(self.stop_interval)
723 def stop_interval(self):
724 self.interval_counter.set_vals(self.coords, self.t)
725 cnt = self.interval_counter.number_steps()
727 logger.info("Number of steps detected for last interval %d, number of coords: %d" % (cnt, len(self.t)))
729 gobject.idle_add(self.update_function, cnt, self.stop_requested)
731 if self.stop_requested:
732 gobject.idle_add(self.stop)
734 gobject.idle_add(self.run)
740 logger.info("Counter has finished")
742 def request_stop(self):
743 self.stop_requested = True
744 self.isRunning = False
746 class CustomButton(hildon.Button):
747 def __init__(self, icon):
748 hildon.Button.__init__(self, gtk.HILDON_SIZE_AUTO_WIDTH, hildon.BUTTON_ARRANGEMENT_VERTICAL)
750 self.set_size_request(int(32 * 1.4), int(30 * 1.0))
751 self.retval = self.connect("expose_event", self.expose)
753 def set_icon(self, icon):
756 def expose(self, widget, event):
757 self.context = widget.window.cairo_create()
758 self.context.rectangle(event.area.x, event.area.y,
759 event.area.width, event.area.height)
762 rect = self.get_allocation()
763 self.context.rectangle(rect.x, rect.y, rect.width, rect.height)
764 self.context.set_source_rgba(1, 1, 1, 0)
766 style = self.rc_get_style()
767 color = style.lookup_color("DefaultBackgroundColor")
768 if self.state == gtk.STATE_ACTIVE:
769 style = self.rc_get_style()
770 color = style.lookup_color("SelectionColor")
771 self.context.set_source_rgba (color.red / 65535.0, color.green / 65335.0, color.blue / 65535.0, 0.75);
774 #img = cairo.ImageSurface.create_from_png(self.icon)
776 #self.context.set_source_surface(img)
777 #self.context.set_source_surface(img, rect.width/2 - img.get_width() /2, 0)
779 img.set_from_file(self.icon)
780 buf = img.get_pixbuf()
781 buf = buf.scale_simple(int(32 * 1.5), int(30 * 1.5), gtk.gdk.INTERP_BILINEAR)
783 self.context.set_source_pixbuf(buf, rect.x + (event.area.width / 2 - 15) - 8, rect.y + 1)
784 self.context.scale(200, 200)
789 class CustomEventBox(gtk.EventBox):
792 gtk.EventBox.__init__(self)
794 def do_expose_event(self, event):
795 self.context = self.window.cairo_create()
796 self.context.rectangle(event.area.x, event.area.y,
797 event.area.width, event.area.height)
800 rect = self.get_allocation()
801 self.context.rectangle(rect.x, rect.y, rect.width, rect.height)
803 if self.state == gtk.STATE_ACTIVE:
804 style = self.rc_get_style()
805 color = style.lookup_color("SelectionColor")
806 self.context.set_source_rgba (color.red / 65535.0, color.green / 65335.0, color.blue / 65535.0, 0.75);
808 self.context.set_source_rgba(1, 1, 1, 0)
811 gtk.EventBox.do_expose_event(self, event)
813 class GraphController(Singleton):
814 ytitles = ["Steps", "Average Speed", "Distance", "Calories"]
815 xtitles = ["Day", "Week"] # "Today"]
818 self.repository = PedoRepositoryXML()
820 PedoController().add_observer(self.update_ui)
822 def set_graph(self, widget):
826 def set_current_view(self, view):
828 current_view % len(ytitles) - gives the ytitle
829 current_view / len(ytitles) - gives the xtitle
831 self.current_view = view
833 if self.current_view == len(self.ytitles) * len(self.xtitles):
834 self.current_view = 0
835 self.x_id = self.current_view / len(self.ytitles)
836 self.y_id = self.current_view % len(self.ytitles)
839 self.set_current_view(self.current_view+1)
841 return self.current_view
843 def last_weeks_labels(self):
845 delta = timedelta(days=7)
848 ret.append(d.strftime("Week %W"))
852 def compute_values(self):
855 values = self.repository.get_last_7_days()
857 delta = timedelta(days=1)
859 labels.append(d.ctime().split()[0])
863 values = self.repository.get_last_weeks()
866 labels.append(d.strftime("Week %W"))
867 d = d - timedelta(days=7)
869 values = self.repository.get_today()
873 yvalues = [line.steps for line in values]
875 yvalues = [line.get_avg_speed() for line in values]
877 yvalues = [line.dist for line in values]
879 yvalues = [line.calories for line in values]
881 #determine values for y lines in graph
882 diff = self.get_best_interval_value(max(yvalues))
885 ytext.append(str(int(i*diff)))
887 if self.widget is not None:
890 self.widget.values = yvalues
891 self.widget.ytext = ytext
892 self.widget.xtext = labels
893 self.widget.max_value = diff * 5
894 self.widget.text = self.xtitles[self.x_id] + " / " + self.ytitles[self.y_id]
895 self.widget.queue_draw()
897 logger.error("Widget not set in GraphController")
899 def get_best_interval_value(self, max_value):
900 diff = 1.0 * max_value / 5
901 l = len(str(int(diff)))
902 d = math.pow(10, l/2)
903 val = int(math.ceil(1.0 * diff / d)) * d
908 def update_ui(self, optional=False):
909 """update graph values every x seconds"""
910 if optional and self.last_update - time.time() < 600:
912 if self.widget is None:
915 self.compute_values()
916 self.last_update = time.time()
918 class GraphWidget(gtk.DrawingArea):
921 gtk.DrawingArea.__init__(self)
922 self.set_size_request(-1, 150)
926 self.ytext = [" 0", "1000", "2000", "3000", "4000", "5000"]
927 self.xtext = ["Monday", "Tuesday", "Wednesday","Thursday", "Friday", "Saturday", "Sunday"]
928 self.values = [1500, 3400, 4000, 3600, 3200, 0, 4500]
929 self.max_value = 5000
930 self.text = "All time steps"
932 def do_expose_event(self, event):
933 context = self.window.cairo_create()
935 # set a clip region for the expose event
936 context.rectangle(event.area.x, event.area.y,
937 event.area.width, event.area.height)
942 context.set_operator(cairo.OPERATOR_SOURCE)
943 style = self.rc_get_style()
945 if self.state == gtk.STATE_ACTIVE:
946 color = style.lookup_color("SelectionColor")
948 color = style.lookup_color("DefaultBackgroundColor")
949 context.set_source_rgba (color.red / 65535.0, color.green / 65335.0, color.blue / 65535.0, 0.75)
961 rect = self.get_allocation()
965 cr.select_font_face("Purisa", cairo.FONT_SLANT_NORMAL,
966 cairo.FONT_WEIGHT_NORMAL)
969 #check space needed to display ylabels
970 te = cr.text_extents(self.ytext[-1])
971 border_left = te[2] + 7
973 cr.set_source_rgb(1, 1, 1)
974 cr.move_to(border_left, space_above)
975 cr.line_to(border_left, y-space_below)
979 cr.move_to(border_left, y-space_below)
980 cr.line_to(x-border_right, y-space_below)
984 ydiff = (y-space_above-space_below) / self.yvalues
985 for i in range(self.yvalues):
986 yy = y-space_below-ydiff*(i+1)
987 cr.move_to(border_left, yy)
988 cr.line_to(x-border_right, yy)
989 cr.set_line_width(0.8)
994 yy = y - space_below - ydiff*i + 5
995 te = cr.text_extents(self.ytext[i])
997 cr.move_to(border_left-te[2]-2, yy)
998 cr.show_text(self.ytext[i])
1000 cr.set_font_size(15)
1001 te = cr.text_extents(self.text)
1002 cr.move_to((x-te[2])/2, y-5)
1003 cr.show_text(self.text)
1005 graph_x_space = x - border_left - border_right
1006 graph_y_space = y - space_below - space_above
1007 bar_width = graph_x_space*0.75 / len(self.values)
1008 bar_distance = graph_x_space*0.25 / (1+len(self.values))
1010 #set dummy max value to avoid exceptions
1011 if self.max_value == 0:
1012 self.max_value = 100
1013 for i in range(len(self.values)):
1014 xx = border_left + (i+1)*bar_distance + i * bar_width
1016 height = graph_y_space * (1.0 * self.values[i] / self.max_value)
1017 cr.set_source_rgba(1, 1, 1, 0.75)
1018 cr.rectangle(int(xx), int(yy-height), int(bar_width), int(height))
1021 cr.set_source_rgba(1, 1, 1, 1)
1022 cr.select_font_face("Purisa", cairo.FONT_SLANT_NORMAL,
1023 cairo.FONT_WEIGHT_NORMAL)
1024 cr.set_font_size(13)
1026 cr.rotate(2*math.pi * (-45) / 180)
1027 for i in range(len(self.values)):
1028 xx = y - space_below - 10
1029 yy = border_left + (i+1)*bar_distance + i * bar_width
1030 cr.move_to(-xx, yy + bar_width*1.25 / 2)
1031 cr.show_text(self.xtext[i])
1033 class PedometerHomePlugin(hildondesktop.HomePluginItem):
1037 labels = ["timer", "count", "dist", "avgSpeed", "calories"]
1042 #second view ( day / week/ alltime)
1045 second_view_labels = ["All-time", "Today", "This week"]
1049 pedometerInterval = None
1050 graph_controller = None
1059 no_idle_time = False
1063 hildondesktop.HomePluginItem.__init__(self)
1065 gobject.type_register(CustomEventBox)
1066 gobject.type_register(GraphWidget)
1068 self.client = gconf.client_get_default()
1070 self.mode = self.client.get_int(MODE)
1071 self.height = self.client.get_int(HEIGHT)
1072 self.weight = self.client.get_int(WEIGHT)
1073 self.unit = self.client.get_int(UNIT)
1074 self.aspect = self.client.get_int(ASPECT)
1075 self.second_view = self.client.get_int(SECONDVIEW)
1076 self.graph_view = self.client.get_int(GRAPHVIEW)
1077 self.no_idle_time = self.client.get_bool(NOIDLETIME)
1078 self.logging = self.client.get_bool(LOGGING)
1081 self.client.set_int(MODE, 0)
1082 self.client.set_int(HEIGHT, 0)
1083 self.client.set_int(UNIT, 0)
1084 self.client.set_int(ASPECT, 0)
1085 self.client.set_int(SECONDVIEW, 0)
1086 self.client.set_int(GRAPHVIEW, 0)
1087 self.client.set_bool(NOIDLETIME, False)
1088 self.client.set_bool(LOGGING, False)
1090 self.controller = PedoController()
1091 self.controller.set_height(self.height)
1092 self.controller.set_weight(self.weight)
1093 self.controller.set_mode(self.mode)
1094 self.controller.set_unit(self.unit)
1095 self.controller.set_second_view(self.second_view)
1096 self.controller.set_callback_ui(self.update_values)
1097 self.controller.set_no_idle_time(self.no_idle_time)
1099 self.graph_controller = GraphController()
1100 self.graph_controller.set_current_view(self.graph_view)
1102 self.alarm_controller = AlarmController()
1104 self.button = CustomButton(ICONSPATH + "play.png")
1105 self.button.connect("clicked", self.button_clicked)
1107 self.create_labels(self.labelsC)
1108 self.create_labels(self.labelsT)
1109 self.label_second_view = self.new_label_heading(self.second_view_labels[self.second_view])
1111 self.update_current()
1114 mainHBox = gtk.HBox(spacing=1)
1116 descVBox = gtk.VBox(spacing=1)
1117 descVBox.add(self.new_label_heading())
1118 descVBox.add(self.new_label_heading("Time:"))
1119 descVBox.add(self.new_label_heading("Steps:"))
1120 descVBox.add(self.new_label_heading("Calories:"))
1121 descVBox.add(self.new_label_heading("Distance:"))
1122 descVBox.add(self.new_label_heading("Avg Speed:"))
1124 currentVBox = gtk.VBox(spacing=1)
1125 currentVBox.add(self.new_label_heading("Current"))
1126 currentVBox.add(self.labelsC["timer"])
1127 currentVBox.add(self.labelsC["count"])
1128 currentVBox.add(self.labelsC["calories"])
1129 currentVBox.add(self.labelsC["dist"])
1130 currentVBox.add(self.labelsC["avgSpeed"])
1131 self.currentBox = currentVBox
1133 totalVBox = gtk.VBox(spacing=1)
1134 totalVBox.add(self.label_second_view)
1135 totalVBox.add(self.labelsT["timer"])
1136 totalVBox.add(self.labelsT["count"])
1137 totalVBox.add(self.labelsT["calories"])
1138 totalVBox.add(self.labelsT["dist"])
1139 totalVBox.add(self.labelsT["avgSpeed"])
1140 self.totalBox = totalVBox
1142 buttonVBox = gtk.VBox(spacing=1)
1143 buttonVBox.add(self.new_label_heading(""))
1144 buttonVBox.add(self.button)
1145 buttonVBox.add(self.new_label_heading(""))
1147 eventBox = CustomEventBox()
1148 eventBox.set_visible_window(False)
1149 eventBox.add(totalVBox)
1150 eventBox.connect("button-press-event", self.eventBox_clicked)
1151 eventBox.connect("button-release-event", self.eventBox_clicked_release)
1153 mainHBox.add(buttonVBox)
1154 mainHBox.add(descVBox)
1155 mainHBox.add(currentVBox)
1156 mainHBox.add(eventBox)
1157 self.mainhbox = mainHBox
1159 graph = GraphWidget()
1160 self.graph_controller.set_graph(graph)
1162 eventBoxGraph = CustomEventBox()
1163 eventBoxGraph.set_visible_window(False)
1164 eventBoxGraph.add(graph)
1166 eventBoxGraph.connect("button-press-event", self.eventBoxGraph_clicked)
1167 eventBoxGraph.connect("button-release-event", self.eventBoxGraph_clicked_release)
1168 self.graphBox = eventBoxGraph
1170 self.mainvbox = gtk.VBox()
1172 self.mainvbox.add(mainHBox)
1173 self.mainvbox.add(eventBoxGraph)
1175 self.mainvbox.show_all()
1176 self.add(self.mainvbox)
1177 self.update_aspect()
1179 self.connect("unrealize", self.close_requested)
1180 self.set_settings(True)
1181 self.connect("show-settings", self.show_settings)
1183 def eventBoxGraph_clicked(self, widget, data=None):
1184 widget.set_state(gtk.STATE_ACTIVE)
1186 def eventBoxGraph_clicked_release(self, widget, data=None):
1187 self.graph_view = self.graph_controller.next_view()
1188 self.client.set_int(GRAPHVIEW, self.graph_view)
1190 widget.set_state(gtk.STATE_NORMAL)
1192 def eventBox_clicked(self, widget, data=None):
1193 widget.set_state(gtk.STATE_ACTIVE)
1195 def eventBox_clicked_release(self, widget, data=None):
1196 widget.set_state(gtk.STATE_NORMAL)
1198 self.second_view = (self.second_view + 1) % 3
1199 self.controller.set_second_view(self.second_view)
1200 self.client.set_int(SECONDVIEW, self.second_view)
1202 def new_label_heading(self, title=""):
1203 l = gtk.Label(title)
1204 hildon.hildon_helper_set_logical_font(l, "SmallSystemFont")
1207 def create_labels(self, new_labels):
1208 for label in self.labels:
1210 hildon.hildon_helper_set_logical_font(l, "SmallSystemFont")
1211 hildon.hildon_helper_set_logical_color(l, gtk.RC_FG, gtk.STATE_NORMAL, "ActiveTextColor")
1212 new_labels[label] = l
1214 def update_aspect(self):
1217 self.graphBox.hide_all()
1219 self.graphBox.show_all()
1221 if self.aspect == 0 or self.aspect == 1:
1222 self.currentBox.show_all()
1223 self.totalBox.show_all()
1224 elif self.aspect == 2:
1225 self.currentBox.show_all()
1226 self.totalBox.hide_all()
1228 self.currentBox.hide_all()
1229 self.totalBox.show_all()
1231 x,y = self.size_request()
1234 def update_ui_values(self, labels, values):
1235 labels["timer"].set_label(values.get_print_time())
1236 labels["count"].set_label(values.get_print_steps())
1237 labels["dist"].set_label(values.get_print_distance())
1238 labels["avgSpeed"].set_label(values.get_print_avg_speed())
1239 labels["calories"].set_label(values.get_print_calories())
1241 def update_current(self):
1242 self.update_ui_values(self.labelsC, self.controller.get_first())
1244 def update_total(self):
1245 self.update_ui_values(self.labelsT, self.controller.get_second())
1247 def show_alarm_settings(self, main_button):
1248 def choose_file(widget):
1249 file = hildon.FileChooserDialog(self, gtk.FILE_CHOOSER_ACTION_OPEN, hildon.FileSystemModel() )
1251 if ( file.run() == gtk.RESPONSE_OK):
1252 fname = file.get_filename()
1253 widget.set_value(fname)
1254 self.alarm_controller.set_alarm_file(fname)
1257 def test_sound(button):
1259 self.alarm_controller.play()
1260 except Exception, e:
1261 logger.error("Could not play alarm sound: %s" % e)
1262 hildon.hildon_banner_show_information(self, "None", "Could not play alarm sound")
1264 def enableButton_changed(button):
1265 value = button.get_active()
1266 self.alarm_controller.set_enable(value)
1268 main_button.set_value("Enabled")
1270 main_button.set_value("Disabled")
1272 def selectorType_changed(selector, data, labelEntry2):
1273 self.alarm_controller.set_type(selector.get_active(0))
1274 labelEntry2.set_label(suffix[self.alarm_controller.get_type()])
1276 dialog = gtk.Dialog()
1277 dialog.set_title("Alarm settings")
1278 dialog.add_button("OK", gtk.RESPONSE_OK)
1280 enableButton = hildon.CheckButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT)
1281 enableButton.set_label("Enable alarm")
1282 enableButton.set_active(self.alarm_controller.get_enable())
1283 enableButton.connect("toggled", enableButton_changed)
1285 testButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1286 testButton.set_alignment(0, 0.8, 1, 1)
1287 testButton.set_title("Test sound")
1288 testButton.connect("pressed", test_sound)
1290 fileButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1291 fileButton.set_alignment(0, 0.8, 1, 1)
1292 fileButton.set_title("Alarm sound")
1293 fileButton.set_value(self.alarm_controller.get_alarm_file())
1294 fileButton.connect("pressed", choose_file)
1296 labelEntry = gtk.Label("Notify every:")
1297 suffix = ["mins", "steps", "m/ft", "calories"]
1298 labelEntry2 = gtk.Label(suffix[self.alarm_controller.get_type()])
1299 intervalEntry = hildon.Entry(gtk.HILDON_SIZE_AUTO_WIDTH)
1300 intervalEntry.set_text(str(self.alarm_controller.get_interval()))
1302 selectorType = hildon.TouchSelector(text=True)
1303 selectorType.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
1304 selectorType.append_text("Time")
1305 selectorType.append_text("Steps")
1306 selectorType.append_text("Distance")
1307 selectorType.append_text("Calories")
1308 selectorType.connect("changed", selectorType_changed, labelEntry2)
1310 typePicker = hildon.PickerButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1311 typePicker.set_alignment(0.0, 0.5, 1.0, 1.0)
1312 typePicker.set_title("Alarm type")
1313 typePicker.set_selector(selectorType)
1314 typePicker.set_active(self.alarm_controller.get_type())
1317 hbox.add(labelEntry)
1318 hbox.add(intervalEntry)
1319 hbox.add(labelEntry2)
1321 dialog.vbox.add(enableButton)
1322 dialog.vbox.add(fileButton)
1323 dialog.vbox.add(testButton)
1324 dialog.vbox.add(typePicker)
1325 dialog.vbox.add(hbox)
1328 response = dialog.run()
1329 if response != gtk.RESPONSE_OK:
1332 value = int(intervalEntry.get_text())
1333 self.alarm_controller.set_interval(value)
1336 hildon.hildon_banner_show_information(self, "None", "Invalid interval")
1340 def show_settings(self, widget):
1341 def reset_total_counter(arg):
1342 note = hildon.hildon_note_new_confirmation(self.dialog, "Are you sure you want to delete all your pedometer history?")
1344 if ret == gtk.RESPONSE_OK:
1345 self.controller.reset_all_values()
1346 hildon.hildon_banner_show_information(self, "None", "All history was deleted")
1349 def alarmButton_pressed(widget):
1350 self.show_alarm_settings(widget)
1352 def selector_changed(selector, data):
1353 widget.mode = selector.get_active(0)
1354 widget.client.set_int(MODE, widget.mode)
1355 widget.controller.set_mode(widget.mode)
1357 def selectorH_changed(selector, data):
1358 widget.height = selectorH.get_active(0)
1359 widget.client.set_int(HEIGHT, widget.height)
1360 widget.controller.set_height(widget.height)
1362 def selectorUnit_changed(selector, data):
1363 widget.unit = selectorUnit.get_active(0)
1364 widget.client.set_int(UNIT, widget.unit)
1365 widget.controller.set_unit(widget.unit)
1367 def selectorUI_changed(selector, data):
1368 widget.aspect = selectorUI.get_active(0)
1369 widget.client.set_int(ASPECT, widget.aspect)
1370 widget.update_aspect()
1372 def logButton_changed(checkButton):
1373 widget.logging = checkButton.get_active()
1374 widget.client.set_bool(LOGGING, widget.logging)
1376 def idleButton_changed(idleButton):
1377 widget.no_idle_time = idleButton.get_active()
1378 widget.client.set_bool(NOIDLETIME, widget.no_idle_time)
1379 widget.controller.set_no_idle_time(widget.no_idle_time)
1381 def weight_dialog(button):
1382 dialog = gtk.Dialog("Weight", self.dialog)
1383 dialog.add_button("OK", gtk.RESPONSE_OK)
1385 label = gtk.Label("Weight:")
1387 entry.set_text(str(self.controller.get_weight()))
1389 suffixLabel = gtk.Label(self.controller.get_str_weight_unit())
1394 hbox.add(suffixLabel)
1396 dialog.vbox.add(hbox)
1399 response = dialog.run()
1400 if response != gtk.RESPONSE_OK:
1403 value = int(entry.get_text())
1406 self.controller.set_weight(value)
1407 self.client.set_int(WEIGHT, value)
1408 weightButton.set_value(str(self.controller.get_weight()) + \
1409 " " + self.controller.get_str_weight_unit() )
1412 hildon.hildon_banner_show_information(self, "None", "Invalid weight")
1415 dialog = gtk.Dialog()
1416 dialog.set_title("Settings")
1417 dialog.add_button("OK", gtk.RESPONSE_OK)
1418 self.dialog = dialog
1420 resetButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1421 resetButton.set_title("Reset total counter")
1422 resetButton.set_alignment(0, 0.8, 1, 1)
1423 resetButton.connect("clicked", reset_total_counter)
1425 alarmButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1426 alarmButton.set_title("Alarm")
1427 if self.alarm_controller.get_enable():
1428 alarmButton.set_value("Enabled")
1430 alarmButton.set_value("Disabled")
1431 alarmButton.set_alignment(0, 0.8, 1, 1)
1432 alarmButton.connect("clicked", alarmButton_pressed)
1434 selector = hildon.TouchSelector(text=True)
1435 selector.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
1436 selector.append_text("Walk")
1437 selector.append_text("Run")
1438 selector.connect("changed", selector_changed)
1440 modePicker = hildon.PickerButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1441 modePicker.set_alignment(0.0, 0.5, 1.0, 1.0)
1442 modePicker.set_title("Mode")
1443 modePicker.set_selector(selector)
1444 modePicker.set_active(widget.mode)
1446 selectorH = hildon.TouchSelector(text=True)
1447 selectorH.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
1448 selectorH.append_text("< 1.50 m")
1449 selectorH.append_text("1.50 - 1.65 m")
1450 selectorH.append_text("1.66 - 1.80 m")
1451 selectorH.append_text("1.81 - 1.95 m")
1452 selectorH.append_text(" > 1.95 m")
1453 selectorH.connect("changed", selectorH_changed)
1455 heightPicker = hildon.PickerButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1456 heightPicker.set_alignment(0.0, 0.5, 1.0, 1.0)
1457 heightPicker.set_title("Height")
1458 heightPicker.set_selector(selectorH)
1459 heightPicker.set_active(widget.height)
1461 weightButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1462 weightButton.set_title("Weight")
1463 weightButton.set_alignment(0, 0.8, 1, 1)
1464 weightButton.set_value(str(self.controller.get_weight()) + " " + self.controller.get_str_weight_unit() )
1465 weightButton.connect("clicked", weight_dialog)
1467 selectorUnit = hildon.TouchSelector(text=True)
1468 selectorUnit.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
1469 selectorUnit.append_text("Metric (km)")
1470 selectorUnit.append_text("English (mi)")
1471 selectorUnit.connect("changed", selectorUnit_changed)
1473 unitPicker = hildon.PickerButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1474 unitPicker.set_alignment(0.0, 0.5, 1.0, 1.0)
1475 unitPicker.set_title("Unit")
1476 unitPicker.set_selector(selectorUnit)
1477 unitPicker.set_active(widget.unit)
1479 selectorUI = hildon.TouchSelector(text=True)
1480 selectorUI = hildon.TouchSelector(text=True)
1481 selectorUI.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
1482 selectorUI.append_text("Show current + total + graph")
1483 selectorUI.append_text("Show current + total")
1484 selectorUI.append_text("Show only current")
1485 selectorUI.append_text("Show only total")
1486 selectorUI.connect("changed", selectorUI_changed)
1488 UIPicker = hildon.PickerButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1489 UIPicker.set_alignment(0.0, 0.5, 1.0, 1.0)
1490 UIPicker.set_title("Widget aspect")
1491 UIPicker.set_selector(selectorUI)
1492 UIPicker.set_active(widget.aspect)
1494 logButton = hildon.CheckButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT)
1495 logButton.set_label("Log data")
1496 logButton.set_active(widget.logging)
1497 logButton.connect("toggled", logButton_changed)
1499 idleButton = hildon.CheckButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT)
1500 idleButton.set_label("Pause time when not walking")
1501 idleButton.set_active(widget.no_idle_time)
1502 idleButton.connect("toggled", idleButton_changed)
1504 pan_area = hildon.PannableArea()
1506 vbox.add(alarmButton)
1507 vbox.add(modePicker)
1508 vbox.add(heightPicker)
1509 vbox.add(weightButton)
1510 vbox.add(unitPicker)
1512 vbox.add(idleButton)
1513 vbox.add(resetButton)
1514 #vbox.add(logButton)
1516 pan_area.add_with_viewport(vbox)
1517 pan_area.set_size_request(-1, 300)
1519 dialog.vbox.add(pan_area)
1521 response = dialog.run()
1522 #hildon.hildon_banner_show_information(self, "None", "You have to Stop/Start the counter to apply the new settings")
1525 def close_requested(self, widget):
1526 if self.pedometer is None:
1529 self.pedometer.request_stop()
1531 def update_values(self):
1532 #TODO: do not update if the widget is not on the active desktop
1533 self.label_second_view.set_label(self.second_view_labels[self.second_view])
1534 self.update_current()
1537 def button_clicked(self, button):
1538 if self.controller.is_running:
1539 self.controller.stop_pedometer()
1540 self.button.set_icon(ICONSPATH + "play.png")
1542 self.controller.start_pedometer()
1543 self.button.set_icon(ICONSPATH + "stop.png")
1545 def do_expose_event(self, event):
1546 cr = self.window.cairo_create()
1547 cr.region(event.window.get_clip_region())
1549 #cr.set_source_rgba(0.4, 0.64, 0.564, 0.5)
1550 style = self.rc_get_style()
1551 color = style.lookup_color("DefaultBackgroundColor")
1552 cr.set_source_rgba (color.red / 65535.0, color.green / 65335.0, color.blue / 65535.0, 0.75);
1555 width = self.allocation.width
1556 height = self.allocation.height
1558 x = self.allocation.x
1559 y = self.allocation.y
1561 cr.move_to(x + radius, y)
1562 cr.line_to(x + width - radius, y)
1563 cr.curve_to(x + width - radius, y, x + width, y, x + width, y + radius)
1564 cr.line_to(x + width, y + height - radius)
1565 cr.curve_to(x + width, y + height - radius, x + width, y + height, x + width - radius, y + height)
1566 cr.line_to(x + radius, y + height)
1567 cr.curve_to(x + radius, y + height, x, y + height, x, y + height - radius)
1568 cr.line_to(x, y + radius)
1569 cr.curve_to(x, y + radius, x, y, x + radius, y)
1571 cr.set_operator(cairo.OPERATOR_SOURCE)
1574 color = style.lookup_color("ActiveTextColor")
1575 cr.set_source_rgba (color.red / 65535.0, color.green / 65335.0, color.blue / 65535.0, 0.5);
1576 cr.set_line_width(1)
1579 hildondesktop.HomePluginItem.do_expose_event(self, event)
1581 def do_realize(self):
1582 screen = self.get_screen()
1583 self.set_colormap(screen.get_rgba_colormap())
1584 self.set_app_paintable(True)
1585 hildondesktop.HomePluginItem.do_realize(self)
1587 hd_plugin_type = PedometerHomePlugin
1592 logger = logging.getLogger("pedometer")
1593 logger.setLevel(logging.INFO)
1595 ch = logging.StreamHandler()
1596 formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
1597 ch.setFormatter(formatter)
1598 logger.addHandler(ch)
1600 # The code below is just for testing purposes.
1601 # It allows to run the widget as a standalone process.
1602 if __name__ == "__main__":
1604 gobject.type_register(hd_plugin_type)
1605 obj = gobject.new(hd_plugin_type, plugin_id="plugin_id")