Support completing input with builtin keypad
[stuff-for-openmoko-freerunner:debian-usr-bin.git] / zhone
1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
3 """
4 Zen Phone - A Phone UI
5
6 (C) 2007 Johannes 'Josch' Schauer
7 (C) 2008 Michael 'Mickey' Lauer <mlauer@vanille-media.de>
8 (C) 2008 Jan 'Shoragan' Luebbe
9 (C) 2008 Daniel 'Alphaone' Willmann
10 (C) 2008 Openmoko, Inc.
11 GPLv2 or later
12
13 Warning: Please don't let yourself be inspired too much by this software,
14 it's lacking proper design, modularity, and stability. We did this as a
15 quick hack to demonstrate the feasibility of our dbus service level
16 framework. A proper redesign would be in order, but alas the framework
17 team has no resources to do that.
18
19 You have been warned :)
20
21 """
22
23 __version__ = "0.0.0"
24 MODULE_NAME = "zhone"
25
26 from time import sleep
27
28 # Locale support
29 import gettext
30
31 try:
32     cat = gettext.Catalog("zhone", "/usr/share/zhone/locale")
33     _ = cat.gettext
34 except IOError:
35     _ = lambda x: x
36
37 import logging
38 logger = logging.getLogger( MODULE_NAME )
39 logging.basicConfig( level    = logging.DEBUG,
40                     format   = '%(asctime)s %(levelname)s %(message)s',
41                     filename = '/tmp/zhone.log',
42                     filemode = 'w' )
43 handler = logging.StreamHandler()
44 handler.setFormatter( logging.Formatter( '%(asctime)s %(levelname)s %(message)s' ))
45 logger.addHandler( handler )
46 logger.setLevel( logging.DEBUG )
47
48 def log_dbus_error( e, desc ):
49     logger.error( "%s (%s %s: %s)" % ( desc, e.__class__.__name__, e.get_dbus_name(), e.get_dbus_message() ) )
50
51 def handle_dbus_error( desc ):
52     return lambda e: log_dbus_error( e, desc = desc )
53
54 def textblock_escape( text ):
55     text = text.replace('&', '&amp;')
56     text = text.replace('<', '&lt;')
57     text = text.replace('>', '&gt;')
58     return text
59
60 #----------------------------------------------------------------------------#
61 WIDTH = 480
62 HEIGHT = 640
63
64 TITLE = "zhone"
65 WM_NAME = "zhone"
66 WM_CLASS = "zhone"
67
68 #----------------------------------------------------------------------------#
69 import os
70 import sys
71 import e_dbus
72 import evas
73 import evas.decorators
74 import edje
75 import edje.decorators
76 import ecore
77 import ecore.evas
78 import cairo
79 from dbus import SystemBus, Interface
80 from dbus.exceptions import DBusException
81 from optparse import OptionParser
82 import time
83 import math
84
85 illume = None
86 try:
87     import illume
88 except ImportError:
89     logger.warning( "could not load illume interface module" )
90
91 #----------------------------------------------------------------------------#
92
93 edjepaths = "./zhone.edj ../data/themes/zhone.edj ../share/zhone.edj /usr/local/share/zhone/zhone.edj /usr/share/zhone/zhone.edj".split()
94
95 for i in edjepaths:
96     if os.path.exists( i ):
97        global edjepath
98        edjepath = i
99        break
100 else:
101     raise Exception( "zhone.edj not found. looked in %s" % edjepaths )
102
103 #----------------------------------------------------------------------------#
104 class edje_group(edje.Edje):
105 #----------------------------------------------------------------------------#
106     def __init__(self, main, group, parent_name="main"):
107         self.main = main
108         self.parent_name = parent_name
109         self.group = group
110         global edjepath
111         f = edjepath
112         try:
113             edje.Edje.__init__(self, self.main.evas_canvas.evas_obj.evas, file=f, group=group)
114         except edje.EdjeLoadError, e:
115             raise SystemExit("error loading %s: %s" % (f, e))
116         self.size = self.main.evas_canvas.evas_obj.evas.size
117
118     def onShow( self ):
119         pass
120
121     def onHide( self ):
122         pass
123
124     @edje.decorators.signal_callback("mouse,clicked,1", "button_bottom_right")
125     def on_edje_signal_button_bottom_right_pressed(self, emission, source):
126         self.main.transition_to(self.parent_name)
127
128     @edje.decorators.signal_callback("mouse,clicked,1", "button_bottom_left")
129     def on_edje_signal_button_bottom_left_pressed(self, emission, source):
130         self.main.groups["main_menu"].activate( self.group )
131         self.main.transition_to("main_menu")
132
133 #----------------------------------------------------------------------------#
134 class pyphone_main(edje_group):
135 #----------------------------------------------------------------------------#
136     def __init__(self, main):
137         edje_group.__init__(self, main, "main")
138         self.targets = {
139             "phone": False,
140             "contacts": False,
141             "sms": False,
142             "wireless": True,
143             "location": False,
144             "configuration": True,
145         }
146         self.update()
147
148     def update( self ):
149         for key, value in self.targets.items():
150             if value:
151                 self.signal_emit( "activate_target_icon_%s" % key, "" )
152             else:
153                 self.signal_emit( "deactivate_target_icon_%s" % key, "" )
154
155     @edje.decorators.signal_callback("mouse,clicked,1", "target_*")
156     def on_edje_signal_button_pressed(self, emission, source):
157         target = source.split('_', 1)[1]
158         if not self.targets[target]:
159             return
160         if target == "phone" and not self.main.groups["call"].status in ["idle" , "release"]:
161             target = "call"
162         self.main.transition_to(target)
163
164 #----------------------------------------------------------------------------#
165 class pyphone_phone(edje_group):
166 #----------------------------------------------------------------------------#
167     TIMEOUT = 2.0
168     def __init__(self, main):
169         edje_group.__init__(self, main, "phone")
170         self.text = []
171         self.last = 0.0
172
173     @edje.decorators.signal_callback( "mouse,clicked,1", "button_*" )
174     def on_edje_signal_dialer_button_pressed(self, emission, source):
175         key = source.split("_", 1)[1]
176         if key in ("0", "1", "2", "3", "4", "5", "6", "7", "8", "9"):
177             self.text.append(key)
178             # The trailing whitespace is a workaround for the one char invisible
179             # bug due to some problems with scaling of text parts.
180             self.part_text_set("label", u"".join(self.text)+u" ")
181         elif key in "star":
182             if self.text and ( time.time()-self.last < self.TIMEOUT ):
183                 if self.text[-1] == "*":
184                     del self.text[-1]
185                     self.text.append( "+" )
186                 elif self.text[-1] == "+":
187                     del self.text[-1]
188                     self.text.append( "*" )
189                 else:
190                     self.text.append( "*" )
191             else:
192                 self.text.append("*")
193             self.part_text_set( "label", u"".join(self.text)+u" " )
194         elif key in "hash":
195             self.text += "#"
196             self.part_text_set( "label", u"".join(self.text)+u" " )
197         elif key in "delete":
198             self.text = self.text[:-1]
199             self.part_text_set("label", u"".join(self.text)+u" ")
200         elif key in "dial":
201             if dbus_object.gsm_device_obj:
202                 if "".join(self.text)[0] == "*":
203                     dbus_object.gsm_network_iface.SendUssdRequest( "".join(self.text) )
204                 else:
205                     dbus_object.gsm_call_iface.Initiate( "".join(self.text), "voice" )
206             else:
207                 # Fake onCallStatus...
208                 self.main.groups["call"].onCallStatus( None, "outgoing", {"peer": "".join(self.text)} )
209         self.last = time.time()
210
211     def onNetworkStatus( self, status ):
212         logger.info( "network status changed: %s" % status )
213         #if(status.has_key("registration") != "busy")  # XXX this might work too.  which is better?
214         if(status.has_key('provider')):
215             self.main.agents["gsm"].setState(_("Registered: %s") % status['provider'])
216             self.main.groups["main"].targets["phone"] = True
217             self.main.groups["main"].update()
218             if dbus_object.gsm_sim_iface.GetSimReady():
219                 self.main.groups["contacts"].prepare()
220                 self.main.groups["sms"].prepare()
221         else:
222             self.main.agents["gsm"].setState(_("Failed to register to network"))
223             self.main.groups["main"].targets["phone"] = False
224             self.main.groups["main"].update()
225
226     def onIncomingUssd( self, mode, message ):
227         logger.info( "USSD Message: %s" % message )
228         self.main.groups["alert"].activate( "<title>Operator Message</title>%s" % message, [("OK")] )
229
230 _("active")
231 _("incoming")
232 _("held")
233 _("outgoing")
234 _("released")
235 #----------------------------------------------------------------------------#
236 class pyphone_call(edje_group):
237 #----------------------------------------------------------------------------#
238     def __init__(self, main):
239         edje_group.__init__(self, main, "call")
240         self.update_status("idle")
241         self.call = None
242
243     def onCallStatus( self, id, status, properties ):
244         self.call = id
245         self.update_status(status)
246         if not status in ["release"]:
247             if "peer" in properties:
248                 self.part_text_set( "label", self.main.groups["contacts"].tryNumberToName( properties[ "peer" ] ) )
249                 self.part_text_set( "sublabel", properties[ "peer" ] )
250             else:
251                 self.part_text_set( "label", _("unknown number") )
252                 self.part_text_set( "sublabel", "" )
253
254     @edje.decorators.signal_callback("call_button_pressed", "button_left")
255     def on_edje_signal_call_button_left_pressed(self, emission, source):
256         if self.status == "active":
257             if dbus_object.gsm_device_obj:
258                 #dbus_object.gsm_call_iface.Hold(self.call)
259                 pass
260             else:
261                 self.update_status("held")
262         elif self.status in ["incoming", "held"]:
263             if dbus_object.gsm_device_obj:
264                 dbus_object.gsm_call_iface.Activate(self.call)
265             else:
266                 self.update_status("active")
267
268     @edje.decorators.signal_callback("call_button_pressed", "button_right")
269     def on_edje_signal_call_button_right_pressed(self, emission, source):
270         if self.status in ["outgoing", "active", "incoming", "held"]:
271             if dbus_object.gsm_device_obj:
272                 dbus_object.gsm_call_iface.Release(self.call)
273             else:
274                 self.update_status("release")
275
276     def update_status(self, status):
277         self.part_text_set( "label_description", _(status) )
278         if status == "outgoing":
279             self.part_text_set( "label_left", u"" )
280             self.part_text_set( "label_right", _("cancel") )
281         elif status == "active":
282             self.part_text_set( "label_left", _("hold") )
283             self.part_text_set( "label_right", _("hangup") )
284         elif status == "incoming":
285             self.main.groups["lock"].deactivate()
286             self.part_text_set( "label_left", _("answer") )
287             self.part_text_set( "label_right", _("reject") )
288         elif status == "held":
289             self.part_text_set( "label_left", _("resume") )
290             self.part_text_set( "label_right", _("hangup") )
291         else:
292             self.part_text_set( "label_left", u"" )
293             self.part_text_set( "label_right", u"" )
294         if status in ["incoming", "outgoing"] and not self.status == status:
295             # TODO make sure we get displayed
296             self.main.transition_to("call")
297         self.status = status
298
299     @edje.decorators.signal_callback( "mouse,clicked,1", "button_bottom_middle" )
300     def on_edje_signal_button_bottom_middle_pressed( self, emission, source ):
301         self.main.transition_to("dtmf")
302
303 #----------------------------------------------------------------------------#
304 class pyphone_dtmf(edje_group):
305 #----------------------------------------------------------------------------#
306     def __init__(self, main):
307         edje_group.__init__(self, main, "dtmf")
308         self.text = []
309
310     @edje.decorators.signal_callback( "mouse,clicked,1", "button_*" )
311     def on_edje_signal_dialer_button_pressed(self, emission, source):
312         key = source.split("_", 1)[1]
313         if key in ("0", "1", "2", "3", "4", "5", "6", "7", "8", "9"):
314             dbus_object.gsm_call_iface.SendDtmf( key )
315             self.text.append(key)
316         elif key in "star":
317             dbus_object.gsm_call_iface.SendDtmf( "*" )
318             self.text.append("*")
319         elif key in "hash":
320             dbus_object.gsm_call_iface.SendDtmf( "#" )
321             self.text += "#"
322         # The trailing whitespace is a workaround for the one char invisible
323         # bug due to some problems with scaling of text parts.
324         self.part_text_set("label", "".join(self.text)+" ")
325
326     @edje.decorators.signal_callback("call_button_pressed", "button_right")
327     def on_edje_signal_call_button_right_pressed(self, emission, source):
328         self.main.transition_to("call")
329
330 #----------------------------------------------------------------------------#
331 class pyphone_sms(edje_group):
332 #----------------------------------------------------------------------------#
333     DELAY = 300.0
334     def __init__(self, main):
335         edje_group.__init__(self, main, "sms")
336         self.ready = False
337         self.busy = False
338         self.messagebook = []
339         self.current = []
340         self.page = 0
341         self.selected = None
342         self.newindex = None
343         self.timer = None
344         self.part_text_set( "label_action_open", _("open") )
345
346     def sendMessage( self, index ):
347         logger.info( "trying to send message w/ index %d..." % index )
348         dbus_object.gsm_sim_iface.SendStoredMessage( index, reply_handler=self.cbSendReply, error_handler=self.cbSendError )
349
350     def cbSendReply( self, reference, timestamp ):
351         logger.info( "sent message successfully w/ reference number %d (timestamp: %s)" % ( reference, timestamp ) )
352
353     def cbSendError( self, e ):
354         logger.error( "could not send message. leaving in outgoing. error was: %s" % e )
355
356     def cbStoreReply( self, result ):
357         logger.info( "stored message lives at SIM position %d" % result )
358         self.sendMessage( result )
359         if not self.busy:
360             self.cancelTimer()
361             dbus_object.gsm_sim_iface.RetrieveMessagebook(
362                 "all",
363                 reply_handler=self.cbMessagebookReply,
364                 error_handler=self.cbMessagebookError
365             )
366             self.busy = True
367
368     def cbStoreError( self, e ):
369         logger.warning( "error while storing message - %s" % e.get_dbus_name() )
370         if e.get_dbus_name() == "org.freesmartphone.GSM.SIM.MemoryFull":
371             self.main.groups["alert"].activate( "<title>Failed to send message</title>SIM Memory Full", [("OK")] )
372
373     def cbSend1( self, contact, cb_data ):
374         self.main.groups["text_edit"].setup(
375             "sms",
376             "", # text
377             contact[0], # title
378             contact[1], # reference
379             self.cbSend2
380         )
381
382     def cbSend2( self, text, number ):
383         if dbus_object.gsm_device_obj:
384             dbus_object.gsm_sim_iface.StoreMessage(
385                 number, text, {},
386                 reply_handler=self.cbStoreReply,
387                 error_handler=self.cbStoreError
388             )
389
390     def cbDelete( self, result, reference ):
391         if result == "abort":
392             return
393         if dbus_object.gsm_device_obj:
394             dbus_object.gsm_sim_iface.DeleteMessage(
395                 reference
396             )
397         for i in range( len( self.messagebook ) ):
398             if self.messagebook[i][0] == reference:
399                 del self.messagebook[i]
400                 break
401         self.updateList()
402         self.main.transition_to( "sms" )
403
404     def cbForward( self, contact, text ):
405         if dbus_object.gsm_device_obj:
406             dbus_object.gsm_sim_iface.StoreMessage(
407                 contact[1], text, {},
408                 reply_handler=self.cbStoreReply,
409                 error_handler=self.cbStoreError
410             )
411
412     def cbReply( self, text, number):
413         if dbus_object.gsm_device_obj:
414             dbus_object.gsm_sim_iface.StoreMessage(
415                 number, text, {},
416                 reply_handler=self.cbStoreReply,
417                 error_handler=self.cbStoreError
418             )
419
420     def cbMenu( self, result ):
421         if result == _("send"):
422             self.main.groups["contacts"].prepare()
423             if self.main.groups["contacts"].ready:
424                 self.main.groups["list_choose"].setup(
425                     "sms",
426                     [ (x[1], x[2]) for x in  self.main.groups["contacts"].phonebook],
427                     None,
428                     self.cbSend1
429                 )
430         elif result == _("delete"):
431             self.main.groups["alert"].activate(
432                 _("delete?"),
433                 (_("abort"), _("delete")),
434                 self.current[self.selected][0], # reference
435                 self.cbDelete
436             )
437         elif result == _("forward"):
438             self.main.groups["contacts"].prepare()
439             if self.main.groups["contacts"].ready:
440                 self.main.groups["list_choose"].setup(
441                     "sms",
442                     [ (x[1], x[2] ) for x in  self.main.groups["contacts"].phonebook],
443                     self.current[self.selected][3],
444                     self.cbForward
445                 )
446         elif result == _("reply"):
447             self.main.groups["text_edit"].setup(
448                 "sms",
449                 "", # text
450                 self.main.groups["contacts"].tryNumberToName( self.current[self.selected][2] ), # title = contact name or number
451                 self.current[self.selected][2], # reference = number
452                 self.cbReply
453             )
454
455     def cbMessagebookReply( self, result ):
456         logger.info( "retrieved messagebook: %s" % result )
457         self.busy = False
458         self.messagebook = result
459         self.ready = True
460         self.updateList()
461         self.main.groups["main"].targets["sms"] = True
462         self.main.groups["main"].update()
463         if not self.newindex is None:
464             message = [x for x in self.messagebook if x[0] == self.newindex]
465             self.newindex = None
466             if message and self.main.current_group == self.main.groups["main"]:
467                 self.displayMessage( message[0] )
468             if dbus_object.gsm_device_obj:
469                 messagebookInfo = dbus_object.gsm_sim_iface.GetMessagebookInfo()
470                 if messagebookInfo["used"] == messagebookInfo["last"]:
471                     self.main.groups["alert"].activate( "<title>Warning</title>SIM Memory Full", [("OK")] )
472         else:
473             message = [x for x in self.messagebook if x[1] == 'unread']
474             if message:
475                 logger.info( 'play sound to announce unread messages' )
476                 os.system( '/usr/bin/aplay /usr/share/sounds/kayleigh.wav &' )
477                 if self.main.current_group == self.main.groups["main"]:
478                     logger.info( 'show missed message' )
479                     self.displayMessage( message[0] )
480         self.startTimer()
481
482     def displayMessage( self, message ):
483         if "read" in message[1]:
484             from_to = _("From")
485             timestamp = message[4]["timestamp"]
486         else:
487             from_to = _("To")
488             timestamp = _("Unknown")
489         from_text = self.main.groups["contacts"].tryNumberToName( message[2] )
490         self.main.groups["text_show"].setup(
491             "sms",
492             _("%s: %s<br>Date: %s<p>%s") % (
493                 from_to,
494                 from_text,
495                 timestamp,
496                 textblock_escape( message[3] ).replace( '\n', '<br>' )
497                 )
498             )
499
500     def cbTimer( self ):
501         logger.info( "check for missing received messages..." )
502         self.timer = None
503         self.onIncomingMessage( None )
504         return 0
505
506     def startTimer( self ):
507         if self.timer is None:
508             self.timer = ecore.timer_add( self.DELAY, self.cbTimer )
509
510     def cancelTimer( self ):
511         if self.timer is not None:
512             self.timer.delete()
513             self.timer = None
514
515     def cbMessagebookError( self, e ):
516         logger.warning( "error while retrieving messagebook" )
517         self.busy = False
518         self.startTimer()
519
520     def onIncomingMessage( self, index ):
521         logger.info( "new message! Retrieving messagebook..." )
522         self.newindex = index
523         if not self.busy:
524             self.cancelTimer()
525             dbus_object.gsm_sim_iface.RetrieveMessagebook(
526                 "all",
527                 reply_handler=self.cbMessagebookReply,
528                 error_handler=self.cbMessagebookError
529             )
530             self.busy = True
531
532     def prepare( self ):
533         if not self.ready and not self.busy:
534             if dbus_object.gsm_device_obj:
535                 logger.info( "retrieving messagebook..." )
536                 self.cancelTimer()
537                 dbus_object.gsm_sim_iface.RetrieveMessagebook(
538                     "all",
539                     reply_handler=self.cbMessagebookReply,
540                     error_handler=self.cbMessagebookError
541                 )
542                 self.busy = True
543             else:
544                 # Fake messagebook...
545                 self.cbMessagebookReply( [
546                     (0, "read", "+4544555", "Hello World!"),
547                     (1, "read", "+456663443", "Zhone!"),
548                     (2, "read", "+456663443", "Hi Guy\nGuess what, I now "+
549                         "know to write multi-line SMSs.\nIsn't that "+
550                         "nice?\n\nSome Buddy"),
551                     (3, "read", "Flash SMS", "An SMS without digits. Strange, isn't it?"),
552                 ] )
553
554     def onReadyStatus( self, status ):
555         logger.debug( "SIM is ready: %s" % status )
556         if status:
557             # Force update
558             self.ready = False
559             self.prepare()
560
561     def onShow( self ):
562         self.prepare()
563
564     def updateList( self):
565         self.main.groups["contacts"].prepare()
566         self.pages = max( ( len( self.messagebook ) - 1 ) / 6 + 1, 1 )
567         if self.page >= self.pages:
568             self.page = self.pages - 1
569         if self.page < 0:
570             self.page = 0
571         self.current = self.messagebook[self.page*6:(self.page+1)*6]
572         text = u"".join( [u"□"]*self.page+[u"▣"]+[u"□"]*(self.pages-self.page-1) )
573         self.part_text_set( "pager", text )
574         for i in range( 0, len( self.current ) ):
575             main_text = self.main.groups["contacts"].tryNumberToName( self.current[i][2] )
576             self.part_text_set( "label_main_list_%i" % i, main_text )
577             sub_text = " ".join(self.current[i][3].splitlines())
578             self.part_text_set( "label_sub_list_%i" % i, u"(%s) %s" % ( self.current[i][1], sub_text ) )
579         for i in range( len( self.current ), 6):
580             self.part_text_set( "label_main_list_%i" % i, u"" )
581             self.part_text_set( "label_sub_list_%i" % i, u"" )
582         self.selected = None
583         for i in range( 6 ):
584             self.signal_emit( "deactivate_target_list_%i" % i, "" )
585
586     def selectPrev( self ):
587         if self.selected is None:
588             return
589         if (self.selected == 0) and (self.page == 0):
590             return
591         self.signal_emit( "deactivate_target_list_%i" % self.selected, "" )
592         if self.selected == 0:
593             self.page -= 1
594             self.updateList()
595             self.selected = 5
596         else:
597             self.selected -= 1
598         self.signal_emit( "activate_target_list_%i" % self.selected, "" )
599
600     def selectNext( self ):
601         if self.selected is None:
602             return
603         if (self.selected == len( self.current ) - 1) and (self.page == self.pages - 1):
604             return
605         self.signal_emit( "deactivate_target_list_%i" % self.selected, "" )
606         self.selected += 1
607         if self.selected == len( self.current ):
608             self.page += 1
609             self.updateList()
610             self.selected = 0
611         self.signal_emit( "activate_target_list_%i" % self.selected, "" )
612
613     @edje.decorators.signal_callback( "mouse,clicked,1", "target_list_*" )
614     def on_edje_signal_button_list_pressed( self, emission, source ):
615         id = int( source.split( "_" )[-1] )
616         if self.selected == id:
617             return
618         if self.selected is not None:
619             self.signal_emit( "deactivate_target_list_%i" % self.selected, "" )
620         self.signal_emit( "activate_target_list_%i" % id, "" )
621         self.selected = id
622
623     @edje.decorators.signal_callback( "mouse,clicked,1", "button_action_left" )
624     def on_edje_signal_button_action_left_pressed( self, emission, source ):
625         self.page -= 1
626         self.updateList()
627
628     @edje.decorators.signal_callback( "mouse,clicked,1", "button_action_right" )
629     def on_edje_signal_button_action_right_pressed( self, emission, source ):
630         self.page += 1
631         self.updateList()
632
633     @edje.decorators.signal_callback( "mouse,clicked,1", "button_action_open" )
634     def on_edje_signal_button_action_open_pressed( self, emission, source ):
635         if self.selected is not None:
636             self.displayMessage( self.current[self.selected] )
637
638     @edje.decorators.signal_callback( "mouse,clicked,1", "button_bottom_middle" )
639     def on_edje_signal_button_bottom_middle_pressed( self, emission, source ):
640         self.main.groups["menu"].activate( ( _("send"), _("delete"), _("forward"), _("reply") ), self.cbMenu )
641         self.main.groups["menu"].part_text_set( "target_label_cancel", _("cancel") )
642
643
644 #----------------------------------------------------------------------------#
645 class pyphone_configuration(edje_group):
646 #----------------------------------------------------------------------------#
647     def __init__(self, main):
648         edje_group.__init__(self, main, "configuration")
649         self.selected = None
650         self.profiles = []
651
652         self.part_text_set( "label_action_use", _("use") )
653
654     def onShow( self ):
655         self.prepare()
656         self.updateList()
657
658     def prepare( self ):
659         # Set the current profile label
660         if dbus_object.prefs_obj:
661             current = dbus_object.prefs_iface.GetProfile()
662             self.profiles = dbus_object.prefs_iface.GetProfiles()
663         else:
664             current = 'Test1'
665             self.profiles = ["Test1", "Test2"]
666         self.part_text_set( "current_label", _("Current : %s") % current )
667
668     def updateList( self):
669         self.main.groups["configuration"].prepare()
670         # We put all the profile names in the slots
671         for i in range(6):
672             if i < len(self.profiles):
673                 name = self.profiles[i]
674             else:
675                 name = u""
676             self.part_text_set( "label_main_list_%i" % i, name )
677             self.part_text_set( "label_sub_list_%i" % i, u"" )
678         self.selected = None
679         for i in range( 6 ):
680             self.signal_emit( "deactivate_target_list_%i" % i, "" )
681
682     @edje.decorators.signal_callback( "mouse,clicked,1", "target_list_*" )
683     def on_edje_signal_button_list_pressed( self, emission, source ):
684         id = int( source.split( "_" )[-1] )
685         if self.selected == id:
686             return
687         if self.selected is not None:
688             self.signal_emit( "deactivate_target_list_%i" % self.selected, "" )
689         self.signal_emit( "activate_target_list_%i" % id, "" )
690         self.selected = id
691
692     @edje.decorators.signal_callback( "mouse,clicked,1", "button_action_use" )
693     def on_edje_signal_button_action_use_pressed( self, emission, source ):
694         if self.selected is None:
695             return
696         if self.selected >= len(self.profiles):
697             return
698         profile = self.profiles[self.selected]
699         self.part_text_set( "current_label", _("Current : %s") % profile )
700         if dbus_object.prefs_obj:
701             dbus_object.prefs_iface.SetProfile(profile)
702
703 #----------------------------------------------------------------------------#
704 class pyphone_contacts(edje_group):
705 #----------------------------------------------------------------------------#
706     def __init__(self, main):
707         edje_group.__init__(self, main, "contacts")
708         self.ready = False
709         self.busy = False
710         self.phonebook = []
711         self.current = []
712         self.page = 0
713         self.selected = None
714
715         self.part_text_set( "label_action_dial", _("dial") )
716
717     def cbNameEdit( self, name, reference ):
718         for i in range( len( self.phonebook ) ):
719             if self.phonebook[i][0] == reference:
720                 self.phonebook[i] = ( reference, name, self.phonebook[i][2] )
721                 if dbus_object.gsm_device_obj:
722                     dbus_object.gsm_sim_iface.StoreEntry(
723                         "contacts",
724                         reference,
725                         name,
726                         self.phonebook[i][2]
727                     )
728                 break
729         self.phonebook.sort( key = lambda x: x[1].lower() )
730         self.updateList()
731
732     def cbNumberEdit( self, number, reference ):
733         for i in range( len( self.phonebook ) ):
734             if self.phonebook[i][0] == reference:
735                 self.phonebook[i] = ( reference, self.phonebook[i][1], number )
736                 if dbus_object.gsm_device_obj:
737                     dbus_object.gsm_sim_iface.StoreEntry(
738                         "contacts",
739                         reference,
740                         self.phonebook[i][1],
741                         number
742                     )
743                 break
744         self.updateList()
745
746     def cbNew1( self, name, none ):
747         self.main.groups["number_edit"].setup(
748             "contacts",
749             "", # number
750             name, # title = new name
751             name, # reference
752             self.cbNew2
753         )
754
755     def cbNew2( self, number, name ):
756         ids = [ x[0] for x in self.phonebook ]
757         ids.sort()
758         reference = None
759         for i in range(1, 250):
760             if not i in ids:
761                 reference = i
762                 break
763         if reference is None:
764             return # no space?
765         if dbus_object.gsm_device_obj:
766             dbus_object.gsm_sim_iface.StoreEntry(
767                 "contacts",
768                 reference,
769                 name,
770                 number
771             )
772         self.phonebook.append( ( reference, name, number ) )
773         self.phonebook.sort( key = lambda x: x[1].lower() )
774         self.updateList()
775
776     def cbDelete( self, result, reference ):
777         if result == "abort":
778             return
779         if dbus_object.gsm_device_obj:
780             dbus_object.gsm_sim_iface.DeleteEntry(
781                 "contacts",
782                 reference
783             )
784         for i in range( len( self.phonebook ) ):
785             if self.phonebook[i][0] == reference:
786                 del self.phonebook[i]
787                 break
788         self.updateList()
789
790     def cbMenu( self, result ):
791         if result == _("edit name"):
792             self.main.groups["text_edit"].setup(
793                 "contacts",
794                 self.current[self.selected][1], # name
795                 self.current[self.selected][2], # title = number
796                 self.current[self.selected][0], # reference
797                 self.cbNameEdit
798             )
799         elif result == _("edit number"):
800             self.main.groups["number_edit"].setup(
801                 "contacts",
802                 self.current[self.selected][2], # number
803                 self.current[self.selected][1], # title = name
804                 self.current[self.selected][0], # reference
805                 self.cbNumberEdit
806             )
807         elif result == _("new"):
808             self.main.groups["text_edit"].setup(
809                 "contacts",
810                 "", # name
811                 _("name?"), # title
812                 None, # reference
813                 self.cbNew1
814             )
815         elif result == _("delete"):
816             self.main.groups["alert"].activate(
817                 _("delete '%s'?") % self.current[self.selected][1],
818                 (_("abort"), _("delete")),
819                 self.current[self.selected][0], # reference
820                 self.cbDelete
821             )
822
823     def cbPhonebookReply( self, result ):
824         logger.info( "retrieved phonebook: %s" % result )
825         self.busy = False
826         self.phonebook = result
827         self.phonebook.sort( key = lambda x: x[1].lower() )
828         self.ready = True
829         self.updateList()
830         self.main.groups["main"].targets["contacts"] = True
831         self.main.groups["main"].update()
832         self.main.groups["sms"].updateList()
833
834     def cbPhonebookError( self, e ):
835         logger.error( "error while retrieving phonebook %s" % e )
836         self.busy = False
837
838     def prepare( self ):
839         if not self.ready and not self.busy:
840             if dbus_object.gsm_device_obj:
841                 logger.info( "retrieving phonebook..." )
842                 dbus_object.gsm_sim_iface.RetrievePhonebook(
843                     "contacts",
844                     reply_handler=self.cbPhonebookReply,
845                     error_handler=self.cbPhonebookError
846                 )
847                 self.busy = True
848             else:
849                 # Fake phonebook...
850                 self.cbPhonebookReply( [
851                     (1, u'Kirk', '+023224433'),
852                     (2, u'Spock', '+034433463'),
853                     (3, u'McCoy', '+013244344'),
854                     (4, u'Scott', '+013244344'),
855                     (5, u'Uhura', '+013244344'),
856                     (6, u'Sulu', '+013244344'),
857                     (7, u'Chekov', '+456663443'),
858                 ] )
859
860     def onReadyStatus( self, status ):
861         logger.debug( "SIM is ready: %s" % status )
862         if status:
863             # Force update
864             self.ready = False
865             self.prepare()
866
867     def onShow( self ):
868         self.prepare()
869         self.updateList()
870
871     def comparePhoneNumber(self, number1, number2):
872         '''
873         Compares two phone numbers. They are considered equal if:
874           a) One does not contain digits, and they are equal as strings
875         or
876           b) Both start with a "+", and all following digits are equal
877         or
878           c) At least one of them does not start with a "+", and the
879              last 7 digits are equal
880         '''
881         digits1 = filter (lambda c: c.isdigit() or c == '+', number1)
882         digits2 = filter (lambda c: c.isdigit() or c == '+', number2)
883
884         if digits1 == '' or digits2 == '':
885             return number1 == number2
886         if digits1[0] == digits2[0] == '+':
887             return digits1 == digits2
888         else:
889             return digits1[-7:] == digits2[-7:]
890
891     def tryNumberToName( self, number ):
892         for i in range( len( self.phonebook ) ):
893             if self.comparePhoneNumber(self.phonebook[i][2], number):
894                 return self.phonebook[i][1]
895         return number
896
897     def updateList( self ):
898         self.pages = max( ( len( self.phonebook ) - 1 ) / 6 + 1, 1 )
899         if self.page >= self.pages:
900             self.page = self.pages - 1
901         if self.page < 0:
902             self.page = 0
903         self.current = self.phonebook[self.page*6:(self.page+1)*6]
904         text = u"".join( [u"□"]*self.page+[u"▣"]+[u"□"]*(self.pages-self.page-1) )
905         self.part_text_set( "pager", text )
906         for i in range( 0, len( self.current ) ):
907             self.part_text_set( "label_main_list_%i" % i, self.current[i][1] )
908             self.part_text_set( "label_sub_list_%i" % i, self.current[i][2] )
909         for i in range( len( self.current ), 6):
910             self.part_text_set( "label_main_list_%i" % i, u"" )
911             self.part_text_set( "label_sub_list_%i" % i, u"" )
912         self.selected = None
913         for i in range( 6 ):
914             self.signal_emit( "deactivate_target_list_%i" % i, "" )
915
916     @edje.decorators.signal_callback( "mouse,clicked,1", "target_list_*" )
917     def on_edje_signal_button_list_pressed( self, emission, source ):
918         id = int( source.split( "_" )[-1] )
919         if self.selected == id:
920             return
921         if self.selected is not None:
922             self.signal_emit( "deactivate_target_list_%i" % self.selected, "" )
923         self.signal_emit( "activate_target_list_%i" % id, "" )
924         self.selected = id
925
926     @edje.decorators.signal_callback( "mouse,clicked,1", "button_action_left" )
927     def on_edje_signal_button_action_left_pressed( self, emission, source ):
928         self.page -= 1
929         self.updateList()
930
931     @edje.decorators.signal_callback( "mouse,clicked,1", "button_action_right" )
932     def on_edje_signal_button_action_right_pressed( self, emission, source ):
933         self.page += 1
934         self.updateList()
935
936     @edje.decorators.signal_callback( "mouse,clicked,1", "button_action_dial" )
937     def on_edje_signal_button_action_dial_pressed( self, emission, source ):
938         if self.selected is not None:
939             if dbus_object.gsm_device_obj:
940                 if self.current[self.selected][2][0] == "*":
941                     dbus_object.gsm_network_iface.SendUssdRequest( self.current[self.selected][2] )
942                 else:
943                     dbus_object.gsm_call_iface.Initiate( self.current[self.selected][2], "voice" )
944
945     @edje.decorators.signal_callback( "mouse,clicked,1", "button_bottom_middle" )
946     def on_edje_signal_button_bottom_middle_pressed( self, emission, source ):
947         self.main.groups["menu"].activate((_("edit name"), _("edit number"), _("new"), _("delete")), self.cbMenu)
948         self.main.groups["menu"].part_text_set( "target_label_cancel", _("cancel") )
949
950 #----------------------------------------------------------------------------#
951 class pyphone_wireless( edje_group ):
952 #----------------------------------------------------------------------------#
953     class NeighbourGraph( evas.ClippedSmartObject ):
954         MAXCELLS = 7
955         MAXRXLEV = 64
956         def __init__( self, *args, **kargs ):
957             evas.ClippedSmartObject.__init__( self, *args, **kargs )
958             self.img = self.Image()
959             self.img.alpha = True
960             self.img.colorspace = evas.EVAS_COLORSPACE_ARGB8888
961             self.img.pos = self.pos
962             self.img.show()
963             self.surface = None
964             self.ctx = None
965             self.cells = None
966             self.maxsignal = self.MAXRXLEV
967             self.current = False
968
969         def show( self ):
970             evas.ClippedSmartObject.show( self )
971             self.current = True
972             self.update()
973
974         def hide( self ):
975             evas.ClippedSmartObject.hide( self )
976             self.current = False
977
978         def resize( self, w, h ):
979             self.surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, w, h)
980             self.ctx = cairo.Context(self.surface)
981             self.ctx.scale( w, h )
982             #self.ctx.set_antialias( cairo.ANTIALIAS_NONE )
983             self.img.image_size = ( w, h )
984             self.update()
985             self.img.resize( *self.img.image_size )
986             self.img.fill_set(0, 0, *self.img.image_size )
987
988         def update( self, cells = None ):
989             if cells is not None:
990                 self.cells = cells
991             if self.ctx and self.current:
992                 self.ctx.set_operator( cairo.OPERATOR_CLEAR )
993                 self.ctx.paint()
994                 self.ctx.set_operator( cairo.OPERATOR_OVER )
995                 pixel = self.ctx.device_to_user_distance( 1, 1 )
996                 self.ctx.set_line_width( max( pixel ) )
997                 self.ctx.set_source_rgba(1, 1, 1, 0.5)
998                 self.ctx.rectangle(0.0, 0.0, 1.0, 1.0)
999                 self.ctx.fill()
1000                 rows = []
1001                 if self.cells is not None:
1002                     logger.debug( "updating %d cells", len(self.cells) )
1003                     for cell in self.cells:
1004                         signal = cell["rxlev"]
1005                         text = []
1006                         text.append( "%s" % cell["arfcn"] )
1007                         text.append( "%s/%s" % ( cell["lac"], cell["cid"] ) )
1008                         text.append( "%s" % cell["bsic"] )
1009                         text.append( "%s" % cell["rxlev"] )
1010                         rows.append( ( signal, text ) )
1011                 if rows:
1012                     count = 1 + len( rows )
1013                     self.maxsignal = float( max( [x[0] for x in rows] + [self.maxsignal] ) )
1014                     barwidth = 1.0/(1 + self.MAXCELLS)
1015                     self.ctx.set_font_size( barwidth*0.4 )
1016                     self.ctx.set_source_rgba(0, 0, 0, 1)
1017                     head = [ "ARFCN", "LAC/CID", "BSIC", "RXLEV" ]
1018                     x_bearing, y_bearing, width, height = self.ctx.text_extents( " ".join( head ) )[:4]
1019                     w = 0.5*self.ctx.text_extents( u"\u25CF" )[2]
1020                     x = pixel[0]*3
1021                     cols = []
1022                     for h in head:
1023                         width = 0.5*self.ctx.text_extents( h )[2]
1024                         self.ctx.move_to( x + w, 0.5*barwidth - height / 2 - y_bearing )
1025                         cols.append( x + w + width )
1026                         x += w + width + width + w
1027                         self.ctx.show_text( h )
1028                     cols.append( x + w)
1029                     index = 1
1030                     for signal, text in rows:
1031                         barlength = signal/self.maxsignal
1032                         self.ctx.set_source_rgba(0.0, 1.0, 0.0, 1.0)
1033                         self.ctx.rectangle( 0.0, index*barwidth+pixel[1]*2, barlength, barwidth-pixel[1]*4 )
1034                         self.ctx.fill()
1035                         self.ctx.set_source_rgba(0, 0, 0, 1)
1036                         x_bearing, y_bearing, width, height = self.ctx.text_extents( " ".join( text ) )[:4]
1037                         for i in range( len( text ) ):
1038                             w = 0.5*self.ctx.text_extents( text[i] )[2]
1039                             self.ctx.move_to( cols[i] - w, (index+0.5)*barwidth - height / 2 - y_bearing )
1040                             self.ctx.show_text( text[i] )
1041                         index += 1
1042                 self.img.image_data_set( self.surface.get_data() )
1043                 self.img.pixels_dirty = True
1044
1045     DELAY = 10.0
1046     def __init__(self, main):
1047         edje_group.__init__( self, main, "wireless" )
1048         self.neighbourgraph = self.NeighbourGraph( self.evas )
1049         self.page = "left"
1050         self.oldpage = None
1051         self.signal_emit( "activate_button_select_%s" % self.page, "" )
1052
1053         self.timer = None
1054
1055         self.code = None
1056         self.gsm_serving = None
1057         self.gsm_neighbour = None
1058         self.location = None
1059         self.update()
1060
1061     def update( self ):
1062         text = []
1063         if self.code:
1064             text.append( "MCC/MNC:<tab>%s/%s" % ( self.code[:3], self.code[3:] ) )
1065         else:
1066             text.append( "MCC/MNC:<tab>N/A" )
1067         if self.gsm_serving:
1068             text.append( "ARFCN:<tab>%s" % self.gsm_serving['arfcn'] )
1069             text.append( "LAC/CID:<tab>%s/%s" % ( self.gsm_serving['lac'], self.gsm_serving['cid'] ) )
1070             text.append( "RXLEV/C1/C2:<tab>%s/%s/%s" % (
1071                 self.gsm_serving['rxlev'], self.gsm_serving['c1'], self.gsm_serving['c2']
1072             ) )
1073             text.append( "RXLEV-F/-S:<tab>%s/%s" % ( self.gsm_serving['rxlev_f'], self.gsm_serving['rxlev_s'] ) )
1074             text.append( "TXLEV:<tab>%s" % self.gsm_serving['txlev'] )
1075             text.append( "TN:<tab>%s" % self.gsm_serving['tn'] )
1076             text.append( "TAV:<tab>%s" % self.gsm_serving['tav'] )
1077             text.append( "DSC:<tab>%s" % self.gsm_serving['dsc'] )
1078             text.append( "Cell Type:<tab>%s" % self.gsm_serving['ctype'] )
1079         else:
1080             text.append( "Serving Cell:<tab>N/A" )
1081         celldb = []
1082         if self.location:
1083             celldb.append( "Lat:<tab>%s" % self.location[1] )
1084             celldb.append( "Lon:<tab>%s" % self.location[2] )
1085             celldb.append( "Alt:<tab>%s" % self.location[3] )
1086             celldb.append( "Size:<tab>%s" % self.location[4] )
1087             celldb.append( "Count:<tab>%s" % self.location[5] )
1088         else:
1089             celldb.append( "Cell DB:<tab>N/A" )
1090         cells = []
1091         if self.gsm_serving:
1092             cells.append(self.gsm_serving)
1093         if self.gsm_neighbour:
1094             cells.extend(self.gsm_neighbour)
1095         if self.page != self.oldpage:
1096             self.oldpage = self.page
1097             if self.part_swallow_get( "swallow" ):
1098                 self.part_swallow_get( "swallow" ).hide()
1099                 self.part_unswallow( self.part_swallow_get( "swallow" ) )
1100             if self.page == "left":
1101                 pass
1102             elif self.page == "middle":
1103                 self.part_swallow( "swallow", self.neighbourgraph )
1104                 self.neighbourgraph.show()
1105             elif self.page == "right":
1106                 pass
1107         if self.page == "left":
1108             self.part_text_set( "status", u"<br>".join( text ) )
1109         elif self.page == "middle":
1110             self.part_text_set( "status", u"" )
1111             self.neighbourgraph.update(cells)
1112         elif self.page == "right":
1113             self.part_text_set( "status", u"<br>".join( celldb ) )
1114
1115     def cbServingCellReply( self, serving ):
1116         logger.debug( "gsm serving cell status updated: %s" % serving )
1117         self.gsm_serving = serving
1118         self.update()
1119
1120     def cbServingCellError( self, e ):
1121         log_dbus_error( e, "error while requesting serving cell info" )
1122         self.gsm_serving = None
1123         self.update()
1124
1125     def cbNeighbourCellReply( self, neighbour ):
1126         logger.debug( "gsm neighbour cell status updated: %s" % neighbour )
1127         self.gsm_neighbour = neighbour
1128         self.update()
1129
1130     def cbNeighbourCellError( self, e ):
1131         log_dbus_error( e, "error while requesting neighbour cell info" )
1132         self.gsm_neighbour = None
1133         self.update()
1134
1135     def onShow( self ):
1136         if self.timer:
1137             self.timer.delete()
1138         self.cbTimer()
1139         self.timer = ecore.timer_add( self.DELAY, self.cbTimer )
1140
1141     def onHide( self ):
1142         if self.timer:
1143             self.timer.delete()
1144
1145     def cbTimer( self ):
1146         dbus_object.gsm_monitor_iface.GetServingCellInformation(
1147             reply_handler=self.cbServingCellReply,
1148             error_handler=self.cbServingCellError,
1149         )
1150         dbus_object.gsm_monitor_iface.GetNeighbourCellInformation(
1151             reply_handler=self.cbNeighbourCellReply,
1152             error_handler=self.cbNeighbourCellError,
1153         )
1154         cells = []
1155         if self.gsm_serving:
1156             cells.append(self.gsm_serving)
1157         if self.gsm_neighbour:
1158             cells.extend(self.gsm_neighbour)
1159         if self.code and cells:
1160             ids = []
1161             for cell in cells:
1162                 ids.append((int(cell['lac'], 16), int(cell['cid'], 16)))
1163             print (self.code[:3], self.code[3:], ids)
1164             location = dbus_object.gsm_data_iface.GetCellLocation(self.code[:3], self.code[3:], ids)
1165             print location
1166             if location is None:
1167                 self.location = None
1168             if location[0] is False:
1169                 self.location = None
1170             else:
1171                 self.location = location
1172         else:
1173             self.location = None
1174         return True
1175
1176     def onNetworkStatus( self, status ):
1177         if 'code' in status:
1178             self.code = str(status['code'])
1179         else:
1180             self.code = None
1181
1182     @edje.decorators.signal_callback( "mouse,clicked,1", "button_select_*" )
1183     def on_edje_signal_button_list_pressed( self, emission, source ):
1184         self.page = source.split( "_" )[-1]
1185         self.signal_emit( "deactivate_button_select_left", "" )
1186         self.signal_emit( "deactivate_button_select_middle", "" )
1187         self.signal_emit( "deactivate_button_select_right", "" )
1188         self.signal_emit( "activate_button_select_%s" % self.page, "" )
1189         self.update()
1190
1191 #----------------------------------------------------------------------------#
1192 class pyphone_location( edje_group ):
1193 #----------------------------------------------------------------------------#
1194     class SignalGraph( evas.ClippedSmartObject ):
1195         def __init__( self, *args, **kargs ):
1196             evas.ClippedSmartObject.__init__( self, *args, **kargs )
1197             self.img = self.Image()
1198             self.img.alpha = True
1199             self.img.colorspace = evas.EVAS_COLORSPACE_ARGB8888
1200             self.img.pos = self.pos
1201             self.img.show()
1202             self.surface = None
1203             self.ctx = None
1204             self.generic = None
1205             self.ubxinfo = None
1206             self.maxsignal = 50.0
1207             self.current = False
1208
1209         def show( self ):
1210             super( pyphone_location.SignalGraph, self ).show()
1211             self.current = True
1212             self.update()
1213
1214         def hide( self ):
1215             super( pyphone_location.SignalGraph, self ).hide()
1216             self.current = False
1217
1218         def resize( self, w, h ):
1219             self.surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, w, h)
1220             self.ctx = cairo.Context(self.surface)
1221             self.ctx.scale( w, h )
1222             #self.ctx.set_antialias( cairo.ANTIALIAS_NONE )
1223             self.img.image_size = ( w, h )
1224             self.update()
1225             self.img.resize( *self.img.image_size )
1226             self.img.fill_set(0, 0, *self.img.image_size )
1227
1228         def update( self, generic = None, ubxinfo = None ):
1229             if generic is not None:
1230                 self.generic = {}
1231                 for sv, used, signal in generic:
1232                     self.generic[sv] = ( used, signal )
1233             if ubxinfo is not None:
1234                 self.ubxinfo = ubxinfo[1:]
1235             if self.ctx and self.current:
1236                 self.ctx.set_operator( cairo.OPERATOR_CLEAR )
1237                 self.ctx.paint()
1238                 self.ctx.set_operator( cairo.OPERATOR_OVER )
1239                 pixel = self.ctx.device_to_user_distance( 1, 1 )
1240                 self.ctx.set_line_width( max( pixel ) )
1241                 self.ctx.set_source_rgba(1, 1, 1, 0.5)
1242                 self.ctx.rectangle(0.0, 0.0, 1.0, 1.0)
1243                 self.ctx.fill()
1244                 rows = []
1245                 if self.ubxinfo:
1246                     for sv in self.ubxinfo:
1247                         chn = sv["chn"]
1248                         svid = sv["SVID"]
1249                         used = sv["Flags"] & 0x01
1250                         diff = sv["Flags"] & 0x02
1251                         almoreph = sv["Flags"] & 0x04
1252                         eph = sv["Flags"] & 0x08
1253                         bad = sv["Flags"] & 0x10
1254                         qi = ("%i: " % sv["QI"]) + {
1255                             0: "idle",
1256                             1: "searching",
1257                             2: "signal acquired",
1258                             3: "signal unusable",
1259                             4: "code lock",
1260                             5: "code&carrier lock",
1261                             6: "code&carrier lock",
1262                             7: "receiving data"
1263                         }[sv["QI"]]
1264                         cno = sv["CNO"]
1265                         elev = sv["Elev"]
1266                         azim = sv["Azim"]
1267                         text = []
1268                         text.append( "%i" % chn )
1269                         text.append( "%i" % svid )
1270                         text.append( "%i" % cno )
1271                         if almoreph: text.append( u"\u25CF" )
1272                         else: text.append( "" )
1273                         if eph: text.append( u"\u25CF" )
1274                         else: text.append( "" )
1275                         if diff: text.append( u"\u25CF" )
1276                         else: text.append( "" )
1277                         rows.append( ( cno, used, bad, text, qi ) )
1278                 elif self.generic:
1279                     for sv, (used, signal) in self.generic.items():
1280                         text = [ "", "%i" % sv, "%i" % signal, "", "", "" ]
1281                         rows.append( ( signal, used, 0, text, "" ) )
1282                 if rows:
1283                     count = 1 + len( rows )
1284                     self.maxsignal = float( max( [x[0] for x in rows] + [self.maxsignal] ) )
1285                     barwidth = 1.0/count
1286                     self.ctx.set_font_size( barwidth*0.6 )
1287                     self.ctx.set_source_rgba(0, 0, 0, 1)
1288                     head = [ "CHN", " SV ", "SN", "ALM", "EPH", "DIFF" ]
1289                     x_bearing, y_bearing, width, height = self.ctx.text_extents( " ".join( head ) )[:4]
1290                     w = 0.5*self.ctx.text_extents( u"\u25CF" )[2]
1291                     x = pixel[0]*3
1292                     cols = []
1293                     for h in head:
1294                         width = 0.5*self.ctx.text_extents( h )[2]
1295                         self.ctx.move_to( x + w, 0.5*barwidth - height / 2 - y_bearing )
1296                         cols.append( x + w + width )
1297                         x += w + width + width + w
1298                         self.ctx.show_text( h )
1299                     cols.append( x + w)
1300                     self.ctx.move_to( x + w, 0.5*barwidth - height / 2 - y_bearing )
1301                     self.ctx.show_text( "Quality" )
1302                     index = 1
1303                     for signal, used, bad, text, qi in rows:
1304                         barlength = signal/self.maxsignal
1305                         if used:
1306                             self.ctx.set_source_rgba(0.0, 1.0, 0.0, 1.0)
1307                         else:
1308                             self.ctx.set_source_rgba(0.75, 0.75, 0.75, 1.0)
1309                         self.ctx.rectangle( 0.0, index*barwidth+pixel[1]*2, barlength, barwidth-pixel[1]*4 )
1310                         self.ctx.fill()
1311                         self.ctx.set_source_rgba(0, 0, 0, 1)
1312                         x_bearing, y_bearing, width, height = self.ctx.text_extents( " ".join( text ) )[:4]
1313                         for i in range( len( text ) ):
1314                             w = 0.5*self.ctx.text_extents( text[i] )[2]
1315                             self.ctx.move_to( cols[i] - w, (index+0.5)*barwidth - height / 2 - y_bearing )
1316                             self.ctx.show_text( text[i] )
1317                         self.ctx.move_to( cols[len( text )], (index+0.5)*barwidth - height / 2 - y_bearing )
1318                         self.ctx.show_text( qi )
1319                         if bad:
1320                             self.ctx.move_to( pixel[0]*2, (index+0.5)*barwidth )
1321                             self.ctx.rel_line_to( 1-(pixel[0]*4), 0 )
1322                             self.ctx.close_path()
1323                             self.ctx.stroke()
1324                         index += 1
1325                 self.img.image_data_set( self.surface.get_data() )
1326                 self.img.pixels_dirty = True
1327
1328     class PositionGraph( evas.ClippedSmartObject ):
1329         def __init__( self, *args, **kargs ):
1330             evas.ClippedSmartObject.__init__( self, *args, **kargs )
1331             self.img = self.Image()
1332             self.img.alpha = True
1333             self.img.colorspace = evas.EVAS_COLORSPACE_ARGB8888
1334             self.img.pos = self.pos
1335             self.img.show()
1336             self.surface = None
1337             self.ctx = None
1338             self.values = []
1339             self.current = False
1340
1341         def show( self ):
1342             super( pyphone_location.PositionGraph, self ).show()
1343             self.current = True
1344             self.update()
1345
1346         def hide( self ):
1347             super( pyphone_location.PositionGraph, self ).hide()
1348             self.current = False
1349
1350         def proj( self, azim, elev ):
1351             x = math.sin( math.radians( azim ) )*( ( 90.0 - elev )/90.0 )
1352             y = math.cos( math.radians( azim ) )*( ( 90.0 - elev )/90.0 )
1353             return ( x, y )
1354
1355         def resize( self, w, h ):
1356             self.surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, w, h)
1357             self.ctx = cairo.Context(self.surface)
1358             self.ctx.scale( w, h )
1359             #self.ctx.set_antialias( cairo.ANTIALIAS_NONE )
1360             self.img.image_size = ( w, h )
1361             self.update()
1362             self.img.resize( *self.img.image_size )
1363             self.img.fill_set(0, 0, *self.img.image_size )
1364
1365         def update( self, values = None ):
1366             if values is not None:
1367                 self.values = values
1368             if self.ctx and self.current:
1369                 self.ctx.set_operator( cairo.OPERATOR_CLEAR )
1370                 self.ctx.paint()
1371                 self.ctx.set_operator( cairo.OPERATOR_OVER )
1372                 self.ctx.set_line_width( max( self.ctx.device_to_user_distance( 2, 2 ) ) )
1373                 #self.ctx.move_to(0.20, 0.10)
1374                 #self.ctx.line_to(0.40, 0.30)
1375                 #self.ctx.rel_line_to(-0.20, 0.0)
1376                 #self.ctx.close_path()
1377                 #self.ctx.stroke()
1378                 # background
1379                 self.ctx.set_source_rgba(1, 1, 1, 0.5)
1380                 self.ctx.arc( 0.50, 0.50, 0.45, 0.0, math.pi*2 )
1381                 self.ctx.fill()
1382                 self.ctx.set_source_rgba(0, 0, 0, 1)
1383                 self.ctx.arc( 0.50, 0.50, 0.45, 0.0, math.pi*2 )
1384                 self.ctx.new_sub_path()
1385                 self.ctx.arc( 0.50, 0.50, 0.30, 0.0, math.pi*2 )
1386                 self.ctx.new_sub_path()
1387                 self.ctx.arc( 0.50, 0.50, 0.15, 0.0, math.pi*2 )
1388                 self.ctx.stroke()
1389                 # locations
1390                 for sv, used, azim, elev in self.values:
1391                     sv = str( sv )
1392                     x, y = self.proj( azim, elev )
1393                     if used:
1394                         self.ctx.set_source_rgba(0.0, 1.0, 0.0, 1.0)
1395                     else:
1396                         self.ctx.set_source_rgba(0.75, 0.75, 0.75, 1.0)
1397                     self.ctx.arc(
1398                         0.50+x*0.45,
1399                         0.50+y*0.45,
1400                         max( self.ctx.device_to_user_distance( 15, 15 ) ),
1401                         0.0, math.pi*2
1402                     )
1403                     self.ctx.fill()
1404                     self.ctx.set_source_rgba(0, 0, 0, 1)
1405                     self.ctx.set_font_size( max( self.ctx.device_to_user_distance( 20, 20 ) ))
1406                     x_bearing, y_bearing, width, height = self.ctx.text_extents(sv)[:4]
1407                     self.ctx.move_to(
1408                         0.50+x*0.45 - width / 2 - x_bearing,
1409                         0.50+y*0.45 - height / 2 - y_bearing
1410                     )
1411                     self.ctx.show_text( sv )
1412                 #self.surface.write_to_png( "/tmp/zhone-location.png" )
1413                 #self.img.file_set( "/tmp/zhone-location.png" )
1414                 self.img.image_data_set( self.surface.get_data() )
1415                 self.img.pixels_dirty = True
1416
1417     def __init__(self, main):
1418         edje_group.__init__( self, main, "location" )
1419         self.signalgraph = self.SignalGraph( self.evas )
1420         self.positiongraph = self.PositionGraph( self.evas )
1421
1422         self.page = "left"
1423         self.oldpage = None
1424         self.signal_emit( "activate_button_select_%s" % self.page, "" )
1425
1426         self.time = None
1427         self.fix = None
1428         self.accuracy = None
1429         self.course = None
1430         self.position = None
1431         self.ttff = None
1432         self.update()
1433
1434         #self.signalgraph.update( values = [(1, True, 20.0), (2, False, 34.0), (5, True, 25.0), (10, False, 0.0)] )
1435         #self.positiongraph.update( values = [(1,  True, 20.0, 90.0), (2, False, 34.3, 45.0), (5, True, 225.9, 0.0), (10, False, 0.5, 30.0)] )
1436
1437     def update( self ):
1438         text = []
1439         if self.time:
1440             text.append( _("GPS Time:<tab>%s") % time.strftime("%H:%M:%S", time.gmtime(self.time)) )
1441         else:
1442             text.append( _("GPS Time:<tab>N/A") )
1443         if self.fix and self.ttff:
1444             text.append( _("Fix:<tab>%s (%.1fs)") % ( { 1: _("none"), 2: "2D", 3: "3D" }[self.fix], self.ttff ) )
1445         elif self.fix:
1446             text.append( _("Fix:<tab>%s") % { 1: _("none"), 2: "2D", 3: "3D" }[self.fix] )
1447         else:
1448             text.append( _("Fix:<tab>N/A") )
1449         if self.position:
1450             text.append( _("Lat:<tab>%s<br>Lon:<tab>%s<br>Alt:<tab>%s") % self.position )
1451         else:
1452             text.append( _("Lat:<tab>N/A<br>Lon:<tab>N/A<br>Alt:<tab>N/A") )
1453         if self.course:
1454             text.append( _("Course:<tab>%s<br>Speed:<tab>%s<br>Climb:<tab>%s") % self.course )
1455         else:
1456             text.append( _("Course:<tab>N/A<br>Speed:<tab>N/A<br>Climb:<tab>N/A") )
1457         if self.accuracy:
1458             text.append( _("P/H/V-DOP:<tab>%s/%s/%s") % self.accuracy )
1459         else:
1460             text.append( _("P/H/V-DOP:<tab>N/A") )
1461         if self.page != self.oldpage:
1462             self.oldpage = self.page
1463             if self.part_swallow_get( "swallow" ):
1464                 self.part_swallow_get( "swallow" ).hide()
1465                 self.part_unswallow( self.part_swallow_get( "swallow" ) )
1466             if self.page == "left":
1467                 pass
1468             elif self.page == "middle":
1469                 self.part_swallow( "swallow", self.signalgraph )
1470                 self.signalgraph.show()
1471             elif self.page == "right":
1472                 self.part_swallow( "swallow", self.positiongraph )
1473                 self.positiongraph.show()
1474         if self.page == "left":
1475             self.part_text_set( "status", u"<br>".join( text ) )
1476         elif self.page == "middle":
1477             self.part_text_set( "status", u"" )
1478         elif self.page == "right":
1479             self.part_text_set( "status", u"" )
1480
1481     def onFixStatusChanged( self, fixstatus ):
1482         logger.debug( "gps fix status changed: %s" % fixstatus )
1483         self.fix = fixstatus
1484         self.update()
1485
1486     def onAccuracyChanged( self, fields, pdop, hdop, vdop ):
1487         logger.debug( "gps accuracy changed: %s/%s/%s" % ( pdop, hdop, vdop ) )
1488         self.accuracy = ( pdop, hdop, vdop )
1489         self.update()
1490
1491     def onCourseChanged( self, fields, timestamp, speed, heading, climb ):
1492         logger.debug( "gps course changed: %s/%s/%s" % ( speed, heading, climb ) )
1493         self.time = timestamp
1494         self.course = ( heading, speed, climb )
1495         self.update()
1496
1497     def onPositionChanged( self, fields, timestamp, lat, lon, alt ):
1498         logger.debug( "gps position changed: %s/%s/%s" % ( lat, lon, alt ) )
1499         self.time = timestamp
1500         self.position = ( lat, lon, alt )
1501         self.update()
1502
1503     def onSatellitesChanged( self, satellites ):
1504         logger.debug( "gps satellites changed" )
1505         satellites.sort()
1506         signallist = [ (int(sat[0]), sat[1], float(sat[4])) for sat in satellites ]
1507         self.signalgraph.update( generic = signallist )
1508         positionlist = [ (int(sat[0]), sat[1], int(sat[3]), int(sat[2])) for sat in satellites ]
1509         self.positiongraph.update( positionlist )
1510
1511     def onTimeChanged( self, timestamp ):
1512         logger.debug( "gps time changed: %s (%s)" % (time.strftime("%H:%M:%S", time.gmtime(timestamp)), timestamp) )
1513         self.time = timestamp
1514         self.update()
1515
1516     def onUBXDebugPacket( self, clid, length, data ):
1517         logger.debug( "gps got ubxdebug packet" )
1518         if clid == "NAV-STATUS" and data and 'TTFF' in data[0]:
1519             if data[0]['TTFF']:
1520                 self.ttff = data[0]['TTFF']/1000.0
1521             else:
1522                 self.ttff = None
1523         elif clid == "NAV-SVINFO":
1524             self.signalgraph.update( ubxinfo = data )
1525
1526     def onShow( self ):
1527         self.main.agents["usage"].request( "GPS" )
1528         self.update()
1529
1530     def onHide( self ):
1531         self.main.agents["usage"].release( "GPS" )
1532
1533     @edje.decorators.signal_callback( "mouse,clicked,1", "button_select_*" )
1534     def on_edje_signal_button_list_pressed( self, emission, source ):
1535         self.page = source.split( "_" )[-1]
1536         self.signal_emit( "deactivate_button_select_left", "" )
1537         self.signal_emit( "deactivate_button_select_middle", "" )
1538         self.signal_emit( "deactivate_button_select_right", "" )
1539         self.signal_emit( "activate_button_select_%s" % self.page, "" )
1540         self.update()
1541
1542 #----------------------------------------------------------------------------#
1543 class pyphone_message(edje_group):
1544 #----------------------------------------------------------------------------#
1545     def __init__(self, main):
1546         edje_group.__init__(self, main, "message")
1547         self.button_labels2 = [
1548             [
1549                 [u".,?!", u"abc", "def", ""],
1550                 [u"ghi", u"jkl", "mno", ""],
1551                 [u"pqrs", u"tuv", "wxyz", ""],
1552                 [u"", u"", u"⇦⇧⇨", ""],
1553             ],
1554             [
1555                 ["", "", "", ""],
1556                 ["", "", "", ""],
1557                 ["", "", "", ""],
1558                 ["", "", "", ""],
1559             ]
1560         ]
1561         self.button_labels = [
1562             [
1563                 ["1", "2", "3", u"↤"],
1564                 ["4", "5", "6", u"↲"],
1565                 ["7", "8", "9", "Abc"],
1566                 ["+", "0", u"⇩", "+"],
1567             ],
1568             [
1569                 ["1", "?", "", ""],
1570                 [".", ",", "", ""],
1571                 ["!", "", "", ""],
1572                 ["", "", "", ""],
1573             ],
1574             [
1575                 ["", "2", "c", ""],
1576                 ["", "a", "b", ""],
1577                 ["", "", "", ""],
1578                 ["", "", "", ""],
1579             ],
1580             [
1581                 ["", "", "3", "f"],
1582                 ["", "", "d", "e"],
1583                 ["", "", "", ""],
1584                 ["", "", "", ""],
1585             ],
1586             [
1587                 ["", "", "", u"↤"],
1588                 ["", "", "", ""],
1589                 ["", "", "", ""],
1590                 ["", "", "", ""],
1591             ],
1592             [
1593                 ["", "", "", ""],
1594                 ["4", "i", "", ""],
1595                 ["g", "h", "", ""],
1596                 ["", "", "", ""],
1597             ],
1598             [
1599                 ["", "", "", ""],
1600                 ["", "5", "l", ""],
1601                 ["", "j", "k", ""],
1602                 ["", "", "", ""],
1603             ],
1604             [
1605                 ["", "", "", ""],
1606                 ["", "", "6", "o"],
1607                 ["", "", "m", "n"],
1608                 ["", "", "", ""],
1609             ],
1610             [
1611                 ["", "", "", ""],
1612                 ["", "", "", ""],
1613                 ["", "", "", ""],
1614                 ["", "", "", ""],
1615             ],
1616             [
1617                 ["", "", "", ""],
1618                 ["", "s", "", ""],
1619                 ["7", "r", "", ""],
1620                 ["p", "q", "", ""],
1621             ],
1622             [
1623                 ["", "", "", ""],
1624                 ["", "", "", ""],
1625                 ["", "8", "v", ""],
1626                 ["", "t", "u", ""],
1627             ],
1628             [
1629                 ["", "", "", ""],
1630                 ["", "", "", "z"],
1631                 ["", "", "9", "y"],
1632                 ["", "", "w", "x"],
1633             ],
1634             [
1635                 ["", "", "", ""],
1636                 ["", "", "", ""],
1637                 ["", "", "", ""],
1638                 ["", "", "", ""],
1639             ],
1640             [
1641                 ["", "", "", ""],
1642                 ["", "", "", ""],
1643                 ["", "", "", ""],
1644                 ["", "", "", ""],
1645             ],
1646             [
1647                 ["", "", "", ""],
1648                 ["", "", "", ""],
1649                 ["", "", "", ""],
1650                 ["", " ", "", ""],
1651             ],
1652             [
1653                 ["", "", "", ""],
1654                 ["", "", "", ""],
1655                 ["", "", u"⇧", ""],
1656                 ["", u"⇦", u"⇩", u"⇨"],
1657             ],
1658             [
1659                 ["", "", "", ""],
1660                 ["", "", "", ""],
1661                 ["", "", "", ""],
1662                 ["", "", "", ""],
1663             ]
1664         ]
1665
1666     @edje.decorators.signal_callback("kb_button_mouse_up", "*")
1667     def on_edje_signal_dialer_button_mouse_up(self, emission, source):
1668         #now = time.time()
1669         x = int(source[-3:-2])
1670         y = int(source[-1:])
1671         key = self.button_labels[self.active][y][x]
1672         self.text.append(key)
1673         # The trailing whitespace is a workaround for the one char invisible
1674         # bug due to some problems with scaling of text parts.
1675         self.part_text_set("label", "".join(self.text)+" ")
1676         self.set_button_text(0)
1677         #logger.debug( "mouse up: %s" % time.time()-now )
1678
1679     @edje.decorators.signal_callback("kb_button_mouse_down", "*")
1680     def on_edje_signal_dialer_button_mouse_down(self, emission, source):
1681         #now = time.time()
1682         x = int(source[-3:-2])
1683         y = int(source[-1:])
1684         num = 4*y+x+1
1685         if self.active == 0:
1686             self.set_button_text(num)
1687         #logger.debug( "mouse down: %s" % time.time()-now )
1688
1689     @edje.decorators.signal_callback("kb_mutton_mouse_in", "*")
1690     def on_edje_signal_dialer_button_mouse_in(self, emission, source):
1691         #now = time.time()
1692         x = int(source[-3:-2])
1693         y = int(source[-1:])
1694         self.part_text_set("label_preview", self.button_labels[self.active][y][x])
1695         #logger.debug( "mouse in: %s" % time.time()-now )
1696
1697     def set_button_text(self, num):
1698         for i in xrange(4):
1699             for j in xrange(4):
1700                 text = self.button_labels[num][j][i]
1701                 self.part_text_set("label_%d_%d" % (i,j) , text)
1702         self.active = num
1703
1704         if num != 0:
1705             num = 1
1706
1707         for i in xrange(4):
1708             for j in xrange(4):
1709                 text = self.button_labels2[num][j][i]
1710                 self.part_text_set("label2_%d_%d" % (i,j) , text)
1711
1712     @edje.decorators.signal_callback( "mouse,clicked,1", "button_*" )
1713     def on_edje_signal_button_pressed( self, emission, source ):
1714         key = source.split( "_", 1 )[1]
1715         if key == "done":
1716             if self.text == "":
1717                 self.main.groups["text_edit"].setup(
1718                     self.parent_name,
1719                     self.text,
1720                     self.title,
1721                     self.cb_data,
1722                     self.cb
1723                     )
1724             else:
1725                 self.main.transition_to(self.parent_name)
1726                 self.cb( self.text, self.cb_data )
1727         elif key == "cancel":
1728             self.main.transition_to(self.parent_name)
1729
1730     def setup( self, parent_name, text, title, cb_data, cb ):
1731         self.parent_name = parent_name
1732         self.text = []
1733         self.title = title
1734         self.cb_data = cb_data
1735         self.cb = cb
1736         #self.part_text_set( "label_description", u" %s " % self.title )
1737         self.part_text_set( "label", u"" )
1738         self.set_button_text(0)
1739         self.active = 0
1740         self.main.transition_to("message")
1741
1742 #----------------------------------------------------------------------------#
1743 class pyphone_list_choose(edje_group):
1744 #----------------------------------------------------------------------------#
1745     def __init__( self, main ):
1746         edje_group.__init__( self, main, "list_choose" )
1747         self.text = ""
1748         self.list_data = None
1749         self.cb_data = None
1750         self.cb = None
1751
1752     def updateList( self):
1753         self.pages = max( ( len( self.list_data ) - 1 ) / 6 + 1, 1 )
1754         if self.page >= self.pages:
1755             self.page = self.pages - 1
1756         if self.page < 0:
1757             self.page = 0
1758         self.current = self.list_data[self.page*6:(self.page+1)*6]
1759         self.part_text_set( "pager", u"".join( [u"□"]*self.page+[u"▣"]+[u"□"]*(self.pages-self.page-1) ) )
1760         for i in range( 0, len( self.current ) ):
1761             self.part_text_set( "label_main_list_%i" % i, self.current[i][0] )
1762             self.part_text_set( "label_sub_list_%i" % i, self.current[i][1] )
1763         for i in range( len( self.current ), 6):
1764             self.part_text_set( "label_main_list_%i" % i, u"" )
1765             self.part_text_set( "label_sub_list_%i" % i, u"" )
1766
1767     @edje.decorators.signal_callback( "mouse,clicked,1", "target_list_*" )
1768     def on_edje_signal_button_list_pressed( self, emission, source ):
1769         id = int( source.split( "_" )[-1] )
1770         self.main.transition_to(self.parent_name)
1771         self.cb( self.current[id], self.cb_data )
1772
1773     @edje.decorators.signal_callback( "mouse,clicked,1", "button_action_left" )
1774     def on_edje_signal_button_action_left_pressed( self, emission, source ):
1775         self.page -= 1
1776         self.updateList()
1777
1778     @edje.decorators.signal_callback( "mouse,clicked,1", "button_action_right" )
1779     def on_edje_signal_button_action_right_pressed( self, emission, source ):
1780         self.page += 1
1781         self.updateList()
1782
1783     def setup( self, parent_name, list_data, cb_data, cb ):
1784         self.parent_name = parent_name
1785         self.list_data = list_data
1786         self.cb_data = cb_data
1787         self.cb = cb
1788
1789         self.current = []
1790         self.page = 0
1791         self.main.transition_to("list_choose")
1792
1793         self.updateList()
1794
1795 #----------------------------------------------------------------------------#
1796 class pyphone_number_edit( edje_group ):
1797 #----------------------------------------------------------------------------#
1798     TIMEOUT = 2.0
1799     def __init__( self, main ):
1800         edje_group.__init__( self, main, "number_edit" )
1801         self.text = ""
1802         self.cb_data = None
1803         self.cb = None
1804         self.last = 0.0
1805
1806     @edje.decorators.signal_callback( "mouse,clicked,1", "button_*" )
1807     def on_edje_signal_button_pressed( self, emission, source ):
1808         key = source.split( "_", 1 )[1]
1809         if key in ( "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "#" ):
1810             self.text += key
1811             # The trailing whitespace is a workaround for the one char invisible
1812             # bug due to some problems with scaling of text parts.
1813             self.part_text_set( "label", u" %s " % self.text )
1814         elif key in "star":
1815             if self.text and ( time.time()-self.last < self.TIMEOUT ):
1816                 if self.text[-1] == "*":
1817                     self.text = self.text[:-1]+"+"
1818                 elif self.text[-1] == "+":
1819                     self.text = self.text[:-1]+"*"
1820                 else:
1821                     self.text += "*"
1822             else:
1823                 self.text += "*"
1824             self.part_text_set( "label", u" %s " % self.text )
1825         elif key in "hash":
1826             self.text += "#"
1827             self.part_text_set( "label", u" %s " % self.text )
1828         elif key in "delete":
1829             self.text = self.text[:-1]
1830             self.part_text_set( "label", u" %s " % self.text )
1831         elif key in "done":
1832             self.main.transition_to(self.parent_name)
1833             self.cb( self.text, self.cb_data )
1834         self.last = time.time()
1835
1836     def setup( self, parent_name, text, title, cb_data, cb ):
1837         self.parent_name = parent_name
1838         self.text = text
1839         self.title = title
1840         self.cb_data = cb_data
1841         self.cb = cb
1842         self.part_text_set( "label_description", u" %s " % self.title )
1843         self.part_text_set( "label", u" %s " % self.text )
1844         self.main.transition_to("number_edit")
1845
1846 #----------------------------------------------------------------------------#
1847 class pyphone_pin_edit( edje_group ):
1848 #----------------------------------------------------------------------------#
1849     DELAY = 1.0
1850     def __init__( self, main ):
1851         edje_group.__init__( self, main, "number_edit" )
1852         self.text = ""
1853         self.cb_data = None
1854         self.cb = None
1855         self.timer = None
1856         self.last = 0.0
1857         self.part_text_set( "label_main_star", u"" )
1858         self.part_text_set( "label_sub_star", u"" )
1859         self.part_text_set( "label_main_hash", u"" )
1860         self.part_text_set( "label_sub_hash", u"" )
1861
1862     @edje.decorators.signal_callback( "mouse,clicked,1", "button_*" )
1863     def on_edje_signal_button_pressed( self, emission, source ):
1864         key = source.split( "_", 1 )[1]
1865         if key in ( "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" ):
1866             self.text += key
1867             # The trailing whitespace is a workaround for the one char invisible
1868             # bug due to some problems with scaling of text parts.
1869             output = u"●"*( len( self.text ) - 1) + self.text[-1]
1870             self.part_text_set( "label", u" %s " % output )
1871             if self.timer:
1872                 self.timer.delete()
1873             self.timer = ecore.timer_add( self.DELAY, self.timerCb )
1874         elif key in "delete":
1875             self.text = self.text[:-1]
1876             output = u"●"*len( self.text )
1877             self.part_text_set( "label", u" %s " % output )
1878         elif key in "done":
1879             self.main.transition_to(self.parent_name)
1880             self.cb( self.text, self.cb_data )
1881         self.last = time.time()
1882
1883     def timerCb( self ):
1884         output = u"●"*len( self.text )
1885         self.part_text_set( "label", u" %s " % output )
1886         self.timer = None
1887         return False
1888
1889     def setup( self, parent_name, text, title, cb_data, cb ):
1890         self.parent_name = parent_name
1891         self.text = text
1892         self.title = title
1893         self.cb_data = cb_data
1894         self.cb = cb
1895         self.part_text_set( "label_description", u" %s " % self.title )
1896         self.part_text_set( "label", u" %s " % self.text )
1897         self.main.transition_to("pin_edit")
1898
1899 #----------------------------------------------------------------------------#
1900 class pyphone_text_show( edje_group ):
1901 #----------------------------------------------------------------------------#
1902     def __init__( self, main ):
1903         edje_group.__init__( self, main, "text_show" )
1904
1905     def setup( self, parent_name, text ):
1906         self.parent_name = parent_name
1907         self.part_text_set( "text", text )
1908         self.main.transition_to("text_show")
1909
1910     @edje.decorators.signal_callback( "mouse,clicked,1", "button_bottom_middle" )
1911     def on_edje_signal_button_bottom_middle_pressed( self, emission, source ):
1912         self.main.groups["menu"].activate( ( _("send"), _("delete"), _("forward"), _("reply") ),
1913                                            self.main.groups["sms"].cbMenu )
1914         self.main.groups["menu"].part_text_set( "target_label_cancel", _("cancel") )
1915
1916     @edje.decorators.signal_callback( "mouse,clicked,1", "button_action_left" )
1917     def on_edje_signal_button_action_left_pressed( self, emission, source ):
1918         self.main.groups["sms"].selectPrev()
1919
1920     @edje.decorators.signal_callback( "mouse,clicked,1", "button_action_right" )
1921     def on_edje_signal_button_action_right_pressed( self, emission, source ):
1922         self.main.groups["sms"].selectNext()
1923
1924 #----------------------------------------------------------------------------#
1925 class pyphone_text_edit( edje_group ):
1926 #----------------------------------------------------------------------------#
1927     def __init__( self, main ):
1928         edje_group.__init__( self, main, "text_edit" )
1929         self.text = ""
1930         self.cb_data = None
1931         self.cb = None
1932         self.shift_down = False
1933
1934     def onShow( self ):
1935         self.focus = True
1936         if illume:
1937             illume.kbd_show()
1938
1939     def onHide( self ):
1940         self.focus = False
1941         if illume:
1942             illume.kbd_hide()
1943
1944     @evas.decorators.key_down_callback
1945     def on_key_down( self, event ):
1946         key = event.string
1947         if key == "\x08":
1948             self.text = self.text[:-1]
1949         # Check if the key is aumlaut or oumlaut
1950         elif event.key == "adiaeresis":
1951             self.text += u"ä"
1952         elif event.key == "odiaeresis":
1953             self.text += u"ö"
1954         elif event.key == "Adiaeresis":
1955             self.text += u"Ä"
1956         elif event.key == "Odiaeresis":
1957             self.text += u"Ö"
1958         elif key is not None:
1959             self.text += key
1960         self.part_text_set( "label", "%d: %s" % ( len( self.text ), self.text ))
1961
1962     @edje.decorators.signal_callback( "mouse,clicked,1", "button_*" )
1963     def on_edje_signal_button_pressed( self, emission, source ):
1964         key = source.split( "_", 1 )[1]
1965         if key == "done":
1966             if self.text == "":
1967                 self.main.groups["message"].setup(
1968                     self.parent_name,
1969                     self.text,
1970                     self.title,
1971                     self.cb_data,
1972                     self.cb
1973                     )
1974             else:
1975                 self.main.transition_to(self.parent_name)
1976                 self.cb( self.text, self.cb_data )
1977         elif key == "cancel":
1978             self.main.transition_to(self.parent_name)
1979
1980     def setup( self, parent_name, text, title, cb_data, cb ):
1981         self.parent_name = parent_name
1982         self.text = text
1983         self.title = title
1984         self.cb_data = cb_data
1985         self.cb = cb
1986         self.part_text_set( "label_description", u" %s " % self.title )
1987         self.part_text_set( "label", self.text )
1988         self.main.transition_to("text_edit")
1989
1990 #----------------------------------------------------------------------------#
1991 class pyphone_menu( edje_group ):
1992 #----------------------------------------------------------------------------#
1993     def __init__( self, main ):
1994         edje_group.__init__( self, main, "menu" )
1995         self.buttons = None
1996         self.cb = None
1997         self.deactivate()
1998
1999     @edje.decorators.signal_callback( "mouse,clicked,1", "target_*" )
2000     def on_edje_signal_button_bottom_middle_pressed( self, emission, source ):
2001         if source == "target_cancel":
2002             self.deactivate()
2003             self.cb( "cancel" )
2004         else:
2005             id = int( source.split( "_", 1 )[1] )
2006             self.deactivate()
2007             self.cb( self.part_text_get( "target_label_%i" % id ) )
2008
2009     def activate( self, buttons, cb ):
2010         self.buttons = buttons
2011         self.cb = cb
2012         count = len( buttons )
2013         assert count == 4
2014         self.part_text_set( "target_label_0", buttons[0] )
2015         self.part_text_set( "target_label_1", buttons[1] )
2016         self.part_text_set( "target_label_2", buttons[2] )
2017         self.part_text_set( "target_label_3", buttons[3] )
2018         self.signal_emit( "visible", "" )
2019
2020     def deactivate( self ):
2021         self.signal_emit( "invisible", "" )
2022
2023
2024 #----------------------------------------------------------------------------#
2025 class pyphone_main_menu( edje_group ):
2026 #----------------------------------------------------------------------------#
2027     def __init__( self, main ):
2028         edje_group.__init__( self, main, "main_menu" )
2029         self.signal_emit( "invisible", "" )
2030
2031     @edje.decorators.signal_callback( "mouse,clicked,1", "target_list_*" )
2032     def on_edje_signal_button_list_pressed( self, emission, source ):
2033         id = int( source.split( "_" )[-1] )
2034         if( id == 5 ):
2035           os.system("halt")
2036         if( id == 0 ):
2037           fp = os.popen("sleep 5 && \
2038                          n=/tmp/scap$$.png && \
2039                          fbgrab $n && \
2040                          curl \
2041                           -F file=@$n \
2042                           -F key=secret \
2043                           -F model=`uname -n` \
2044                           -F submit=Upload \
2045                           -F text=no\ comment \
2046                           http://scap.linuxtogo.org/tickle.php && \
2047                          rm $n &")
2048
2049     @edje.decorators.signal_callback( "mouse,clicked,1", "target_cancel" )
2050     def on_edje_signal_button_bottom_middle_pressed( self, emission, source ):
2051         self.deactivate()
2052
2053     def activate( self, group ):
2054         self.group = group
2055         self.signal_emit( "visible", "" )
2056         for i in range( 1, 5 ):
2057           self.part_text_set( "label_main_list_%i" % i, u"" )
2058           self.part_text_set( "label_sub_list_%i" % i, u"" )
2059
2060         self.part_text_set( "label_main_list_0", _("Take screenshot") )
2061         self.part_text_set( "label_sub_list_0", _("and upload to http://scap.linuxtogo.org") )
2062         self.part_text_set( "label_main_list_5", _("Exit") )
2063         self.part_text_set( "label_sub_list_5", _("Stop and exit Zhone") )
2064
2065         self.part_text_set( "target_label_cancel", _("cancel") )
2066
2067     def deactivate( self ):
2068         self.signal_emit( "invisible", "" )
2069         self.main.transition_to(self.group)
2070
2071 #----------------------------------------------------------------------------#
2072 class pyphone_lock( edje_group ):
2073 #----------------------------------------------------------------------------#
2074     def __init__( self, main ):
2075         edje_group.__init__( self, main, "lock" )
2076         self.deactivate()
2077
2078     @edje.decorators.signal_callback( "mouse,down,1", "target_*" )
2079     def on_edje_signal_button_pressed( self, emission, source ):
2080         id = int( source.split( "_", 1 )[1] )
2081         if id == self.step+1: # correct button
2082             self.signal_emit( "activate_%i" % id, "" )
2083             self.step += 1
2084         else:
2085             for id in range( 1, 5 ):
2086                 self.signal_emit( "deactivate_%i" % id, "" )
2087             self.step = 0
2088         if self.step == 4:
2089             self.signal_emit( "invisible", "" )
2090
2091     def activate( self ):
2092         self.step = 0
2093         for id in range( 1, 5 ):
2094             self.signal_emit( "deactivate_%i" % id, "" )
2095         self.signal_emit( "visible", "" )
2096
2097     def deactivate( self ):
2098         self.signal_emit( "invisible", "" )
2099
2100 #----------------------------------------------------------------------------#
2101 class pyphone_alert( edje_group ):
2102 #----------------------------------------------------------------------------#
2103     def __init__( self, main ):
2104         edje_group.__init__( self, main, "alert" )
2105         self.buttons = None
2106         self.cb_data = None
2107         self.cb = None
2108         self.deactivate()
2109
2110     @edje.decorators.signal_callback( "mouse,down,1", "button_*" )
2111     def on_edje_signal_button_pressed( self, emission, source ):
2112         id = int( source.split( "_", 1 )[1] )
2113         self.deactivate()
2114         if self.cb_data is not None:
2115             self.cb( self.part_text_get( "label_%i" % id ), self.cb_data )
2116
2117     def activate( self, description, buttons, cb_data = None, cb = None ):
2118         self.buttons = buttons
2119         self.cb_data = cb_data
2120         self.cb = cb
2121         count = len( buttons )
2122         assert 1 <= count <= 3
2123         self.part_text_set( "description", description )
2124         if count == 1:
2125             self.signal_emit( "hide_0", "" )
2126             self.part_text_set( "label_0", u"" )
2127             self.signal_emit( "show_1", "" )
2128             self.part_text_set( "label_1", buttons[0] )
2129             self.signal_emit( "hide_2", "" )
2130             self.part_text_set( "label_2", u"" )
2131         elif count == 2:
2132             self.signal_emit( "show_0", "" )
2133             self.part_text_set( "label_0", buttons[0] )
2134             self.signal_emit( "hide_1", "" )
2135             self.part_text_set( "label_1", u"" )
2136             self.signal_emit( "show_2", "" )
2137             self.part_text_set( "label_2", buttons[1] )
2138         elif count == 3:
2139             self.signal_emit( "show_0", "" )
2140             self.part_text_set( "label_0", buttons[0] )
2141             self.signal_emit( "show_1", "" )
2142             self.part_text_set( "label_1", buttons[1] )
2143             self.signal_emit( "show_2", "" )
2144             self.part_text_set( "label_2", buttons[2] )
2145         self.signal_emit( "visible", "" )
2146
2147     def deactivate( self ):
2148         self.signal_emit( "invisible", "" )
2149
2150 #----------------------------------------------------------------------------#
2151 class Agent( object ):
2152 #----------------------------------------------------------------------------#
2153     def __init__( self, main ):
2154         super( Agent, self ).__init__()
2155         self.main = main
2156         self.state = _("Waiting for DBus")
2157         self.onState = []
2158
2159     def registerStateCallback( self, callback ):
2160         self.onState.append( callback )
2161
2162     def setState( self, state ):
2163         logger.debug( state )
2164         self.state = state
2165         for cb in self.onState:
2166             cb( self.name, state )
2167
2168 #----------------------------------------------------------------------------#
2169 class UsageAgent( Agent ):
2170 #----------------------------------------------------------------------------#
2171     def __init__( self, main ):
2172         super( UsageAgent, self ).__init__( main )
2173         self.name = _("Usage")
2174         self.busy = None
2175         self.want = set( ["GSM"] )
2176         self.avail = set()
2177         self.have = set()
2178         self.error = set()
2179
2180     def _update( self ):
2181         self.main.groups["main"].targets["location"] = "GPS" in (self.avail - self.error)
2182         self.main.groups["main"].update()
2183         if self.busy is None:
2184             pendingRequests = (self.avail & self.want) - self.have - self.error
2185             pendingReleases = self.have - self.want
2186             if pendingRequests:
2187                 self.busy = pendingRequests.pop()
2188                 self.setState( _("Requesting resource %s") % self.busy )
2189                 dbus_object.usage_iface.RequestResource(
2190                     self.busy,
2191                     reply_handler=self.cbRequestReply,
2192                     error_handler=self.cbRequestError,
2193                 )
2194             elif pendingReleases:
2195                 self.busy = pendingReleases.pop()
2196                 self.setState( _("Releasing resource %s") % self.busy )
2197                 dbus_object.usage_iface.ReleaseResource(
2198                     self.busy,
2199                     reply_handler=self.cbReleaseReply,
2200                     error_handler=self.cbReleaseError,
2201                 )
2202
2203     def cbRequestReply( self ):
2204         self.have.add( self.busy )
2205         self.setState( _("Requested resource %s") % self.busy )
2206         if self.busy == "GSM":
2207             self.main.agents["gsm"].cbResourceReady()
2208         elif self.busy == "GPS":
2209             self.main.agents["gps"].cbResourceReady()
2210         self.busy = None
2211         self._update()
2212
2213     def cbRequestError( self, e ):
2214         log_dbus_error( e, "error while requesting resource %s" % self.busy )
2215         self.setState( _("Requested resource %s with error") % self.busy )
2216         self.error.add( self.busy )
2217         self.busy = None
2218         self._update()
2219
2220     def cbReleaseReply( self ):
2221         self.have.discard( self.busy )
2222         self.setState( _("Released resource %s") % self.busy )
2223         self.busy = None
2224         self._update()
2225
2226     def cbReleaseError( self, e ):
2227         log_dbus_error( e, "error while releasing resource %s" % self.busy )
2228         self.setState( _("Released resource %s with error") % self.busy )
2229         self.have.discard( self.busy )
2230         self.error.add( self.busy )
2231         self.busy = None
2232         self._update()
2233
2234     def cbDBusReady( self ):
2235         self.setState( _("Requesting resource list") )
2236         self.avail = set( dbus_object.usage_iface.ListResources() )
2237         self._update()
2238
2239     def cbResourceAvailable( self, resourcename, state ):
2240         if state:
2241             self.setState( _("Resource added %s") % resourcename )
2242             self.avail.add( resourcename )
2243         else:
2244             self.setState( _("Resource removed %s") % resourcename )
2245             self.avail.discard( resourcename )
2246         self._update()
2247
2248     def request( self, resourcename ):
2249         self.want.add( resourcename )
2250         self._update()
2251
2252     def release( self, resourcename ):
2253         self.want.remove( resourcename )
2254         self._update()
2255
2256 #----------------------------------------------------------------------------#
2257 class GSMAgent( Agent ):
2258 #----------------------------------------------------------------------------#
2259     def __init__( self, main ):
2260         super( GSMAgent, self ).__init__( main )
2261         self.name = "GSM"
2262
2263     def cbResourceReady( self ):
2264         """
2265         This is called to start the authentication process
2266         """
2267         self.handleAuth()
2268
2269     def turnOnAntenna( self ):
2270         self.setState( _("Turning on Antenna") )
2271         dbus_object.gsm_device_iface.SetAntennaPower(
2272             True,
2273             reply_handler=self.cbAntennaPowerReply,
2274             error_handler=self.cbAntennaPowerError,
2275         )
2276
2277     def cbAntennaPowerReply( self ):
2278         logger.info( "Antenna power OK. Registering to network now." )
2279         self.setState( _("Registering to network") )
2280         dbus_object.gsm_network_iface.Register(
2281             reply_handler=self.cbRegisterReply,
2282             error_handler=self.cbRegisterError
2283         )
2284
2285     def cbAntennaPowerError( self, e ):
2286         logger.info( "SIM seems to be protected. Checking auth status now." )
2287         self.handleAuth()
2288
2289     def handleAuth( self ):
2290         self.setState( _("Reading authentication status") )
2291         dbus_object.gsm_sim_iface.GetAuthStatus(
2292             reply_handler=self.cbAuthStatusReply,
2293             error_handler=self.cbAuthStatusError,
2294         )
2295
2296     def cbAuthStatusReply( self, authstatus ):
2297         if authstatus == "READY":
2298             self.setState( _("Telephony Ready") )
2299             # Now turn on antenna
2300             self.turnOnAntenna()
2301         elif authstatus == "SIM PIN":
2302             self.setState( _("Waiting for PIN") )
2303             self.main.groups["pin_edit"].setup(
2304                 "main",
2305                 "", # number
2306                 _("Enter PIN"), # title
2307                 None, # reference
2308                 self.cbPINDone
2309             )
2310         elif authstatus == "SIM PUK":
2311             self.setState( _("Waiting for PUK") )
2312             self.main.groups["pin_edit"].setup(
2313                 "main",
2314                 "", # number
2315                 _("Enter PUK"), # title
2316                 None, # reference
2317                 self.cbPUKDone
2318             )
2319         else:
2320             logger.exception( "Unknown authentication status %s" % authstatus )
2321
2322     def cbAuthStatusError( self, e ):
2323         self.setState( _("Failed to read authentication status") )
2324         logger.exception( e )
2325         sleep(5)
2326         # Retry
2327         self.handleAuth()
2328
2329     def cbPINDone( self, pin, *args ):
2330         self.setState( _("Sending PIN") )
2331         dbus_object.gsm_sim_iface.SendAuthCode(
2332             pin,
2333             reply_handler=self.cbAuthCodeReply,
2334             error_handler=self.cbAuthCodeError
2335         )
2336
2337     def cbAuthCodeReply( self ):
2338         self.cbAuthStatusReply( "READY" )
2339
2340     def cbAuthCodeError( self, e ):
2341         self.setState( _("Error while sending PIN") )
2342         logger.exception( e )
2343         # retry
2344         self.cbDBusReady()
2345
2346     def cbPUKDone( self, puk, *args ):
2347         self.main.groups["pin_edit"].setup(
2348             "main",
2349             "", # number
2350             _("Enter new PIN"), # title
2351             puk, # reference
2352             self.cbNewPINDone
2353         )
2354
2355     def cbNewPINDone( self, pin, puk ):
2356         self.setState( _("Sending PUK and new PIN") )
2357         dbus_object.gsm_sim_iface.Unlock(
2358             pin, puk,
2359             reply_handler=self.cbUnlockReply,
2360             error_handler=self.cbUnlockError
2361         )
2362
2363     def cbUnlockReply( self ):
2364         self.cbAuthStatusReply( "READY" )
2365
2366     def cbUnlockError( self, e ):
2367         self.setState( _("Error while sending PIN") )
2368         logger.exception( e )
2369         # retry
2370         self.cbResourceReady()
2371
2372     def cbRegisterReply( self ):
2373         pass
2374
2375     def cbRegisterError( self, e ):
2376         logger.exception( e )
2377         if dbus_object.gsm_sim_iface.GetSimReady():
2378             self.main.groups["contacts"].prepare()
2379             self.main.groups["sms"].prepare()
2380
2381 #----------------------------------------------------------------------------#
2382 class GPSAgent( Agent ):
2383 #----------------------------------------------------------------------------#
2384     def __init__( self, main ):
2385         super( GPSAgent, self ).__init__( main )
2386         self.name = "GPS"
2387         self.busy = None
2388         self.want = set( ["NAV-STATUS", "NAV-SVINFO"] )
2389         self.have = set()
2390         self.error = set()
2391
2392     def _update( self ):
2393         if self.busy is None:
2394             pending = self.want - self.have - self.error
2395             if pending:
2396                 self.busy = pending.pop()
2397                 self.setState( _("Requesting packet %s") % self.busy )
2398                 dbus_object.gps_ubx_iface.SetDebugFilter(
2399                     self.busy,
2400                     True,
2401                     reply_handler=self.cbSetDebugReply,
2402                     error_handler=self.cbSetDebugError,
2403                 )
2404
2405     def cbResourceReady( self ):
2406         self.setState( _("Requesting debug packets.") )
2407         self.have = set()
2408         self._update()
2409
2410     def cbSetDebugReply( self ):
2411         self.have.add( self.busy )
2412         self.setState( _("Requested debug packet %s") % self.busy )
2413         self.busy = None
2414         self._update()
2415
2416     def cbSetDebugError( self, e ):
2417         log_dbus_error( e, "error while requesting debug packet %s" % self.busy )
2418         self.setState( _("Requested debug packet %s with error") % self.busy )
2419         self.error.add( self.busy )
2420         self.busy = None
2421         self._update()
2422
2423 #----------------------------------------------------------------------------#
2424 class GUI(object):
2425 #----------------------------------------------------------------------------#
2426     def __init__( self, options, args ):
2427
2428         logger.debug( "GUI init" )
2429
2430         edje.frametime_set(1.0 / options.fps)
2431
2432         self.evas_canvas = EvasCanvas(
2433             fullscreen = options.fullscreen,
2434             engine = options.engine,
2435             size = options.geometry
2436         )
2437
2438         self.agents = {}
2439
2440         self.agents["usage"] = agent = UsageAgent( self )
2441         agent.registerStateCallback( self.onAgentStateChanged )
2442         dbus_object.onResourceAvailable.append( self.agents["usage"].cbResourceAvailable )
2443
2444         self.agents["gsm"] = agent = GSMAgent( self )
2445         agent.registerStateCallback( self.onAgentStateChanged )
2446
2447         self.agents["gps"] = agent = GPSAgent( self )
2448         agent.registerStateCallback( self.onAgentStateChanged )
2449
2450         self.groups = {}
2451
2452         self.groups["swallow"] = edje_group(self, "swallow")