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