Add option to show/hie graph in Settings -> Widget aspect
[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         self.graphBox = eventBoxGraph
1152
1153         self.mainvbox = gtk.VBox()
1154
1155         self.mainvbox.add(mainHBox)
1156         self.mainvbox.add(eventBoxGraph)
1157
1158         self.mainvbox.show_all()
1159         self.add(self.mainvbox)
1160         self.update_aspect()
1161
1162         self.connect("unrealize", self.close_requested)
1163         self.set_settings(True)
1164         self.connect("show-settings", self.show_settings)
1165
1166     def eventBoxGraph_clicked(self, widget, data=None):
1167         widget.set_state(gtk.STATE_ACTIVE)
1168
1169     def eventBoxGraph_clicked_release(self, widget, data=None):
1170         self.graph_view = self.graph_controller.next_view()
1171         self.client.set_int(GRAPHVIEW, self.graph_view)
1172
1173         widget.set_state(gtk.STATE_NORMAL)
1174
1175     def eventBox_clicked(self, widget, data=None):
1176         widget.set_state(gtk.STATE_ACTIVE)
1177
1178     def eventBox_clicked_release(self, widget, data=None):
1179         widget.set_state(gtk.STATE_NORMAL)
1180
1181         self.second_view = (self.second_view + 1) % 3
1182         self.controller.set_second_view(self.second_view)
1183         self.client.set_int(SECONDVIEW, self.second_view)
1184
1185     def new_label_heading(self, title=""):
1186         l = gtk.Label(title)
1187         hildon.hildon_helper_set_logical_font(l, "SmallSystemFont")
1188         return l
1189
1190     def create_labels(self, new_labels):
1191         for label in self.labels:
1192             l = gtk.Label()
1193             hildon.hildon_helper_set_logical_font(l, "SmallSystemFont")
1194             hildon.hildon_helper_set_logical_color(l, gtk.RC_FG, gtk.STATE_NORMAL, "ActiveTextColor")
1195             new_labels[label] = l
1196
1197     def update_aspect(self):
1198
1199         if self.aspect > 0:
1200             self.graphBox.hide_all()
1201         else:
1202             self.graphBox.show_all()
1203
1204         if self.aspect == 0 or self.aspect == 1:
1205             self.currentBox.show_all()
1206             self.totalBox.show_all()
1207         elif self.aspect == 2:
1208             self.currentBox.show_all()
1209             self.totalBox.hide_all()
1210         else:
1211             self.currentBox.hide_all()
1212             self.totalBox.show_all()
1213
1214         x,y = self.size_request()
1215         self.resize(x,y)
1216
1217     def update_ui_values(self, labels, values):
1218         labels["timer"].set_label(values.get_print_time())
1219         labels["count"].set_label(values.get_print_steps())
1220         labels["dist"].set_label(values.get_print_distance())
1221         labels["avgSpeed"].set_label(values.get_print_avg_speed())
1222         labels["calories"].set_label(values.get_print_calories())
1223
1224     def update_current(self):
1225         self.update_ui_values(self.labelsC, self.controller.get_first())
1226
1227     def update_total(self):
1228         self.update_ui_values(self.labelsT, self.controller.get_second())
1229
1230     def show_alarm_settings(self, main_button):
1231         def choose_file(widget):
1232             file = hildon.FileChooserDialog(self, gtk.FILE_CHOOSER_ACTION_OPEN, hildon.FileSystemModel() )
1233             file.show()
1234             if ( file.run() == gtk.RESPONSE_OK):
1235                 fname = file.get_filename()
1236                 widget.set_value(fname)
1237                 self.alarm_controller.set_alarm_file(fname)
1238             file.destroy()
1239
1240         def test_sound(button):
1241             try:
1242                 self.alarm_controller.play()
1243             except Exception, e:
1244                 logger.error("Could not play alarm sound: %s" % e)
1245                 hildon.hildon_banner_show_information(self, "None", "Could not play alarm sound")
1246
1247         def enableButton_changed(button):
1248             value = button.get_active()
1249             self.alarm_controller.set_enable(value)
1250             if value:
1251                 main_button.set_value("Enabled")
1252             else:
1253                 main_button.set_value("Disabled")
1254
1255         def selectorType_changed(selector, data, labelEntry2):
1256             self.alarm_controller.set_type(selector.get_active(0))
1257             labelEntry2.set_label(suffix[self.alarm_controller.get_type()])
1258
1259         dialog = gtk.Dialog()
1260         dialog.set_title("Alarm settings")
1261         dialog.add_button("OK", gtk.RESPONSE_OK)
1262
1263         enableButton = hildon.CheckButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT)
1264         enableButton.set_label("Enable alarm")
1265         enableButton.set_active(self.alarm_controller.get_enable())
1266         enableButton.connect("toggled", enableButton_changed)
1267
1268         testButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1269         testButton.set_alignment(0, 0.8, 1, 1)
1270         testButton.set_title("Test sound")
1271         testButton.connect("pressed", test_sound)
1272
1273         fileButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1274         fileButton.set_alignment(0, 0.8, 1, 1)
1275         fileButton.set_title("Alarm sound")
1276         fileButton.set_value(self.alarm_controller.get_alarm_file())
1277         fileButton.connect("pressed", choose_file)
1278
1279         labelEntry = gtk.Label("Notify every:")
1280         suffix = ["mins", "steps", "m/ft", "calories"]
1281         labelEntry2 = gtk.Label(suffix[self.alarm_controller.get_type()])
1282         intervalEntry = hildon.Entry(gtk.HILDON_SIZE_AUTO_WIDTH)
1283         intervalEntry.set_text(str(self.alarm_controller.get_interval()))
1284
1285         selectorType = hildon.TouchSelector(text=True)
1286         selectorType.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
1287         selectorType.append_text("Time")
1288         selectorType.append_text("Steps")
1289         selectorType.append_text("Distance")
1290         selectorType.append_text("Calories")
1291         selectorType.connect("changed", selectorType_changed, labelEntry2)
1292
1293         typePicker = hildon.PickerButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1294         typePicker.set_alignment(0.0, 0.5, 1.0, 1.0)
1295         typePicker.set_title("Alarm type")
1296         typePicker.set_selector(selectorType)
1297         typePicker.set_active(self.alarm_controller.get_type())
1298
1299         hbox = gtk.HBox()
1300         hbox.add(labelEntry)
1301         hbox.add(intervalEntry)
1302         hbox.add(labelEntry2)
1303
1304         dialog.vbox.add(enableButton)
1305         dialog.vbox.add(fileButton)
1306         dialog.vbox.add(testButton)
1307         dialog.vbox.add(typePicker)
1308         dialog.vbox.add(hbox)
1309         dialog.show_all()
1310         while 1:
1311             response = dialog.run()
1312             if response != gtk.RESPONSE_OK:
1313                 break
1314             try:
1315                 value = int(intervalEntry.get_text())
1316                 self.alarm_controller.set_interval(value)
1317                 break
1318             except:
1319                 hildon.hildon_banner_show_information(self, "None", "Invalid interval")
1320
1321         dialog.destroy()
1322
1323     def show_settings(self, widget):
1324         def reset_total_counter(arg):
1325             note = hildon.hildon_note_new_confirmation(self.dialog, "Are you sure you want to delete all your pedometer history?")
1326             ret = note.run()
1327             if ret == gtk.RESPONSE_OK:
1328                 self.controller.reset_all_values()
1329                 hildon.hildon_banner_show_information(self, "None", "All history was deleted")
1330             note.destroy()
1331
1332         def alarmButton_pressed(widget):
1333             self.show_alarm_settings(widget)
1334
1335         def selector_changed(selector, data):
1336             widget.mode = selector.get_active(0)
1337             widget.client.set_int(MODE, widget.mode)
1338             widget.controller.set_mode(widget.mode)
1339
1340         def selectorH_changed(selector, data):
1341             widget.height = selectorH.get_active(0)
1342             widget.client.set_int(HEIGHT, widget.height)
1343             widget.controller.set_height(widget.height)
1344
1345         def selectorUnit_changed(selector, data):
1346             widget.unit = selectorUnit.get_active(0)
1347             widget.client.set_int(UNIT, widget.unit)
1348             widget.controller.set_unit(widget.unit)
1349
1350         def selectorUI_changed(selector, data):
1351             widget.aspect = selectorUI.get_active(0)
1352             widget.client.set_int(ASPECT, widget.aspect)
1353             widget.update_aspect()
1354
1355         def logButton_changed(checkButton):
1356             widget.logging = checkButton.get_active()
1357             widget.client.set_bool(LOGGING, widget.logging)
1358
1359         def idleButton_changed(idleButton):
1360             widget.no_idle_time = idleButton.get_active()
1361             widget.client.set_bool(NOIDLETIME, widget.no_idle_time)
1362             widget.controller.set_no_idle_time(widget.no_idle_time)
1363
1364         dialog = gtk.Dialog()
1365         dialog.set_title("Settings")
1366         dialog.add_button("OK", gtk.RESPONSE_OK)
1367         self.dialog = dialog
1368
1369         resetButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1370         resetButton.set_title("Reset total counter")
1371         resetButton.set_alignment(0, 0.8, 1, 1)
1372         resetButton.connect("clicked", reset_total_counter)
1373
1374         alarmButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1375         alarmButton.set_title("Alarm")
1376         if self.alarm_controller.get_enable():
1377             alarmButton.set_value("Enabled")
1378         else:
1379             alarmButton.set_value("Disabled")
1380         alarmButton.set_alignment(0, 0.8, 1, 1)
1381         alarmButton.connect("clicked", alarmButton_pressed)
1382
1383         selector = hildon.TouchSelector(text=True)
1384         selector.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
1385         selector.append_text("Walk")
1386         selector.append_text("Run")
1387         selector.connect("changed", selector_changed)
1388
1389         modePicker = hildon.PickerButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1390         modePicker.set_alignment(0.0, 0.5, 1.0, 1.0)
1391         modePicker.set_title("Select mode")
1392         modePicker.set_selector(selector)
1393         modePicker.set_active(widget.mode)
1394
1395         selectorH = hildon.TouchSelector(text=True)
1396         selectorH.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
1397         selectorH.append_text("< 1.50 m")
1398         selectorH.append_text("1.50 - 1.65 m")
1399         selectorH.append_text("1.66 - 1.80 m")
1400         selectorH.append_text("1.81 - 1.95 m")
1401         selectorH.append_text(" > 1.95 m")
1402         selectorH.connect("changed", selectorH_changed)
1403
1404         heightPicker = hildon.PickerButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1405         heightPicker.set_alignment(0.0, 0.5, 1.0, 1.0)
1406         heightPicker.set_title("Select height")
1407         heightPicker.set_selector(selectorH)
1408         heightPicker.set_active(widget.height)
1409
1410         selectorUnit = hildon.TouchSelector(text=True)
1411         selectorUnit.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
1412         selectorUnit.append_text("Metric (km)")
1413         selectorUnit.append_text("English (mi)")
1414         selectorUnit.connect("changed", selectorUnit_changed)
1415
1416         unitPicker = hildon.PickerButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1417         unitPicker.set_alignment(0.0, 0.5, 1.0, 1.0)
1418         unitPicker.set_title("Units")
1419         unitPicker.set_selector(selectorUnit)
1420         unitPicker.set_active(widget.unit)
1421
1422         selectorUI = hildon.TouchSelector(text=True)
1423         selectorUI = hildon.TouchSelector(text=True)
1424         selectorUI.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
1425         selectorUI.append_text("Show current + total + graph")
1426         selectorUI.append_text("Show current + total")
1427         selectorUI.append_text("Show only current")
1428         selectorUI.append_text("Show only total")
1429         selectorUI.connect("changed", selectorUI_changed)
1430
1431         UIPicker = hildon.PickerButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1432         UIPicker.set_alignment(0.0, 0.5, 1.0, 1.0)
1433         UIPicker.set_title("Widget aspect")
1434         UIPicker.set_selector(selectorUI)
1435         UIPicker.set_active(widget.aspect)
1436
1437         logButton = hildon.CheckButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT)
1438         logButton.set_label("Log data")
1439         logButton.set_active(widget.logging)
1440         logButton.connect("toggled", logButton_changed)
1441
1442         idleButton = hildon.CheckButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT)
1443         idleButton.set_label("Pause time when not walking")
1444         idleButton.set_active(widget.no_idle_time)
1445         idleButton.connect("toggled", idleButton_changed)
1446
1447         pan_area = hildon.PannableArea()
1448         vbox = gtk.VBox()
1449         vbox.add(alarmButton)
1450         vbox.add(modePicker)
1451         vbox.add(heightPicker)
1452         vbox.add(unitPicker)
1453         vbox.add(UIPicker)
1454         vbox.add(idleButton)
1455         vbox.add(resetButton)
1456         #vbox.add(logButton)
1457
1458         pan_area.add_with_viewport(vbox)
1459         pan_area.set_size_request(-1, 300)
1460
1461         dialog.vbox.add(pan_area)
1462         dialog.show_all()
1463         response = dialog.run()
1464         #hildon.hildon_banner_show_information(self, "None", "You have to Stop/Start the counter to apply the new settings")
1465         dialog.destroy()
1466
1467     def close_requested(self, widget):
1468         if self.pedometer is None:
1469             return
1470
1471         self.pedometer.request_stop()
1472
1473     def update_values(self):
1474         #TODO: do not update if the widget is not on the active desktop
1475         self.label_second_view.set_label(self.second_view_labels[self.second_view])
1476         self.update_current()
1477         self.update_total()
1478
1479     def button_clicked(self, button):
1480         if self.controller.is_running:
1481             self.controller.stop_pedometer()
1482             self.button.set_icon(ICONSPATH + "play.png")
1483         else:
1484             self.controller.start_pedometer()
1485             self.button.set_icon(ICONSPATH + "stop.png")
1486
1487     def do_expose_event(self, event):
1488         cr = self.window.cairo_create()
1489         cr.region(event.window.get_clip_region())
1490         cr.clip()
1491         #cr.set_source_rgba(0.4, 0.64, 0.564, 0.5)
1492         style = self.rc_get_style()
1493         color = style.lookup_color("DefaultBackgroundColor")
1494         cr.set_source_rgba (color.red / 65535.0, color.green / 65335.0, color.blue / 65535.0, 0.75);
1495
1496         radius = 5
1497         width = self.allocation.width
1498         height = self.allocation.height
1499
1500         x = self.allocation.x
1501         y = self.allocation.y
1502
1503         cr.move_to(x + radius, y)
1504         cr.line_to(x + width - radius, y)
1505         cr.curve_to(x + width - radius, y, x + width, y, x + width, y + radius)
1506         cr.line_to(x + width, y + height - radius)
1507         cr.curve_to(x + width, y + height - radius, x + width, y + height, x + width - radius, y + height)
1508         cr.line_to(x + radius, y + height)
1509         cr.curve_to(x + radius, y + height, x, y + height, x, y + height - radius)
1510         cr.line_to(x, y + radius)
1511         cr.curve_to(x, y + radius, x, y, x + radius, y)
1512
1513         cr.set_operator(cairo.OPERATOR_SOURCE)
1514         cr.fill_preserve()
1515
1516         color = style.lookup_color("ActiveTextColor")
1517         cr.set_source_rgba (color.red / 65535.0, color.green / 65335.0, color.blue / 65535.0, 0.5);
1518         cr.set_line_width(1)
1519         cr.stroke()
1520
1521         hildondesktop.HomePluginItem.do_expose_event(self, event)
1522
1523     def do_realize(self):
1524         screen = self.get_screen()
1525         self.set_colormap(screen.get_rgba_colormap())
1526         self.set_app_paintable(True)
1527         hildondesktop.HomePluginItem.do_realize(self)
1528
1529 hd_plugin_type = PedometerHomePlugin
1530
1531 import math
1532 import logging
1533
1534 logger = logging.getLogger("pedometer")
1535 logger.setLevel(logging.INFO)
1536
1537 ch = logging.StreamHandler()
1538 formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
1539 ch.setFormatter(formatter)
1540 logger.addHandler(ch)
1541
1542 # The code below is just for testing purposes.
1543 # It allows to run the widget as a standalone process.
1544 if __name__ == "__main__":
1545     import gobject
1546     gobject.type_register(hd_plugin_type)
1547     obj = gobject.new(hd_plugin_type, plugin_id="plugin_id")
1548     obj.show_all()
1549     gtk.main()