Update UI at midnight
[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     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 = 10
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     pedometer = None
1085     pedometerInterval = None
1086     graph_controller = None
1087
1088     mode = 0
1089     height = 0
1090     weight = 70
1091     unit = 0
1092     aspect = 0
1093     second_view = 0
1094     graph_view = 0
1095     no_idle_time = False
1096     logging = False
1097
1098     def __init__(self):
1099         hildondesktop.HomePluginItem.__init__(self)
1100
1101         gobject.type_register(CustomEventBox)
1102         gobject.type_register(GraphWidget)
1103
1104         self.client = gconf.client_get_default()
1105
1106         self.mode = self.client.get_int(MODE)
1107         self.height = self.client.get_int(HEIGHT)
1108         self.weight = self.client.get_int(WEIGHT)
1109         self.unit = self.client.get_int(UNIT)
1110         self.aspect = self.client.get_int(ASPECT)
1111         self.second_view = self.client.get_int(SECONDVIEW)
1112         self.graph_view = self.client.get_int(GRAPHVIEW)
1113         self.no_idle_time = self.client.get_bool(NOIDLETIME)
1114         self.logging = self.client.get_bool(LOGGING)
1115
1116         self.controller = PedoController()
1117         self.controller.set_height(self.height)
1118         self.controller.set_weight(self.weight)
1119         self.controller.set_mode(self.mode)
1120         self.controller.set_unit(self.unit)
1121         self.controller.set_second_view(self.second_view)
1122         self.controller.set_callback_ui(self.update_values)
1123         self.controller.set_no_idle_time(self.no_idle_time)
1124
1125         self.graph_controller = GraphController()
1126         self.graph_controller.set_current_view(self.graph_view)
1127
1128         self.alarm_controller = AlarmController()
1129
1130         self.button = CustomButton(ICONSPATH + "play.png")
1131         self.button.connect("clicked", self.button_clicked)
1132
1133         self.create_labels(self.labelsC)
1134         self.create_labels(self.labelsT)
1135         self.label_second_view = self.new_label_heading(self.second_view_labels[self.second_view])
1136
1137         self.update_current()
1138         self.update_total()
1139
1140         mainHBox = gtk.HBox(spacing=1)
1141
1142         descVBox = gtk.VBox(spacing=1)
1143         descVBox.add(self.new_label_heading())
1144         descVBox.add(self.new_label_heading("Time:"))
1145         descVBox.add(self.new_label_heading("Steps:"))
1146         descVBox.add(self.new_label_heading("Calories:"))
1147         descVBox.add(self.new_label_heading("Distance:"))
1148         descVBox.add(self.new_label_heading("Avg Speed:"))
1149
1150         currentVBox = gtk.VBox(spacing=1)
1151         currentVBox.add(self.new_label_heading("Current"))
1152         currentVBox.add(self.labelsC["timer"])
1153         currentVBox.add(self.labelsC["count"])
1154         currentVBox.add(self.labelsC["calories"])
1155         currentVBox.add(self.labelsC["dist"])
1156         currentVBox.add(self.labelsC["avgSpeed"])
1157         self.currentBox = currentVBox
1158
1159         totalVBox = gtk.VBox(spacing=1)
1160         totalVBox.add(self.label_second_view)
1161         totalVBox.add(self.labelsT["timer"])
1162         totalVBox.add(self.labelsT["count"])
1163         totalVBox.add(self.labelsT["calories"])
1164         totalVBox.add(self.labelsT["dist"])
1165         totalVBox.add(self.labelsT["avgSpeed"])
1166         self.totalBox = totalVBox
1167
1168         buttonVBox = gtk.VBox(spacing=1)
1169         buttonVBox.add(self.new_label_heading(""))
1170         buttonVBox.add(self.button)
1171         buttonVBox.add(self.new_label_heading(""))
1172
1173         eventBox = CustomEventBox()
1174         eventBox.set_visible_window(False)
1175         eventBox.add(totalVBox)
1176         eventBox.connect("button-press-event", self.eventBox_clicked)
1177         eventBox.connect("button-release-event", self.eventBox_clicked_release)
1178
1179         mainHBox.add(buttonVBox)
1180         mainHBox.add(descVBox)
1181         mainHBox.add(currentVBox)
1182         mainHBox.add(eventBox)
1183         self.mainhbox = mainHBox
1184
1185         graph = GraphWidget()
1186         self.graph_controller.set_graph(graph)
1187
1188         eventBoxGraph = CustomEventBox()
1189         eventBoxGraph.set_visible_window(False)
1190         eventBoxGraph.add(graph)
1191         self.graph = graph
1192         eventBoxGraph.connect("button-press-event", self.eventBoxGraph_clicked)
1193         eventBoxGraph.connect("button-release-event", self.eventBoxGraph_clicked_release)
1194         self.graphBox = eventBoxGraph
1195
1196         self.mainvbox = gtk.VBox()
1197
1198         self.mainvbox.add(mainHBox)
1199         self.mainvbox.add(eventBoxGraph)
1200
1201         self.mainvbox.show_all()
1202         self.add(self.mainvbox)
1203         self.update_aspect()
1204
1205         self.connect("unrealize", self.close_requested)
1206         self.set_settings(True)
1207         self.connect("show-settings", self.show_settings)
1208
1209     def eventBoxGraph_clicked(self, widget, data=None):
1210         widget.set_state(gtk.STATE_ACTIVE)
1211
1212     def eventBoxGraph_clicked_release(self, widget, data=None):
1213         self.graph_view = self.graph_controller.next_view()
1214         self.client.set_int(GRAPHVIEW, self.graph_view)
1215
1216         widget.set_state(gtk.STATE_NORMAL)
1217
1218     def eventBox_clicked(self, widget, data=None):
1219         widget.set_state(gtk.STATE_ACTIVE)
1220
1221     def eventBox_clicked_release(self, widget, data=None):
1222         widget.set_state(gtk.STATE_NORMAL)
1223
1224         self.second_view = (self.second_view + 1) % 3
1225         self.controller.set_second_view(self.second_view)
1226         self.client.set_int(SECONDVIEW, self.second_view)
1227
1228     def new_label_heading(self, title=""):
1229         l = gtk.Label(title)
1230         hildon.hildon_helper_set_logical_font(l, "SmallSystemFont")
1231         return l
1232
1233     def create_labels(self, new_labels):
1234         for label in self.labels:
1235             l = gtk.Label()
1236             hildon.hildon_helper_set_logical_font(l, "SmallSystemFont")
1237             hildon.hildon_helper_set_logical_color(l, gtk.RC_FG, gtk.STATE_NORMAL, "ActiveTextColor")
1238             new_labels[label] = l
1239
1240     def update_aspect(self):
1241
1242         if self.aspect > 0:
1243             self.graphBox.hide_all()
1244         else:
1245             self.graphBox.show_all()
1246
1247         if self.aspect == 0 or self.aspect == 1:
1248             self.currentBox.show_all()
1249             self.totalBox.show_all()
1250         elif self.aspect == 2:
1251             self.currentBox.show_all()
1252             self.totalBox.hide_all()
1253         else:
1254             self.currentBox.hide_all()
1255             self.totalBox.show_all()
1256
1257         x,y = self.size_request()
1258         self.resize(x,y)
1259
1260     def update_ui_values(self, labels, values):
1261         labels["timer"].set_label(values.get_print_time())
1262         labels["count"].set_label(values.get_print_steps())
1263         labels["dist"].set_label(values.get_print_distance())
1264         labels["avgSpeed"].set_label(values.get_print_avg_speed())
1265         labels["calories"].set_label(values.get_print_calories())
1266
1267     def update_current(self):
1268         self.update_ui_values(self.labelsC, self.controller.get_first())
1269
1270     def update_total(self):
1271         self.update_ui_values(self.labelsT, self.controller.get_second())
1272
1273     def show_alarm_settings(self, main_button):
1274         def choose_file(widget):
1275             file = hildon.FileChooserDialog(self, gtk.FILE_CHOOSER_ACTION_OPEN, hildon.FileSystemModel() )
1276             file.show()
1277             if ( file.run() == gtk.RESPONSE_OK):
1278                 fname = file.get_filename()
1279                 widget.set_value(fname)
1280                 self.alarm_controller.set_alarm_file(fname)
1281             file.destroy()
1282
1283         def test_sound(button):
1284             try:
1285                 self.alarm_controller.play()
1286             except Exception, e:
1287                 logger.error("Could not play alarm sound: %s" % e)
1288                 hildon.hildon_banner_show_information(self, "None", "Could not play alarm sound")
1289
1290         def enableButton_changed(button):
1291             value = button.get_active()
1292             self.alarm_controller.set_enable(value)
1293             if value:
1294                 main_button.set_value("Enabled")
1295             else:
1296                 main_button.set_value("Disabled")
1297
1298         def selectorType_changed(selector, data, labelEntry2):
1299             self.alarm_controller.set_type(selector.get_active(0))
1300             labelEntry2.set_label(suffix[self.alarm_controller.get_type()])
1301
1302         dialog = gtk.Dialog()
1303         dialog.set_title("Alarm settings")
1304         dialog.add_button("OK", gtk.RESPONSE_OK)
1305
1306         enableButton = hildon.CheckButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT)
1307         enableButton.set_label("Enable alarm")
1308         enableButton.set_active(self.alarm_controller.get_enable())
1309         enableButton.connect("toggled", enableButton_changed)
1310
1311         testButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1312         testButton.set_alignment(0, 0.8, 1, 1)
1313         testButton.set_title("Test sound")
1314         testButton.connect("pressed", test_sound)
1315
1316         fileButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1317         fileButton.set_alignment(0, 0.8, 1, 1)
1318         fileButton.set_title("Alarm sound")
1319         fileButton.set_value(self.alarm_controller.get_alarm_file())
1320         fileButton.connect("pressed", choose_file)
1321
1322         labelEntry = gtk.Label("Notify every:")
1323         suffix = ["mins", "steps", "m/ft", "calories"]
1324         labelEntry2 = gtk.Label(suffix[self.alarm_controller.get_type()])
1325         intervalEntry = hildon.Entry(gtk.HILDON_SIZE_AUTO_WIDTH)
1326         intervalEntry.set_text(str(self.alarm_controller.get_interval()))
1327
1328         selectorType = hildon.TouchSelector(text=True)
1329         selectorType.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
1330         selectorType.append_text("Time")
1331         selectorType.append_text("Steps")
1332         selectorType.append_text("Distance")
1333         selectorType.append_text("Calories")
1334         selectorType.connect("changed", selectorType_changed, labelEntry2)
1335
1336         typePicker = hildon.PickerButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1337         typePicker.set_alignment(0.0, 0.5, 1.0, 1.0)
1338         typePicker.set_title("Alarm type")
1339         typePicker.set_selector(selectorType)
1340         typePicker.set_active(self.alarm_controller.get_type())
1341
1342         hbox = gtk.HBox()
1343         hbox.add(labelEntry)
1344         hbox.add(intervalEntry)
1345         hbox.add(labelEntry2)
1346
1347         dialog.vbox.add(enableButton)
1348         dialog.vbox.add(fileButton)
1349         dialog.vbox.add(testButton)
1350         dialog.vbox.add(typePicker)
1351         dialog.vbox.add(hbox)
1352         dialog.show_all()
1353         while 1:
1354             response = dialog.run()
1355             if response != gtk.RESPONSE_OK:
1356                 break
1357             try:
1358                 value = int(intervalEntry.get_text())
1359                 self.alarm_controller.set_interval(value)
1360                 break
1361             except:
1362                 hildon.hildon_banner_show_information(self, "None", "Invalid interval")
1363
1364         dialog.destroy()
1365
1366     def show_settings(self, widget):
1367         def reset_total_counter(arg):
1368             note = hildon.hildon_note_new_confirmation(self.dialog, "Are you sure you want to delete all your pedometer history?")
1369             ret = note.run()
1370             if ret == gtk.RESPONSE_OK:
1371                 self.controller.reset_all_values()
1372                 hildon.hildon_banner_show_information(self, "None", "All history was deleted")
1373             note.destroy()
1374
1375         def alarmButton_pressed(widget):
1376             self.show_alarm_settings(widget)
1377
1378         def selector_changed(selector, data):
1379             widget.mode = selector.get_active(0)
1380             widget.client.set_int(MODE, widget.mode)
1381             widget.controller.set_mode(widget.mode)
1382
1383         def selectorH_changed(selector, data):
1384             widget.height = selectorH.get_active(0)
1385             widget.client.set_int(HEIGHT, widget.height)
1386             widget.controller.set_height(widget.height)
1387
1388         def selectorUnit_changed(selector, data):
1389             widget.unit = selectorUnit.get_active(0)
1390             widget.client.set_int(UNIT, widget.unit)
1391             widget.controller.set_unit(widget.unit)
1392
1393         def selectorUI_changed(selector, data):
1394             widget.aspect = selectorUI.get_active(0)
1395             widget.client.set_int(ASPECT, widget.aspect)
1396             widget.update_aspect()
1397
1398         def logButton_changed(checkButton):
1399             widget.logging = checkButton.get_active()
1400             widget.client.set_bool(LOGGING, widget.logging)
1401
1402         def idleButton_changed(idleButton):
1403             widget.no_idle_time = idleButton.get_active()
1404             widget.client.set_bool(NOIDLETIME, widget.no_idle_time)
1405             widget.controller.set_no_idle_time(widget.no_idle_time)
1406
1407         def weight_dialog(button):
1408             dialog = gtk.Dialog("Weight", self.dialog)
1409             dialog.add_button("OK", gtk.RESPONSE_OK)
1410
1411             label = gtk.Label("Weight:")
1412             entry = gtk.Entry()
1413             entry.set_text(str(self.controller.get_weight()))
1414
1415             suffixLabel = gtk.Label(self.controller.get_str_weight_unit())
1416
1417             hbox = gtk.HBox()
1418             hbox.add(label)
1419             hbox.add(entry)
1420             hbox.add(suffixLabel)
1421
1422             dialog.vbox.add(hbox)
1423             dialog.show_all()
1424             while 1:
1425                 response = dialog.run()
1426                 if response != gtk.RESPONSE_OK:
1427                     break
1428                 try:
1429                     value = int(entry.get_text())
1430                     if value <= 0:
1431                         raise ValueError
1432                     self.controller.set_weight(value)
1433                     self.client.set_int(WEIGHT, value)
1434                     weightButton.set_value(str(self.controller.get_weight()) + \
1435                                            " " + self.controller.get_str_weight_unit() )
1436                     break
1437                 except:
1438                     hildon.hildon_banner_show_information(self, "None", "Invalid weight")
1439             dialog.destroy()
1440
1441         dialog = gtk.Dialog()
1442         dialog.set_title("Settings")
1443         dialog.add_button("OK", gtk.RESPONSE_OK)
1444         self.dialog = dialog
1445
1446         resetButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1447         resetButton.set_title("Reset total counter")
1448         resetButton.set_alignment(0, 0.8, 1, 1)
1449         resetButton.connect("clicked", reset_total_counter)
1450
1451         alarmButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1452         alarmButton.set_title("Alarm")
1453         if self.alarm_controller.get_enable():
1454             alarmButton.set_value("Enabled")
1455         else:
1456             alarmButton.set_value("Disabled")
1457         alarmButton.set_alignment(0, 0.8, 1, 1)
1458         alarmButton.connect("clicked", alarmButton_pressed)
1459
1460         selector = hildon.TouchSelector(text=True)
1461         selector.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
1462         selector.append_text("Walk")
1463         selector.append_text("Run")
1464         selector.connect("changed", selector_changed)
1465
1466         modePicker = hildon.PickerButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1467         modePicker.set_alignment(0.0, 0.5, 1.0, 1.0)
1468         modePicker.set_title("Mode")
1469         modePicker.set_selector(selector)
1470         modePicker.set_active(widget.mode)
1471
1472         selectorH = hildon.TouchSelector(text=True)
1473         selectorH.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
1474         selectorH.append_text("< 1.50 m")
1475         selectorH.append_text("1.50 - 1.65 m")
1476         selectorH.append_text("1.66 - 1.80 m")
1477         selectorH.append_text("1.81 - 1.95 m")
1478         selectorH.append_text(" > 1.95 m")
1479         selectorH.connect("changed", selectorH_changed)
1480
1481         heightPicker = hildon.PickerButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1482         heightPicker.set_alignment(0.0, 0.5, 1.0, 1.0)
1483         heightPicker.set_title("Height")
1484         heightPicker.set_selector(selectorH)
1485         heightPicker.set_active(widget.height)
1486
1487         weightButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1488         weightButton.set_title("Weight")
1489         weightButton.set_alignment(0, 0.8, 1, 1)
1490         weightButton.set_value(str(self.controller.get_weight()) + " " + self.controller.get_str_weight_unit() )
1491         weightButton.connect("clicked", weight_dialog)
1492
1493         selectorUnit = hildon.TouchSelector(text=True)
1494         selectorUnit.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
1495         selectorUnit.append_text("Metric (km)")
1496         selectorUnit.append_text("English (mi)")
1497         selectorUnit.connect("changed", selectorUnit_changed)
1498
1499         unitPicker = hildon.PickerButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1500         unitPicker.set_alignment(0.0, 0.5, 1.0, 1.0)
1501         unitPicker.set_title("Unit")
1502         unitPicker.set_selector(selectorUnit)
1503         unitPicker.set_active(widget.unit)
1504
1505         selectorUI = hildon.TouchSelector(text=True)
1506         selectorUI = hildon.TouchSelector(text=True)
1507         selectorUI.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
1508         selectorUI.append_text("Show current + total + graph")
1509         selectorUI.append_text("Show current + total")
1510         selectorUI.append_text("Show only current")
1511         selectorUI.append_text("Show only total")
1512         selectorUI.connect("changed", selectorUI_changed)
1513
1514         UIPicker = hildon.PickerButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1515         UIPicker.set_alignment(0.0, 0.5, 1.0, 1.0)
1516         UIPicker.set_title("Widget aspect")
1517         UIPicker.set_selector(selectorUI)
1518         UIPicker.set_active(widget.aspect)
1519
1520         logButton = hildon.CheckButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT)
1521         logButton.set_label("Log data")
1522         logButton.set_active(widget.logging)
1523         logButton.connect("toggled", logButton_changed)
1524
1525         idleButton = hildon.CheckButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT)
1526         idleButton.set_label("Pause time when not walking")
1527         idleButton.set_active(widget.no_idle_time)
1528         idleButton.connect("toggled", idleButton_changed)
1529
1530         pan_area = hildon.PannableArea()
1531         vbox = gtk.VBox()
1532         vbox.add(alarmButton)
1533         vbox.add(modePicker)
1534         vbox.add(heightPicker)
1535         vbox.add(weightButton)
1536         vbox.add(unitPicker)
1537         vbox.add(UIPicker)
1538         vbox.add(idleButton)
1539         vbox.add(resetButton)
1540         #vbox.add(logButton)
1541
1542         pan_area.add_with_viewport(vbox)
1543         pan_area.set_size_request(-1, 300)
1544
1545         dialog.vbox.add(pan_area)
1546         dialog.show_all()
1547         response = dialog.run()
1548         #hildon.hildon_banner_show_information(self, "None", "You have to Stop/Start the counter to apply the new settings")
1549         dialog.destroy()
1550
1551     def close_requested(self, widget):
1552         if self.pedometer is None:
1553             return
1554
1555         self.pedometer.request_stop()
1556         self.controller.stop_midnight_callback()
1557
1558     def update_values(self):
1559         #TODO: do not update if the widget is not on the active desktop
1560         self.label_second_view.set_label(self.second_view_labels[self.second_view])
1561         self.update_current()
1562         self.update_total()
1563
1564     def button_clicked(self, button):
1565         if self.controller.is_running:
1566             self.controller.stop_pedometer()
1567             self.button.set_icon(ICONSPATH + "play.png")
1568         else:
1569             self.controller.start_pedometer()
1570             self.button.set_icon(ICONSPATH + "stop.png")
1571
1572     def do_expose_event(self, event):
1573         cr = self.window.cairo_create()
1574         cr.region(event.window.get_clip_region())
1575         cr.clip()
1576         #cr.set_source_rgba(0.4, 0.64, 0.564, 0.5)
1577         style = self.rc_get_style()
1578         color = style.lookup_color("DefaultBackgroundColor")
1579         cr.set_source_rgba (color.red / 65535.0, color.green / 65335.0, color.blue / 65535.0, 0.75);
1580
1581         radius = 5
1582         width = self.allocation.width
1583         height = self.allocation.height
1584
1585         x = self.allocation.x
1586         y = self.allocation.y
1587
1588         cr.move_to(x + radius, y)
1589         cr.line_to(x + width - radius, y)
1590         cr.curve_to(x + width - radius, y, x + width, y, x + width, y + radius)
1591         cr.line_to(x + width, y + height - radius)
1592         cr.curve_to(x + width, y + height - radius, x + width, y + height, x + width - radius, y + height)
1593         cr.line_to(x + radius, y + height)
1594         cr.curve_to(x + radius, y + height, x, y + height, x, y + height - radius)
1595         cr.line_to(x, y + radius)
1596         cr.curve_to(x, y + radius, x, y, x + radius, y)
1597
1598         cr.set_operator(cairo.OPERATOR_SOURCE)
1599         cr.fill_preserve()
1600
1601         color = style.lookup_color("ActiveTextColor")
1602         cr.set_source_rgba (color.red / 65535.0, color.green / 65335.0, color.blue / 65535.0, 0.5);
1603         cr.set_line_width(1)
1604         cr.stroke()
1605
1606         hildondesktop.HomePluginItem.do_expose_event(self, event)
1607
1608     def do_realize(self):
1609         screen = self.get_screen()
1610         self.set_colormap(screen.get_rgba_colormap())
1611         self.set_app_paintable(True)
1612         hildondesktop.HomePluginItem.do_realize(self)
1613
1614 hd_plugin_type = PedometerHomePlugin
1615
1616 import math
1617 import logging
1618
1619 logger = logging.getLogger("pedometer")
1620 logger.setLevel(logging.INFO)
1621
1622 ch = logging.StreamHandler()
1623 formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
1624 ch.setFormatter(formatter)
1625 logger.addHandler(ch)
1626
1627 # The code below is just for testing purposes.
1628 # It allows to run the widget as a standalone process.
1629 if __name__ == "__main__":
1630     import gobject
1631     gobject.type_register(hd_plugin_type)
1632     obj = gobject.new(hd_plugin_type, plugin_id="plugin_id")
1633     obj.show_all()
1634     gtk.main()