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