Fix bug to be able to run widget as standalone application
[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 stop_pedometer(self):
433         self.is_running = False
434         self.pedometer.request_stop()
435
436     def get_first(self):
437         return self.v[0]
438
439     def get_second(self):
440         if self.is_running:
441             return self.v[0] + self.v[1]
442         else:
443             return self.v[1]
444
445     def update_current(self):
446         """
447         Update distance and calories for current values based on new height, mode values
448         """
449         self.v[0].dist = self.get_distance(self.v[0].steps)
450         self.v[0].calories = self.get_calories(self.v[0].steps)
451
452     def steps_detected(self, cnt, last_steps=False):
453         if not last_steps and cnt == 0 and self.no_idle_time:
454             logger.info("No steps detected, timer is paused")
455         else:
456             self.v[0].steps += cnt
457             self.v[0].dist += self.get_distance(cnt)
458             self.v[0].calories += self.get_distance(cnt)
459             self.v[0].time += time.time() - self.last_time
460             if last_steps:
461                 self.save_values()
462                 self.notify()
463             else:
464                 self.notify(True)
465         self.last_time = time.time()
466
467     def set_mode(self, mode):
468         self.mode = mode
469         self.set_height(self.height_interval)
470         self.notify()
471
472     def set_unit(self, new_unit):
473         self.unit = new_unit
474         unit = new_unit
475
476     def set_second_view(self, second_view):
477         self.second_view = second_view
478         self.load_values()
479         self.notify()
480
481     def set_callback_ui(self, func):
482         self.callback_update_ui = func
483
484     def set_height(self, height_interval):
485         self.height_inteval = height_interval
486         #set height, will affect the distance
487         if height_interval == 0:
488             self.STEP_LENGTH = 0.59
489         elif height_interval == 1:
490             self.STEP_LENGTH = 0.64
491         elif height_interval == 2:
492             self.STEP_LENGTH = 0.71
493         elif height_interval == 3:
494             self.STEP_LENGTH = 0.77
495         elif height_interval == 4:
496             self.STEP_LENGTH = 0.83
497         #increase step length if RUNNING
498         if self.mode == 1:
499             self.STEP_LENGTH *= 1.45
500         self.notify()
501
502     def set_no_idle_time(self, value):
503         self.no_idle_time = value
504
505     def get_distance(self, steps=None):
506         if steps == None:
507             steps = self.counter
508         return self.STEP_LENGTH * steps;
509
510     def get_calories(self, steps):
511         return steps
512
513     def add_observer(self, func):
514         try:
515             self.observers.index(func)
516         except:
517             self.observers.append(func)
518
519     def remove_observer(self, func):
520         self.observers.remove(func)
521
522     def notify(self, optional=False):
523         if self.callback_update_ui is not None:
524             self.callback_update_ui()
525
526         for func in self.observers:
527             func(optional)
528
529 class AlarmController(Singleton):
530     enable = False
531     fname = "/home/user/MyDocs/.sounds/Ringtones/Bicycle.aac"
532     interval = 5
533     type = 0
534
535     player = None
536     is_playing = False
537     pedo_controller = None
538
539     def __init__(self):
540         self.client = gconf.client_get_default()
541         try:
542             self.enable = self.client.get_bool(ALARM_ENABLE)
543             self.fname = self.client.get_string(ALARM_FNAME)
544             self.interval = self.client.get_int(ALARM_INTERVAL)
545             self.type = self.client.get_int(ALARM_TYPE)
546         except:
547             self.client.set_bool(ALARM_ENABLE, self.enable)
548             self.client.set_string(ALARM_FNAME, self.fname)
549             self.client.set_int(ALARM_INTERVAL, self.interval)
550             self.client.set_int(ALARM_TYPE, self.type)
551
552         self.pedo_controller = PedoController()
553         if self.enable:
554             self.init_player()
555             self.pedo_controller.add_observer(self.update)
556             self.start_value = self.pedo_controller.get_first()
557
558     def init_player(self):
559         self.player = gst.element_factory_make("playbin2", "player")
560         fakesink = gst.element_factory_make("fakesink", "fakesink")
561         self.player.set_property("video-sink", fakesink)
562
563         bus = self.player.get_bus()
564         bus.add_signal_watch()
565         bus.connect("message", self.on_message)
566
567     def on_message(self, bus, message):
568         t = message.type
569         if t == gst.MESSAGE_EOS:
570             self.player.set_state(gst.STATE_NULL)
571             self.is_playing = False
572         elif t == gst.MESSAGE_ERROR:
573             self.player.set_state(gst.STATE_NULL)
574             self.is_playing = False
575             err, debug = message.parse_error()
576             logger.error("ERROR: %s, %s" % (err, debug) )
577
578     def update(self, optional):
579         print "alarm update"
580         diff = self.pedo_controller.get_first() - self.start_value
581         if self.type == 0 and diff.time >= self.interval * 60 or \
582                    self.type == 1 and diff.steps >= self.interval or \
583                    self.type == 2 and diff.dist >= self.interval or \
584                    self.type == 3 and diff.calories >= self.interval:
585             self.play()
586             #get new instance of current values
587             self.start_value = PedoValues() + self.pedo_controller.get_first()
588             logger.info("Alarm!")
589
590     def play(self):
591         if self.player is None:
592             self.init_player()
593         if self.is_playing:
594             self.player.set_state(gst.STATE_NULL)
595             self.is_playing = False
596         else:
597             self.player.set_property("uri", "file://" + self.fname)
598             self.player.set_state(gst.STATE_PLAYING)
599             self.is_playing = True
600
601     def stop(self):
602         self.player.set_state(gst.STATE_NULL)
603
604     def set_enable(self, value):
605        self.enable = value
606        self.client.set_bool(ALARM_ENABLE, value)
607        if self.enable:
608            self.init_player()
609            self.pedo_controller.add_observer(self.update)
610            self.start_value = self.pedo_controller.get_first()
611        else:
612            self.stop()
613            self.player = None
614            self.pedo_controller.remove_observer(self.update)
615
616     def get_enable(self):
617         return self.enable
618
619     def set_alarm_file(self, fname):
620         self.fname = fname
621         self.client.set_string(ALARM_FNAME, fname)
622
623     def get_alarm_file(self):
624         if self.fname == None:
625             return ""
626         return self.fname
627
628     def set_interval(self, interval):
629         self.interval = interval
630         self.client.set_int(ALARM_INTERVAL, interval)
631
632     def get_interval(self):
633         return self.interval
634
635     def set_type(self, type):
636         self.type = type
637         self.client.set_int(ALARM_TYPE, type)
638
639     def get_type(self):
640         return self.type
641
642 class PedoCounter(Singleton):
643     COORD_FNAME = "/sys/class/i2c-adapter/i2c-3/3-001d/coord"
644     COORD_FNAME_SDK = "/home/andrei/pedometer-widget-0.1/date.txt"
645     LOGFILE = "/home/user/log_pedometer"
646     #time in ms between two accelerometer data reads
647     COORD_GET_INTERVAL = 10
648
649     COUNT_INTERVAL = 5
650
651     interval_counter = None
652     stop_requested = False
653     update_function = None
654     logging = False
655     isRunning = False
656
657     def __init__(self, update_function=None):
658         if not os.path.exists(self.COORD_FNAME):
659             self.COORD_FNAME = self.COORD_FNAME_SDK
660
661         self.interval_counter = PedoIntervalCounter()
662         self.update_function = update_function
663
664     def set_logging(self, value):
665         self.logging = value
666
667     def get_rotation(self):
668         f = open(self.COORD_FNAME, 'r')
669         coords = [int(w) for w in f.readline().split()]
670         f.close()
671         return coords
672
673     def start(self):
674         logger.info("Counter started")
675         self.isRunning = True
676         self.stop_requested = False
677         if self.logging:
678             fname = "%d_%d_%d_%d_%d_%d" % time.localtime()[0:6]
679             self.file = open(self.LOGFILE + fname + ".txt", "w")
680         gobject.idle_add(self.run)
681
682     def run(self):
683         self.coords = [[], [], []]
684         self.stime = time.time()
685         self.t = []
686         gobject.timeout_add(self.COORD_GET_INTERVAL, self.read_coords)
687         return False
688
689     def read_coords(self):
690         x, y, z = self.get_rotation()
691         self.coords[0].append(int(x))
692         self.coords[1].append(int(y))
693         self.coords[2].append(int(z))
694         now = time.time() - self.stime
695         if self.logging:
696             self.file.write("%d %d %d %f\n" % (self.coords[0][-1], self.coords[1][-1], self.coords[2][-1], now))
697
698         self.t.append(now)
699         #call stop_interval
700         ret = True
701         if self.t[-1] > self.COUNT_INTERVAL or self.stop_requested:
702             ret = False
703             gobject.idle_add(self.stop_interval)
704         return ret
705
706     def stop_interval(self):
707         self.interval_counter.set_vals(self.coords, self.t)
708         cnt = self.interval_counter.number_steps()
709
710         logger.info("Number of steps detected for last interval %d, number of coords: %d" % (cnt, len(self.t)))
711
712         gobject.idle_add(self.update_function, cnt, self.stop_requested)
713
714         if self.stop_requested:
715             gobject.idle_add(self.stop)
716         else:
717             gobject.idle_add(self.run)
718         return False
719
720     def stop(self):
721         if self.logging:
722             self.file.close()
723         logger.info("Counter has finished")
724
725     def request_stop(self):
726         self.stop_requested = True
727         self.isRunning = False
728
729 class CustomButton(hildon.Button):
730     def __init__(self, icon):
731         hildon.Button.__init__(self, gtk.HILDON_SIZE_AUTO_WIDTH, hildon.BUTTON_ARRANGEMENT_VERTICAL)
732         self.icon = icon
733         self.set_size_request(int(32 * 1.4), int(30 * 1.0))
734         self.retval = self.connect("expose_event", self.expose)
735
736     def set_icon(self, icon):
737         self.icon = icon
738
739     def expose(self, widget, event):
740         self.context = widget.window.cairo_create()
741         self.context.rectangle(event.area.x, event.area.y,
742                             event.area.width, event.area.height)
743
744         self.context.clip()
745         rect = self.get_allocation()
746         self.context.rectangle(rect.x, rect.y, rect.width, rect.height)
747         self.context.set_source_rgba(1, 1, 1, 0)
748
749         style = self.rc_get_style()
750         color = style.lookup_color("DefaultBackgroundColor")
751         if self.state == gtk.STATE_ACTIVE:
752             style = self.rc_get_style()
753             color = style.lookup_color("SelectionColor")
754             self.context.set_source_rgba (color.red / 65535.0, color.green / 65335.0, color.blue / 65535.0, 0.75);
755         self.context.fill()
756
757         #img = cairo.ImageSurface.create_from_png(self.icon)
758
759         #self.context.set_source_surface(img)
760         #self.context.set_source_surface(img, rect.width/2 - img.get_width() /2, 0)
761         img = gtk.Image()
762         img.set_from_file(self.icon)
763         buf = img.get_pixbuf()
764         buf = buf.scale_simple(int(32 * 1.5), int(30 * 1.5), gtk.gdk.INTERP_BILINEAR)
765
766         self.context.set_source_pixbuf(buf, rect.x + (event.area.width / 2 - 15) - 8, rect.y + 1)
767         self.context.scale(200, 200)
768         self.context.paint()
769
770         return self.retval
771
772 class CustomEventBox(gtk.EventBox):
773
774     def __init__(self):
775         gtk.EventBox.__init__(self)
776
777     def do_expose_event(self, event):
778         self.context = self.window.cairo_create()
779         self.context.rectangle(event.area.x, event.area.y,
780                             event.area.width, event.area.height)
781
782         self.context.clip()
783         rect = self.get_allocation()
784         self.context.rectangle(rect.x, rect.y, rect.width, rect.height)
785
786         if self.state == gtk.STATE_ACTIVE:
787             style = self.rc_get_style()
788             color = style.lookup_color("SelectionColor")
789             self.context.set_source_rgba (color.red / 65535.0, color.green / 65335.0, color.blue / 65535.0, 0.75);
790         else:
791             self.context.set_source_rgba(1, 1, 1, 0)
792         self.context.fill()
793
794         gtk.EventBox.do_expose_event(self, event)
795
796 class GraphController(Singleton):
797     ytitles = ["Steps", "Average Speed", "Distance", "Calories"]
798     xtitles = ["Day", "Week"] # "Today"]
799     widget = None
800     def __init__(self):
801         self.repository = PedoRepositoryXML()
802         self.last_update = 0
803         PedoController().add_observer(self.update_ui)
804
805     def set_graph(self, widget):
806         self.widget = widget
807         self.update_ui()
808
809     def set_current_view(self, view):
810         """
811         current_view % len(ytitles) - gives the ytitle
812         current_view / len(ytitles) - gives the xtitle
813         """
814         self.current_view = view
815
816         if self.current_view == len(self.ytitles) * len(self.xtitles):
817             self.current_view = 0
818         self.x_id = self.current_view / len(self.ytitles)
819         self.y_id = self.current_view % len(self.ytitles)
820
821     def next_view(self):
822         self.set_current_view(self.current_view+1)
823         self.update_ui()
824         return self.current_view
825
826     def last_weeks_labels(self):
827         d = date.today()
828         delta = timedelta(days=7)
829         ret = []
830         for i in range(7):
831             ret.append(d.strftime("Week %W"))
832             d = d - delta
833         return ret
834
835     def compute_values(self):
836         labels = []
837         if self.x_id == 0:
838             values = self.repository.get_last_7_days()
839             d = date.today()
840             delta = timedelta(days=1)
841             for i in range(7):
842                 labels.append(d.ctime().split()[0])
843                 d = d - delta
844
845         elif self.x_id == 1:
846             values = self.repository.get_last_weeks()
847             d = date.today()
848             for i in range(7):
849                 labels.append(d.strftime("Week %W"))
850                 d = d - timedelta(days=7)
851         else:
852             values = self.repository.get_today()
853             #TODO get labels
854
855         if self.y_id == 0:
856             yvalues = [line.steps for line in values]
857         elif self.y_id == 1:
858             yvalues = [line.get_avg_speed() for line in values]
859         elif self.y_id == 2:
860             yvalues = [line.dist for line in values]
861         else:
862             yvalues = [line.calories for line in values]
863
864         #determine values for y lines in graph
865         diff = self.get_best_interval_value(max(yvalues))
866         ytext = []
867         for i in range(6):
868             ytext.append(str(int(i*diff)))
869
870         if self.widget is not None:
871             yvalues.reverse()
872             labels.reverse()
873             self.widget.values = yvalues
874             self.widget.ytext = ytext
875             self.widget.xtext = labels
876             self.widget.max_value = diff * 5
877             self.widget.text = self.xtitles[self.x_id] + " / " + self.ytitles[self.y_id]
878             self.widget.queue_draw()
879         else:
880             logger.error("Widget not set in GraphController")
881
882     def get_best_interval_value(self, max_value):
883         diff =  1.0 * max_value / 5
884         l = len(str(int(diff)))
885         d = math.pow(10, l/2)
886         val = int(math.ceil(1.0 * diff / d)) * d
887         if val == 0:
888             val = 1
889         return val
890
891     def update_ui(self, optional=False):
892         """update graph values every x seconds"""
893         if optional and self.last_update - time.time() < 600:
894             return
895         if self.widget is None:
896             return
897
898         self.compute_values()
899         self.last_update = time.time()
900
901 class GraphWidget(gtk.DrawingArea):
902
903     def __init__(self):
904         gtk.DrawingArea.__init__(self)
905         self.set_size_request(-1, 150)
906         self.yvalues = 5
907
908         """sample values"""
909         self.ytext = ["   0", "1000", "2000", "3000", "4000", "5000"]
910         self.xtext = ["Monday", "Tuesday", "Wednesday","Thursday", "Friday", "Saturday", "Sunday"]
911         self.values = [1500, 3400, 4000, 3600, 3200, 0, 4500]
912         self.max_value = 5000
913         self.text = "All time steps"
914
915     def do_expose_event(self, event):
916         context = self.window.cairo_create()
917
918         # set a clip region for the expose event
919         context.rectangle(event.area.x, event.area.y,
920                                event.area.width, event.area.height)
921         context.clip()
922
923         context.save()
924
925         context.set_operator(cairo.OPERATOR_SOURCE)
926         style = self.rc_get_style()
927
928         if self.state == gtk.STATE_ACTIVE:
929             color = style.lookup_color("SelectionColor")
930         else:
931              color = style.lookup_color("DefaultBackgroundColor")
932         context.set_source_rgba (color.red / 65535.0, color.green / 65335.0, color.blue / 65535.0, 0.75)
933
934         context.paint()
935         context.restore();
936         self.draw(context)
937
938     def draw(self, cr):
939         space_below = 20
940         space_above = 10
941         border_right = 10
942         border_left = 30
943
944         rect = self.get_allocation()
945         x = rect.width
946         y = rect.height
947
948         cr.select_font_face("Purisa", cairo.FONT_SLANT_NORMAL,
949             cairo.FONT_WEIGHT_NORMAL)
950         cr.set_font_size(13)
951
952         #check space needed to display ylabels
953         te = cr.text_extents(self.ytext[-1])
954         border_left = te[2] + 7
955
956         cr.set_source_rgb(1, 1, 1)
957         cr.move_to(border_left, space_above)
958         cr.line_to(border_left, y-space_below)
959         cr.set_line_width(2)
960         cr.stroke()
961
962         cr.move_to(border_left, y-space_below)
963         cr.line_to(x-border_right, y-space_below)
964         cr.set_line_width(2)
965         cr.stroke()
966
967         ydiff = (y-space_above-space_below) / self.yvalues
968         for i in range(self.yvalues):
969             yy = y-space_below-ydiff*(i+1)
970             cr.move_to(border_left, yy)
971             cr.line_to(x-border_right, yy)
972             cr.set_line_width(0.8)
973             cr.stroke()
974
975
976         for i in range(6):
977             yy = y - space_below - ydiff*i + 5
978             te = cr.text_extents(self.ytext[i])
979
980             cr.move_to(border_left-te[2]-2, yy)
981             cr.show_text(self.ytext[i])
982
983         cr.set_font_size(15)
984         te = cr.text_extents(self.text)
985         cr.move_to((x-te[2])/2, y-5)
986         cr.show_text(self.text)
987
988         graph_x_space = x - border_left - border_right
989         graph_y_space = y - space_below - space_above
990         bar_width = graph_x_space*0.75 / len(self.values)
991         bar_distance = graph_x_space*0.25 / (1+len(self.values))
992
993         #set dummy max value to avoid exceptions
994         if self.max_value == 0:
995             self.max_value = 100
996         for i in range(len(self.values)):
997             xx = border_left + (i+1)*bar_distance + i * bar_width
998             yy = y-space_below
999             height = graph_y_space * (1.0 * self.values[i] / self.max_value)
1000             cr.set_source_rgba(1, 1, 1, 0.75)
1001             cr.rectangle(int(xx), int(yy-height), int(bar_width), int(height))
1002             cr.fill()
1003
1004         cr.set_source_rgba(1, 1, 1, 1)
1005         cr.select_font_face("Purisa", cairo.FONT_SLANT_NORMAL,
1006                             cairo.FONT_WEIGHT_NORMAL)
1007         cr.set_font_size(13)
1008
1009         cr.rotate(2*math.pi * (-45) / 180)
1010         for i in range(len(self.values)):
1011             xx = y - space_below - 10
1012             yy = border_left + (i+1)*bar_distance + i * bar_width
1013             cr.move_to(-xx, yy + bar_width*1.25 / 2)
1014             cr.show_text(self.xtext[i])
1015
1016 class PedometerHomePlugin(hildondesktop.HomePluginItem):
1017     button = None
1018
1019     #labels to display
1020     labels = ["timer", "count", "dist", "avgSpeed", "calories"]
1021
1022     #current view
1023     labelsC = {}
1024
1025     #second view ( day / week/ alltime)
1026     labelsT = {}
1027
1028     second_view_labels = ["All-time", "Today", "This week"]
1029
1030     controller = None
1031     pedometer = None
1032     pedometerInterval = None
1033     graph_controller = None
1034
1035     mode = 0
1036     height = 0
1037     unit = 0
1038     aspect = 0
1039     second_view = 0
1040     graph_view = 0
1041     no_idle_time = False
1042     logging = False
1043
1044     def __init__(self):
1045         hildondesktop.HomePluginItem.__init__(self)
1046
1047         gobject.type_register(CustomEventBox)
1048         gobject.type_register(GraphWidget)
1049
1050         self.client = gconf.client_get_default()
1051         try:
1052             self.mode = self.client.get_int(MODE)
1053             self.height = self.client.get_int(HEIGHT)
1054             self.unit = self.client.get_int(UNIT)
1055             self.aspect = self.client.get_int(ASPECT)
1056             self.second_view = self.client.get_int(SECONDVIEW)
1057             self.graph_view = self.client.get_int(GRAPHVIEW)
1058             self.no_idle_time = self.client.get_bool(NOIDLETIME)
1059             self.logging = self.client.get_bool(LOGGING)
1060
1061         except:
1062             self.client.set_int(MODE, 0)
1063             self.client.set_int(HEIGHT, 0)
1064             self.client.set_int(UNIT, 0)
1065             self.client.set_int(ASPECT, 0)
1066             self.client.set_int(SECONDVIEW, 0)
1067             self.client.set_int(GRAPHVIEW, 0)
1068             self.client.set_bool(NOIDLETIME, False)
1069             self.client.set_bool(LOGGING, False)
1070
1071         self.controller = PedoController()
1072         self.controller.set_height(self.height)
1073         self.controller.set_mode(self.mode)
1074         self.controller.set_unit(self.unit)
1075         self.controller.set_second_view(self.second_view)
1076         self.controller.set_callback_ui(self.update_values)
1077         self.controller.set_no_idle_time(self.no_idle_time)
1078
1079         self.graph_controller = GraphController()
1080         self.graph_controller.set_current_view(self.graph_view)
1081
1082         self.alarm_controller = AlarmController()
1083
1084         self.button = CustomButton(ICONSPATH + "play.png")
1085         self.button.connect("clicked", self.button_clicked)
1086
1087         self.create_labels(self.labelsC)
1088         self.create_labels(self.labelsT)
1089         self.label_second_view = self.new_label_heading(self.second_view_labels[self.second_view])
1090
1091         self.update_current()
1092         self.update_total()
1093
1094         mainHBox = gtk.HBox(spacing=1)
1095
1096         descVBox = gtk.VBox(spacing=1)
1097         descVBox.add(self.new_label_heading())
1098         descVBox.add(self.new_label_heading("Time:"))
1099         descVBox.add(self.new_label_heading("Steps:"))
1100         descVBox.add(self.new_label_heading("Calories:"))
1101         descVBox.add(self.new_label_heading("Distance:"))
1102         descVBox.add(self.new_label_heading("Avg Speed:"))
1103
1104         currentVBox = gtk.VBox(spacing=1)
1105         currentVBox.add(self.new_label_heading("Current"))
1106         currentVBox.add(self.labelsC["timer"])
1107         currentVBox.add(self.labelsC["count"])
1108         currentVBox.add(self.labelsC["calories"])
1109         currentVBox.add(self.labelsC["dist"])
1110         currentVBox.add(self.labelsC["avgSpeed"])
1111         self.currentBox = currentVBox
1112
1113         totalVBox = gtk.VBox(spacing=1)
1114         totalVBox.add(self.label_second_view)
1115         totalVBox.add(self.labelsT["timer"])
1116         totalVBox.add(self.labelsT["count"])
1117         totalVBox.add(self.labelsT["calories"])
1118         totalVBox.add(self.labelsT["dist"])
1119         totalVBox.add(self.labelsT["avgSpeed"])
1120         self.totalBox = totalVBox
1121
1122         buttonVBox = gtk.VBox(spacing=1)
1123         buttonVBox.add(self.new_label_heading(""))
1124         buttonVBox.add(self.button)
1125         buttonVBox.add(self.new_label_heading(""))
1126
1127         eventBox = CustomEventBox()
1128         eventBox.set_visible_window(False)
1129         eventBox.add(totalVBox)
1130         eventBox.connect("button-press-event", self.eventBox_clicked)
1131         eventBox.connect("button-release-event", self.eventBox_clicked_release)
1132
1133         mainHBox.add(buttonVBox)
1134         mainHBox.add(descVBox)
1135         mainHBox.add(currentVBox)
1136         mainHBox.add(eventBox)
1137         self.mainhbox = mainHBox
1138
1139         graph = GraphWidget()
1140         self.graph_controller.set_graph(graph)
1141
1142         eventBoxGraph = CustomEventBox()
1143         eventBoxGraph.set_visible_window(False)
1144         eventBoxGraph.add(graph)
1145         self.graph = graph
1146         eventBoxGraph.connect("button-press-event", self.eventBoxGraph_clicked)
1147         eventBoxGraph.connect("button-release-event", self.eventBoxGraph_clicked_release)
1148
1149         self.mainvbox = gtk.VBox()
1150
1151         self.mainvbox.add(mainHBox)
1152         self.mainvbox.add(eventBoxGraph)
1153
1154         self.mainvbox.show_all()
1155         self.add(self.mainvbox)
1156         self.update_aspect()
1157
1158         self.connect("unrealize", self.close_requested)
1159         self.set_settings(True)
1160         self.connect("show-settings", self.show_settings)
1161
1162     def eventBoxGraph_clicked(self, widget, data=None):
1163         widget.set_state(gtk.STATE_ACTIVE)
1164
1165     def eventBoxGraph_clicked_release(self, widget, data=None):
1166         self.graph_view = self.graph_controller.next_view()
1167         self.client.set_int(GRAPHVIEW, self.graph_view)
1168
1169         widget.set_state(gtk.STATE_NORMAL)
1170
1171     def eventBox_clicked(self, widget, data=None):
1172         widget.set_state(gtk.STATE_ACTIVE)
1173
1174     def eventBox_clicked_release(self, widget, data=None):
1175         widget.set_state(gtk.STATE_NORMAL)
1176
1177         self.second_view = (self.second_view + 1) % 3
1178         self.controller.set_second_view(self.second_view)
1179         self.client.set_int(SECONDVIEW, self.second_view)
1180
1181     def new_label_heading(self, title=""):
1182         l = gtk.Label(title)
1183         hildon.hildon_helper_set_logical_font(l, "SmallSystemFont")
1184         return l
1185
1186     def create_labels(self, new_labels):
1187         for label in self.labels:
1188             l = gtk.Label()
1189             hildon.hildon_helper_set_logical_font(l, "SmallSystemFont")
1190             hildon.hildon_helper_set_logical_color(l, gtk.RC_FG, gtk.STATE_NORMAL, "ActiveTextColor")
1191             new_labels[label] = l
1192
1193     def update_aspect(self):
1194         if self.aspect == 0:
1195             self.currentBox.show_all()
1196             self.totalBox.show_all()
1197         elif self.aspect == 1:
1198             self.currentBox.show_all()
1199             self.totalBox.hide_all()
1200         else:
1201             self.currentBox.hide_all()
1202             self.totalBox.show_all()
1203
1204     def update_ui_values(self, labels, values):
1205         labels["timer"].set_label(values.get_print_time())
1206         labels["count"].set_label(values.get_print_steps())
1207         labels["dist"].set_label(values.get_print_distance())
1208         labels["avgSpeed"].set_label(values.get_print_avg_speed())
1209         labels["calories"].set_label(values.get_print_calories())
1210
1211     def update_current(self):
1212         self.update_ui_values(self.labelsC, self.controller.get_first())
1213
1214     def update_total(self):
1215         self.update_ui_values(self.labelsT, self.controller.get_second())
1216
1217     def show_alarm_settings(self, main_button):
1218         def choose_file(widget):
1219             file = hildon.FileChooserDialog(self, gtk.FILE_CHOOSER_ACTION_OPEN, hildon.FileSystemModel() )
1220             file.show()
1221             if ( file.run() == gtk.RESPONSE_OK):
1222                 fname = file.get_filename()
1223                 widget.set_value(fname)
1224                 self.alarm_controller.set_alarm_file(fname)
1225             file.destroy()
1226
1227         def test_sound(button):
1228             try:
1229                 self.alarm_controller.play()
1230             except Exception, e:
1231                 logger.error("Could not play alarm sound: %s" % e)
1232                 hildon.hildon_banner_show_information(self, "None", "Could not play alarm sound")
1233
1234         def enableButton_changed(button):
1235             value = button.get_active()
1236             self.alarm_controller.set_enable(value)
1237             if value:
1238                 main_button.set_value("Enabled")
1239             else:
1240                 main_button.set_value("Disabled")
1241
1242         def selectorType_changed(selector, data, labelEntry2):
1243             self.alarm_controller.set_type(selector.get_active(0))
1244             labelEntry2.set_label(suffix[self.alarm_controller.get_type()])
1245
1246         dialog = gtk.Dialog()
1247         dialog.set_title("Alarm settings")
1248         dialog.add_button("OK", gtk.RESPONSE_OK)
1249
1250         enableButton = hildon.CheckButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT)
1251         enableButton.set_label("Enable alarm")
1252         enableButton.set_active(self.alarm_controller.get_enable())
1253         enableButton.connect("toggled", enableButton_changed)
1254
1255         testButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1256         testButton.set_alignment(0, 0.8, 1, 1)
1257         testButton.set_title("Test sound")
1258         testButton.connect("pressed", test_sound)
1259
1260         fileButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1261         fileButton.set_alignment(0, 0.8, 1, 1)
1262         fileButton.set_title("Alarm sound")
1263         fileButton.set_value(self.alarm_controller.get_alarm_file())
1264         fileButton.connect("pressed", choose_file)
1265
1266         labelEntry = gtk.Label("Notify every:")
1267         suffix = ["mins", "steps", "m/ft", "calories"]
1268         labelEntry2 = gtk.Label(suffix[self.alarm_controller.get_type()])
1269         intervalEntry = hildon.Entry(gtk.HILDON_SIZE_AUTO_WIDTH)
1270         intervalEntry.set_text(str(self.alarm_controller.get_interval()))
1271
1272         selectorType = hildon.TouchSelector(text=True)
1273         selectorType.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
1274         selectorType.append_text("Time")
1275         selectorType.append_text("Steps")
1276         selectorType.append_text("Distance")
1277         selectorType.append_text("Calories")
1278         selectorType.connect("changed", selectorType_changed, labelEntry2)
1279
1280         typePicker = hildon.PickerButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1281         typePicker.set_alignment(0.0, 0.5, 1.0, 1.0)
1282         typePicker.set_title("Alarm type")
1283         typePicker.set_selector(selectorType)
1284         typePicker.set_active(self.alarm_controller.get_type())
1285
1286         hbox = gtk.HBox()
1287         hbox.add(labelEntry)
1288         hbox.add(intervalEntry)
1289         hbox.add(labelEntry2)
1290
1291         dialog.vbox.add(enableButton)
1292         dialog.vbox.add(fileButton)
1293         dialog.vbox.add(testButton)
1294         dialog.vbox.add(typePicker)
1295         dialog.vbox.add(hbox)
1296         dialog.show_all()
1297         while 1:
1298             response = dialog.run()
1299             if response != gtk.RESPONSE_OK:
1300                 break
1301             try:
1302                 value = int(intervalEntry.get_text())
1303                 self.alarm_controller.set_interval(value)
1304                 break
1305             except:
1306                 hildon.hildon_banner_show_information(self, "None", "Invalid interval")
1307
1308         dialog.destroy()
1309
1310     def show_settings(self, widget):
1311         def reset_total_counter(arg):
1312             widget.totalCounter = 0
1313             widget.totalTime = 0
1314             widget.update_total()
1315             hildon.hildon_banner_show_information(self, "None", "Total counter was resetted")
1316
1317         def alarmButton_pressed(widget):
1318             self.show_alarm_settings(widget)
1319
1320         def selector_changed(selector, data):
1321             widget.mode = selector.get_active(0)
1322             widget.client.set_int(MODE, widget.mode)
1323             widget.controller.set_mode(widget.mode)
1324
1325         def selectorH_changed(selector, data):
1326             widget.height = selectorH.get_active(0)
1327             widget.client.set_int(HEIGHT, widget.height)
1328             widget.controller.set_height(widget.height)
1329
1330         def selectorUnit_changed(selector, data):
1331             widget.unit = selectorUnit.get_active(0)
1332             widget.client.set_int(UNIT, widget.unit)
1333             widget.controller.set_unit(widget.unit)
1334
1335         def selectorUI_changed(selector, data):
1336             widget.aspect = selectorUI.get_active(0)
1337             widget.client.set_int(ASPECT, widget.aspect)
1338             widget.update_aspect()
1339
1340         def logButton_changed(checkButton):
1341             widget.logging = checkButton.get_active()
1342             widget.client.set_bool(LOGGING, widget.logging)
1343
1344         def idleButton_changed(idleButton):
1345             widget.no_idle_time = idleButton.get_active()
1346             widget.client.set_bool(NOIDLETIME, widget.no_idle_time)
1347             widget.controller.set_no_idle_time(widget.no_idle_time)
1348
1349         dialog = gtk.Dialog()
1350         dialog.set_title("Settings")
1351         dialog.add_button("OK", gtk.RESPONSE_OK)
1352
1353
1354         button = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1355         button.set_title("Reset total counter")
1356         button.set_alignment(0, 0.8, 1, 1)
1357         button.connect("clicked", reset_total_counter)
1358
1359         alarmButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1360         alarmButton.set_title("Alarm")
1361         if self.alarm_controller.get_enable():
1362             alarmButton.set_value("Enabled")
1363         else:
1364             alarmButton.set_value("Disabled")
1365         alarmButton.set_alignment(0, 0.8, 1, 1)
1366         alarmButton.connect("clicked", alarmButton_pressed)
1367
1368         selector = hildon.TouchSelector(text=True)
1369         selector.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
1370         selector.append_text("Walk")
1371         selector.append_text("Run")
1372         selector.connect("changed", selector_changed)
1373
1374         modePicker = hildon.PickerButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1375         modePicker.set_alignment(0.0, 0.5, 1.0, 1.0)
1376         modePicker.set_title("Select mode")
1377         modePicker.set_selector(selector)
1378         modePicker.set_active(widget.mode)
1379
1380         selectorH = hildon.TouchSelector(text=True)
1381         selectorH.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
1382         selectorH.append_text("< 1.50 m")
1383         selectorH.append_text("1.50 - 1.65 m")
1384         selectorH.append_text("1.66 - 1.80 m")
1385         selectorH.append_text("1.81 - 1.95 m")
1386         selectorH.append_text(" > 1.95 m")
1387         selectorH.connect("changed", selectorH_changed)
1388
1389         heightPicker = hildon.PickerButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1390         heightPicker.set_alignment(0.0, 0.5, 1.0, 1.0)
1391         heightPicker.set_title("Select height")
1392         heightPicker.set_selector(selectorH)
1393         heightPicker.set_active(widget.height)
1394
1395         selectorUnit = hildon.TouchSelector(text=True)
1396         selectorUnit.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
1397         selectorUnit.append_text("Metric (km)")
1398         selectorUnit.append_text("English (mi)")
1399         selectorUnit.connect("changed", selectorUnit_changed)
1400
1401         unitPicker = hildon.PickerButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1402         unitPicker.set_alignment(0.0, 0.5, 1.0, 1.0)
1403         unitPicker.set_title("Units")
1404         unitPicker.set_selector(selectorUnit)
1405         unitPicker.set_active(widget.unit)
1406
1407         selectorUI = hildon.TouchSelector(text=True)
1408         selectorUI = hildon.TouchSelector(text=True)
1409         selectorUI.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
1410         selectorUI.append_text("Show current + total")
1411         selectorUI.append_text("Show only current")
1412         selectorUI.append_text("Show only total")
1413         selectorUI.connect("changed", selectorUI_changed)
1414
1415         UIPicker = hildon.PickerButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
1416         UIPicker.set_alignment(0.0, 0.5, 1.0, 1.0)
1417         UIPicker.set_title("Widget aspect")
1418         UIPicker.set_selector(selectorUI)
1419         UIPicker.set_active(widget.aspect)
1420
1421         logButton = hildon.CheckButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT)
1422         logButton.set_label("Log data")
1423         logButton.set_active(widget.logging)
1424         logButton.connect("toggled", logButton_changed)
1425
1426         idleButton = hildon.CheckButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT)
1427         idleButton.set_label("Pause time when not walking")
1428         idleButton.set_active(widget.no_idle_time)
1429         idleButton.connect("toggled", idleButton_changed)
1430
1431         pan_area = hildon.PannableArea()
1432         vbox = gtk.VBox()
1433         vbox.add(button)
1434         vbox.add(alarmButton)
1435         vbox.add(modePicker)
1436         vbox.add(heightPicker)
1437         vbox.add(unitPicker)
1438         vbox.add(UIPicker)
1439         vbox.add(idleButton)
1440         #vbox.add(logButton)
1441
1442         pan_area.add_with_viewport(vbox)
1443         pan_area.set_size_request(-1, 300)
1444
1445         dialog.vbox.add(pan_area)
1446         dialog.show_all()
1447         response = dialog.run()
1448         #hildon.hildon_banner_show_information(self, "None", "You have to Stop/Start the counter to apply the new settings")
1449         dialog.destroy()
1450
1451     def close_requested(self, widget):
1452         if self.pedometer is None:
1453             return
1454
1455         self.pedometer.request_stop()
1456
1457     def update_values(self):
1458         #TODO: do not update if the widget is not on the active desktop
1459         self.label_second_view.set_label(self.second_view_labels[self.second_view])
1460         self.update_current()
1461         self.update_total()
1462
1463     def button_clicked(self, button):
1464         if self.controller.is_running:
1465             self.controller.stop_pedometer()
1466             self.button.set_icon(ICONSPATH + "play.png")
1467         else:
1468             self.controller.start_pedometer()
1469             self.button.set_icon(ICONSPATH + "stop.png")
1470
1471     def do_expose_event(self, event):
1472         cr = self.window.cairo_create()
1473         cr.region(event.window.get_clip_region())
1474         cr.clip()
1475         #cr.set_source_rgba(0.4, 0.64, 0.564, 0.5)
1476         style = self.rc_get_style()
1477         color = style.lookup_color("DefaultBackgroundColor")
1478         cr.set_source_rgba (color.red / 65535.0, color.green / 65335.0, color.blue / 65535.0, 0.75);
1479
1480         radius = 5
1481         width = self.allocation.width
1482         height = self.allocation.height
1483
1484         x = self.allocation.x
1485         y = self.allocation.y
1486
1487         cr.move_to(x + radius, y)
1488         cr.line_to(x + width - radius, y)
1489         cr.curve_to(x + width - radius, y, x + width, y, x + width, y + radius)
1490         cr.line_to(x + width, y + height - radius)
1491         cr.curve_to(x + width, y + height - radius, x + width, y + height, x + width - radius, y + height)
1492         cr.line_to(x + radius, y + height)
1493         cr.curve_to(x + radius, y + height, x, y + height, x, y + height - radius)
1494         cr.line_to(x, y + radius)
1495         cr.curve_to(x, y + radius, x, y, x + radius, y)
1496
1497         cr.set_operator(cairo.OPERATOR_SOURCE)
1498         cr.fill_preserve()
1499
1500         color = style.lookup_color("ActiveTextColor")
1501         cr.set_source_rgba (color.red / 65535.0, color.green / 65335.0, color.blue / 65535.0, 0.5);
1502         cr.set_line_width(1)
1503         cr.stroke()
1504
1505         hildondesktop.HomePluginItem.do_expose_event(self, event)
1506
1507     def do_realize(self):
1508         screen = self.get_screen()
1509         self.set_colormap(screen.get_rgba_colormap())
1510         self.set_app_paintable(True)
1511         hildondesktop.HomePluginItem.do_realize(self)
1512
1513 hd_plugin_type = PedometerHomePlugin
1514
1515 import math
1516 import logging
1517
1518 logger = logging.getLogger("pedometer")
1519 logger.setLevel(logging.INFO)
1520
1521 ch = logging.StreamHandler()
1522 formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
1523 ch.setFormatter(formatter)
1524 logger.addHandler(ch)
1525
1526 # The code below is just for testing purposes.
1527 # It allows to run the widget as a standalone process.
1528 if __name__ == "__main__":
1529     import gobject
1530     gobject.type_register(hd_plugin_type)
1531     obj = gobject.new(hd_plugin_type, plugin_id="plugin_id")
1532     obj.show_all()
1533     gtk.main()