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
37 APP_NAME = "pedometer"
39 PATH = "/apps/pedometerhomewidget"
41 HEIGHT = PATH + "/height"
42 STEP_LENGTH = PATH + "/step_length"
43 WEIGHT = PATH + "/weight"
45 SENSITIVITY = PATH + "/sensitivity"
46 ASPECT = PATH + "/aspect"
47 SECONDVIEW = PATH + "/secondview"
48 GRAPHVIEW = PATH + "/graphview"
49 NOIDLETIME = PATH + "/noidletime"
50 LOGGING = PATH + "/logging"
52 ALARM_PATH = PATH + "/alarm"
53 ALARM_ENABLE = ALARM_PATH + "/enable"
54 ALARM_FNAME = ALARM_PATH + "/fname"
55 ALARM_TYPE = ALARM_PATH + "/type"
56 ALARM_INTERVAL = ALARM_PATH + "/interval"
58 ICONSPATH = "/opt/pedometerhomewidget/"
62 class Singleton(object):
65 def __new__(cls, *args, **kwargs):
68 cls._instance = super(Singleton, cls).__new__(
72 class Translate(Singleton):
75 if self._references > 1:
77 #Get the local directory since we are not installing anything
78 #self.local_path = os.path.realpath(os.path.dirname(sys.argv[0]))
79 self.local_path = os.path.join(os.path.expanduser("~"), "pedometer-widget-0.1", "locale")
80 # Init the list of languages to support
82 #Check the default locale
83 lc, encoding = locale.getdefaultlocale()
85 #If we have a default, it's the first in the list
87 # Now lets get all of the supported languages on the system
88 language = os.environ.get('LANGUAGE', None)
90 """langage comes back something like en_CA:en_US:en_GB:en
91 on linuxy systems, on Win32 it's nothing, so we need to
92 split it up into a list"""
93 langs += language.split(":")
94 """Now add on to the back of the list the translations that we
95 know that we have, our defaults"""
96 langs += ["en_CA", "en_US", "ro_RO"]
98 """Now langs is a list of all of the languages that we are going
99 to try to use. First we check the default, then what the system
100 told us, and finally the 'known' list"""
102 gettext.bindtextdomain(APP_NAME, self.local_path)
103 print self.local_path
105 gettext.textdomain(APP_NAME)
106 # Get the language to use
107 self.lang = gettext.translation(APP_NAME, self.local_path
108 , languages=langs, fallback = True)
109 """Install the language, map _() (which we marked our
110 strings to translate with) to self.lang.gettext() which will
113 _ = Translate().lang.gettext
115 class PedoIntervalCounter(Singleton):
125 #TODO: check if last detected step is at the end of the interval
127 def set_vals(self, coords, tval):
133 def set_mode(self, mode):
134 #runnig, higher threshold to prevent fake steps
137 self.MIN_THRESHOLD = 650.0 * (200 - self.sensitivity) / 100
138 self.MIN_TIME_STEPS = 0.35
141 self.MIN_THRESHOLD = 500.0 * (200 - self.sensitivity) / 100
142 self.MIN_TIME_STEPS = 0.5
144 def set_sensitivity(self, value):
145 self.sensitivity = value
146 self.set_mode(self.mode)
148 def calc_mean(self, vals):
153 return sum / len(vals)
156 def calc_stdev(self, vals):
158 mean = self.calc_mean(vals)
160 rez += pow(abs(mean - i), 2)
161 return math.sqrt(rez / len(vals))
163 def calc_threshold(self, vals):
166 mean = self.calc_mean(vals)
167 threshold = max (abs(mean - vmax), abs(mean - vmin))
170 def count_steps(self, vals, t):
171 threshold = self.MIN_THRESHOLD
172 mean = self.calc_mean(vals)
176 if abs(vals[i] - mean) > threshold:
178 ntime = t[i] + self.MIN_TIME_STEPS
179 while i < len(vals) and t[i] < ntime:
184 def get_best_values(self, x, y, z):
185 dev1 = self.calc_stdev(x)
186 dev2 = self.calc_stdev(y)
187 dev3 = self.calc_stdev(z)
188 dev_max = max(dev1, dev2, dev3)
190 if (abs(dev1 - dev_max) < 0.001):
191 logger.info("X chosen as best axis, stdev %f" % dev1)
193 elif (abs(dev2 - dev_max) < 0.001):
194 logger.info("Y chosen as best axis, stdev %f" % dev2)
197 logger.info("Z chosen as best axis, stdev %f" % dev3)
200 def number_steps(self):
201 vals = self.get_best_values(self.x, self.y, self.z)
202 return self.count_steps(vals, self.t)
205 def __init__(self, time=0, steps=0, dist=0, calories=0):
208 self.calories = calories
211 def __add__(self, other):
212 return PedoValues(self.time + other.time,
213 self.steps + other.steps,
214 self.dist + other.dist,
215 self.calories + other.calories)
217 def __sub__(self, other):
218 return PedoValues(self.time - other.time,
219 self.steps - other.steps,
220 self.dist - other.dist,
221 self.calories - other.calories)
223 def get_print_time(self):
225 hours = int(tdelta / 3600)
226 tdelta -= 3600 * hours
227 mins = int(tdelta / 60)
230 strtime = "%.2d:%.2d:%.2d" % (hours, mins, secs)
233 def get_print_distance(self):
237 return "%.2f km" % (self.dist / 1000)
239 return "%.2f mi" % (self.dist / 1609.344)
242 return "%d m" % self.dist
244 return "%d ft" % int(self.dist * 3.2808)
246 def get_avg_speed(self):
256 speed = 1.0 * self.dist / self.time
259 def get_print_avg_speed(self):
271 return "N/A " + suffix
272 speed = 1.0 * self.dist / self.time
273 #convert from meters per second to km/h or mi/h
275 return "%.2f %s" % (speed, suffix)
277 def get_print_steps(self):
278 return str(self.steps)
280 def get_print_calories(self):
281 return "%.2f" % self.calories
283 class PedoRepository(Singleton):
287 raise NotImplementedError("Must be implemented by subclass")
290 raise NotImplementedError("Must be implemented by subclass")
292 def reset_values(self):
296 def get_history_count(self):
297 """return the number of days in the log"""
300 def get_values(self):
303 def add_values(self, values, when=None):
306 """add PedoValues values to repository """
308 self.values[when] = self.values[when] + values
310 self.values[when] = values
312 def get_last_7_days(self):
317 ret.append(self.values[day])
319 ret.append(PedoValues())
320 day = day - timedelta(days=1)
323 def get_last_weeks(self):
324 delta = timedelta(days=1)
326 week = int(date.today().strftime("%W"))
331 val += self.values[day]
334 w = int(day.strftime("%W"))
344 def get_alltime_values(self):
346 for k, v in self.values.iteritems():
350 def get_today_values(self):
352 return self.values[date.today()]
356 def get_this_week_values(self):
361 ret += self.values[day]
364 if day.weekday() == 0:
366 day = day - timedelta(days=1)
370 class PedoRepositoryXML(PedoRepository):
371 DIR = os.path.join(os.path.expanduser("~"), ".pedometer")
372 FILE = os.path.join(DIR, "data.xml")
373 FILE2 = os.path.join(DIR, "pickle.log")
375 if not os.path.exists(self.DIR):
376 os.makedirs(self.DIR)
377 PedoRepository.__init__(self)
381 f = open(self.FILE, "r")
382 dom = parseString(f.read())
383 values = dom.getElementsByTagName("pedometer")[0]
384 for v in values.getElementsByTagName("date"):
385 d = int(v.getAttribute("ordinal_day"))
386 steps = int(v.getAttribute("steps"))
387 calories = float(v.getAttribute("calories"))
388 dist = float(v.getAttribute("dist"))
389 time = float(v.getAttribute("time"))
390 day = date.fromordinal(d)
391 self.values[day] = PedoValues(time, steps, dist, calories)
395 logger.error("Error while loading data from xml file: %s" % e)
399 f = open(self.FILE, "w")
401 impl = getDOMImplementation()
403 newdoc = impl.createDocument(None, "pedometer", None)
404 top_element = newdoc.documentElement
405 for k, v in self.values.iteritems():
406 d = newdoc.createElement('date')
407 d.setAttribute("day", str(k.isoformat()))
408 d.setAttribute("ordinal_day", str(k.toordinal()))
409 d.setAttribute("steps", str(v.steps))
410 d.setAttribute("time", str(v.time))
411 d.setAttribute("dist", str(v.dist))
412 d.setAttribute("calories", str(v.calories))
413 top_element.appendChild(d)
415 newdoc.appendChild(top_element)
417 #f.write(newdoc.toprettyxml())
420 logger.error("Error while saving data to xml file: %s" % e)
422 class PedoRepositoryPickle(PedoRepository):
423 DIR = os.path.join(os.path.expanduser("~"), ".pedometer")
424 FILE = os.path.join(DIR, "pickle.log")
427 if not os.path.exists(self.DIR):
428 os.makedirs(self.DIR)
429 PedoRepository.__init__(self)
433 f = open(self.FILE, "rb")
434 self.values = pickle.load(f)
437 logger.error("Error while loading pickle file: %s" % e)
441 f = open(self.FILE, "wb")
442 pickle.dump(self.values, f)
445 logger.error("Error while saving data to pickle: %s" % e)
447 class PedoController(Singleton):
453 #what to display in second view - 0 - alltime, 1 - today, 2 - week
455 callback_update_ui = None
460 #The interval(number of steps) between two file updates
461 BUFFER_STEPS_INTERVAL = 100
462 #values for the two views in the widget ( current and day/week/alltime)
463 #third value to count the steps that were not yet written to file
464 v = [PedoValues(), PedoValues(), PedoValues()]
472 midnight_source_id = None
473 midnight_before_source_id = None
477 self.pedometer = PedoCounter(self.steps_detected)
478 self.pedometerInterval = PedoIntervalCounter()
479 self.pedometerInterval.set_mode(self.mode)
480 self.repository = PedoRepositoryXML()
481 self.repository.load()
485 if not self.midnight_set:
486 self.update_at_midnight()
487 self.midnight_set = True
489 self.config = Config()
490 self.config.add_observer(self.load_config)
492 def update_at_midnight(self):
493 next_day = date.today() + timedelta(days=1)
494 diff = int(time.mktime(next_day.timetuple()) - time.time())
495 diff_before = diff - 5
496 diff_after = diff + 5
497 self.midnight_source_id = gobject.timeout_add_seconds(diff_after, self.midnight_callback, True)
498 self.midnight_before_source_id = gobject.timeout_add_seconds(diff_before, self.midnight_before_callback, True)
500 def stop_midnight_callback(self):
501 if self.midnight_source_id is not None:
502 gobject.source_remove(self.midnight_source_id)
503 if self.midnight_before_source_id is not None:
504 gobject.source_remove(self.midnight_before_source_id)
506 def midnight_before_callback(self, first=False):
507 logger.info("Before midnight callback")
509 self.stop_pedometer()
510 self.start_pedometer()
512 self.midnight_before_source_id = gobject.timeout_add_seconds(24*3600, self.midnight_before_callback)
517 def midnight_callback(self, first=False):
518 logger.info("Midnight callback")
522 self.midnight_source_id = gobject.timeout_add_seconds(24*3600, self.midnight_callback)
527 def load_config(self):
528 self.set_height(self.config.get_height(), self.config.get_step_length())
529 self.set_mode(self.config.get_mode())
530 self.set_unit(self.config.get_unit())
531 self.set_weight(self.config.get_weight())
532 self.set_second_view(self.config.get_secondview())
533 self.set_no_idle_time(self.config.get_noidletime())
534 self.set_sensitivity(self.config.get_sensitivity())
536 def load_values(self):
537 if self.second_view == 0:
538 self.v[1] = self.repository.get_alltime_values()
539 elif self.second_view == 1:
540 self.v[1] = self.repository.get_today_values()
542 self.v[1] = self.repository.get_this_week_values()
544 def save_values(self):
545 logger.info("Saving values to file")
546 self.repository.add_values(self.v[2])
547 self.repository.save()
550 def start_pedometer(self):
551 self.v[0] = PedoValues()
552 self.v[2] = PedoValues()
553 self.last_time = time.time()
554 self.is_running = True
555 self.pedometer.start()
558 def reset_all_values(self):
559 self.repository.reset_values()
560 self.v[0] = PedoValues()
561 self.v[1] = PedoValues()
562 self.v[2] = PedoValues()
565 def stop_pedometer(self):
566 self.is_running = False
567 self.pedometer.request_stop()
572 def get_second(self):
574 return self.v[2] + self.v[1]
578 def update_current(self):
580 Update distance and calories for current values based on new height, mode values
582 self.v[0].dist = self.get_distance(self.v[0].steps)
583 self.v[0].calories = self.get_calories(self.v[0].steps)
585 def steps_detected(self, cnt, last_steps=False):
586 if not last_steps and cnt == 0 and self.no_idle_time:
587 logger.info("No steps detected, timer is paused")
589 self.v[0].steps += cnt
590 self.v[0].dist += self.get_distance(cnt)
591 self.v[0].calories += self.get_calories(self.get_distance(cnt))
592 self.v[0].time += time.time() - self.last_time
594 self.v[2].steps += cnt
595 self.v[2].dist += self.get_distance(cnt)
596 self.v[2].calories += self.get_calories(self.get_distance(cnt))
597 self.v[2].time += time.time() - self.last_time
599 if not last_steps and self.v[2].steps > self.BUFFER_STEPS_INTERVAL:
602 self.v[2] = PedoValues()
609 self.last_time = time.time()
611 def get_calories(self, distance):
612 """calculate lost calories for the distance and weight given as parameters
614 #different coefficient for running and walking
620 #convert distance from meters to miles
621 distance *= 0.000621371192
624 #convert weight from kg to pounds
627 return weight * distance * coef
629 def set_mode(self, mode):
631 self.set_height(self.height_interval)
632 self.pedometerInterval.set_mode(self.mode)
635 def set_unit(self, new_unit):
641 def get_str_weight_unit(self, unit=None):
649 def set_weight(self, value):
653 def get_weight(self):
656 def set_sensitivity(self, value):
657 self.sensitivity = value
658 self.pedometerInterval.set_sensitivity(value)
660 def get_sensitivity(self):
661 return self.sensitivity
663 def set_second_view(self, second_view):
664 self.second_view = second_view
668 def set_callback_ui(self, func):
669 self.callback_update_ui = func
671 def set_height(self, height_interval, step_length=None):
672 self.height_interval = height_interval
674 if step_length is None:
675 step_length = self.STEP_LENGTH
676 #set height, will affect the distance
677 if height_interval == 0:
678 self.STEP_LENGTH = 0.59
679 elif height_interval == 1:
680 self.STEP_LENGTH = 0.64
681 elif height_interval == 2:
682 self.STEP_LENGTH = 0.71
683 elif height_interval == 3:
684 self.STEP_LENGTH = 0.77
685 elif height_interval == 4:
686 self.STEP_LENGTH = 0.83
687 elif height_interval == 5:
688 self.STEP_LENGTH = step_length
689 #increase step length if RUNNING
691 self.STEP_LENGTH *= 1.45
694 def set_no_idle_time(self, value):
695 self.no_idle_time = value
697 def get_distance(self, steps=None):
700 return self.STEP_LENGTH * steps;
702 def add_observer(self, func):
704 self.observers.index(func)
706 self.observers.append(func)
708 def remove_observer(self, func):
709 self.observers.remove(func)
711 def notify(self, optional=False):
712 if self.callback_update_ui is not None:
713 self.callback_update_ui()
715 for func in self.observers:
718 class AlarmController(Singleton):
720 fname = "/home/user/MyDocs/.sounds/Ringtones/Bicycle.aac"
726 pedo_controller = None
729 self.client = gconf.client_get_default()
730 self.config = Config()
731 self.config.add_observer(self.load_config)
733 self.pedo_controller = PedoController()
736 self.pedo_controller.add_observer(self.update)
737 self.start_value = self.pedo_controller.get_first()
739 def init_player(self):
740 self.player = gst.element_factory_make("playbin2", "player")
741 fakesink = gst.element_factory_make("fakesink", "fakesink")
742 self.player.set_property("video-sink", fakesink)
744 bus = self.player.get_bus()
745 bus.add_signal_watch()
746 bus.connect("message", self.on_message)
748 def on_message(self, bus, message):
750 if t == gst.MESSAGE_EOS:
751 self.player.set_state(gst.STATE_NULL)
752 self.is_playing = False
753 elif t == gst.MESSAGE_ERROR:
754 self.player.set_state(gst.STATE_NULL)
755 self.is_playing = False
756 err, debug = message.parse_error()
757 logger.error("ERROR: %s, %s" % (err, debug) )
759 def update(self, optional):
760 diff = self.pedo_controller.get_first() - self.start_value
761 if self.type == 0 and diff.time >= self.interval * 60 or \
762 self.type == 1 and diff.steps >= self.interval or \
763 self.type == 2 and diff.dist >= self.interval or \
764 self.type == 3 and diff.calories >= self.interval:
766 #get new instance of current values
767 self.start_value = PedoValues() + self.pedo_controller.get_first()
768 logger.info("Alarm!")
771 if self.player is None:
774 self.player.set_state(gst.STATE_NULL)
775 self.is_playing = False
777 self.player.set_property("uri", "file://" + self.fname)
778 self.player.set_state(gst.STATE_PLAYING)
779 self.is_playing = True
782 if self.player is not None:
783 self.player.set_state(gst.STATE_NULL)
785 def load_config(self):
786 self.enable = self.config.get_alarm_enable()
787 self.set_alarm_file(self.config.get_alarm_fname())
788 self.set_interval(self.config.get_alarm_interval())
789 self.set_type(self.config.get_alarm_type())
791 def set_enable(self, value):
795 self.pedo_controller.add_observer(self.update)
796 self.start_value = self.pedo_controller.get_first()
800 self.pedo_controller.remove_observer(self.update)
802 def get_enable(self):
805 def set_alarm_file(self, fname):
808 def get_alarm_file(self):
809 if self.fname == None:
813 def set_interval(self, interval):
814 self.interval = interval
816 def get_interval(self):
819 def set_type(self, type):
825 class PedoCounter(Singleton):
826 COORD_FNAME = "/sys/class/i2c-adapter/i2c-3/3-001d/coord"
827 COORD_FNAME_SDK = "/home/andrei/pedometer-widget-0.1/date.txt"
828 LOGFILE = "/home/user/log_pedometer"
829 #time in ms between two accelerometer data reads
830 COORD_GET_INTERVAL = 25
834 interval_counter = None
835 stop_requested = False
836 update_function = None
840 def __init__(self, update_function=None):
841 if not os.path.exists(self.COORD_FNAME):
842 self.COORD_FNAME = self.COORD_FNAME_SDK
844 self.interval_counter = PedoIntervalCounter()
845 self.update_function = update_function
847 def set_logging(self, value):
850 def get_rotation(self):
851 f = open(self.COORD_FNAME, 'r')
852 coords = [int(w) for w in f.readline().split()]
857 logger.info("Counter started")
858 self.isRunning = True
859 self.stop_requested = False
861 fname = "%d_%d_%d_%d_%d_%d" % time.localtime()[0:6]
862 self.file = open(self.LOGFILE + fname + ".txt", "w")
863 gobject.idle_add(self.run)
866 self.coords = [[], [], []]
867 self.stime = time.time()
869 gobject.timeout_add(self.COORD_GET_INTERVAL, self.read_coords)
872 def read_coords(self):
873 x, y, z = self.get_rotation()
874 self.coords[0].append(int(x))
875 self.coords[1].append(int(y))
876 self.coords[2].append(int(z))
877 now = time.time() - self.stime
879 self.file.write("%d %d %d %f\n" % (self.coords[0][-1], self.coords[1][-1], self.coords[2][-1], now))
884 if self.t[-1] > self.COUNT_INTERVAL or self.stop_requested:
886 gobject.idle_add(self.stop_interval)
889 def stop_interval(self):
890 self.interval_counter.set_vals(self.coords, self.t)
891 cnt = self.interval_counter.number_steps()
893 logger.info("Number of steps detected for last interval %d, number of coords: %d" % (cnt, len(self.t)))
895 gobject.idle_add(self.update_function, cnt, self.stop_requested)
897 if self.stop_requested:
898 gobject.idle_add(self.stop)
900 gobject.idle_add(self.run)
906 logger.info("Counter has finished")
908 def request_stop(self):
909 self.stop_requested = True
910 self.isRunning = False
912 class CustomButton(hildon.Button):
913 def __init__(self, icon):
914 hildon.Button.__init__(self, gtk.HILDON_SIZE_AUTO_WIDTH, hildon.BUTTON_ARRANGEMENT_VERTICAL)
916 self.set_size_request(int(32 * 1.4), int(30 * 1.0))
917 self.retval = self.connect("expose_event", self.expose)
919 def set_icon(self, icon):
922 def expose(self, widget, event):
923 self.context = widget.window.cairo_create()
924 self.context.rectangle(event.area.x, event.area.y,
925 event.area.width, event.area.height)
928 rect = self.get_allocation()
929 self.context.rectangle(rect.x, rect.y, rect.width, rect.height)
930 self.context.set_source_rgba(1, 1, 1, 0)
932 style = self.rc_get_style()
933 color = style.lookup_color("DefaultBackgroundColor")
934 if self.state == gtk.STATE_ACTIVE:
935 style = self.rc_get_style()
936 color = style.lookup_color("SelectionColor")
937 self.context.set_source_rgba (color.red / 65535.0, color.green / 65335.0, color.blue / 65535.0, 0.75);
940 #img = cairo.ImageSurface.create_from_png(self.icon)
942 #self.context.set_source_surface(img)
943 #self.context.set_source_surface(img, rect.width/2 - img.get_width() /2, 0)
945 img.set_from_file(self.icon)
946 buf = img.get_pixbuf()
947 buf = buf.scale_simple(int(32 * 1.5), int(30 * 1.5), gtk.gdk.INTERP_BILINEAR)
949 self.context.set_source_pixbuf(buf, rect.x + (event.area.width / 2 - 15) - 8, rect.y + 1)
950 self.context.scale(200, 200)
955 class CustomEventBox(gtk.EventBox):
958 gtk.EventBox.__init__(self)
960 def do_expose_event(self, event):
961 self.context = self.window.cairo_create()
962 self.context.rectangle(event.area.x, event.area.y,
963 event.area.width, event.area.height)
966 rect = self.get_allocation()
967 self.context.rectangle(rect.x, rect.y, rect.width, rect.height)
969 if self.state == gtk.STATE_ACTIVE:
970 style = self.rc_get_style()
971 color = style.lookup_color("SelectionColor")
972 self.context.set_source_rgba (color.red / 65535.0, color.green / 65335.0, color.blue / 65535.0, 0.75);
974 self.context.set_source_rgba(1, 1, 1, 0)
977 gtk.EventBox.do_expose_event(self, event)
979 class GraphController(Singleton):
980 ytitles = [_("Steps"), _("Average Speed"), _("Distance"), _("Calories")]
981 xtitles = [_("Day"), _("Week")] # "Today"]
987 self.repository = PedoRepositoryXML()
989 PedoController().add_observer(self.update_ui)
990 self.config = Config()
991 self.config.add_observer(self.load_config)
993 def load_config(self):
994 self.set_current_view(self.config.get_graphview())
996 def set_graph(self, widget):
1000 def set_current_view(self, view):
1002 current_view % len(ytitles) - gives the ytitle
1003 current_view / len(ytitles) - gives the xtitle
1005 self.x_id = view / len(self.ytitles)
1006 self.y_id = view % len(self.ytitles)
1009 def next_view(self):
1010 current_view = self.config.get_graphview() + 1
1011 if current_view == len(self.ytitles) * len(self.xtitles):
1013 self.config.set_graphview(current_view)
1015 def last_weeks_labels(self):
1017 delta = timedelta(days=7)
1020 ret.append(_("Week") + d.strftime("%W"))
1024 def compute_values(self):
1027 values = self.repository.get_last_7_days()
1029 delta = timedelta(days=1)
1031 labels.append(d.ctime().split()[0])
1034 elif self.x_id == 1:
1035 values = self.repository.get_last_weeks()
1038 labels.append(_("Week") + " " + d.strftime("%W"))
1039 d = d - timedelta(days=7)
1041 values = self.repository.get_today()
1045 yvalues = [line.steps for line in values]
1046 elif self.y_id == 1:
1047 yvalues = [line.get_avg_speed() for line in values]
1048 elif self.y_id == 2:
1049 yvalues = [line.dist for line in values]
1051 yvalues = [line.calories for line in values]
1053 #determine values for y lines in graph
1054 diff = self.get_best_interval_value(max(yvalues))
1057 ytext.append(str(int(i*diff)))
1059 if self.widget is not None:
1062 self.widget.values = yvalues
1063 self.widget.ytext = ytext
1064 self.widget.xtext = labels
1065 self.widget.max_value = diff * 5
1066 self.widget.text = self.xtitles[self.x_id] + " / " + self.ytitles[self.y_id]
1067 self.widget.queue_draw()
1069 logger.error("Widget not set in GraphController")
1071 def get_best_interval_value(self, max_value):
1072 diff = 1.0 * max_value / 5
1073 l = len(str(int(diff)))
1074 d = math.pow(10, l/2)
1075 val = int(math.ceil(1.0 * diff / d)) * d
1080 def update_ui(self, optional=False):
1081 """update graph values every x seconds"""
1082 if optional and self.last_update - time.time() < 600:
1084 if self.widget is None:
1087 self.compute_values()
1088 self.last_update = time.time()
1090 class GraphWidget(gtk.DrawingArea):
1093 gtk.DrawingArea.__init__(self)
1094 self.set_size_request(-1, 150)
1098 self.ytext = [" 0", "1000", "2000", "3000", "4000", "5000"]
1099 self.xtext = [_("Monday"), _("Tuesday"), _("Wednesday"), _("Thursday"), _("Friday"), _("Saturday"), _("Sunday")]
1100 self.values = [1500, 3400, 4000, 3600, 3200, 0, 4500]
1101 self.max_value = 5000
1102 self.text = _("All time steps")
1104 def do_expose_event(self, event):
1105 context = self.window.cairo_create()
1107 # set a clip region for the expose event
1108 context.rectangle(event.area.x, event.area.y,
1109 event.area.width, event.area.height)
1114 context.set_operator(cairo.OPERATOR_SOURCE)
1115 style = self.rc_get_style()
1117 if self.state == gtk.STATE_ACTIVE:
1118 color = style.lookup_color("SelectionColor")
1120 color = style.lookup_color("DefaultBackgroundColor")
1121 context.set_source_rgba (color.red / 65535.0, color.green / 65335.0, color.blue / 65535.0, 0.75)
1133 rect = self.get_allocation()
1137 cr.select_font_face("Purisa", cairo.FONT_SLANT_NORMAL,
1138 cairo.FONT_WEIGHT_NORMAL)
1139 cr.set_font_size(13)
1141 #check space needed to display ylabels
1142 te = cr.text_extents(self.ytext[-1])
1143 border_left = te[2] + 7
1145 cr.set_source_rgb(1, 1, 1)
1146 cr.move_to(border_left, space_above)
1147 cr.line_to(border_left, y-space_below)
1148 cr.set_line_width(2)
1151 cr.move_to(border_left, y-space_below)
1152 cr.line_to(x-border_right, y-space_below)
1153 cr.set_line_width(2)
1156 ydiff = (y-space_above-space_below) / self.yvalues
1157 for i in range(self.yvalues):
1158 yy = y-space_below-ydiff*(i+1)
1159 cr.move_to(border_left, yy)
1160 cr.line_to(x-border_right, yy)
1161 cr.set_line_width(0.8)
1166 yy = y - space_below - ydiff*i + 5
1167 te = cr.text_extents(self.ytext[i])
1169 cr.move_to(border_left-te[2]-2, yy)
1170 cr.show_text(self.ytext[i])
1172 cr.set_font_size(15)
1173 te = cr.text_extents(self.text)
1174 cr.move_to((x-te[2])/2, y-5)
1175 cr.show_text(self.text)
1177 graph_x_space = x - border_left - border_right
1178 graph_y_space = y - space_below - space_above
1179 bar_width = graph_x_space*0.75 / len(self.values)
1180 bar_distance = graph_x_space*0.25 / (1+len(self.values))
1182 #set dummy max value to avoid exceptions
1183 if self.max_value == 0:
1184 self.max_value = 100
1185 for i in range(len(self.values)):
1186 xx = border_left + (i+1)*bar_distance + i * bar_width
1188 height = graph_y_space * (1.0 * self.values[i] / self.max_value)
1189 cr.set_source_rgba(1, 1, 1, 0.75)
1190 cr.rectangle(int(xx), int(yy-height), int(bar_width), int(height))
1193 cr.set_source_rgba(1, 1, 1, 1)
1194 cr.select_font_face("Purisa", cairo.FONT_SLANT_NORMAL,
1195 cairo.FONT_WEIGHT_NORMAL)
1196 cr.set_font_size(13)
1198 cr.rotate(2*math.pi * (-45) / 180)
1199 for i in range(len(self.values)):
1200 xx = y - space_below - 10
1201 yy = border_left + (i+1)*bar_distance + i * bar_width
1202 cr.move_to(-xx, yy + bar_width*1.25 / 2)
1203 cr.show_text(self.xtext[i])
1205 class Config(Singleton):
1216 no_idle_time = False
1219 alarm_enable = False
1220 alarm_fname = "/home/user/MyDocs/.sounds/Ringtones/Bicycle.aac"
1227 if self._references > 1:
1229 self.client = gconf.client_get_default()
1230 self.client.add_dir('/apps/pedometerhomewidget', gconf.CLIENT_PRELOAD_RECURSIVE)
1231 self.notify_id = self.client.notify_add('/apps/pedometerhomewidget', self.gconf_changed)
1233 def add_observer(self, func):
1235 self.observers.index(func)
1237 self.observers.append(func)
1240 def remove_observer(self, func):
1241 self.observers.remove(func)
1243 def gconf_changed(self, client, *args, **kargs):
1248 for func in self.observers:
1251 logger.info("Update took: %f seconds" % (t2-t1))
1254 return self.client.get_int(MODE)
1256 def set_mode(self, value):
1257 self.client.set_int(MODE, value)
1259 def get_height(self):
1260 return self.client.get_int(HEIGHT)
1262 def set_height(self, value):
1263 self.client.set_int(HEIGHT, value)
1265 def get_step_length(self):
1266 return self.client.get_float(STEP_LENGTH)
1268 def set_step_length(self, value):
1269 self.client.set_float(STEP_LENGTH, value)
1271 def get_weight(self):
1272 return self.client.get_int(WEIGHT)
1274 def set_weight(self, value):
1275 self.client.set_int(WEIGHT, value)
1277 def get_sensitivity(self):
1278 return self.client.get_int(SENSITIVITY)
1280 def set_sensitivity(self, value):
1281 self.client.set_int(SENSITIVITY, value)
1284 return self.client.get_int(UNIT)
1286 def set_unit(self, value):
1287 self.client.set_int(UNIT, value)
1289 def get_aspect(self):
1290 return self.client.get_int(ASPECT)
1292 def set_aspect(self, value):
1293 self.client.set_int(ASPECT, value)
1295 def get_secondview(self):
1296 value = self.client.get_int(SECONDVIEW)
1297 if value < 0 or value > 2:
1299 logger.error("Invalid secondview value read from Gconf. Using default value")
1303 def set_secondview(self, value):
1304 self.client.set_int(SECONDVIEW, value)
1306 def get_graphview(self):
1307 return self.client.get_int(GRAPHVIEW)
1309 def set_graphview(self, value):
1310 self.client.set_int(GRAPHVIEW, value)
1312 def get_noidletime(self):
1313 return self.client.get_bool(NOIDLETIME)
1315 def set_noidletime(self, value):
1316 self.client.set_bool(NOIDLETIME, value)
1318 def get_logging(self):
1319 return self.client.get_bool(LOGGING)
1321 def set_logging(self, value):
1322 self.client.set_bool(LOGGING, value)
1324 def get_alarm_enable(self):
1325 return self.client.get_bool(ALARM_ENABLE)
1327 def set_alarm_enable(self, value):
1328 self.client.set_bool(ALARM_ENABLE, value)
1330 def get_alarm_fname(self):
1331 return self.client.get_string(ALARM_FNAME)
1333 def set_alarm_fname(self, value):
1334 self.client.set_string(ALARM_FNAME, value)
1336 def get_alarm_interval(self):
1337 return self.client.get_int(ALARM_INTERVAL)
1339 def set_alarrm_interval(self, value):
1340 self.client.set_int(ALARM_INTERVAL, value)
1342 def get_alarm_type(self):
1343 return self.client.get_int(ALARM_TYPE)
1345 def set_alarm_type(self, value):
1346 self.client.set_int(ALARM_TYPE, value)
1349 class PedometerHomePlugin(hildondesktop.HomePluginItem):
1353 labels = ["timer", "count", "dist", "avgSpeed", "calories"]
1358 #second view ( day / week/ alltime)
1361 second_view_labels = [_("All-time"), _("Today"), _("This week")]
1364 graph_controller = None
1369 hildondesktop.HomePluginItem.__init__(self)
1371 print "!!!!!!!!!!!!!!!Pedometer init"
1372 gobject.type_register(CustomEventBox)
1373 gobject.type_register(GraphWidget)
1378 self.config = Config()
1380 self.button = CustomButton(ICONSPATH + "play.png")
1381 self.button.connect("clicked", self.button_clicked)
1383 self.create_labels(self.labelsC)
1384 self.create_labels(self.labelsT)
1385 self.label_second_view = self.new_label_heading(self.second_view_labels[self.config.get_secondview()])
1387 self.controller = PedoController()
1388 self.controller.set_callback_ui(self.update_values)
1390 self.graph_controller = GraphController()
1391 self.alarm_controller = AlarmController()
1393 self.update_current()
1396 mainHBox = gtk.HBox(spacing=1)
1398 descVBox = gtk.VBox(spacing=1)
1399 descVBox.add(self.new_label_heading())
1400 descVBox.add(self.new_label_heading(_("Time") + ":"))
1401 descVBox.add(self.new_label_heading(_("Steps") + ":"))
1402 descVBox.add(self.new_label_heading(_("Calories") + ":"))
1403 descVBox.add(self.new_label_heading(_("Distance") + ":"))
1404 descVBox.add(self.new_label_heading(_("Avg Speed") + ":"))
1406 currentVBox = gtk.VBox(spacing=1)
1407 currentVBox.add(self.new_label_heading(_("Current")))
1408 currentVBox.add(self.labelsC["timer"])
1409 currentVBox.add(self.labelsC["count"])
1410 currentVBox.add(self.labelsC["calories"])
1411 currentVBox.add(self.labelsC["dist"])
1412 currentVBox.add(self.labelsC["avgSpeed"])
1413 self.currentBox = currentVBox
1415 totalVBox = gtk.VBox(spacing=1)
1416 totalVBox.add(self.label_second_view)
1417 totalVBox.add(self.labelsT["timer"])
1418 totalVBox.add(self.labelsT["count"])
1419 totalVBox.add(self.labelsT["calories"])
1420 totalVBox.add(self.labelsT["dist"])
1421 totalVBox.add(self.labelsT["avgSpeed"])
1422 self.totalBox = totalVBox
1424 buttonVBox = gtk.VBox(spacing=1)
1425 buttonVBox.add(self.new_label_heading(""))
1426 buttonVBox.add(self.button)
1427 buttonVBox.add(self.new_label_heading(""))
1429 eventBox = CustomEventBox()
1430 eventBox.set_visible_window(False)
1431 eventBox.add(totalVBox)
1432 eventBox.connect("button-press-event", self.eventBox_clicked)
1433 eventBox.connect("button-release-event", self.eventBox_clicked_release)
1435 mainHBox.add(buttonVBox)
1436 mainHBox.add(descVBox)
1437 mainHBox.add(currentVBox)
1438 mainHBox.add(eventBox)
1439 self.mainhbox = mainHBox
1441 graph = GraphWidget()
1442 self.graph_controller.set_graph(graph)
1444 eventBoxGraph = CustomEventBox()
1445 eventBoxGraph.set_visible_window(False)
1446 eventBoxGraph.add(graph)
1448 eventBoxGraph.connect("button-press-event", self.eventBoxGraph_clicked)
1449 eventBoxGraph.connect("button-release-event", self.eventBoxGraph_clicked_release)
1450 self.graphBox = eventBoxGraph
1452 self.mainvbox = gtk.VBox()
1454 self.mainvbox.add(mainHBox)
1455 self.mainvbox.add(eventBoxGraph)
1457 self.mainvbox.show_all()
1458 self.add(self.mainvbox)
1459 self.update_aspect()
1461 self.connect("unrealize", self.close_requested)
1462 self.set_settings(True)
1463 self.connect("show-settings", self.show_settings)
1465 def eventBoxGraph_clicked(self, widget, data=None):
1466 widget.set_state(gtk.STATE_ACTIVE)
1468 def eventBoxGraph_clicked_release(self, widget, data=None):
1469 self.graph_controller.next_view()
1470 widget.set_state(gtk.STATE_NORMAL)
1472 def eventBox_clicked(self, widget, data=None):
1473 widget.set_state(gtk.STATE_ACTIVE)
1475 def eventBox_clicked_release(self, widget, data=None):
1476 widget.set_state(gtk.STATE_NORMAL)
1478 second_view = self.config.get_secondview()
1479 second_view = (second_view + 1) % 3
1480 self.config.set_secondview(second_view)
1482 def new_label_heading(self, title=""):
1483 l = gtk.Label(title)
1484 hildon.hildon_helper_set_logical_font(l, "SmallSystemFont")
1487 def create_labels(self, new_labels):
1488 for label in self.labels:
1490 hildon.hildon_helper_set_logical_font(l, "SmallSystemFont")
1491 hildon.hildon_helper_set_logical_color(l, gtk.RC_FG, gtk.STATE_NORMAL, "ActiveTextColor")
1492 new_labels[label] = l
1494 def update_aspect(self):
1495 aspect = self.config.get_aspect()
1497 self.graphBox.hide_all()
1499 self.graphBox.show_all()
1501 if aspect == 0 or aspect == 1:
1502 self.currentBox.show_all()
1503 self.totalBox.show_all()
1505 self.currentBox.show_all()
1506 self.totalBox.hide_all()
1508 self.currentBox.hide_all()
1509 self.totalBox.show_all()
1511 x,y = self.size_request()
1514 def update_ui_values(self, labels, values):
1515 labels["timer"].set_label(values.get_print_time())
1516 labels["count"].set_label(values.get_print_steps())
1517 labels["dist"].set_label(values.get_print_distance())
1518 labels["avgSpeed"].set_label(values.get_print_avg_speed())
1519 labels["calories"].set_label(values.get_print_calories())
1521 def update_current(self):
1522 self.update_ui_values(self.labelsC, self.controller.get_first())
1524 def update_total(self):
1525 self.update_ui_values(self.labelsT, self.controller.get_second())
1527 def show_alarm_settings(self, main_button):
1528 def choose_file(widget):
1529 file = hildon.FileChooserDialog(self, gtk.FILE_CHOOSER_ACTION_OPEN, hildon.FileSystemModel() )
1531 if ( file.run() == gtk.RESPONSE_OK):
1532 fname = file.get_filename()
1533 widget.set_value(fname)
1534 self.config.set_alarm_fname(fname)
1537 def test_sound(button):
1539 self.alarm_controller.play()
1540 except Exception, e:
1541 logger.error("Could not play alarm sound: %s" % e)
1542 hildon.hildon_banner_show_information(self, "None", "Could not play alarm sound")
1544 def enableButton_changed(button):
1545 value = button.get_active()
1546 self.config.set_alarm_enable(value)
1548 main_button.set_value("Enabled")
1550 main_button.set_value("Disabled")
1552 def selectorType_changed(selector, data, labelEntry2):
1553 type = selector.get_active(0)
1554 self.config.set_alarm_type(type)
1555 labelEntry2.set_label(suffix[type])
1557 dialog = gtk.Dialog()
1558 dialog.set_title(_("Alarm settings"))
1559 dialog.add_button(_("OK"), gtk.RESPONSE_OK)
1561 enableButton = hildon.CheckButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT)
1562 enableButton.set_label(_("Enable alarm"))
1563 enableButton.set_active(self.alarm_controller.get_enable())
1564 enableButton.connect("toggled", enableButton_changed)
1566 testButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1567 testButton.set_alignment(0, 0.8, 1, 1)
1568 testButton.set_title(_("Test sound"))
1569 testButton.connect("pressed", test_sound)
1571 fileButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1572 fileButton.set_alignment(0, 0.8, 1, 1)
1573 fileButton.set_title(_("Alarm sound"))
1574 fileButton.set_value(self.alarm_controller.get_alarm_file())
1575 fileButton.connect("pressed", choose_file)
1577 labelEntry = gtk.Label(_("Notify every") + ":")
1578 suffix = [_("mins"), _("steps"), _("m/ft"), _("calories")]
1579 labelEntry2 = gtk.Label(suffix[self.alarm_controller.get_type()])
1580 intervalEntry = hildon.Entry(gtk.HILDON_SIZE_AUTO_WIDTH)
1581 intervalEntry.set_text(str(self.alarm_controller.get_interval()))
1583 selectorType = hildon.TouchSelector(text=True)
1584 selectorType.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
1585 selectorType.append_text(_("Time"))
1586 selectorType.append_text(_("Steps"))
1587 selectorType.append_text(_("Distance"))
1588 selectorType.append_text(_("Calories"))
1589 selectorType.connect("changed", selectorType_changed, labelEntry2)
1591 typePicker = hildon.PickerButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1592 typePicker.set_alignment(0.0, 0.5, 1.0, 1.0)
1593 typePicker.set_title(_("Alarm type"))
1594 typePicker.set_selector(selectorType)
1595 typePicker.set_active(self.alarm_controller.get_type())
1598 hbox.add(labelEntry)
1599 hbox.add(intervalEntry)
1600 hbox.add(labelEntry2)
1602 dialog.vbox.add(enableButton)
1603 dialog.vbox.add(fileButton)
1604 dialog.vbox.add(testButton)
1605 dialog.vbox.add(typePicker)
1606 dialog.vbox.add(hbox)
1609 response = dialog.run()
1610 if response != gtk.RESPONSE_OK:
1613 value = int(intervalEntry.get_text())
1614 self.config.set_alarrm_interval(value)
1617 hildon.hildon_banner_show_information(self, "None", _("Invalid interval"))
1621 def show_settings(self, widget):
1622 def reset_total_counter(arg):
1623 note = hildon.hildon_note_new_confirmation(self.dialog, _("Are you sure you want to delete all your pedometer history?"))
1625 if ret == gtk.RESPONSE_OK:
1626 self.controller.reset_all_values()
1627 hildon.hildon_banner_show_information(self, "None", _("All history was deleted"))
1630 def alarmButton_pressed(widget):
1631 self.show_alarm_settings(widget)
1633 def selector_changed(selector, data):
1634 mode = selector.get_active(0)
1635 self.config.set_mode(mode)
1637 def selectorUnit_changed(selector, data):
1638 unit = selector.get_active(0)
1639 self.config.set_unit(unit)
1641 update_weight_button()
1642 stepLengthButton_value_update()
1644 def selectorUI_changed(selector, data):
1645 aspect = selectorUI.get_active(0)
1646 widget.update_aspect()
1648 def logButton_changed(checkButton):
1649 logging = checkButton.get_active()
1650 self.config.set_logging(logging)
1652 def idleButton_changed(idleButton):
1653 no_idle_time = idleButton.get_active()
1654 self.config.set_noidletime(no_idle_time)
1656 def update_weight_button():
1657 weightButton.set_value(str(self.config.get_weight()) + \
1658 " " + self.controller.get_str_weight_unit(self.config.get_unit()) )
1660 def weight_dialog(button):
1661 dialog = gtk.Dialog(_("Weight"), self.dialog)
1662 dialog.add_button(_("OK"), gtk.RESPONSE_OK)
1664 label = gtk.Label(_("Weight") + ":")
1666 entry.set_text(str(self.config.get_weight()))
1668 suffixLabel = gtk.Label(self.controller.get_str_weight_unit(self.config.get_unit()))
1673 hbox.add(suffixLabel)
1675 dialog.vbox.add(hbox)
1678 response = dialog.run()
1679 if response != gtk.RESPONSE_OK:
1682 value = int(entry.get_text())
1685 self.config.set_weight(value)
1686 update_weight_button()
1689 hildon.hildon_banner_show_information(self, "None", _("Invalid weight"))
1692 def sensitivity_dialog(button):
1693 def seekbar_changed(seekbar):
1694 label.set_text(str(seekbar.get_position()) + " %")
1696 dialog = gtk.Dialog(_("Sensitivity"), self.dialog)
1697 dialog.add_button(_("OK"), gtk.RESPONSE_OK)
1698 seekbar = hildon.Seekbar()
1699 seekbar.set_size_request(400, -1)
1700 seekbar.set_total_time(200)
1701 seekbar.set_position(self.config.get_sensitivity())
1702 seekbar.connect("value-changed", seekbar_changed)
1706 label = gtk.Label(str(self.config.get_sensitivity()) + " %")
1707 label.set_size_request(30, -1)
1710 dialog.vbox.add(hbox)
1713 if dialog.run() == gtk.RESPONSE_OK:
1714 value = seekbar.get_position()
1715 self.config.set_sensitivity(value)
1716 button.set_value(str(value) + " %")
1720 def stepLengthButton_value_update():
1721 if self.config.get_height() == 5:
1722 l_unit = ["m", "ft"]
1723 stepLengthButton.set_value(_("Custom value") + ": %.2f %s" % (self.config.get_step_length(), l_unit[self.config.get_unit()]))
1725 h = [ ["< 1.50 m", "1.50 - 1.65 m", "1.66 - 1.80 m", "1.81 - 1.95 m", " > 1.95 m"],
1726 ["< 5 ft", "5 - 5.5 ft", "5.5 - 6 ft", "6 - 6.5 ft", "> 6.5 ft"]]
1727 str = _("Using predefined value for height") + ": %s" % h[self.config.get_unit()][self.config.get_height()]
1728 stepLengthButton.set_value(str)
1730 def stepLength_dialog(button):
1731 def selectorH_changed(selector, data, dialog):
1732 height = selector.get_active(0)
1733 self.config.set_height(height)
1734 stepLengthButton_value_update()
1736 def manualButton_clicked(button, dialog):
1738 dlg.set_title(_("Custom step length"))
1739 dlg.add_button(_("OK"), gtk.RESPONSE_OK)
1741 label = gtk.Label(_("Length"))
1743 entry = hildon.Entry(gtk.HILDON_SIZE_AUTO_WIDTH)
1744 if self.config.get_height() == 5:
1745 entry.set_text(str(self.config.get_step_length()))
1747 labelSuffix = gtk.Label()
1748 if self.config.get_unit() == 0:
1749 labelSuffix.set_label("m")
1751 labelSuffix.set_label("ft")
1755 hbox.add(labelSuffix)
1760 response = dlg.run()
1761 if response != gtk.RESPONSE_OK:
1764 value = float(entry.get_text())
1767 self.config.set_step_length(value)
1768 self.config.set_height(5)
1769 stepLengthButton_value_update()
1772 hildon.hildon_banner_show_information(self, "None", _("Invalid length"))
1776 def heightButton_clicked(button, dialog):
1779 dialog = gtk.Dialog()
1780 dialog.set_title(_("Step length"))
1782 manualButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1783 manualButton.set_title(_("Enter custom value"))
1784 manualButton.set_alignment(0, 0.8, 1, 1)
1785 manualButton.connect("clicked", manualButton_clicked, dialog)
1787 selectorH = hildon.TouchSelector(text=True)
1788 selectorH.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
1789 selectorH.append_text("< 1.50 m")
1790 selectorH.append_text("1.50 - 1.65 m")
1791 selectorH.append_text("1.66 - 1.80 m")
1792 selectorH.append_text("1.81 - 1.95 m")
1793 selectorH.append_text(" > 1.95 m")
1795 selectorH_English = hildon.TouchSelector(text=True)
1796 selectorH_English.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
1797 selectorH_English.append_text("< 5 ft")
1798 selectorH_English.append_text("5 - 5.5 ft")
1799 selectorH_English.append_text("5.5 - 6 ft")
1800 selectorH_English.append_text("6 - 6.5 ft")
1801 selectorH_English.append_text("> 6.5 ft")
1803 heightPicker = hildon.PickerButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1804 heightPicker.set_alignment(0.0, 0.5, 1.0, 1.0)
1805 heightPicker.set_title(_("Use predefined values for height"))
1808 unit = self.config.get_unit()
1810 heightPicker.set_selector(selectorH)
1812 heightPicker.set_selector(selectorH_English)
1814 height = self.config.get_height()
1816 heightPicker.set_active(height)
1818 heightPicker.get_selector().connect("changed", selectorH_changed, dialog)
1819 heightPicker.connect("value-changed", heightButton_clicked, dialog)
1821 dialog.vbox.add(heightPicker)
1822 dialog.vbox.add(manualButton)
1825 if dialog.run() == gtk.RESPONSE_DELETE_EVENT:
1828 def donateButton_clicked(button, dialog):
1829 url = "https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=BKE6E9SLK7NP4&lc=RO&item_name=Pedometer%20Widget¤cy_code=EUR&bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted"
1830 command = "dbus-send --system --type=method_call --dest=\"com.nokia.osso_browser\" --print-reply /com/nokia/osso_browser/request com.nokia.osso_browser.load_url string:\"%s\"" % url
1833 dialog = gtk.Dialog()
1834 dialog.set_title(_("Settings"))
1835 dialog.add_button(_("OK"), gtk.RESPONSE_OK)
1836 self.dialog = dialog
1838 stepLengthButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1839 stepLengthButton.set_title(_("Step length"))
1840 stepLengthButton.set_alignment(0, 0.8, 1, 1)
1841 stepLengthButton.connect("clicked", stepLength_dialog)
1842 stepLengthButton_value_update()
1844 resetButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1845 resetButton.set_title(_("Reset"))
1846 resetButton.set_value(_("All the stored values will be erased"))
1847 resetButton.set_alignment(0, 0.8, 1, 1)
1848 resetButton.connect("clicked", reset_total_counter)
1850 alarmButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1851 alarmButton.set_title(_("Alarm"))
1852 if self.config.get_alarm_enable():
1853 alarmButton.set_value(_("Enabled"))
1855 alarmButton.set_value(_("Disabled"))
1856 alarmButton.set_alignment(0, 0.8, 1, 1)
1857 alarmButton.connect("clicked", alarmButton_pressed)
1859 selector = hildon.TouchSelector(text=True)
1860 selector.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
1861 selector.append_text(_("Walk"))
1862 selector.append_text(_("Run"))
1863 selector.connect("changed", selector_changed)
1865 modePicker = hildon.PickerButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1866 modePicker.set_alignment(0.0, 0.5, 1.0, 1.0)
1867 modePicker.set_title(_("Mode"))
1868 modePicker.set_selector(selector)
1869 modePicker.set_active(self.config.get_mode())
1871 weightButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1872 weightButton.set_title(_("Weight"))
1873 weightButton.set_alignment(0, 0.8, 1, 1)
1874 update_weight_button()
1875 weightButton.connect("clicked", weight_dialog)
1877 selectorUnit = hildon.TouchSelector(text=True)
1878 selectorUnit.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
1879 selectorUnit.append_text(_("Metric (km)"))
1880 selectorUnit.append_text(_("English (mi)"))
1881 selectorUnit.connect("changed", selectorUnit_changed)
1883 unitPicker = hildon.PickerButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1884 unitPicker.set_alignment(0.0, 0.5, 1.0, 1.0)
1885 unitPicker.set_title(_("Unit"))
1886 unitPicker.set_selector(selectorUnit)
1887 unitPicker.set_active(self.config.get_unit())
1889 selectorUI = hildon.TouchSelector(text=True)
1890 selectorUI = hildon.TouchSelector(text=True)
1891 selectorUI.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
1892 selectorUI.append_text(_("Show current + total + graph"))
1893 selectorUI.append_text(_("Show current + total"))
1894 selectorUI.append_text(_("Show only current"))
1895 selectorUI.append_text(_("Show only total"))
1896 selectorUI.connect("changed", selectorUI_changed)
1898 UIPicker = hildon.PickerButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1899 UIPicker.set_alignment(0.0, 0.5, 1.0, 1.0)
1900 UIPicker.set_title(_("Widget aspect"))
1901 UIPicker.set_selector(selectorUI)
1902 UIPicker.set_active(self.config.get_aspect())
1904 sensitivityButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1905 sensitivityButton.set_title(_("Sensitivity"))
1906 sensitivityButton.set_alignment(0, 0.8, 1, 1)
1907 sensitivityButton.set_value(str(self.config.get_sensitivity()) + " %")
1908 sensitivityButton.connect("clicked", sensitivity_dialog)
1910 donateButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1911 donateButton.set_title(_("Donate"))
1912 donateButton.set_value(_("Please support the development of this opensource widget!"))
1913 donateButton.set_alignment(0, 0.8, 1, 1)
1914 donateButton.connect("clicked", donateButton_clicked, dialog)
1916 logButton = hildon.CheckButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT)
1917 logButton.set_label(_("Log data"))
1918 logButton.set_active(self.config.get_logging())
1919 logButton.connect("toggled", logButton_changed)
1921 idleButton = hildon.CheckButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT)
1922 idleButton.set_label(_("Pause time when not walking"))
1923 idleButton.set_active(self.config.get_noidletime())
1924 idleButton.connect("toggled", idleButton_changed)
1926 pan_area = hildon.PannableArea()
1928 vbox.add(alarmButton)
1929 vbox.add(modePicker)
1930 vbox.add(stepLengthButton)
1931 vbox.add(weightButton)
1932 vbox.add(unitPicker)
1933 vbox.add(sensitivityButton)
1935 vbox.add(idleButton)
1936 vbox.add(resetButton)
1937 vbox.add(donateButton)
1938 #vbox.add(logButton)
1940 pan_area.add_with_viewport(vbox)
1941 pan_area.set_size_request(-1, 300)
1943 dialog.vbox.add(pan_area)
1946 response = dialog.run()
1949 def close_requested(self, widget):
1950 if self.controller.is_running:
1951 self.controller.stop_pedometer()
1952 self.controller.stop_midnight_callback()
1954 def update_values(self):
1955 #TODO: do not update if the widget is not on the active desktop
1956 self.label_second_view.set_label(self.second_view_labels[self.config.get_secondview()])
1957 self.update_current()
1960 def button_clicked(self, button):
1961 if self.controller.is_running:
1962 self.controller.stop_pedometer()
1963 self.button.set_icon(ICONSPATH + "play.png")
1965 self.controller.start_pedometer()
1966 self.button.set_icon(ICONSPATH + "stop.png")
1967 hildon.hildon_banner_show_information(self, "None", _("Keep the N900 in a pocket close to your hip for best results"))
1969 def do_expose_event(self, event):
1970 cr = self.window.cairo_create()
1971 cr.region(event.window.get_clip_region())
1973 #cr.set_source_rgba(0.4, 0.64, 0.564, 0.5)
1974 style = self.rc_get_style()
1975 color = style.lookup_color("DefaultBackgroundColor")
1976 cr.set_source_rgba (color.red / 65535.0, color.green / 65335.0, color.blue / 65535.0, 0.75);
1979 width = self.allocation.width
1980 height = self.allocation.height
1982 x = self.allocation.x
1983 y = self.allocation.y
1985 cr.move_to(x + radius, y)
1986 cr.line_to(x + width - radius, y)
1987 cr.curve_to(x + width - radius, y, x + width, y, x + width, y + radius)
1988 cr.line_to(x + width, y + height - radius)
1989 cr.curve_to(x + width, y + height - radius, x + width, y + height, x + width - radius, y + height)
1990 cr.line_to(x + radius, y + height)
1991 cr.curve_to(x + radius, y + height, x, y + height, x, y + height - radius)
1992 cr.line_to(x, y + radius)
1993 cr.curve_to(x, y + radius, x, y, x + radius, y)
1995 cr.set_operator(cairo.OPERATOR_SOURCE)
1998 color = style.lookup_color("ActiveTextColor")
1999 cr.set_source_rgba (color.red / 65535.0, color.green / 65335.0, color.blue / 65535.0, 0.5);
2000 cr.set_line_width(1)
2003 hildondesktop.HomePluginItem.do_expose_event(self, event)
2005 def do_realize(self):
2006 screen = self.get_screen()
2007 self.set_colormap(screen.get_rgba_colormap())
2008 self.set_app_paintable(True)
2009 hildondesktop.HomePluginItem.do_realize(self)
2011 hd_plugin_type = PedometerHomePlugin
2016 logger = logging.getLogger("pedometer")
2017 logger.setLevel(logging.INFO)
2019 ch = logging.StreamHandler()
2020 formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
2021 ch.setFormatter(formatter)
2022 logger.addHandler(ch)
2024 # The code below is just for testing purposes.
2025 # It allows to run the widget as a standalone process.
2026 if __name__ == "__main__":
2028 gobject.type_register(hd_plugin_type)
2029 obj = gobject.new(hd_plugin_type, plugin_id="plugin_id")