ea1f39c2e684ca0fe92720d653f4badf205c909f
[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 "%.2f" % 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     midnight_set = False
405     midnight_source_id = None
406
407     def __init__(self):
408         self.pedometer = PedoCounter(self.steps_detected)
409         self.pedometerInterval = PedoIntervalCounter()
410         self.pedometerInterval.set_mode(self.mode)
411         self.repository = PedoRepositoryXML()
412         self.repository.load()
413
414         self.load_values()
415
416         if not self.midnight_set:
417             self.update_at_midnight()
418             self.midnight_set = True
419
420     def update_at_midnight(self):
421         next_day = date.today() + timedelta(days=1)
422         diff = time.mktime(next_day.timetuple()) - time.time()
423         diff = int(diff+5)
424         self.midnight_source_id = gobject.timeout_add_seconds(diff, self.midnight_callback, True)
425
426     def stop_midnight_callback(self):
427         if self.midnight_source_id is not None:
428             gobject.source_remove(self.midnight_source_id)
429
430     def midnight_callback(self, first=False):
431         self.load_values()
432         self.notify()
433         if first:
434             self.midnight_source_id = gobject.timeout_add_seconds(24*3600, self.midnight_callback)
435             return False
436         else:
437             return True
438
439     def load_values(self):
440         if self.second_view == 0:
441             self.v[1] = self.repository.get_alltime_values()
442         elif self.second_view == 1:
443             self.v[1] = self.repository.get_today_values()
444         else:
445             self.v[1] = self.repository.get_this_week_values()
446
447     def save_values(self):
448         self.repository.add_values(self.v[0])
449         self.repository.save()
450         self.load_values()
451
452     def start_pedometer(self):
453         self.v[0] = PedoValues()
454         self.last_time = time.time()
455         self.is_running = True
456         self.pedometer.start()
457         self.notify(True)
458
459     def reset_all_values(self):
460         self.repository.reset_values()
461         self.notify()
462
463     def stop_pedometer(self):
464         self.is_running = False
465         self.pedometer.request_stop()
466
467     def get_first(self):
468         return self.v[0]
469
470     def get_second(self):
471         if self.is_running:
472             return self.v[0] + self.v[1]
473         else:
474             return self.v[1]
475
476     def update_current(self):
477         """
478         Update distance and calories for current values based on new height, mode values
479         """
480         self.v[0].dist = self.get_distance(self.v[0].steps)
481         self.v[0].calories = self.get_calories(self.v[0].steps)
482
483     def steps_detected(self, cnt, last_steps=False):
484         if not last_steps and cnt == 0 and self.no_idle_time:
485             logger.info("No steps detected, timer is paused")
486         else:
487             self.v[0].steps += cnt
488             self.v[0].dist += self.get_distance(cnt)
489             self.v[0].calories += self.get_calories(self.get_distance(cnt))
490             self.v[0].time += time.time() - self.last_time
491             if last_steps:
492                 self.save_values()
493                 self.notify()
494             else:
495                 self.notify(True)
496         self.last_time = time.time()
497
498     def get_calories(self, distance):
499         """calculate lost calories for the distance and weight given as parameters
500         """
501         #different coefficient for running and walking
502         if self.mode == 0:
503             coef = 0.53
504         else:
505             coef = 0.75
506
507         #convert distance from meters to miles
508         distance *= 0.000621371192
509
510         weight = self.weight
511         #convert weight from kg to pounds
512         if self.unit == 0:
513             weight *= 2.20462262
514         return weight * distance * coef
515
516     def set_mode(self, mode):
517         self.mode = mode
518         self.set_height(self.height_interval)
519         self.notify()
520
521     def set_unit(self, new_unit):
522         self.unit = new_unit
523         unit = new_unit
524
525     def get_str_weight_unit(self):
526         if self.unit == 0:
527             return "kg"
528         else:
529             return "lb"
530
531     def set_weight(self, value):
532         self.weight = value
533         self.notify()
534
535     def get_weight(self):
536         return self.weight
537
538     def set_second_view(self, second_view):
539         self.second_view = second_view
540         self.load_values()
541         self.notify()
542
543     def set_callback_ui(self, func):
544         self.callback_update_ui = func
545
546     def set_height(self, height_interval):
547         self.height_inteval = height_interval
548         #set height, will affect the distance
549         if height_interval == 0:
550             self.STEP_LENGTH = 0.59
551         elif height_interval == 1:
552             self.STEP_LENGTH = 0.64
553         elif height_interval == 2:
554             self.STEP_LENGTH = 0.71
555         elif height_interval == 3:
556             self.STEP_LENGTH = 0.77
557         elif height_interval == 4:
558             self.STEP_LENGTH = 0.83
559         #increase step length if RUNNING
560         if self.mode == 1:
561             self.STEP_LENGTH *= 1.45
562         self.notify()
563
564     def set_no_idle_time(self, value):
565         self.no_idle_time = value
566
567     def get_distance(self, steps=None):
568         if steps == None:
569             steps = self.counter
570         return self.STEP_LENGTH * steps;
571
572     def add_observer(self, func):
573         try:
574             self.observers.index(func)
575         except:
576             self.observers.append(func)
577
578     def remove_observer(self, func):
579         self.observers.remove(func)
580
581     def notify(self, optional=False):
582         if self.callback_update_ui is not None:
583             self.callback_update_ui()
584
585         for func in self.observers:
586             func(optional)
587
588 class AlarmController(Singleton):
589     enable = False
590     fname = "/home/user/MyDocs/.sounds/Ringtones/Bicycle.aac"
591     interval = 5
592     type = 0
593
594     player = None
595     is_playing = False
596     pedo_controller = None
597
598     def __init__(self):
599         self.client = gconf.client_get_default()
600
601         self.enable = self.client.get_bool(ALARM_ENABLE)
602         self.fname = self.client.get_string(ALARM_FNAME)
603         self.interval = self.client.get_int(ALARM_INTERVAL)
604         self.type = self.client.get_int(ALARM_TYPE)
605
606         self.pedo_controller = PedoController()
607         if self.enable:
608             self.init_player()
609             self.pedo_controller.add_observer(self.update)
610             self.start_value = self.pedo_controller.get_first()
611
612     def init_player(self):
613         self.player = gst.element_factory_make("playbin2", "player")
614         fakesink = gst.element_factory_make("fakesink", "fakesink")
615         self.player.set_property("video-sink", fakesink)
616
617         bus = self.player.get_bus()
618         bus.add_signal_watch()
619         bus.connect("message", self.on_message)
620
621     def on_message(self, bus, message):
622         t = message.type
623         if t == gst.MESSAGE_EOS:
624             self.player.set_state(gst.STATE_NULL)
625             self.is_playing = False
626         elif t == gst.MESSAGE_ERROR:
627             self.player.set_state(gst.STATE_NULL)
628             self.is_playing = False
629             err, debug = message.parse_error()
630             logger.error("ERROR: %s, %s" % (err, debug) )
631
632     def update(self, optional):
633         diff = self.pedo_controller.get_first() - self.start_value
634         if self.type == 0 and diff.time >= self.interval * 60 or \
635                    self.type == 1 and diff.steps >= self.interval or \
636                    self.type == 2 and diff.dist >= self.interval or \
637                    self.type == 3 and diff.calories >= self.interval:
638             self.play()
639             #get new instance of current values
640             self.start_value = PedoValues() + self.pedo_controller.get_first()
641             logger.info("Alarm!")
642
643     def play(self):
644         if self.player is None:
645             self.init_player()
646         if self.is_playing:
647             self.player.set_state(gst.STATE_NULL)
648             self.is_playing = False
649         else:
650             self.player.set_property("uri", "file://" + self.fname)
651             self.player.set_state(gst.STATE_PLAYING)
652             self.is_playing = True
653
654     def stop(self):
655         self.player.set_state(gst.STATE_NULL)
656
657     def set_enable(self, value):
658        self.enable = value
659        self.client.set_bool(ALARM_ENABLE, value)
660        if self.enable:
661            self.init_player()
662            self.pedo_controller.add_observer(self.update)
663            self.start_value = self.pedo_controller.get_first()
664        else:
665            self.stop()
666            self.player = None
667            self.pedo_controller.remove_observer(self.update)
668
669     def get_enable(self):
670         return self.enable
671
672     def set_alarm_file(self, fname):
673         self.fname = fname
674         self.client.set_string(ALARM_FNAME, fname)
675
676     def get_alarm_file(self):
677         if self.fname == None:
678             return ""
679         return self.fname
680
681     def set_interval(self, interval):
682         self.interval = interval
683         self.client.set_int(ALARM_INTERVAL, interval)
684
685     def get_interval(self):
686         return self.interval
687
688     def set_type(self, type):
689         self.type = type
690         self.client.set_int(ALARM_TYPE, type)
691
692     def get_type(self):
693         return self.type
694
695 class PedoCounter(Singleton):
696     COORD_FNAME = "/sys/class/i2c-adapter/i2c-3/3-001d/coord"
697     COORD_FNAME_SDK = "/home/andrei/pedometer-widget-0.1/date.txt"
698     LOGFILE = "/home/user/log_pedometer"
699     #time in ms between two accelerometer data reads
700     COORD_GET_INTERVAL = 25
701
702     COUNT_INTERVAL = 5
703
704     interval_counter = None
705     stop_requested = False
706     update_function = None
707     logging = False
708     isRunning = False
709
710     def __init__(self, update_function=None):
711         if not os.path.exists(self.COORD_FNAME):
712             self.COORD_FNAME = self.COORD_FNAME_SDK
713
714         self.interval_counter = PedoIntervalCounter()
715         self.update_function = update_function
716
717     def set_logging(self, value):
718         self.logging = value
719
720     def get_rotation(self):
721         f = open(self.COORD_FNAME, 'r')
722         coords = [int(w) for w in f.readline().split()]
723         f.close()
724         return coords
725
726     def start(self):
727         logger.info("Counter started")
728         self.isRunning = True
729         self.stop_requested = False
730         if self.logging:
731             fname = "%d_%d_%d_%d_%d_%d" % time.localtime()[0:6]
732             self.file = open(self.LOGFILE + fname + ".txt", "w")
733         gobject.idle_add(self.run)
734
735     def run(self):
736         self.coords = [[], [], []]
737         self.stime = time.time()
738         self.t = []
739         gobject.timeout_add(self.COORD_GET_INTERVAL, self.read_coords)
740         return False
741
742     def read_coords(self):
743         x, y, z = self.get_rotation()
744         self.coords[0].append(int(x))
745         self.coords[1].append(int(y))
746         self.coords[2].append(int(z))
747         now = time.time() - self.stime
748         if self.logging:
749             self.file.write("%d %d %d %f\n" % (self.coords[0][-1], self.coords[1][-1], self.coords[2][-1], now))
750
751         self.t.append(now)
752         #call stop_interval
753         ret = True
754         if self.t[-1] > self.COUNT_INTERVAL or self.stop_requested:
755             ret = False
756             gobject.idle_add(self.stop_interval)
757         return ret
758
759     def stop_interval(self):
760         self.interval_counter.set_vals(self.coords, self.t)
761         cnt = self.interval_counter.number_steps()
762
763         logger.info("Number of steps detected for last interval %d, number of coords: %d" % (cnt, len(self.t)))
764
765         gobject.idle_add(self.update_function, cnt, self.stop_requested)
766
767         if self.stop_requested:
768             gobject.idle_add(self.stop)
769         else:
770             gobject.idle_add(self.run)
771         return False
772
773     def stop(self):
774         if self.logging:
775             self.file.close()
776         logger.info("Counter has finished")
777
778     def request_stop(self):
779         self.stop_requested = True
780         self.isRunning = False
781
782 class CustomButton(hildon.Button):
783     def __init__(self, icon):
784         hildon.Button.__init__(self, gtk.HILDON_SIZE_AUTO_WIDTH, hildon.BUTTON_ARRANGEMENT_VERTICAL)
785         self.icon = icon
786         self.set_size_request(int(32 * 1.4), int(30 * 1.0))
787         self.retval = self.connect("expose_event", self.expose)
788
789     def set_icon(self, icon):
790         self.icon = icon
791
792     def expose(self, widget, event):
793         self.context = widget.window.cairo_create()
794         self.context.rectangle(event.area.x, event.area.y,
795                             event.area.width, event.area.height)
796
797         self.context.clip()
798         rect = self.get_allocation()
799         self.context.rectangle(rect.x, rect.y, rect.width, rect.height)
800         self.context.set_source_rgba(1, 1, 1, 0)
801
802         style = self.rc_get_style()
803         color = style.lookup_color("DefaultBackgroundColor")
804         if self.state == gtk.STATE_ACTIVE:
805             style = self.rc_get_style()
806             color = style.lookup_color("SelectionColor")
807             self.context.set_source_rgba (color.red / 65535.0, color.green / 65335.0, color.blue / 65535.0, 0.75);
808         self.context.fill()
809
810         #img = cairo.ImageSurface.create_from_png(self.icon)
811
812         #self.context.set_source_surface(img)
813         #self.context.set_source_surface(img, rect.width/2 - img.get_width() /2, 0)
814         img = gtk.Image()
815         img.set_from_file(self.icon)
816         buf = img.get_pixbuf()
817         buf = buf.scale_simple(int(32 * 1.5), int(30 * 1.5), gtk.gdk.INTERP_BILINEAR)
818
819         self.context.set_source_pixbuf(buf, rect.x + (event.area.width / 2 - 15) - 8, rect.y + 1)
820         self.context.scale(200, 200)
821         self.context.paint()
822
823         return self.retval
824
825 class CustomEventBox(gtk.EventBox):
826
827     def __init__(self):
828         gtk.EventBox.__init__(self)
829
830     def do_expose_event(self, event):
831         self.context = self.window.cairo_create()
832         self.context.rectangle(event.area.x, event.area.y,
833                             event.area.width, event.area.height)
834
835         self.context.clip()
836         rect = self.get_allocation()
837         self.context.rectangle(rect.x, rect.y, rect.width, rect.height)
838
839         if self.state == gtk.STATE_ACTIVE:
840             style = self.rc_get_style()
841             color = style.lookup_color("SelectionColor")
842             self.context.set_source_rgba (color.red / 65535.0, color.green / 65335.0, color.blue / 65535.0, 0.75);
843         else:
844             self.context.set_source_rgba(1, 1, 1, 0)
845         self.context.fill()
846
847         gtk.EventBox.do_expose_event(self, event)
848
849 class GraphController(Singleton):
850     ytitles = ["Steps", "Average Speed", "Distance", "Calories"]
851     xtitles = ["Day", "Week"] # "Today"]
852     widget = None
853     def __init__(self):
854         self.repository = PedoRepositoryXML()
855         self.last_update = 0
856         PedoController().add_observer(self.update_ui)
857
858     def set_graph(self, widget):
859         self.widget = widget
860         self.update_ui()
861
862     def set_current_view(self, view):
863         """
864         current_view % len(ytitles) - gives the ytitle
865         current_view / len(ytitles) - gives the xtitle
866         """
867         self.current_view = view
868
869         if self.current_view == len(self.ytitles) * len(self.xtitles):
870             self.current_view = 0
871         self.x_id = self.current_view / len(self.ytitles)
872         self.y_id = self.current_view % len(self.ytitles)
873
874     def next_view(self):
875         self.set_current_view(self.current_view+1)
876         self.update_ui()
877         return self.current_view
878
879     def last_weeks_labels(self):
880         d = date.today()
881         delta = timedelta(days=7)
882         ret = []
883         for i in range(7):
884             ret.append(d.strftime("Week %W"))
885             d = d - delta
886         return ret
887
888     def compute_values(self):
889         labels = []
890         if self.x_id == 0:
891             values = self.repository.get_last_7_days()
892             d = date.today()
893             delta = timedelta(days=1)
894             for i in range(7):
895                 labels.append(d.ctime().split()[0])
896                 d = d - delta
897
898         elif self.x_id == 1:
899             values = self.repository.get_last_weeks()
900             d = date.today()
901             for i in range(7):
902                 labels.append(d.strftime("Week %W"))
903                 d = d - timedelta(days=7)
904         else:
905             values = self.repository.get_today()
906             #TODO get labels
907
908         if self.y_id == 0:
909             yvalues = [line.steps for line in values]
910         elif self.y_id == 1:
911             yvalues = [line.get_avg_speed() for line in values]
912         elif self.y_id == 2:
913             yvalues = [line.dist for line in values]
914         else:
915             yvalues = [line.calories for line in values]
916
917         #determine values for y lines in graph
918         diff = self.get_best_interval_value(max(yvalues))
919         ytext = []
920         for i in range(6):
921             ytext.append(str(int(i*diff)))
922
923         if self.widget is not None:
924             yvalues.reverse()
925             labels.reverse()
926             self.widget.values = yvalues
927             self.widget.ytext = ytext
928             self.widget.xtext = labels
929             self.widget.max_value = diff * 5
930             self.widget.text = self.xtitles[self.x_id] + " / " + self.ytitles[self.y_id]
931             self.widget.queue_draw()
932         else:
933             logger.error("Widget not set in GraphController")
934
935     def get_best_interval_value(self, max_value):
936         diff =  1.0 * max_value / 5
937         l = len(str(int(diff)))
938         d = math.pow(10, l/2)
939         val = int(math.ceil(1.0 * diff / d)) * d
940         if val == 0:
941             val = 1
942         return val
943
944     def update_ui(self, optional=False):
945         """update graph values every x seconds"""
946         if optional and self.last_update - time.time() < 600:
947             return
948         if self.widget is None:
949             return
950
951         self.compute_values()
952         self.last_update = time.time()
953
954 class GraphWidget(gtk.DrawingArea):
955
956     def __init__(self):
957         gtk.DrawingArea.__init__(self)
958         self.set_size_request(-1, 150)
959         self.yvalues = 5
960
961         """sample values"""
962         self.ytext = ["   0", "1000", "2000", "3000", "4000", "5000"]
963         self.xtext = ["Monday", "Tuesday", "Wednesday","Thursday", "Friday", "Saturday", "Sunday"]
964         self.values = [1500, 3400, 4000, 3600, 3200, 0, 4500]
965         self.max_value = 5000
966         self.text = "All time steps"
967
968     def do_expose_event(self, event):
969         context = self.window.cairo_create()
970
971         # set a clip region for the expose event
972         context.rectangle(event.area.x, event.area.y,
973                                event.area.width, event.area.height)
974         context.clip()
975
976         context.save()
977
978         context.set_operator(cairo.OPERATOR_SOURCE)
979         style = self.rc_get_style()
980
981         if self.state == gtk.STATE_ACTIVE:
982             color = style.lookup_color("SelectionColor")
983         else:
984              color = style.lookup_color("DefaultBackgroundColor")
985         context.set_source_rgba (color.red / 65535.0, color.green / 65335.0, color.blue / 65535.0, 0.75)
986
987         context.paint()
988         context.restore();
989         self.draw(context)
990
991     def draw(self, cr):
992         space_below = 20
993         space_above = 10
994         border_right = 10
995         border_left = 30
996
997         rect = self.get_allocation()
998         x = rect.width
999         y = rect.height
1000
1001         cr.select_font_face("Purisa", cairo.FONT_SLANT_NORMAL,
1002             cairo.FONT_WEIGHT_NORMAL)
1003         cr.set_font_size(13)
1004
1005         #check space needed to display ylabels
1006         te = cr.text_extents(self.ytext[-1])
1007         border_left = te[2] + 7
1008
1009         cr.set_source_rgb(1, 1, 1)
1010         cr.move_to(border_left, space_above)
1011         cr.line_to(border_left, y-space_below)
1012         cr.set_line_width(2)
1013         cr.stroke()
1014
1015         cr.move_to(border_left, y-space_below)
1016         cr.line_to(x-border_right, y-space_below)
1017         cr.set_line_width(2)
1018         cr.stroke()
1019
1020         ydiff = (y-space_above-space_below) / self.yvalues
1021         for i in range(self.yvalues):
1022             yy = y-space_below-ydiff*(i+1)
1023             cr.move_to(border_left, yy)
1024             cr.line_to(x-border_right, yy)
1025             cr.set_line_width(0.8)
1026             cr.stroke()
1027
1028
1029         for i in range(6):
1030             yy = y - space_below - ydiff*i + 5
1031             te = cr.text_extents(self.ytext[i])
1032
1033             cr.move_to(border_left-te[2]-2, yy)
1034             cr.show_text(self.ytext[i])
1035
1036         cr.set_font_size(15)
1037         te = cr.text_extents(self.text)
1038         cr.move_to((x-te[2])/2, y-5)
1039         cr.show_text(self.text)
1040
1041         graph_x_space = x - border_left - border_right
1042         graph_y_space = y - space_below - space_above
1043         bar_width = graph_x_space*0.75 / len(self.values)
1044         bar_distance = graph_x_space*0.25 / (1+len(self.values))
1045
1046         #set dummy max value to avoid exceptions
1047         if self.max_value == 0:
1048             self.max_value = 100
1049         for i in range(len(self.values)):
1050             xx = border_left + (i+1)*bar_distance + i * bar_width
1051             yy = y-space_below
1052             height = graph_y_space * (1.0 * self.values[i] / self.max_value)
1053             cr.set_source_rgba(1, 1, 1, 0.75)
1054             cr.rectangle(int(xx), int(yy-height), int(bar_width), int(height))
1055             cr.fill()
1056
1057         cr.set_source_rgba(1, 1, 1, 1)
1058         cr.select_font_face("Purisa", cairo.FONT_SLANT_NORMAL,
1059                             cairo.FONT_WEIGHT_NORMAL)
1060         cr.set_font_size(13)
1061
1062         cr.rotate(2*math.pi * (-45) / 180)
1063         for i in range(len(self.values)):
1064             xx = y - space_below - 10
1065             yy = border_left + (i+1)*bar_distance + i * bar_width
1066             cr.move_to(-xx, yy + bar_width*1.25 / 2)
1067             cr.show_text(self.xtext[i])
1068
1069 class PedometerHomePlugin(hildondesktop.HomePluginItem):
1070     button = None
1071
1072     #labels to display
1073     labels = ["timer", "count", "dist", "avgSpeed", "calories"]
1074
1075     #current view
1076     labelsC = {}
1077
1078     #second view ( day / week/ alltime)
1079     labelsT = {}
1080
1081     second_view_labels = ["All-time", "Today", "This week"]
1082
1083     controller = None
1084     graph_controller = None
1085
1086     mode = 0
1087     height = 0
1088     weight = 70
1089     unit = 0
1090     aspect = 0
1091     second_view = 0
1092     graph_view = 0
1093     no_idle_time = False
1094     logging = False
1095
1096     def __init__(self):
1097         hildondesktop.HomePluginItem.__init__(self)
1098
1099         gobject.type_register(CustomEventBox)
1100         gobject.type_register(GraphWidget)
1101
1102         self.client = gconf.client_get_default()
1103
1104         self.mode = self.client.get_int(MODE)
1105         self.height = self.client.get_int(HEIGHT)
1106         self.weight = self.client.get_int(WEIGHT)
1107         self.unit = self.client.get_int(UNIT)
1108         self.aspect = self.client.get_int(ASPECT)
1109         self.second_view = self.client.get_int(SECONDVIEW)
1110         self.graph_view = self.client.get_int(GRAPHVIEW)
1111         self.no_idle_time = self.client.get_bool(NOIDLETIME)
1112         self.logging = self.client.get_bool(LOGGING)
1113
1114         self.controller = PedoController()
1115         self.controller.set_height(self.height)
1116         self.controller.set_weight(self.weight)
1117         self.controller.set_mode(self.mode)
1118         self.controller.set_unit(self.unit)
1119         self.controller.set_second_view(self.second_view)
1120         self.controller.set_callback_ui(self.update_values)
1121         self.controller.set_no_idle_time(self.no_idle_time)
1122
1123         self.graph_controller = GraphController()
1124         self.graph_controller.set_current_view(self.graph_view)
1125
1126         self.alarm_controller = AlarmController()
1127
1128         self.button = CustomButton(ICONSPATH + "play.png")
1129         self.button.connect("clicked", self.button_clicked)
1130
1131         self.create_labels(self.labelsC)
1132         self.create_labels(self.labelsT)
1133         self.label_second_view = self.new_label_heading(self.second_view_labels[self.second_view])
1134
1135         self.update_current()
1136         self.update_total()
1137
1138         mainHBox = gtk.HBox(spacing=1)
1139
1140         descVBox = gtk.VBox(spacing=1)
1141         descVBox.add(self.new_label_heading())
1142         descVBox.add(self.new_label_heading("Time:"))
1143         descVBox.add(self.new_label_heading("Steps:"))
1144         descVBox.add(self.new_label_heading("Calories:"))
1145         descVBox.add(self.new_label_heading("Distance:"))
1146         descVBox.add(self.new_label_heading("Avg Speed:"))
1147
1148         currentVBox = gtk.VBox(spacing=1)
1149         currentVBox.add(self.new_label_heading("Current"))
1150         currentVBox.add(self.labelsC["timer"])
1151         currentVBox.add(self.labelsC["count"])
1152         currentVBox.add(self.labelsC["calories"])
1153         currentVBox.add(self.labelsC["dist"])
1154         currentVBox.add(self.labelsC["avgSpeed"])
1155         self.currentBox = currentVBox
1156
1157         totalVBox = gtk.VBox(spacing=1)
1158         totalVBox.add(self.label_second_view)
1159         totalVBox.add(self.labelsT["timer"])
1160         totalVBox.add(self.labelsT["count"])
1161         totalVBox.add(self.labelsT["calories"])
1162         totalVBox.add(self.labelsT["dist"])
1163         totalVBox.add(self.labelsT["avgSpeed"])
1164         self.totalBox = totalVBox
1165
1166         buttonVBox = gtk.VBox(spacing=1)
1167         buttonVBox.add(self.new_label_heading(""))
1168         buttonVBox.add(self.button)
1169         buttonVBox.add(self.new_label_heading(""))
1170
1171         eventBox = CustomEventBox()
1172         eventBox.set_visible_window(False)
1173         eventBox.add(totalVBox)
1174         eventBox.connect("button-press-event", self.eventBox_clicked)
1175         eventBox.connect("button-release-event", self.eventBox_clicked_release)
1176
1177         mainHBox.add(buttonVBox)
1178         mainHBox.add(descVBox)
1179         mainHBox.add(currentVBox)
1180         mainHBox.add(eventBox)
1181         self.mainhbox = mainHBox
1182
1183         graph = GraphWidget()
1184         self.graph_controller.set_graph(graph)
1185
1186         eventBoxGraph = CustomEventBox()
1187         eventBoxGraph.set_visible_window(False)
1188         eventBoxGraph.add(graph)
1189         self.graph = graph
1190         eventBoxGraph.connect("button-press-event", self.eventBoxGraph_clicked)
1191         eventBoxGraph.connect("button-release-event", self.eventBoxGraph_clicked_release)
1192         self.graphBox = eventBoxGraph
1193
1194         self.mainvbox = gtk.VBox()
1195
1196         self.mainvbox.add(mainHBox)
1197         self.mainvbox.add(eventBoxGraph)
1198
1199         self.mainvbox.show_all()
1200         self.add(self.mainvbox)
1201         self.update_aspect()
1202
1203         self.connect("unrealize", self.close_requested)
1204         self.set_settings(True)
1205         self.connect("show-settings", self.show_settings)
1206
1207     def eventBoxGraph_clicked(self, widget, data=None):
1208         widget.set_state(gtk.STATE_ACTIVE)
1209
1210     def eventBoxGraph_clicked_release(self, widget, data=None):
1211         self.graph_view = self.graph_controller.next_view()
1212         self.client.set_int(GRAPHVIEW, self.graph_view)
1213
1214         widget.set_state(gtk.STATE_NORMAL)
1215
1216     def eventBox_clicked(self, widget, data=None):
1217         widget.set_state(gtk.STATE_ACTIVE)
1218
1219     def eventBox_clicked_release(self, widget, data=None):
1220         widget.set_state(gtk.STATE_NORMAL)
1221
1222         self.second_view = (self.second_view + 1) % 3
1223         self.controller.set_second_view(self.second_view)
1224         self.client.set_int(SECONDVIEW, self.second_view)
1225
1226     def new_label_heading(self, title=""):
1227         l = gtk.Label(title)
1228         hildon.hildon_helper_set_logical_font(l, "SmallSystemFont")
1229         return l
1230
1231     def create_labels(self, new_labels):
1232         for label in self.labels:
1233             l = gtk.Label()
1234             hildon.hildon_helper_set_logical_font(l, "SmallSystemFont")
1235             hildon.hildon_helper_set_logical_color(l, gtk.RC_FG, gtk.STATE_NORMAL, "ActiveTextColor")
1236             new_labels[label] = l
1237
1238     def update_aspect(self):
1239
1240         if self.aspect > 0:
1241             self.graphBox.hide_all()
1242         else:
1243             self.graphBox.show_all()
1244
1245         if self.aspect == 0 or self.aspect == 1:
1246             self.currentBox.show_all()
1247             self.totalBox.show_all()
1248         elif self.aspect == 2:
1249             self.currentBox.show_all()
1250             self.totalBox.hide_all()
1251         else:
1252             self.currentBox.hide_all()
1253             self.totalBox.show_all()
1254
1255         x,y = self.size_request()
1256         self.resize(x,y)
1257
1258     def update_ui_values(self, labels, values):
1259         labels["timer"].set_label(values.get_print_time())
1260         labels["count"].set_label(values.get_print_steps())
1261         labels["dist"].set_label(values.get_print_distance())
1262         labels["avgSpeed"].set_label(values.get_print_avg_speed())
1263         labels["calories"].set_label(values.get_print_calories())
1264
1265     def update_current(self):
1266         self.update_ui_values(self.labelsC, self.controller.get_first())
1267
1268     def update_total(self):
1269         self.update_ui_values(self.labelsT, self.controller.get_second())
1270
1271     def show_alarm_settings(self, main_button):
1272         def choose_file(widget):
1273             file = hildon.FileChooserDialog(self, gtk.FILE_CHOOSER_ACTION_OPEN, hildon.FileSystemModel() )
1274             file.show()
1275             if ( file.run() == gtk.RESPONSE_OK):
1276                 fname = file.get_filename()
1277                 widget.set_value(fname)
1278                 self.alarm_controller.set_alarm_file(fname)
1279             file.destroy()
1280
1281         def test_sound(button):
1282             try:
1283                 self.alarm_controller.play()
1284             except Exception, e:
1285                 logger.error("Could not play alarm sound: %s" % e)
1286                 hildon.hildon_banner_show_information(self, "None", "Could not play alarm sound")
1287
1288         def enableButton_changed(button):
1289             value = button.get_active()
1290             self.alarm_controller.set_enable(value)
1291             if value:
1292                 main_button.set_value("Enabled")
1293             else:
1294                 main_button.set_value("Disabled")
1295
1296         def selectorType_changed(selector, data, labelEntry2):
1297             self.alarm_controller.set_type(selector.get_active(0))
1298             labelEntry2.set_label(suffix[self.alarm_controller.get_type()])
1299
1300         dialog = gtk.Dialog()
1301         dialog.set_title("Alarm settings")
1302         dialog.add_button("OK", gtk.RESPONSE_OK)
1303
1304         enableButton = hildon.CheckButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT)
1305         enableButton.set_label("Enable alarm")
1306         enableButton.set_active(self.alarm_controller.get_enable())
1307         enableButton.connect("toggled", enableButton_changed)
1308
1309         testButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1310         testButton.set_alignment(0, 0.8, 1, 1)
1311         testButton.set_title("Test sound")
1312         testButton.connect("pressed", test_sound)
1313
1314         fileButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1315         fileButton.set_alignment(0, 0.8, 1, 1)
1316         fileButton.set_title("Alarm sound")
1317         fileButton.set_value(self.alarm_controller.get_alarm_file())
1318         fileButton.connect("pressed", choose_file)
1319
1320         labelEntry = gtk.Label("Notify every:")
1321         suffix = ["mins", "steps", "m/ft", "calories"]
1322         labelEntry2 = gtk.Label(suffix[self.alarm_controller.get_type()])
1323         intervalEntry = hildon.Entry(gtk.HILDON_SIZE_AUTO_WIDTH)
1324         intervalEntry.set_text(str(self.alarm_controller.get_interval()))
1325
1326         selectorType = hildon.TouchSelector(text=True)
1327         selectorType.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
1328         selectorType.append_text("Time")
1329         selectorType.append_text("Steps")
1330         selectorType.append_text("Distance")
1331         selectorType.append_text("Calories")
1332         selectorType.connect("changed", selectorType_changed, labelEntry2)
1333
1334         typePicker = hildon.PickerButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1335         typePicker.set_alignment(0.0, 0.5, 1.0, 1.0)
1336         typePicker.set_title("Alarm type")
1337         typePicker.set_selector(selectorType)
1338         typePicker.set_active(self.alarm_controller.get_type())
1339
1340         hbox = gtk.HBox()
1341         hbox.add(labelEntry)
1342         hbox.add(intervalEntry)
1343         hbox.add(labelEntry2)
1344
1345         dialog.vbox.add(enableButton)
1346         dialog.vbox.add(fileButton)
1347         dialog.vbox.add(testButton)
1348         dialog.vbox.add(typePicker)
1349         dialog.vbox.add(hbox)
1350         dialog.show_all()
1351         while 1:
1352             response = dialog.run()
1353             if response != gtk.RESPONSE_OK:
1354                 break
1355             try:
1356                 value = int(intervalEntry.get_text())
1357                 self.alarm_controller.set_interval(value)
1358                 break
1359             except:
1360                 hildon.hildon_banner_show_information(self, "None", "Invalid interval")
1361
1362         dialog.destroy()
1363
1364     def show_settings(self, widget):
1365         def reset_total_counter(arg):
1366             note = hildon.hildon_note_new_confirmation(self.dialog, "Are you sure you want to delete all your pedometer history?")
1367             ret = note.run()
1368             if ret == gtk.RESPONSE_OK:
1369                 self.controller.reset_all_values()
1370                 hildon.hildon_banner_show_information(self, "None", "All history was deleted")
1371             note.destroy()
1372
1373         def alarmButton_pressed(widget):
1374             self.show_alarm_settings(widget)
1375
1376         def selector_changed(selector, data):
1377             widget.mode = selector.get_active(0)
1378             widget.client.set_int(MODE, widget.mode)
1379             widget.controller.set_mode(widget.mode)
1380
1381         def selectorH_changed(selector, data):
1382             widget.height = selectorH.get_active(0)
1383             widget.client.set_int(HEIGHT, widget.height)
1384             widget.controller.set_height(widget.height)
1385
1386         def selectorUnit_changed(selector, data):
1387             widget.unit = selectorUnit.get_active(0)
1388             widget.client.set_int(UNIT, widget.unit)
1389             widget.controller.set_unit(widget.unit)
1390
1391         def selectorUI_changed(selector, data):
1392             widget.aspect = selectorUI.get_active(0)
1393             widget.client.set_int(ASPECT, widget.aspect)
1394             widget.update_aspect()
1395
1396         def logButton_changed(checkButton):
1397             widget.logging = checkButton.get_active()
1398             widget.client.set_bool(LOGGING, widget.logging)
1399
1400         def idleButton_changed(idleButton):
1401             widget.no_idle_time = idleButton.get_active()
1402             widget.client.set_bool(NOIDLETIME, widget.no_idle_time)
1403             widget.controller.set_no_idle_time(widget.no_idle_time)
1404
1405         def weight_dialog(button):
1406             dialog = gtk.Dialog("Weight", self.dialog)
1407             dialog.add_button("OK", gtk.RESPONSE_OK)
1408
1409             label = gtk.Label("Weight:")
1410             entry = gtk.Entry()
1411             entry.set_text(str(self.controller.get_weight()))
1412
1413             suffixLabel = gtk.Label(self.controller.get_str_weight_unit())
1414
1415             hbox = gtk.HBox()
1416             hbox.add(label)
1417             hbox.add(entry)
1418             hbox.add(suffixLabel)
1419
1420             dialog.vbox.add(hbox)
1421             dialog.show_all()
1422             while 1:
1423                 response = dialog.run()
1424                 if response != gtk.RESPONSE_OK:
1425                     break
1426                 try:
1427                     value = int(entry.get_text())
1428                     if value <= 0:
1429                         raise ValueError
1430                     self.controller.set_weight(value)
1431                     self.client.set_int(WEIGHT, value)
1432                     weightButton.set_value(str(self.controller.get_weight()) + \
1433                                            " " + self.controller.get_str_weight_unit() )
1434                     break
1435                 except:
1436                     hildon.hildon_banner_show_information(self, "None", "Invalid weight")
1437             dialog.destroy()
1438
1439         dialog = gtk.Dialog()
1440         dialog.set_title("Settings")
1441         dialog.add_button("OK", gtk.RESPONSE_OK)
1442         self.dialog = dialog
1443
1444         resetButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1445         resetButton.set_title("Reset total counter")
1446         resetButton.set_alignment(0, 0.8, 1, 1)
1447         resetButton.connect("clicked", reset_total_counter)
1448
1449         alarmButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1450         alarmButton.set_title("Alarm")
1451         if self.alarm_controller.get_enable():
1452             alarmButton.set_value("Enabled")
1453         else:
1454             alarmButton.set_value("Disabled")
1455         alarmButton.set_alignment(0, 0.8, 1, 1)
1456         alarmButton.connect("clicked", alarmButton_pressed)
1457
1458         selector = hildon.TouchSelector(text=True)
1459         selector.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
1460         selector.append_text("Walk")
1461         selector.append_text("Run")
1462         selector.connect("changed", selector_changed)
1463
1464         modePicker = hildon.PickerButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1465         modePicker.set_alignment(0.0, 0.5, 1.0, 1.0)
1466         modePicker.set_title("Mode")
1467         modePicker.set_selector(selector)
1468         modePicker.set_active(widget.mode)
1469
1470         selectorH = hildon.TouchSelector(text=True)
1471         selectorH.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
1472         selectorH.append_text("< 1.50 m")
1473         selectorH.append_text("1.50 - 1.65 m")
1474         selectorH.append_text("1.66 - 1.80 m")
1475         selectorH.append_text("1.81 - 1.95 m")
1476         selectorH.append_text(" > 1.95 m")
1477         selectorH.connect("changed", selectorH_changed)
1478
1479         heightPicker = hildon.PickerButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1480         heightPicker.set_alignment(0.0, 0.5, 1.0, 1.0)
1481         heightPicker.set_title("Height")
1482         heightPicker.set_selector(selectorH)
1483         heightPicker.set_active(widget.height)
1484
1485         weightButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1486         weightButton.set_title("Weight")
1487         weightButton.set_alignment(0, 0.8, 1, 1)
1488         weightButton.set_value(str(self.controller.get_weight()) + " " + self.controller.get_str_weight_unit() )
1489         weightButton.connect("clicked", weight_dialog)
1490
1491         selectorUnit = hildon.TouchSelector(text=True)
1492         selectorUnit.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
1493         selectorUnit.append_text("Metric (km)")
1494         selectorUnit.append_text("English (mi)")
1495         selectorUnit.connect("changed", selectorUnit_changed)
1496
1497         unitPicker = hildon.PickerButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1498         unitPicker.set_alignment(0.0, 0.5, 1.0, 1.0)
1499         unitPicker.set_title("Unit")
1500         unitPicker.set_selector(selectorUnit)
1501         unitPicker.set_active(widget.unit)
1502
1503         selectorUI = hildon.TouchSelector(text=True)
1504         selectorUI = hildon.TouchSelector(text=True)
1505         selectorUI.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
1506         selectorUI.append_text("Show current + total + graph")
1507         selectorUI.append_text("Show current + total")
1508         selectorUI.append_text("Show only current")
1509         selectorUI.append_text("Show only total")
1510         selectorUI.connect("changed", selectorUI_changed)
1511
1512         UIPicker = hildon.PickerButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1513         UIPicker.set_alignment(0.0, 0.5, 1.0, 1.0)
1514         UIPicker.set_title("Widget aspect")
1515         UIPicker.set_selector(selectorUI)
1516         UIPicker.set_active(widget.aspect)
1517
1518         logButton = hildon.CheckButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT)
1519         logButton.set_label("Log data")
1520         logButton.set_active(widget.logging)
1521         logButton.connect("toggled", logButton_changed)
1522
1523         idleButton = hildon.CheckButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT)
1524         idleButton.set_label("Pause time when not walking")
1525         idleButton.set_active(widget.no_idle_time)
1526         idleButton.connect("toggled", idleButton_changed)
1527
1528         pan_area = hildon.PannableArea()
1529         vbox = gtk.VBox()
1530         vbox.add(alarmButton)
1531         vbox.add(modePicker)
1532         vbox.add(heightPicker)
1533         vbox.add(weightButton)
1534         vbox.add(unitPicker)
1535         vbox.add(UIPicker)
1536         vbox.add(idleButton)
1537         vbox.add(resetButton)
1538         #vbox.add(logButton)
1539
1540         pan_area.add_with_viewport(vbox)
1541         pan_area.set_size_request(-1, 300)
1542
1543         dialog.vbox.add(pan_area)
1544         dialog.show_all()
1545         response = dialog.run()
1546         #hildon.hildon_banner_show_information(self, "None", "You have to Stop/Start the counter to apply the new settings")
1547         dialog.destroy()
1548
1549     def close_requested(self, widget):
1550         if self.controller.is_running:
1551             self.controller.stop_pedometer()
1552         self.controller.stop_midnight_callback()
1553
1554     def update_values(self):
1555         #TODO: do not update if the widget is not on the active desktop
1556         self.label_second_view.set_label(self.second_view_labels[self.second_view])
1557         self.update_current()
1558         self.update_total()
1559
1560     def button_clicked(self, button):
1561         if self.controller.is_running:
1562             self.controller.stop_pedometer()
1563             self.button.set_icon(ICONSPATH + "play.png")
1564         else:
1565             self.controller.start_pedometer()
1566             self.button.set_icon(ICONSPATH + "stop.png")
1567
1568     def do_expose_event(self, event):
1569         cr = self.window.cairo_create()
1570         cr.region(event.window.get_clip_region())
1571         cr.clip()
1572         #cr.set_source_rgba(0.4, 0.64, 0.564, 0.5)
1573         style = self.rc_get_style()
1574         color = style.lookup_color("DefaultBackgroundColor")
1575         cr.set_source_rgba (color.red / 65535.0, color.green / 65335.0, color.blue / 65535.0, 0.75);
1576
1577         radius = 5
1578         width = self.allocation.width
1579         height = self.allocation.height
1580
1581         x = self.allocation.x
1582         y = self.allocation.y
1583
1584         cr.move_to(x + radius, y)
1585         cr.line_to(x + width - radius, y)
1586         cr.curve_to(x + width - radius, y, x + width, y, x + width, y + radius)
1587         cr.line_to(x + width, y + height - radius)
1588         cr.curve_to(x + width, y + height - radius, x + width, y + height, x + width - radius, y + height)
1589         cr.line_to(x + radius, y + height)
1590         cr.curve_to(x + radius, y + height, x, y + height, x, y + height - radius)
1591         cr.line_to(x, y + radius)
1592         cr.curve_to(x, y + radius, x, y, x + radius, y)
1593
1594         cr.set_operator(cairo.OPERATOR_SOURCE)
1595         cr.fill_preserve()
1596
1597         color = style.lookup_color("ActiveTextColor")
1598         cr.set_source_rgba (color.red / 65535.0, color.green / 65335.0, color.blue / 65535.0, 0.5);
1599         cr.set_line_width(1)
1600         cr.stroke()
1601
1602         hildondesktop.HomePluginItem.do_expose_event(self, event)
1603
1604     def do_realize(self):
1605         screen = self.get_screen()
1606         self.set_colormap(screen.get_rgba_colormap())
1607         self.set_app_paintable(True)
1608         hildondesktop.HomePluginItem.do_realize(self)
1609
1610 hd_plugin_type = PedometerHomePlugin
1611
1612 import math
1613 import logging
1614
1615 logger = logging.getLogger("pedometer")
1616 logger.setLevel(logging.INFO)
1617
1618 ch = logging.StreamHandler()
1619 formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
1620 ch.setFormatter(formatter)
1621 logger.addHandler(ch)
1622
1623 # The code below is just for testing purposes.
1624 # It allows to run the widget as a standalone process.
1625 if __name__ == "__main__":
1626     import gobject
1627     gobject.type_register(hd_plugin_type)
1628     obj = gobject.new(hd_plugin_type, plugin_id="plugin_id")
1629     obj.show_all()
1630     gtk.main()