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