Remove unnecessary try/except
[pedometerwidget] / src / usr / lib / hildon-desktop / pedometer_widget_home.py
1 #Pedometer Home Widget
2 #Author: Mirestean Andrei < andrei.mirestean at gmail.com >
3 #
4 #This program is free software: you can redistribute it and/or modify
5 #it under the terms of the GNU General Public License as published by
6 #the Free Software Foundation, either version 3 of the License, or
7 #(at your option) any later version.
8 #
9 #This program is distributed in the hope that it will be useful,
10 #but WITHOUT ANY WARRANTY; without even the implied warranty of
11 #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 #GNU General Public License for more details.
13 #
14 #You should have received a copy of the GNU General Public License
15 #along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
17 import os
18 import time
19 import pickle
20 from datetime import date, timedelta
21 from xml.dom.minidom import getDOMImplementation, parseString
22
23 import gobject
24 import gconf
25 import gtk
26 import cairo
27
28 import pygst
29 pygst.require("0.10")
30 import gst
31
32 import hildondesktop
33 import hildon
34
35 PATH = "/apps/pedometerhomewidget"
36 MODE = PATH + "/mode"
37 HEIGHT = PATH + "/height"
38 WEIGHT = PATH + "/weight"
39 UNIT = PATH + "/unit"
40 ASPECT = PATH + "/aspect"
41 SECONDVIEW = PATH + "/secondview"
42 GRAPHVIEW = PATH + "/graphview"
43 NOIDLETIME = PATH + "/noidletime"
44 LOGGING = PATH + "/logging"
45
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"
51
52 ICONSPATH = "/opt/pedometerhomewidget/"
53
54 unit = 0
55
56 class Singleton(object):
57     _instance = None
58     def __new__(cls, *args, **kwargs):
59         if not cls._instance:
60             cls._instance = super(Singleton, cls).__new__(
61                                 cls, *args, **kwargs)
62         return cls._instance
63
64 class PedoIntervalCounter(Singleton):
65     MIN_THRESHOLD = 500
66     MIN_TIME_STEPS = 0.5
67     x = []
68     y = []
69     z = []
70     t = []
71
72     #TODO: check if last detected step is at the end of the interval
73
74     def set_vals(self, coords, tval):
75         self.x = coords[0]
76         self.y = coords[1]
77         self.z = coords[2]
78         self.t = tval
79
80     def set_mode(self, mode):
81         #runnig, higher threshold to prevent fake steps
82         self.mode = mode
83         if mode == 1:
84             self.MIN_THRESHOLD = 650
85             self.MIN_TIME_STEPS = 0.35
86         #walking
87         else:
88             self.MIN_THRESHOLD = 500
89             self.MIN_TIME_STEPS = 0.5
90
91     def calc_mean(self, vals):
92         sum = 0
93         for i in vals:
94             sum += i
95         if len(vals) > 0:
96             return sum / len(vals)
97         return 0
98
99     def calc_stdev(self, vals):
100         rez = 0
101         mean = self.calc_mean(vals)
102         for i in vals:
103             rez += pow(abs(mean - i), 2)
104         return math.sqrt(rez / len(vals))
105
106     def calc_threshold(self, vals):
107         vmax = max(vals)
108         vmin = min(vals)
109         mean = self.calc_mean(vals)
110         threshold = max (abs(mean - vmax), abs(mean - vmin))
111         return threshold
112
113     def count_steps(self, vals, t):
114         threshold = self.MIN_THRESHOLD
115         mean = self.calc_mean(vals)
116         cnt = 0
117         i = 0
118         while i < len(vals):
119             if abs(vals[i] - mean) > threshold:
120                 cnt += 1
121                 ntime = t[i] + self.MIN_TIME_STEPS
122                 while i < len(vals) and t[i] < ntime:
123                     i += 1
124             i += 1
125         return cnt
126
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)
132
133         if (abs(dev1 - dev_max) < 0.001):
134             logger.info("X chosen as best axis, stdev %f" % dev1)
135             return x
136         elif (abs(dev2 - dev_max) < 0.001):
137             logger.info("Y chosen as best axis, stdev %f" % dev2)
138             return y
139         else:
140             logger.info("Z chosen as best axis, stdev %f" % dev3)
141             return z
142
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)
146
147 class PedoValues():
148     def __init__(self, time=0, steps=0, dist=0, calories=0):
149         self.time = time
150         self.steps = steps
151         self.calories = calories
152         self.dist = dist
153         self.unit = unit
154
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)
160
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)
166
167     def get_print_time(self):
168         tdelta = self.time
169         hours = int(tdelta / 3600)
170         tdelta -= 3600 * hours
171         mins = int(tdelta / 60)
172         tdelta -= 60 * mins
173         secs = int(tdelta)
174         strtime = "%.2d:%.2d:%.2d" % (hours, mins, secs)
175         return strtime
176
177     def get_print_distance(self):
178         if self.dist > 1000:
179             if self.unit == 0:
180                 return "%.2f km" % (self.dist / 1000)
181             else:
182                 return "%.2f mi" % (self.dist / 1609.344)
183         else:
184             if self.unit == 0:
185                 return "%d m" % self.dist
186             else:
187                 return "%d ft" % int(self.dist * 3.2808)
188
189     def get_avg_speed(self):
190         conv = 0
191         if self.unit:
192             conv = 2.23693629
193         else:
194             conv = 3.6
195
196         if self.time == 0:
197             return 0
198         speed = 1.0 * self.dist / self.time
199         return speed * conv
200
201     def get_print_avg_speed(self):
202         suffix = ""
203         conv = 0
204         if self.unit:
205             suffix = "mi/h"
206             conv = 2.23693629
207         else:
208             suffix = "km/h"
209             conv = 3.6
210
211         if self.time == 0:
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
215         speed *= conv
216         return "%.2f %s" % (speed, suffix)
217
218     def get_print_steps(self):
219         return str(self.steps)
220
221     def get_print_calories(self):
222         return str(self.calories)
223
224 class PedoRepository(Singleton):
225     values = {}
226
227     def load(self):
228         raise NotImplementedError("Must be implemented by subclass")
229
230     def save(self):
231         raise NotImplementedError("Must be implemented by subclass")
232
233     def reset_values(self):
234         self.values = {}
235         self.save()
236
237     def get_history_count(self):
238         """return the number of days in the log"""
239         return len(values)
240
241     def get_values(self):
242         return self.values
243
244     def add_values(self, values, when=date.today()):
245         """add PedoValues values to repository """
246         try:
247             self.values[when] = self.values[when] + values
248         except KeyError:
249             self.values[when] = values
250
251     def get_last_7_days(self):
252         ret = []
253         day = date.today()
254         for i in range(7):
255             try:
256                 ret.append(self.values[day])
257             except KeyError:
258                 ret.append(PedoValues())
259             day = day - timedelta(days=1)
260         return ret
261
262     def get_last_weeks(self):
263         delta = timedelta(days=1)
264         day = date.today()
265         week = int(date.today().strftime("%W"))
266         val = PedoValues()
267         ret = []
268         for i in range(56):
269             try:
270                 val += self.values[day]
271             except KeyError:
272                 pass
273             w = int(day.strftime("%W"))
274             if w != week:
275                 ret.append(val)
276                 val = PedoValues()
277                 week = w
278                 if len(ret) == 7:
279                     break
280             day -= delta
281         return ret
282
283     def get_alltime_values(self):
284         ret = PedoValues()
285         for k, v in self.values.iteritems():
286             ret = ret + v
287         return ret
288
289     def get_today_values(self):
290         try:
291             return self.values[date.today()]
292         except KeyError:
293             return PedoValues()
294
295     def get_this_week_values(self):
296         day = date.today()
297         ret = PedoValues()
298         while True:
299             try:
300                 ret += self.values[day]
301             except:
302                 pass
303             if day.weekday() == 0:
304                 break
305             day = day - timedelta(days=1)
306
307         return ret
308
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")
313     def __init__(self):
314         if not os.path.exists(self.DIR):
315             os.makedirs(self.DIR)
316         PedoRepository.__init__(self)
317
318     def load(self):
319         try:
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)
331
332             f.close()
333         except Exception, e:
334             logger.error("Error while loading data from xml file: %s" % e)
335
336     def save(self):
337         try:
338             f = open(self.FILE, "w")
339
340             impl = getDOMImplementation()
341
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)
353
354             newdoc.appendChild(top_element)
355             newdoc.writexml(f)
356             #f.write(newdoc.toprettyxml())
357             f.close()
358         except Exception, e:
359             logger.error("Error while saving data to xml file: %s" % e)
360
361 class PedoRepositoryPickle(PedoRepository):
362     DIR = os.path.join(os.path.expanduser("~"), ".pedometer")
363     FILE = os.path.join(DIR, "pickle.log")
364
365     def __init__(self):
366         if not os.path.exists(self.DIR):
367             os.makedirs(self.DIR)
368         PedoRepository.__init__(self)
369
370     def load(self):
371         try:
372             f = open(self.FILE, "rb")
373             self.values = pickle.load(f)
374             f.close()
375         except Exception, e:
376             logger.error("Error while loading pickle file: %s" % e)
377
378     def save(self):
379         try:
380             f = open(self.FILE, "wb")
381             pickle.dump(self.values, f)
382             f.close()
383         except Exception, e:
384             logger.error("Error while saving data to pickle: %s" % e)
385
386 class PedoController(Singleton):
387     mode = 0
388     unit = 0
389     height_interval = 0
390     #what to display in second view - 0 - alltime, 1 - today, 2 - week
391     second_view = 0
392     callback_update_ui = None
393     no_idle_time = False
394
395     STEP_LENGTH = 0.7
396     #values for the two views in the widget ( current and day/week/alltime)
397     v = [PedoValues(), PedoValues()]
398
399     last_time = 0
400     is_running = False
401
402     observers = []
403
404     def __init__(self):
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()
410
411         self.load_values()
412
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()
418         else:
419             self.v[1] = self.repository.get_this_week_values()
420
421     def save_values(self):
422         self.repository.add_values(self.v[0])
423         self.repository.save()
424         self.load_values()
425
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()
431         self.notify(True)
432
433     def reset_all_values(self):
434         self.repository.reset_values()
435         self.notify()
436
437     def stop_pedometer(self):
438         self.is_running = False
439         self.pedometer.request_stop()
440
441     def get_first(self):
442         return self.v[0]
443
444     def get_second(self):
445         if self.is_running:
446             return self.v[0] + self.v[1]
447         else:
448             return self.v[1]
449
450     def update_current(self):
451         """
452         Update distance and calories for current values based on new height, mode values
453         """
454         self.v[0].dist = self.get_distance(self.v[0].steps)
455         self.v[0].calories = self.get_calories(self.v[0].steps)
456
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")
460         else:
461             self.v[0].steps += cnt
462             self.v[0].dist += self.get_distance(cnt)
463             self.v[0].calories += self.get_calories(self.get_distance(cnt))
464             self.v[0].time += time.time() - self.last_time
465             if last_steps:
466                 self.save_values()
467                 self.notify()
468             else:
469                 self.notify(True)
470         self.last_time = time.time()
471
472     def get_calories(self, distance):
473         """calculate lost calories for the distance and weight given as parameters
474         """
475         #different coefficient for running and walking
476         if self.mode == 0:
477             coef = 0.53
478         else:
479             coef = 0.75
480
481         #convert distance from meters to miles
482         distance *= 0.000621371192
483
484         weight = self.weight
485         #convert weight from kg to pounds
486         if self.unit == 0:
487             weight *= 2.20462262
488         return weight * distance * coef
489
490     def set_mode(self, mode):
491         self.mode = mode
492         self.set_height(self.height_interval)
493         self.notify()
494
495     def set_unit(self, new_unit):
496         self.unit = new_unit
497         unit = new_unit
498
499     def get_str_weight_unit(self):
500         if self.unit == 0:
501             return "kg"
502         else:
503             return "lb"
504
505     def set_weight(self, value):
506         self.weight = value
507         self.notify()
508
509     def get_weight(self):
510         return self.weight
511
512     def set_second_view(self, second_view):
513         self.second_view = second_view
514         self.load_values()
515         self.notify()
516
517     def set_callback_ui(self, func):
518         self.callback_update_ui = func
519
520     def set_height(self, height_interval):
521         self.height_inteval = height_interval
522         #set height, will affect the distance
523         if height_interval == 0:
524             self.STEP_LENGTH = 0.59
525         elif height_interval == 1:
526             self.STEP_LENGTH = 0.64
527         elif height_interval == 2:
528             self.STEP_LENGTH = 0.71
529         elif height_interval == 3:
530             self.STEP_LENGTH = 0.77
531         elif height_interval == 4:
532             self.STEP_LENGTH = 0.83
533         #increase step length if RUNNING
534         if self.mode == 1:
535             self.STEP_LENGTH *= 1.45
536         self.notify()
537
538     def set_no_idle_time(self, value):
539         self.no_idle_time = value
540
541     def get_distance(self, steps=None):
542         if steps == None:
543             steps = self.counter
544         return self.STEP_LENGTH * steps;
545
546     def add_observer(self, func):
547         try:
548             self.observers.index(func)
549         except:
550             self.observers.append(func)
551
552     def remove_observer(self, func):
553         self.observers.remove(func)
554
555     def notify(self, optional=False):
556         if self.callback_update_ui is not None:
557             self.callback_update_ui()
558
559         for func in self.observers:
560             func(optional)
561
562 class AlarmController(Singleton):
563     enable = False
564     fname = "/home/user/MyDocs/.sounds/Ringtones/Bicycle.aac"
565     interval = 5
566     type = 0
567
568     player = None
569     is_playing = False
570     pedo_controller = None
571
572     def __init__(self):
573         self.client = gconf.client_get_default()
574
575         self.enable = self.client.get_bool(ALARM_ENABLE)
576         self.fname = self.client.get_string(ALARM_FNAME)
577         self.interval = self.client.get_int(ALARM_INTERVAL)
578         self.type = self.client.get_int(ALARM_TYPE)
579
580         self.pedo_controller = PedoController()
581         if self.enable:
582             self.init_player()
583             self.pedo_controller.add_observer(self.update)
584             self.start_value = self.pedo_controller.get_first()
585
586     def init_player(self):
587         self.player = gst.element_factory_make("playbin2", "player")
588         fakesink = gst.element_factory_make("fakesink", "fakesink")
589         self.player.set_property("video-sink", fakesink)
590
591         bus = self.player.get_bus()
592         bus.add_signal_watch()
593         bus.connect("message", self.on_message)
594
595     def on_message(self, bus, message):
596         t = message.type
597         if t == gst.MESSAGE_EOS:
598             self.player.set_state(gst.STATE_NULL)
599             self.is_playing = False
600         elif t == gst.MESSAGE_ERROR:
601             self.player.set_state(gst.STATE_NULL)
602             self.is_playing = False
603             err, debug = message.parse_error()
604             logger.error("ERROR: %s, %s" % (err, debug) )
605
606     def update(self, optional):
607         diff = self.pedo_controller.get_first() - self.start_value
608         if self.type == 0 and diff.time >= self.interval * 60 or \
609                    self.type == 1 and diff.steps >= self.interval or \
610                    self.type == 2 and diff.dist >= self.interval or \
611                    self.type == 3 and diff.calories >= self.interval:
612             self.play()
613             #get new instance of current values
614             self.start_value = PedoValues() + self.pedo_controller.get_first()
615             logger.info("Alarm!")
616
617     def play(self):
618         if self.player is None:
619             self.init_player()
620         if self.is_playing:
621             self.player.set_state(gst.STATE_NULL)
622             self.is_playing = False
623         else:
624             self.player.set_property("uri", "file://" + self.fname)
625             self.player.set_state(gst.STATE_PLAYING)
626             self.is_playing = True
627
628     def stop(self):
629         self.player.set_state(gst.STATE_NULL)
630
631     def set_enable(self, value):
632        self.enable = value
633        self.client.set_bool(ALARM_ENABLE, value)
634        if self.enable:
635            self.init_player()
636            self.pedo_controller.add_observer(self.update)
637            self.start_value = self.pedo_controller.get_first()
638        else:
639            self.stop()
640            self.player = None
641            self.pedo_controller.remove_observer(self.update)
642
643     def get_enable(self):
644         return self.enable
645
646     def set_alarm_file(self, fname):
647         self.fname = fname
648         self.client.set_string(ALARM_FNAME, fname)
649
650     def get_alarm_file(self):
651         if self.fname == None:
652             return ""
653         return self.fname
654
655     def set_interval(self, interval):
656         self.interval = interval
657         self.client.set_int(ALARM_INTERVAL, interval)
658
659     def get_interval(self):
660         return self.interval
661
662     def set_type(self, type):
663         self.type = type
664         self.client.set_int(ALARM_TYPE, type)
665
666     def get_type(self):
667         return self.type
668
669 class PedoCounter(Singleton):
670     COORD_FNAME = "/sys/class/i2c-adapter/i2c-3/3-001d/coord"
671     COORD_FNAME_SDK = "/home/andrei/pedometer-widget-0.1/date.txt"
672     LOGFILE = "/home/user/log_pedometer"
673     #time in ms between two accelerometer data reads
674     COORD_GET_INTERVAL = 10
675
676     COUNT_INTERVAL = 5
677
678     interval_counter = None
679     stop_requested = False
680     update_function = None
681     logging = False
682     isRunning = False
683
684     def __init__(self, update_function=None):
685         if not os.path.exists(self.COORD_FNAME):
686             self.COORD_FNAME = self.COORD_FNAME_SDK
687
688         self.interval_counter = PedoIntervalCounter()
689         self.update_function = update_function
690
691     def set_logging(self, value):
692         self.logging = value
693
694     def get_rotation(self):
695         f = open(self.COORD_FNAME, 'r')
696         coords = [int(w) for w in f.readline().split()]
697         f.close()
698         return coords
699
700     def start(self):
701         logger.info("Counter started")
702         self.isRunning = True
703         self.stop_requested = False
704         if self.logging:
705             fname = "%d_%d_%d_%d_%d_%d" % time.localtime()[0:6]
706             self.file = open(self.LOGFILE + fname + ".txt", "w")
707         gobject.idle_add(self.run)
708
709     def run(self):
710         self.coords = [[], [], []]
711         self.stime = time.time()
712         self.t = []
713         gobject.timeout_add(self.COORD_GET_INTERVAL, self.read_coords)
714         return False
715
716     def read_coords(self):
717         x, y, z = self.get_rotation()
718         self.coords[0].append(int(x))
719         self.coords[1].append(int(y))
720         self.coords[2].append(int(z))
721         now = time.time() - self.stime
722         if self.logging:
723             self.file.write("%d %d %d %f\n" % (self.coords[0][-1], self.coords[1][-1], self.coords[2][-1], now))
724
725         self.t.append(now)
726         #call stop_interval
727         ret = True
728         if self.t[-1] > self.COUNT_INTERVAL or self.stop_requested:
729             ret = False
730             gobject.idle_add(self.stop_interval)
731         return ret
732
733     def stop_interval(self):
734         self.interval_counter.set_vals(self.coords, self.t)
735         cnt = self.interval_counter.number_steps()
736
737         logger.info("Number of steps detected for last interval %d, number of coords: %d" % (cnt, len(self.t)))
738
739         gobject.idle_add(self.update_function, cnt, self.stop_requested)
740
741         if self.stop_requested:
742             gobject.idle_add(self.stop)
743         else:
744             gobject.idle_add(self.run)
745         return False
746
747     def stop(self):
748         if self.logging:
749             self.file.close()
750         logger.info("Counter has finished")
751
752     def request_stop(self):
753         self.stop_requested = True
754         self.isRunning = False
755
756 class CustomButton(hildon.Button):
757     def __init__(self, icon):
758         hildon.Button.__init__(self, gtk.HILDON_SIZE_AUTO_WIDTH, hildon.BUTTON_ARRANGEMENT_VERTICAL)
759         self.icon = icon
760         self.set_size_request(int(32 * 1.4), int(30 * 1.0))
761         self.retval = self.connect("expose_event", self.expose)
762
763     def set_icon(self, icon):
764         self.icon = icon
765
766     def expose(self, widget, event):
767         self.context = widget.window.cairo_create()
768         self.context.rectangle(event.area.x, event.area.y,
769                             event.area.width, event.area.height)
770
771         self.context.clip()
772         rect = self.get_allocation()
773         self.context.rectangle(rect.x, rect.y, rect.width, rect.height)
774         self.context.set_source_rgba(1, 1, 1, 0)
775
776         style = self.rc_get_style()
777         color = style.lookup_color("DefaultBackgroundColor")
778         if self.state == gtk.STATE_ACTIVE:
779             style = self.rc_get_style()
780             color = style.lookup_color("SelectionColor")
781             self.context.set_source_rgba (color.red / 65535.0, color.green / 65335.0, color.blue / 65535.0, 0.75);
782         self.context.fill()
783
784         #img = cairo.ImageSurface.create_from_png(self.icon)
785
786         #self.context.set_source_surface(img)
787         #self.context.set_source_surface(img, rect.width/2 - img.get_width() /2, 0)
788         img = gtk.Image()
789         img.set_from_file(self.icon)
790         buf = img.get_pixbuf()
791         buf = buf.scale_simple(int(32 * 1.5), int(30 * 1.5), gtk.gdk.INTERP_BILINEAR)
792
793         self.context.set_source_pixbuf(buf, rect.x + (event.area.width / 2 - 15) - 8, rect.y + 1)
794         self.context.scale(200, 200)
795         self.context.paint()
796
797         return self.retval
798
799 class CustomEventBox(gtk.EventBox):
800
801     def __init__(self):
802         gtk.EventBox.__init__(self)
803
804     def do_expose_event(self, event):
805         self.context = self.window.cairo_create()
806         self.context.rectangle(event.area.x, event.area.y,
807                             event.area.width, event.area.height)
808
809         self.context.clip()
810         rect = self.get_allocation()
811         self.context.rectangle(rect.x, rect.y, rect.width, rect.height)
812
813         if self.state == gtk.STATE_ACTIVE:
814             style = self.rc_get_style()
815             color = style.lookup_color("SelectionColor")
816             self.context.set_source_rgba (color.red / 65535.0, color.green / 65335.0, color.blue / 65535.0, 0.75);
817         else:
818             self.context.set_source_rgba(1, 1, 1, 0)
819         self.context.fill()
820
821         gtk.EventBox.do_expose_event(self, event)
822
823 class GraphController(Singleton):
824     ytitles = ["Steps", "Average Speed", "Distance", "Calories"]
825     xtitles = ["Day", "Week"] # "Today"]
826     widget = None
827     def __init__(self):
828         self.repository = PedoRepositoryXML()
829         self.last_update = 0
830         PedoController().add_observer(self.update_ui)
831
832     def set_graph(self, widget):
833         self.widget = widget
834         self.update_ui()
835
836     def set_current_view(self, view):
837         """
838         current_view % len(ytitles) - gives the ytitle
839         current_view / len(ytitles) - gives the xtitle
840         """
841         self.current_view = view
842
843         if self.current_view == len(self.ytitles) * len(self.xtitles):
844             self.current_view = 0
845         self.x_id = self.current_view / len(self.ytitles)
846         self.y_id = self.current_view % len(self.ytitles)
847
848     def next_view(self):
849         self.set_current_view(self.current_view+1)
850         self.update_ui()
851         return self.current_view
852
853     def last_weeks_labels(self):
854         d = date.today()
855         delta = timedelta(days=7)
856         ret = []
857         for i in range(7):
858             ret.append(d.strftime("Week %W"))
859             d = d - delta
860         return ret
861
862     def compute_values(self):
863         labels = []
864         if self.x_id == 0:
865             values = self.repository.get_last_7_days()
866             d = date.today()
867             delta = timedelta(days=1)
868             for i in range(7):
869                 labels.append(d.ctime().split()[0])
870                 d = d - delta
871
872         elif self.x_id == 1:
873             values = self.repository.get_last_weeks()
874             d = date.today()
875             for i in range(7):
876                 labels.append(d.strftime("Week %W"))
877                 d = d - timedelta(days=7)
878         else:
879             values = self.repository.get_today()
880             #TODO get labels
881
882         if self.y_id == 0:
883             yvalues = [line.steps for line in values]
884         elif self.y_id == 1:
885             yvalues = [line.get_avg_speed() for line in values]
886         elif self.y_id == 2:
887             yvalues = [line.dist for line in values]
888         else:
889             yvalues = [line.calories for line in values]
890
891         #determine values for y lines in graph
892         diff = self.get_best_interval_value(max(yvalues))
893         ytext = []
894         for i in range(6):
895             ytext.append(str(int(i*diff)))
896
897         if self.widget is not None:
898             yvalues.reverse()
899             labels.reverse()
900             self.widget.values = yvalues
901             self.widget.ytext = ytext
902             self.widget.xtext = labels
903             self.widget.max_value = diff * 5
904             self.widget.text = self.xtitles[self.x_id] + " / " + self.ytitles[self.y_id]
905             self.widget.queue_draw()
906         else:
907             logger.error("Widget not set in GraphController")
908
909     def get_best_interval_value(self, max_value):
910         diff =  1.0 * max_value / 5
911         l = len(str(int(diff)))
912         d = math.pow(10, l/2)
913         val = int(math.ceil(1.0 * diff / d)) * d
914         if val == 0:
915             val = 1
916         return val
917
918     def update_ui(self, optional=False):
919         """update graph values every x seconds"""
920         if optional and self.last_update - time.time() < 600:
921             return
922         if self.widget is None:
923             return
924
925         self.compute_values()
926         self.last_update = time.time()
927
928 class GraphWidget(gtk.DrawingArea):
929
930     def __init__(self):
931         gtk.DrawingArea.__init__(self)
932         self.set_size_request(-1, 150)
933         self.yvalues = 5
934
935         """sample values"""
936         self.ytext = ["   0", "1000", "2000", "3000", "4000", "5000"]
937         self.xtext = ["Monday", "Tuesday", "Wednesday","Thursday", "Friday", "Saturday", "Sunday"]
938         self.values = [1500, 3400, 4000, 3600, 3200, 0, 4500]
939         self.max_value = 5000
940         self.text = "All time steps"
941
942     def do_expose_event(self, event):
943         context = self.window.cairo_create()
944
945         # set a clip region for the expose event
946         context.rectangle(event.area.x, event.area.y,
947                                event.area.width, event.area.height)
948         context.clip()
949
950         context.save()
951
952         context.set_operator(cairo.OPERATOR_SOURCE)
953         style = self.rc_get_style()
954
955         if self.state == gtk.STATE_ACTIVE:
956             color = style.lookup_color("SelectionColor")
957         else:
958              color = style.lookup_color("DefaultBackgroundColor")
959         context.set_source_rgba (color.red / 65535.0, color.green / 65335.0, color.blue / 65535.0, 0.75)
960
961         context.paint()
962         context.restore();
963         self.draw(context)
964
965     def draw(self, cr):
966         space_below = 20
967         space_above = 10
968         border_right = 10
969         border_left = 30
970
971         rect = self.get_allocation()
972         x = rect.width
973         y = rect.height
974
975         cr.select_font_face("Purisa", cairo.FONT_SLANT_NORMAL,
976             cairo.FONT_WEIGHT_NORMAL)
977         cr.set_font_size(13)
978
979         #check space needed to display ylabels
980         te = cr.text_extents(self.ytext[-1])
981         border_left = te[2] + 7
982
983         cr.set_source_rgb(1, 1, 1)
984         cr.move_to(border_left, space_above)
985         cr.line_to(border_left, y-space_below)
986         cr.set_line_width(2)
987         cr.stroke()
988
989         cr.move_to(border_left, y-space_below)
990         cr.line_to(x-border_right, y-space_below)
991         cr.set_line_width(2)
992         cr.stroke()
993
994         ydiff = (y-space_above-space_below) / self.yvalues
995         for i in range(self.yvalues):
996             yy = y-space_below-ydiff*(i+1)
997             cr.move_to(border_left, yy)
998             cr.line_to(x-border_right, yy)
999             cr.set_line_width(0.8)
1000             cr.stroke()
1001
1002
1003         for i in range(6):
1004             yy = y - space_below - ydiff*i + 5
1005             te = cr.text_extents(self.ytext[i])
1006
1007             cr.move_to(border_left-te[2]-2, yy)
1008             cr.show_text(self.ytext[i])
1009
1010         cr.set_font_size(15)
1011         te = cr.text_extents(self.text)
1012         cr.move_to((x-te[2])/2, y-5)
1013         cr.show_text(self.text)
1014
1015         graph_x_space = x - border_left - border_right
1016         graph_y_space = y - space_below - space_above
1017         bar_width = graph_x_space*0.75 / len(self.values)
1018         bar_distance = graph_x_space*0.25 / (1+len(self.values))
1019
1020         #set dummy max value to avoid exceptions
1021         if self.max_value == 0:
1022             self.max_value = 100
1023         for i in range(len(self.values)):
1024             xx = border_left + (i+1)*bar_distance + i * bar_width
1025             yy = y-space_below
1026             height = graph_y_space * (1.0 * self.values[i] / self.max_value)
1027             cr.set_source_rgba(1, 1, 1, 0.75)
1028             cr.rectangle(int(xx), int(yy-height), int(bar_width), int(height))
1029             cr.fill()
1030
1031         cr.set_source_rgba(1, 1, 1, 1)
1032         cr.select_font_face("Purisa", cairo.FONT_SLANT_NORMAL,
1033                             cairo.FONT_WEIGHT_NORMAL)
1034         cr.set_font_size(13)
1035
1036         cr.rotate(2*math.pi * (-45) / 180)
1037         for i in range(len(self.values)):
1038             xx = y - space_below - 10
1039             yy = border_left + (i+1)*bar_distance + i * bar_width
1040             cr.move_to(-xx, yy + bar_width*1.25 / 2)
1041             cr.show_text(self.xtext[i])
1042
1043 class PedometerHomePlugin(hildondesktop.HomePluginItem):
1044     button = None
1045
1046     #labels to display
1047     labels = ["timer", "count", "dist", "avgSpeed", "calories"]
1048
1049     #current view
1050     labelsC = {}
1051
1052     #second view ( day / week/ alltime)
1053     labelsT = {}
1054
1055     second_view_labels = ["All-time", "Today", "This week"]
1056
1057     controller = None
1058     pedometer = None
1059     pedometerInterval = None
1060     graph_controller = None
1061
1062     mode = 0
1063     height = 0
1064     weight = 70
1065     unit = 0
1066     aspect = 0
1067     second_view = 0
1068     graph_view = 0
1069     no_idle_time = False
1070     logging = False
1071
1072     def __init__(self):
1073         hildondesktop.HomePluginItem.__init__(self)
1074
1075         gobject.type_register(CustomEventBox)
1076         gobject.type_register(GraphWidget)
1077
1078         self.client = gconf.client_get_default()
1079
1080         self.mode = self.client.get_int(MODE)
1081         self.height = self.client.get_int(HEIGHT)
1082         self.weight = self.client.get_int(WEIGHT)
1083         self.unit = self.client.get_int(UNIT)
1084         self.aspect = self.client.get_int(ASPECT)
1085         self.second_view = self.client.get_int(SECONDVIEW)
1086         self.graph_view = self.client.get_int(GRAPHVIEW)
1087         self.no_idle_time = self.client.get_bool(NOIDLETIME)
1088         self.logging = self.client.get_bool(LOGGING)
1089
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)
1098
1099         self.graph_controller = GraphController()
1100         self.graph_controller.set_current_view(self.graph_view)
1101
1102         self.alarm_controller = AlarmController()
1103
1104         self.button = CustomButton(ICONSPATH + "play.png")
1105         self.button.connect("clicked", self.button_clicked)
1106
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])
1110
1111         self.update_current()
1112         self.update_total()
1113
1114         mainHBox = gtk.HBox(spacing=1)
1115
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:"))
1123
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
1132
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
1141
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(""))
1146
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)
1152
1153         mainHBox.add(buttonVBox)
1154         mainHBox.add(descVBox)
1155         mainHBox.add(currentVBox)
1156         mainHBox.add(eventBox)
1157         self.mainhbox = mainHBox
1158
1159         graph = GraphWidget()
1160         self.graph_controller.set_graph(graph)
1161
1162         eventBoxGraph = CustomEventBox()
1163         eventBoxGraph.set_visible_window(False)
1164         eventBoxGraph.add(graph)
1165         self.graph = graph
1166         eventBoxGraph.connect("button-press-event", self.eventBoxGraph_clicked)
1167         eventBoxGraph.connect("button-release-event", self.eventBoxGraph_clicked_release)
1168         self.graphBox = eventBoxGraph
1169
1170         self.mainvbox = gtk.VBox()
1171
1172         self.mainvbox.add(mainHBox)
1173         self.mainvbox.add(eventBoxGraph)
1174
1175         self.mainvbox.show_all()
1176         self.add(self.mainvbox)
1177         self.update_aspect()
1178
1179         self.connect("unrealize", self.close_requested)
1180         self.set_settings(True)
1181         self.connect("show-settings", self.show_settings)
1182
1183     def eventBoxGraph_clicked(self, widget, data=None):
1184         widget.set_state(gtk.STATE_ACTIVE)
1185
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)
1189
1190         widget.set_state(gtk.STATE_NORMAL)
1191
1192     def eventBox_clicked(self, widget, data=None):
1193         widget.set_state(gtk.STATE_ACTIVE)
1194
1195     def eventBox_clicked_release(self, widget, data=None):
1196         widget.set_state(gtk.STATE_NORMAL)
1197
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)
1201
1202     def new_label_heading(self, title=""):
1203         l = gtk.Label(title)
1204         hildon.hildon_helper_set_logical_font(l, "SmallSystemFont")
1205         return l
1206
1207     def create_labels(self, new_labels):
1208         for label in self.labels:
1209             l = gtk.Label()
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
1213
1214     def update_aspect(self):
1215
1216         if self.aspect > 0:
1217             self.graphBox.hide_all()
1218         else:
1219             self.graphBox.show_all()
1220
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()
1227         else:
1228             self.currentBox.hide_all()
1229             self.totalBox.show_all()
1230
1231         x,y = self.size_request()
1232         self.resize(x,y)
1233
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())
1240
1241     def update_current(self):
1242         self.update_ui_values(self.labelsC, self.controller.get_first())
1243
1244     def update_total(self):
1245         self.update_ui_values(self.labelsT, self.controller.get_second())
1246
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() )
1250             file.show()
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)
1255             file.destroy()
1256
1257         def test_sound(button):
1258             try:
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")
1263
1264         def enableButton_changed(button):
1265             value = button.get_active()
1266             self.alarm_controller.set_enable(value)
1267             if value:
1268                 main_button.set_value("Enabled")
1269             else:
1270                 main_button.set_value("Disabled")
1271
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()])
1275
1276         dialog = gtk.Dialog()
1277         dialog.set_title("Alarm settings")
1278         dialog.add_button("OK", gtk.RESPONSE_OK)
1279
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)
1284
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)
1289
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)
1295
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()))
1301
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)
1309
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())
1315
1316         hbox = gtk.HBox()
1317         hbox.add(labelEntry)
1318         hbox.add(intervalEntry)
1319         hbox.add(labelEntry2)
1320
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)
1326         dialog.show_all()
1327         while 1:
1328             response = dialog.run()
1329             if response != gtk.RESPONSE_OK:
1330                 break
1331             try:
1332                 value = int(intervalEntry.get_text())
1333                 self.alarm_controller.set_interval(value)
1334                 break
1335             except:
1336                 hildon.hildon_banner_show_information(self, "None", "Invalid interval")
1337
1338         dialog.destroy()
1339
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?")
1343             ret = note.run()
1344             if ret == gtk.RESPONSE_OK:
1345                 self.controller.reset_all_values()
1346                 hildon.hildon_banner_show_information(self, "None", "All history was deleted")
1347             note.destroy()
1348
1349         def alarmButton_pressed(widget):
1350             self.show_alarm_settings(widget)
1351
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)
1356
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)
1361
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)
1366
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()
1371
1372         def logButton_changed(checkButton):
1373             widget.logging = checkButton.get_active()
1374             widget.client.set_bool(LOGGING, widget.logging)
1375
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)
1380
1381         def weight_dialog(button):
1382             dialog = gtk.Dialog("Weight", self.dialog)
1383             dialog.add_button("OK", gtk.RESPONSE_OK)
1384
1385             label = gtk.Label("Weight:")
1386             entry = gtk.Entry()
1387             entry.set_text(str(self.controller.get_weight()))
1388
1389             suffixLabel = gtk.Label(self.controller.get_str_weight_unit())
1390
1391             hbox = gtk.HBox()
1392             hbox.add(label)
1393             hbox.add(entry)
1394             hbox.add(suffixLabel)
1395
1396             dialog.vbox.add(hbox)
1397             dialog.show_all()
1398             while 1:
1399                 response = dialog.run()
1400                 if response != gtk.RESPONSE_OK:
1401                     break
1402                 try:
1403                     value = int(entry.get_text())
1404                     if value <= 0:
1405                         raise ValueError
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() )
1410                     break
1411                 except:
1412                     hildon.hildon_banner_show_information(self, "None", "Invalid weight")
1413             dialog.destroy()
1414
1415         dialog = gtk.Dialog()
1416         dialog.set_title("Settings")
1417         dialog.add_button("OK", gtk.RESPONSE_OK)
1418         self.dialog = dialog
1419
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)
1424
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")
1429         else:
1430             alarmButton.set_value("Disabled")
1431         alarmButton.set_alignment(0, 0.8, 1, 1)
1432         alarmButton.connect("clicked", alarmButton_pressed)
1433
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)
1439
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)
1445
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)
1454
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)
1460
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)
1466
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)
1472
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)
1478
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)
1487
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)
1493
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)
1498
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)
1503
1504         pan_area = hildon.PannableArea()
1505         vbox = gtk.VBox()
1506         vbox.add(alarmButton)
1507         vbox.add(modePicker)
1508         vbox.add(heightPicker)
1509         vbox.add(weightButton)
1510         vbox.add(unitPicker)
1511         vbox.add(UIPicker)
1512         vbox.add(idleButton)
1513         vbox.add(resetButton)
1514         #vbox.add(logButton)
1515
1516         pan_area.add_with_viewport(vbox)
1517         pan_area.set_size_request(-1, 300)
1518
1519         dialog.vbox.add(pan_area)
1520         dialog.show_all()
1521         response = dialog.run()
1522         #hildon.hildon_banner_show_information(self, "None", "You have to Stop/Start the counter to apply the new settings")
1523         dialog.destroy()
1524
1525     def close_requested(self, widget):
1526         if self.pedometer is None:
1527             return
1528
1529         self.pedometer.request_stop()
1530
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()
1535         self.update_total()
1536
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")
1541         else:
1542             self.controller.start_pedometer()
1543             self.button.set_icon(ICONSPATH + "stop.png")
1544
1545     def do_expose_event(self, event):
1546         cr = self.window.cairo_create()
1547         cr.region(event.window.get_clip_region())
1548         cr.clip()
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);
1553
1554         radius = 5
1555         width = self.allocation.width
1556         height = self.allocation.height
1557
1558         x = self.allocation.x
1559         y = self.allocation.y
1560
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)
1570
1571         cr.set_operator(cairo.OPERATOR_SOURCE)
1572         cr.fill_preserve()
1573
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)
1577         cr.stroke()
1578
1579         hildondesktop.HomePluginItem.do_expose_event(self, event)
1580
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)
1586
1587 hd_plugin_type = PedometerHomePlugin
1588
1589 import math
1590 import logging
1591
1592 logger = logging.getLogger("pedometer")
1593 logger.setLevel(logging.INFO)
1594
1595 ch = logging.StreamHandler()
1596 formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
1597 ch.setFormatter(formatter)
1598 logger.addHandler(ch)
1599
1600 # The code below is just for testing purposes.
1601 # It allows to run the widget as a standalone process.
1602 if __name__ == "__main__":
1603     import gobject
1604     gobject.type_register(hd_plugin_type)
1605     obj = gobject.new(hd_plugin_type, plugin_id="plugin_id")
1606     obj.show_all()
1607     gtk.main()