Add frontend for conference/toggle functions
[zhone.git] / src / zhone
1 #!/usr/bin/env python
2 #coding=utf8
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
14 import logging
15 logger = logging.getLogger( "zhone" )
16 logging.basicConfig( level    = logging.DEBUG,
17                     format   = '%(asctime)s %(levelname)s %(message)s',
18                     filename = '/tmp/zhone.log',
19                     filemode = 'w' )
20 logger.addHandler( logging.StreamHandler() )
21 logger.setLevel( logging.DEBUG )
22
23 def dbus_error( desc ):
24     def dbus_error( e, desc = desc ):
25         print dir(e)
26         logger.error( "%s (%s %s: %s)" % ( desc, e.__class__.__name__, e.get_dbus_name(), e.get_dbus_message() ) )
27     return dbus_error
28
29 #----------------------------------------------------------------------------#
30 WIDTH = 480
31 HEIGHT = 640
32
33 TITLE = "zhone"
34 WM_NAME = "zhone"
35 WM_CLASS = "zhone"
36
37 #----------------------------------------------------------------------------#
38 import os
39 import sys
40 import e_dbus
41 import evas
42 import evas.decorators
43 import edje
44 import edje.decorators
45 import ecore
46 import ecore.evas
47 import cairo
48 from dbus import SystemBus, Interface
49 from dbus.exceptions import DBusException
50 from optparse import OptionParser
51 import time
52 import math
53
54 illume = None
55 try:
56     import illume
57 except ImportError:
58     logger.warning( "could not load illume interface module" )
59
60 #----------------------------------------------------------------------------#
61
62 edjepaths = "./zhone.edj ../data/themes/zhone.edj ../share/zhone.edj /usr/local/share/zhone/zhone.edj /usr/share/zhone/zhone.edj".split()
63
64 for i in edjepaths:
65     if os.path.exists( i ):
66        global edjepath
67        edjepath = i
68        break
69 else:
70     raise Exception( "zhone.edj not found. looked in %s" % edjepaths )
71
72 #----------------------------------------------------------------------------#
73 class edje_group(edje.Edje):
74 #----------------------------------------------------------------------------#
75     def __init__(self, main, group, parent_name="main"):
76         self.main = main
77         self.parent_name = parent_name
78         self.group = group
79         global edjepath
80         f = edjepath
81         try:
82             edje.Edje.__init__(self, self.main.evas_canvas.evas_obj.evas, file=f, group=group)
83         except edje.EdjeLoadError, e:
84             raise SystemExit("error loading %s: %s" % (f, e))
85         self.size = self.main.evas_canvas.evas_obj.evas.size
86
87     def onShow( self ):
88         pass
89
90     def onHide( self ):
91         pass
92
93     @edje.decorators.signal_callback("mouse,clicked,1", "button_bottom_right")
94     def on_edje_signal_button_bottom_right_pressed(self, emission, source):
95         self.main.transition_to(self.parent_name)
96
97     @edje.decorators.signal_callback("mouse,clicked,1", "button_bottom_left")
98     def on_edje_signal_button_bottom_left_pressed(self, emission, source):
99         self.main.groups["main_menu"].activate( self.group )
100         self.main.transition_to("main_menu")
101
102 #----------------------------------------------------------------------------#
103 class pyphone_main(edje_group):
104 #----------------------------------------------------------------------------#
105     def __init__(self, main):
106         edje_group.__init__(self, main, "main")
107         self.targets = {
108             "phone": False,
109             "contacts": False,
110             "sms": False,
111             "date": False,
112             "location": False,
113             "configuration": True,
114         }
115         self.update()
116
117     def update( self ):
118         for key, value in self.targets.items():
119             if value:
120                 self.signal_emit( "activate_target_icon_%s" % key, "" )
121             else:
122                 self.signal_emit( "deactivate_target_icon_%s" % key, "" )
123
124     @edje.decorators.signal_callback("mouse,clicked,1", "target_*")
125     def on_edje_signal_button_pressed(self, emission, source):
126         target = source.split('_', 1)[1]
127         if not self.targets[target]:
128             return
129         if target == "phone" and not self.main.groups["call"].status in ["idle" , "release"]:
130             target = "call"
131         self.main.transition_to(target)
132
133 #----------------------------------------------------------------------------#
134 class pyphone_phone(edje_group):
135 #----------------------------------------------------------------------------#
136     TIMEOUT = 2.0
137     def __init__(self, main):
138         edje_group.__init__(self, main, "phone")
139         self.text = []
140         self.last = 0.0
141
142     @edje.decorators.signal_callback( "mouse,clicked,1", "button_*" )
143     def on_edje_signal_dialer_button_pressed(self, emission, source):
144         key = source.split("_", 1)[1]
145         if key in ("0", "1", "2", "3", "4", "5", "6", "7", "8", "9"):
146             self.text.append(key)
147             # The trailing whitespace is a workaround for the one char invisible
148             # bug due to some problems with scaling of text parts.
149             self.part_text_set("label", u"".join(self.text)+u" ")
150         elif key in "star":
151             if self.text and ( time.time()-self.last < self.TIMEOUT ):
152                 if self.text[-1] == "*":
153                     del self.text[-1]
154                     self.text.append( "+" )
155                 elif self.text[-1] == "+":
156                     del self.text[-1]
157                     self.text.append( "*" )
158                 else:
159                     self.text.append( "*" )
160             else:
161                 self.text.append("*")
162             self.part_text_set( "label", u"".join(self.text)+u" " )
163         elif key in "hash":
164             self.text += "#"
165             self.part_text_set( "label", u"".join(self.text)+u" " )
166         elif key in "delete":
167             self.text = self.text[:-1]
168             self.part_text_set("label", u"".join(self.text)+u" ")
169         elif key in "dial":
170             if dbus_object.gsm_device_obj:
171                 dbus_object.gsm_call_iface.Initiate( "".join(self.text), "voice" )
172             else:
173                 # Fake onCallStatus...
174                 self.main.groups["call"].onCallStatus( None, "outgoing", {"peer": "".join(self.text)} )
175         self.last = time.time()
176
177 #----------------------------------------------------------------------------#
178 class pyphone_call(edje_group):
179 #----------------------------------------------------------------------------#
180     def __init__(self, main):
181         edje_group.__init__(self, main, "call")
182         self.update_status("idle")
183         self.call = None
184
185     def onCallStatus( self, id, status, properties ):
186         self.call = id
187         self.update_status(status)
188         try:
189             self.part_text_set( "label", properties[ "peer" ] )
190         except KeyError:
191             pass
192
193     @edje.decorators.signal_callback("call_button_pressed", "button_left")
194     def on_edje_signal_call_button_left_pressed(self, emission, source):
195         if self.status == "active":
196             if dbus_object.gsm_device_obj:
197                 #dbus_object.gsm_call_iface.Hold(self.call)
198                 pass
199             else:
200                 self.update_status("held")
201         elif self.status in ["incoming", "held"]:
202             if dbus_object.gsm_device_obj:
203                 dbus_object.gsm_call_iface.Activate(self.call)
204             else:
205                 self.update_status("active")
206
207     @edje.decorators.signal_callback("call_button_pressed", "button_right")
208     def on_edje_signal_call_button_right_pressed(self, emission, source):
209         if self.status in ["outgoing", "active", "incoming", "held"]:
210             if dbus_object.gsm_device_obj:
211                 dbus_object.gsm_call_iface.Release(self.call)
212             else:
213                 self.update_status("release")
214
215     def update_status(self, status):
216         self.part_text_set( "label_description", status )
217         if status == "outgoing":
218             self.part_text_set( "label_left", u"" )
219             self.part_text_set( "label_right", u"cancel" )
220         elif status == "active":
221             self.part_text_set( "label_left", u"hold" )
222             self.part_text_set( "label_right", u"hangup" )
223         elif status == "incoming":
224             self.part_text_set( "label_left", u"answer" )
225             self.part_text_set( "label_right", u"reject" )
226         elif status == "held":
227             self.part_text_set( "label_left", u"resume" )
228             self.part_text_set( "label_right", u"hangup" )
229         else:
230             self.part_text_set( "label_left", u"" )
231             self.part_text_set( "label_right", u"" )
232         if status in ["incoming", "outgoing"] and not self.status == status:
233             # TODO make sure we get displayed
234             self.main.transition_to("call")
235         self.status = status
236
237     @edje.decorators.signal_callback( "mouse,clicked,1", "button_bottom_middle" )
238     def on_edje_signal_button_bottom_middle_pressed( self, emission, source ):
239         self.main.transition_to("dtmf")
240
241 #----------------------------------------------------------------------------#
242 class pyphone_dtmf(edje_group):
243 #----------------------------------------------------------------------------#
244     def __init__(self, main):
245         edje_group.__init__(self, main, "dtmf")
246         self.text = []
247         self.last = 0.0
248
249     @edje.decorators.signal_callback( "mouse,clicked,1", "button_*" )
250     def on_edje_signal_dialer_button_pressed(self, emission, source):
251         key = source.split("_", 1)[1]
252         if key in ("0", "1", "2", "3", "4", "5", "6", "7", "8", "9"):
253             self.text.append(key)
254             # The trailing whitespace is a workaround for the one char invisible
255             # bug due to some problems with scaling of text parts.
256             self.part_text_set("label", "".join(self.text)+" ")
257         elif key in "star":
258             if self.text and ( time.time()-self.last < self.TIMEOUT ):
259                 if self.text[-1] == "*":
260                     del self.text[-1]
261                     self.text.append( "+" )
262                 elif self.text[-1] == "+":
263                     del self.text[-1]
264                     self.text.append( "*" )
265                 else:
266                     self.text.append( "*" )
267             else:
268                 self.text.append("*")
269             self.part_text_set( "label", "".join(self.text)+" " )
270         elif key in "hash":
271             self.text += "#"
272             self.part_text_set( "label", "".join(self.text)+" " )
273         elif key in "delete":
274             self.text = self.text[:-1]
275             self.part_text_set("label", "".join(self.text)+" ")
276         elif key in "dial":
277             if dbus_object.gsm_device_obj:
278                 dbus_object.gsm_call_iface.SendDtmf( "".join(self.text))
279                 self.main.transition_to("call")
280
281     @edje.decorators.signal_callback("call_button_pressed", "button_right")
282     def on_edje_signal_call_button_right_pressed(self, emission, source):
283         self.main.transition_to("call")
284
285 #----------------------------------------------------------------------------#
286 class pyphone_sms(edje_group):
287 #----------------------------------------------------------------------------#
288     def __init__(self, main):
289         edje_group.__init__(self, main, "sms")
290         self.ready = False
291         self.busy = False
292         self.messagebook = []
293         self.current = []
294         self.page = 0
295         self.selected = None
296         self.newindex = None
297         self.senderTimer = ecore.timer_add( 60, self.processUnsentMessages )
298
299     def processUnsentMessages( self ):
300         if not dbus_object.gsm_device_obj:
301             return True
302
303         logger.info( "checking for unsent messages" )
304         dbus_object.gsm_sim_iface.RetrieveMessagebook( "unsent", reply_handler=self.cbReceiveUnsentReply, error_handler=self.cbReceiveUnsentError )
305
306         return True # call me again
307
308     def cbReceiveUnsentReply( self, messages ):
309         for message in messages:
310             index, status, number, content = message
311             logger.info( "trying to send message w/ index %d..." % index )
312             dbus_object.gsm_sim_iface.SendStoredMessage( index, reply_handler=self.cbSendReply, error_handler=self.cbSendError )
313
314     def cbReceiveUnsentError( self, e ):
315         logger.info( "did not receive any unsent messages: %s" % e )
316
317     def cbSendReply( self, reference ):
318         logger.info( "sent message successfully w/ reference number %d" % reference )
319
320     def cbSendError( self, e ):
321         logger.error( "could not send message. leaving in outgoing. error was: %s" % e )
322
323     def cbStoreReply( self, result ):
324         logger.info( "stored message lives at SIM position %d" % result )
325         self.processUnsentMessages()
326         if not self.busy:
327             dbus_object.gsm_sim_iface.RetrieveMessagebook(
328                 "all",
329                 reply_handler=self.cbMessagebookReply,
330                 error_handler=self.cbMessagebookError
331             )
332             self.busy = True
333
334     def cbStoreError( self, e ):
335         logger.warning( "error while storing message" )
336
337     def cbSend1( self, selected, cb_data ):
338         self.main.groups["text_edit"].setup(
339             "sms",
340             "", # text
341             selected[0], # title
342             selected[1], # reference
343             self.cbSend2
344         )
345
346     def cbSend2( self, text, number ):
347         if dbus_object.gsm_device_obj:
348             dbus_object.gsm_sim_iface.StoreMessage(
349                 number, text,
350                 reply_handler=self.cbStoreReply,
351                 error_handler=self.cbStoreError
352             )
353
354     def cbDelete( self, result, reference ):
355         if result == "abort":
356             return
357         if dbus_object.gsm_device_obj:
358             dbus_object.gsm_sim_iface.DeleteMessage(
359                 reference
360             )
361         for i in range( len( self.messagebook ) ):
362             if self.messagebook[i][0] == reference:
363                 del self.messagebook[i]
364                 break
365         self.updateList()
366
367     def cbForward( self, selected, text ):
368         if dbus_object.gsm_device_obj:
369             dbus_object.gsm_sim_iface.StoreMessage(
370                 selected[1], text,
371                 reply_handler=self.cbStoreReply,
372                 error_handler=self.cbStoreError
373             )
374
375     def cbReply( self, text, number):
376         if dbus_object.gsm_device_obj:
377             dbus_object.gsm_sim_iface.StoreMessage(
378                 number, text,
379                 reply_handler=self.cbStoreReply,
380                 error_handler=self.cbStoreError
381             )
382
383     def cbMenu( self, result ):
384         if result == "send":
385             self.main.groups["contacts"].prepare()
386             if self.main.groups["contacts"].ready:
387                 self.main.groups["list_choose"].setup(
388                     "sms",
389                     [ (x[1], x[2]) for x in  self.main.groups["contacts"].phonebook],
390                     None,
391                     self.cbSend1
392                 )
393         elif result == "delete":
394             self.main.groups["error"].activate(
395                 "delete?",
396                 ("abort", "delete"),
397                 self.current[self.selected][0], # reference
398                 self.cbDelete
399             )
400         elif result == "forward":
401             self.main.groups["contacts"].prepare()
402             if self.main.groups["contacts"].ready:
403                 self.main.groups["list_choose"].setup(
404                     "sms",
405                     [ (x[1], x[2] ) for x in  self.main.groups["contacts"].phonebook],
406                     self.current[self.selected][3],
407                     self.cbForward
408                 )
409         elif result == "reply":
410             self.main.groups["text_edit"].setup(
411                 "sms",
412                 "", # text
413                 self.current[self.selected][2], # title = number
414                 self.current[self.selected][2], # reference = number
415                 self.cbReply
416             )
417
418     def cbMessagebookReply( self, result ):
419         logger.info( "retrieved messagebook: %s" % result )
420         self.busy = False
421         self.messagebook = result
422         self.ready = True
423         self.updateList()
424         self.main.groups["main"].targets["sms"] = True
425         self.main.groups["main"].update()
426         if not self.newindex is None:
427             message = [x for x in self.messagebook if x[0] == self.newindex]
428             self.newindex = None
429             if message and self.main.current_group == self.main.groups["main"]:
430                 message = message[0]
431                 from_text = self.main.groups["contacts"].tryNumberToName( message[2] )
432                 self.main.groups["text_show"].setup(
433                     "sms",
434                     "From: %s<p>%s" % (
435                         from_text,
436                         message[3].replace( '\n', '<br>' )
437                     )
438                 )
439
440     def cbMessagebookError( self, e ):
441         logger.warning( "error while retrieving messagebook" )
442         self.busy = False
443
444     def onIncomingMessage( self, index ):
445         logger.info( "new message! Retrieving messagebook..." )
446         self.newindex = index
447         if not self.busy:
448             dbus_object.gsm_sim_iface.RetrieveMessagebook(
449                 "all",
450                 reply_handler=self.cbMessagebookReply,
451                 error_handler=self.cbMessagebookError
452             )
453             self.busy = True
454
455     def prepare( self ):
456         if not self.ready and not self.busy:
457             if dbus_object.gsm_device_obj:
458                 logger.info( "retrieving messagebook..." )
459                 dbus_object.gsm_sim_iface.RetrieveMessagebook(
460                     "all",
461                     reply_handler=self.cbMessagebookReply,
462                     error_handler=self.cbMessagebookError
463                 )
464                 self.busy = True
465             else:
466                 # Fake messagebook...
467                 self.cbMessagebookReply( [
468                     (0, "read", "+4544555", "Hello World!"),
469                     (1, "read", "+456663443", "Zhone!"),
470                     (2, "read", "+456663443", "Hi Guy\nGuess what, I now "+
471                         "know to write multi-line SMSs.\nIsn't that "+
472                         "nice?\n\nSome Buddy"),
473                     (3, "read", "Flash SMS", "An SMS without digits. Strange, isn't it?"),
474                 ] )
475
476     def onReadyStatus( self, status ):
477         logger.debug( "SIM is ready: %s" % status )
478         if status:
479             # Force update
480             self.ready = False
481             self.prepare()
482
483     def onShow( self ):
484         self.prepare()
485         self.updateList()
486
487     def updateList( self):
488         self.main.groups["contacts"].prepare()
489         self.pages = max( ( len( self.messagebook ) - 1 ) / 6 + 1, 1 )
490         if self.page >= self.pages:
491             self.page = self.pages - 1
492         if self.page < 0:
493             self.page = 0
494         self.current = self.messagebook[self.page*6:(self.page+1)*6]
495         text = u"".join( [u"□"]*self.page+[u"▣"]+[u"□"]*(self.pages-self.page-1) )
496         self.part_text_set( "pager", text )
497         for i in range( 0, len( self.current ) ):
498             main_text = self.main.groups["contacts"].tryNumberToName( self.current[i][2] )
499             self.part_text_set( "label_main_list_%i" % i, main_text )
500             sub_text = " ".join(self.current[i][3].splitlines())
501             self.part_text_set( "label_sub_list_%i" % i, u"(%s) %s" % ( self.current[i][1], sub_text ) )
502         for i in range( len( self.current ), 6):
503             self.part_text_set( "label_main_list_%i" % i, u"" )
504             self.part_text_set( "label_sub_list_%i" % i, u"" )
505         self.selected = None
506         for i in range( 6 ):
507             self.signal_emit( "deactivate_target_list_%i" % i, "" )
508
509
510     @edje.decorators.signal_callback( "mouse,clicked,1", "target_list_*" )
511     def on_edje_signal_button_list_pressed( self, emission, source ):
512         id = int( source.split( "_" )[-1] )
513         if self.selected == id:
514             return
515         if self.selected is not None:
516             self.signal_emit( "deactivate_target_list_%i" % self.selected, "" )
517         self.signal_emit( "activate_target_list_%i" % id, "" )
518         self.selected = id
519
520     @edje.decorators.signal_callback( "mouse,clicked,1", "button_action_left" )
521     def on_edje_signal_button_action_left_pressed( self, emission, source ):
522         self.page -= 1
523         self.updateList()
524
525     @edje.decorators.signal_callback( "mouse,clicked,1", "button_action_right" )
526     def on_edje_signal_button_action_right_pressed( self, emission, source ):
527         self.page += 1
528         self.updateList()
529
530     @edje.decorators.signal_callback( "mouse,clicked,1", "button_action_open" )
531     def on_edje_signal_button_action_open_pressed( self, emission, source ):
532         if self.selected is not None:
533             from_text = self.main.groups["contacts"].tryNumberToName( self.current[self.selected][2] )
534             self.main.groups["text_show"].setup(
535                 "sms",
536                 "From: %s<p>%s" % (
537                     from_text,
538                     self.current[self.selected][3].replace( '\n', '<br>' )
539                 )
540             )
541
542     @edje.decorators.signal_callback( "mouse,clicked,1", "button_bottom_middle" )
543     def on_edje_signal_button_bottom_middle_pressed( self, emission, source ):
544         self.main.groups["menu"].activate( ( "send", "delete", "forward", "reply" ), self.cbMenu )
545
546
547 #----------------------------------------------------------------------------#
548 class pyphone_configuration(edje_group):
549 #----------------------------------------------------------------------------#
550     def __init__(self, main):
551         edje_group.__init__(self, main, "configuration")
552         self.selected = None
553         self.profiles = []
554
555     def onShow( self ):
556         self.prepare()
557         self.updateList()
558
559     def prepare( self ):
560         # Set the current profile label
561         if dbus_object.prefs_obj:
562             current = dbus_object.prefs_iface.GetProfile()
563             self.profiles = dbus_object.prefs_iface.GetProfiles()
564         else:
565             current = 'Test1'
566             self.profiles = ["Test1", "Test2"]
567         self.part_text_set( "current_label", u"Current : %s" % current )
568
569     def updateList( self):
570         self.main.groups["configuration"].prepare()
571         # We put all the profile names in the slots
572         for i in range(6):
573             if i < len(self.profiles):
574                 name = self.profiles[i]
575             else:
576                 name = u""
577             self.part_text_set( "label_main_list_%i" % i, name )
578             self.part_text_set( "label_sub_list_%i" % i, u"" )
579         self.selected = None
580         for i in range( 6 ):
581             self.signal_emit( "deactivate_target_list_%i" % i, "" )
582
583     @edje.decorators.signal_callback( "mouse,clicked,1", "target_list_*" )
584     def on_edje_signal_button_list_pressed( self, emission, source ):
585         id = int( source.split( "_" )[-1] )
586         if self.selected == id:
587             return
588         if self.selected is not None:
589             self.signal_emit( "deactivate_target_list_%i" % self.selected, "" )
590         self.signal_emit( "activate_target_list_%i" % id, "" )
591         self.selected = id
592
593     @edje.decorators.signal_callback( "mouse,clicked,1", "button_action_use" )
594     def on_edje_signal_button_action_use_pressed( self, emission, source ):
595         if self.selected is None:
596             return
597         if self.selected >= len(self.profiles):
598             return
599         profile = self.profiles[self.selected]
600         self.part_text_set( "current_label", u"Current : %s" % profile )
601         if dbus_object.prefs_obj:
602             dbus_object.prefs_iface.SetProfile(profile)
603
604 #----------------------------------------------------------------------------#
605 class pyphone_contacts(edje_group):
606 #----------------------------------------------------------------------------#
607     def __init__(self, main):
608         edje_group.__init__(self, main, "contacts")
609         self.ready = False
610         self.busy = False
611         self.phonebook = []
612         self.current = []
613         self.page = 0
614         self.selected = None
615
616     def cbNameEdit( self, name, reference ):
617         for i in range( len( self.phonebook ) ):
618             if self.phonebook[i][0] == reference:
619                 self.phonebook[i] = ( reference, name, self.phonebook[i][2] )
620                 if dbus_object.gsm_device_obj:
621                     dbus_object.gsm_sim_iface.StoreEntry(
622                         reference,
623                         name,
624                         self.phonebook[i][2]
625                     )
626                 break
627         self.phonebook.sort( key = lambda x: x[1].lower() )
628         self.updateList()
629
630     def cbNumberEdit( self, number, reference ):
631         for i in range( len( self.phonebook ) ):
632             if self.phonebook[i][0] == reference:
633                 self.phonebook[i] = ( reference, self.phonebook[i][1], number )
634                 if dbus_object.gsm_device_obj:
635                     dbus_object.gsm_sim_iface.StoreEntry(
636                         reference,
637                         self.phonebook[i][1],
638                         number
639                     )
640                 break
641         self.updateList()
642
643     def cbNew1( self, name, none ):
644         self.main.groups["number_edit"].setup(
645             "contacts",
646             "", # number
647             name, # title = new name
648             name, # reference
649             self.cbNew2
650         )
651
652     def cbNew2( self, number, name ):
653         ids = [ x[0] for x in self.phonebook ]
654         ids.sort()
655         reference = None
656         for i in range(1, 250):
657             if not i in ids:
658                 reference = i
659                 break
660         if reference is None:
661             return # no space?
662         if dbus_object.gsm_device_obj:
663             dbus_object.gsm_sim_iface.StoreEntry(
664                 reference,
665                 name,
666                 number
667             )
668         self.phonebook.append( ( reference, name, number ) )
669         self.phonebook.sort( key = lambda x: x[1].lower() )
670         self.updateList()
671
672     def cbDelete( self, result, reference ):
673         if result == "abort":
674             return
675         if dbus_object.gsm_device_obj:
676             dbus_object.gsm_sim_iface.DeleteEntry(
677                 reference
678             )
679         for i in range( len( self.phonebook ) ):
680             if self.phonebook[i][0] == reference:
681                 del self.phonebook[i]
682                 break
683         self.updateList()
684
685     def cbMenu( self, result ):
686         if result == "edit name":
687             self.main.groups["text_edit"].setup(
688                 "contacts",
689                 self.current[self.selected][1], # name
690                 self.current[self.selected][2], # title = number
691                 self.current[self.selected][0], # reference
692                 self.cbNameEdit
693             )
694         elif result == "edit number":
695             self.main.groups["number_edit"].setup(
696                 "contacts",
697                 self.current[self.selected][2], # number
698                 self.current[self.selected][1], # title = name
699                 self.current[self.selected][0], # reference
700                 self.cbNumberEdit
701             )
702         elif result == "new":
703             self.main.groups["text_edit"].setup(
704                 "contacts",
705                 "", # name
706                 "name?", # title
707                 None, # reference
708                 self.cbNew1
709             )
710         elif result == "delete":
711             self.main.groups["error"].activate(
712                 "delete '%s'?" % self.current[self.selected][1],
713                 ("abort", "delete"),
714                 self.current[self.selected][0], # reference
715                 self.cbDelete
716             )
717
718     def cbPhonebookReply( self, result ):
719         logger.info( "retrieved phonebook: %s" % result )
720         self.busy = False
721         self.phonebook = result
722         self.phonebook.sort( key = lambda x: x[1].lower() )
723         self.ready = True
724         self.updateList()
725         self.main.groups["main"].targets["contacts"] = True
726         self.main.groups["main"].update()
727
728     def cbPhonebookError( self, e ):
729         logger.error( "error while retrieving phonebook %s" % e )
730         self.busy = False
731
732     def prepare( self ):
733         if not self.ready and not self.busy:
734             if dbus_object.gsm_device_obj:
735                 logger.info( "retrieving phonebook..." )
736                 dbus_object.gsm_sim_iface.RetrievePhonebook(
737                     reply_handler=self.cbPhonebookReply,
738                     error_handler=self.cbPhonebookError
739                 )
740                 self.busy = True
741             else:
742                 # Fake phonebook...
743                 self.cbPhonebookReply( [
744                     (1, u'Kirk', '+023224433'),
745                     (2, u'Spock', '+034433463'),
746                     (3, u'McCoy', '+013244344'),
747                     (4, u'Scott', '+013244344'),
748                     (5, u'Uhura', '+013244344'),
749                     (6, u'Sulu', '+013244344'),
750                     (7, u'Chekov', '+456663443'),
751                 ] )
752
753     def onReadyStatus( self, status ):
754         logger.debug( "SIM is ready: %s" % status )
755         if status:
756             # Force update
757             self.ready = False
758             self.prepare()
759
760     def onShow( self ):
761         self.prepare()
762         self.updateList()
763
764     def comparePhoneNumber(self, number1, number2):
765         '''
766         Compares two phone numbers. They are considered equal if:
767           a) One does not contain digits, and they are equal as strings
768         or
769           b) Both start with a "+", and all following digits are equal
770         or
771           c) At least one of them does not start with a "+", and the
772              last 7 digits are equal
773         '''
774         digits1 = filter (lambda c: c.isdigit() or c == '+', number1)
775         digits2 = filter (lambda c: c.isdigit() or c == '+', number2)
776
777         if digits1 == '' or digits2 == '':
778             return number1 == number2
779         if digits1[0] == digits2[0] == '+':
780             return digits1 == digits2
781         else:
782             return digits1[-7:] == digits2[-7:]
783
784     def tryNumberToName( self, number ):
785         for i in range( len( self.phonebook ) ):
786             if self.comparePhoneNumber(self.phonebook[i][2], number):
787                 return self.phonebook[i][1]
788         return number
789
790     def updateList( self ):
791         self.pages = max( ( len( self.phonebook ) - 1 ) / 6 + 1, 1 )
792         if self.page >= self.pages:
793             self.page = self.pages - 1
794         if self.page < 0:
795             self.page = 0
796         self.current = self.phonebook[self.page*6:(self.page+1)*6]
797         text = u"".join( [u"□"]*self.page+[u"▣"]+[u"□"]*(self.pages-self.page-1) )
798         self.part_text_set( "pager", text )
799         for i in range( 0, len( self.current ) ):
800             self.part_text_set( "label_main_list_%i" % i, self.current[i][1] )
801             self.part_text_set( "label_sub_list_%i" % i, self.current[i][2] )
802         for i in range( len( self.current ), 6):
803             self.part_text_set( "label_main_list_%i" % i, u"" )
804             self.part_text_set( "label_sub_list_%i" % i, u"" )
805         self.selected = None
806         for i in range( 6 ):
807             self.signal_emit( "deactivate_target_list_%i" % i, "" )
808
809     @edje.decorators.signal_callback( "mouse,clicked,1", "target_list_*" )
810     def on_edje_signal_button_list_pressed( self, emission, source ):
811         id = int( source.split( "_" )[-1] )
812         if self.selected == id:
813             return
814         if self.selected is not None:
815             self.signal_emit( "deactivate_target_list_%i" % self.selected, "" )
816         self.signal_emit( "activate_target_list_%i" % id, "" )
817         self.selected = id
818
819     @edje.decorators.signal_callback( "mouse,clicked,1", "button_action_left" )
820     def on_edje_signal_button_action_left_pressed( self, emission, source ):
821         self.page -= 1
822         self.updateList()
823
824     @edje.decorators.signal_callback( "mouse,clicked,1", "button_action_right" )
825     def on_edje_signal_button_action_right_pressed( self, emission, source ):
826         self.page += 1
827         self.updateList()
828
829     @edje.decorators.signal_callback( "mouse,clicked,1", "button_action_dial" )
830     def on_edje_signal_button_action_dial_pressed( self, emission, source ):
831         if self.selected is not None:
832             if dbus_object.gsm_device_obj:
833                 dbus_object.gsm_call_iface.Initiate( self.current[self.selected][2], "voice" )
834             else:
835                 # Fake onCallStatus...
836                 self.main.groups["call"].onCallStatus( None, "outgoing", {"peer": self.current[self.selected][2]} )
837
838     @edje.decorators.signal_callback( "mouse,clicked,1", "button_bottom_middle" )
839     def on_edje_signal_button_bottom_middle_pressed( self, emission, source ):
840         self.main.groups["menu"].activate(("edit name", "edit number", "new", "delete"), self.cbMenu)
841
842 #----------------------------------------------------------------------------#
843 class pyphone_location( edje_group ):
844 #----------------------------------------------------------------------------#
845     class SignalGraph( evas.ClippedSmartObject ):
846         def __init__( self, *args, **kargs ):
847             evas.ClippedSmartObject.__init__( self, *args, **kargs )
848             self.textheight = 45
849             self.font = ( "Sans", 15 )
850             self.children = []
851             self.values = []
852             self.maxvalue = None
853             self.resized = 0
854
855         def resize( self, w, h ):
856             self.w = w
857             self.h = h
858             self.render()
859             self.resized = 1
860
861         def render( self ):
862             x, y = self.pos
863             if len ( self.children ) == 0:
864                 return
865             barwidth = self.w / len( self.children )
866             barheight = self.h - self.textheight
867             for (rect, key_text, value_text), (key, value) in zip( self.children, self.values ):
868                 key_text.text = str( key )
869                 value_text.text = str( value )
870                 barlength = int( barheight*( value/self.maxvalue ) )
871                 valueoffset = max( self.textheight-barlength, 0 )
872                 rect.geometry = (
873                     x+5, y+barheight-barlength,
874                     barwidth-5, barlength
875                 )
876                 key_text.geometry = (
877                     x+(barwidth-key_text.horiz_advance)/2, y+barheight,
878                     barwidth, self.textheight
879                 )
880                 value_text.geometry = (
881                     x+(barwidth-value_text.horiz_advance)/2, y+5+barheight-barlength-valueoffset,
882                     barwidth, self.textheight
883                 )
884                 x += barwidth
885
886
887         def addChild( self ):
888             rect = self.Rectangle( color=( 0, 255, 0, 255 ) )
889             rect.show()
890             key_text = self.Text(
891                 style = evas.EVAS_TEXT_STYLE_SOFT_OUTLINE,
892                 outline_color = ( 0, 0, 0, 255 ),
893                 font = self.font
894             )
895             key_text.show()
896             value_text = self.Text(
897                 style = evas.EVAS_TEXT_STYLE_SOFT_OUTLINE,
898                 outline_color = ( 0, 0, 0, 255 ),
899                 font = self.font
900             )
901             value_text.show()
902             self.children.append( ( rect, key_text, value_text ) )
903
904         def delChild( self ):
905             child = self.children[-1]
906             del self.children[-1]
907             for x in child:
908                 self.member_del( x )
909
910         def update( self, values = None ):
911             if values is not None:
912                 self.values = values
913                 if values == []:
914                     self.maxvalue = 1
915                     self.values = [(0, 0)]
916                 else:
917                     self.maxvalue = max( [ x[1] for x in values] )
918                     if self.maxvalue == 0:
919                         self.maxvalue = 1
920
921             while len( self.children ) > len( self.values ):
922                 self.delChild()
923             while len( self.children ) < len( self.values ):
924                 self.addChild()
925             if self.resized:
926                 self.render()
927             self.resize( *self.size )
928
929     class PositionGraph( evas.ClippedSmartObject ):
930         def __init__( self, *args, **kargs ):
931             evas.ClippedSmartObject.__init__( self, *args, **kargs )
932             self.img = self.Image()
933             self.img.alpha = True
934             self.img.colorspace = evas.EVAS_COLORSPACE_ARGB8888
935             self.img.pos = self.pos
936             self.img.show()
937             self.surface = None
938             self.ctx = None
939             self.values = []
940
941         def proj( self, azim, elev ):
942             x = math.sin( math.radians( azim ) )*( elev/90.0 )
943             y = math.cos( math.radians( azim ) )*( elev/90.0 )
944             return ( x, y )
945
946         def resize( self, w, h ):
947             self.surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, w, h)
948             self.ctx = cairo.Context(self.surface)
949             self.ctx.scale( w, h )
950             #self.ctx.set_antialias( cairo.ANTIALIAS_NONE )
951             self.img.image_size = ( w, h )
952             self.update()
953             self.img.resize( *self.img.image_size )
954             self.img.fill_set(0, 0, *self.img.image_size )
955
956         def update( self, values = None ):
957             if values is not None:
958                 self.values = values
959             if self.ctx:
960                 self.ctx.set_operator( cairo.OPERATOR_CLEAR )
961                 self.ctx.paint()
962                 self.ctx.set_operator( cairo.OPERATOR_OVER )
963                 self.ctx.set_line_width( max( self.ctx.device_to_user_distance( 2, 2 ) ) )
964                 #self.ctx.move_to(0.20, 0.10)
965                 #self.ctx.line_to(0.40, 0.30)
966                 #self.ctx.rel_line_to(-0.20, 0.0)
967                 #self.ctx.close_path()
968                 #self.ctx.stroke()
969                 # background
970                 self.ctx.set_source_rgba(1, 1, 1, 0.5)
971                 self.ctx.arc( 0.50, 0.50, 0.45, 0.0, math.pi*2 )
972                 self.ctx.fill()
973                 self.ctx.set_source_rgba(0, 0, 0, 1)
974                 self.ctx.arc( 0.50, 0.50, 0.45, 0.0, math.pi*2 )
975                 self.ctx.new_sub_path()
976                 self.ctx.arc( 0.50, 0.50, 0.30, 0.0, math.pi*2 )
977                 self.ctx.new_sub_path()
978                 self.ctx.arc( 0.50, 0.50, 0.15, 0.0, math.pi*2 )
979                 self.ctx.stroke()
980                 # locations
981                 for sv, azim, elev in self.values:
982                     sv = str( sv )
983                     x, y = self.proj( azim, elev )
984                     self.ctx.set_source_rgba(0, 1, 0, 1)
985                     self.ctx.arc(
986                         0.50+x*0.45,
987                         0.50+y*0.45,
988                         max( self.ctx.device_to_user_distance( 15, 15 ) ),
989                         0.0, math.pi*2
990                     )
991                     self.ctx.fill()
992                     self.ctx.set_source_rgba(0, 0, 0, 1)
993                     self.ctx.set_font_size( max( self.ctx.device_to_user_distance( 20, 20 ) ))
994                     x_bearing, y_bearing, width, height = self.ctx.text_extents(sv)[:4]
995                     self.ctx.move_to(
996                         0.50+x*0.45 - width / 2 - x_bearing,
997                         0.50+y*0.45 - height / 2 - y_bearing
998                     )
999                     self.ctx.show_text( sv )
1000                 #self.surface.write_to_png( "/tmp/zhone-location.png" )
1001                 #self.img.file_set( "/tmp/zhone-location.png" )
1002                 self.img.image_data_set( self.surface.get_data() )
1003
1004     def __init__(self, main):
1005         edje_group.__init__( self, main, "location" )
1006         self.signalgraph = self.SignalGraph( self.evas )
1007         self.positiongraph = self.PositionGraph( self.evas )
1008
1009         self.page = "left"
1010         self.signal_emit( "activate_button_select_%s" % self.page, "" )
1011
1012         self.accuracy = None
1013         self.position = None
1014         self.update()
1015
1016         #self.signalgraph.update( [(1, 20.5), (2, 34.3), (5, 25.9), (10, 0.5)] )
1017         #self.positiongraph.update( [(1, 20.0, 90.0), (2, 34.3, 45.0), (5, 225.9, 0.0), (10, 0.5, 30.0)] )
1018
1019     def update( self ):
1020         text = []
1021         if self.position:
1022             text.append( "Lat:<tab>%s<br>Lon:<tab>%s<br>Alt:<tab>%s" % self.position )
1023         else:
1024             text.append( "Lat:<tab>N/A<br>Lon:<tab>N/A<br>Alt:<tab>N/A" )
1025         if self.accuracy:
1026             text.append( "P/H/V-DOP:<tab>%s/%s/%s" % self.accuracy )
1027         else:
1028             text.append( "P/H/V-DOP:<tab>N/A")
1029         if self.page == "left":
1030             self.part_text_set( "status", u"<br>".join( text ) )
1031             if self.part_swallow_get( "swallow" ):
1032                 self.part_swallow_get( "swallow" ).hide()
1033                 self.part_unswallow( self.part_swallow_get( "swallow" ) )
1034         elif self.page == "middle":
1035             self.part_text_set( "status", u"" )
1036             if self.part_swallow_get( "swallow" ):
1037                 self.part_swallow_get( "swallow" ).hide()
1038                 self.part_unswallow( self.part_swallow_get( "swallow" ) )
1039             self.part_swallow( "swallow", self.signalgraph )
1040             self.signalgraph.show()
1041         elif self.page == "right":
1042             self.part_text_set( "status", u"" )
1043             if self.part_swallow_get( "swallow" ):
1044                 self.part_swallow_get( "swallow" ).hide()
1045                 self.part_unswallow( self.part_swallow_get( "swallow" ) )
1046             self.part_swallow( "swallow", self.positiongraph )
1047             self.positiongraph.show()
1048
1049     def onAccuracyChanged( self, fields, pdop, hdop, vdop ):
1050         self.accuracy = ( pdop, hdop, vdop )
1051         self.update()
1052
1053     def onPositionChanged( self, fields, timestamp, lat, lon, alt ):
1054         self.position = ( lat, lon, alt )
1055         self.update()
1056
1057     def onSatellitesChanged( self, satellites ):
1058         if self.page == "middle":
1059             signallist = [ (int(sat[0]), float(sat[4])) for sat in satellites ]
1060             self.signalgraph.update( signallist )
1061         positionlist = [ (int(sat[0]), int(sat[3]), int(sat[2])) for sat in satellites ]
1062         self.positiongraph.update( positionlist )
1063
1064     def onShow( self ):
1065         if dbus_object.usage_obj:
1066             dbus_object.usage_iface.RequestResource("GPS")
1067         self.update()
1068
1069     def onHide( self ):
1070         if dbus_object.usage_obj:
1071             dbus_object.usage_iface.ReleaseResource("GPS")
1072
1073     @edje.decorators.signal_callback( "mouse,clicked,1", "button_select_*" )
1074     def on_edje_signal_button_list_pressed( self, emission, source ):
1075         self.page = source.split( "_" )[-1]
1076         self.signal_emit( "deactivate_button_select_left", "" )
1077         self.signal_emit( "deactivate_button_select_middle", "" )
1078         self.signal_emit( "deactivate_button_select_right", "" )
1079         self.signal_emit( "activate_button_select_%s" % self.page, "" )
1080         self.update()
1081
1082 #----------------------------------------------------------------------------#
1083 class pyphone_message(edje_group):
1084 #----------------------------------------------------------------------------#
1085     def __init__(self, main):
1086         edje_group.__init__(self, main, "message")
1087         self.text = []
1088         self.button_labels2 = [
1089             [
1090                 [u".,?!", u"abc", "def", ""],
1091                 [u"ghi", u"jkl", "mno", ""],
1092                 [u"pqrs", u"tuv", "wxyz", ""],
1093                 [u"", u"", u"⇦⇧⇨", ""],
1094             ],
1095             [
1096                 ["", "", "", ""],
1097                 ["", "", "", ""],
1098                 ["", "", "", ""],
1099                 ["", "", "", ""],
1100             ]
1101         ]
1102         self.button_labels = [
1103             [
1104                 ["1", "2", "3", u"↤"],
1105                 ["4", "5", "6", u"↲"],
1106                 ["7", "8", "9", "Abc"],
1107                 ["+", "0", u"⇩", "+"],
1108             ],
1109             [
1110                 ["1", "?", "", ""],
1111                 [".", ",", "", ""],
1112                 ["!", "", "", ""],
1113                 ["", "", "", ""],
1114             ],
1115             [
1116                 ["", "2", "c", ""],
1117                 ["", "a", "b", ""],
1118                 ["", "", "", ""],
1119                 ["", "", "", ""],
1120             ],
1121             [
1122                 ["", "", "3", "f"],
1123                 ["", "", "d", "e"],
1124                 ["", "", "", ""],
1125                 ["", "", "", ""],
1126             ],
1127             [
1128                 ["", "", "", u"↤"],
1129                 ["", "", "", ""],
1130                 ["", "", "", ""],
1131                 ["", "", "", ""],
1132             ],
1133             [
1134                 ["", "", "", ""],
1135                 ["4", "i", "", ""],
1136                 ["g", "h", "", ""],
1137                 ["", "", "", ""],
1138             ],
1139             [
1140                 ["", "", "", ""],
1141                 ["", "5", "l", ""],
1142                 ["", "j", "k", ""],
1143                 ["", "", "", ""],
1144             ],
1145             [
1146                 ["", "", "", ""],
1147                 ["", "", "6", "o"],
1148                 ["", "", "m", "n"],
1149                 ["", "", "", ""],
1150             ],
1151             [
1152                 ["", "", "", ""],
1153                 ["", "", "", ""],
1154                 ["", "", "", ""],
1155                 ["", "", "", ""],
1156             ],
1157             [
1158                 ["", "", "", ""],
1159                 ["", "s", "", ""],
1160                 ["7", "r", "", ""],
1161                 ["p", "q", "", ""],
1162             ],
1163             [
1164                 ["", "", "", ""],
1165                 ["", "", "", ""],
1166                 ["", "8", "v", ""],
1167                 ["", "t", "u", ""],
1168             ],
1169             [
1170                 ["", "", "", ""],
1171                 ["", "", "", "z"],
1172                 ["", "", "9", "y"],
1173                 ["", "", "w", "x"],
1174             ],
1175             [
1176                 ["", "", "", ""],
1177                 ["", "", "", ""],
1178                 ["", "", "", ""],
1179                 ["", "", "", ""],
1180             ],
1181             [
1182                 ["", "", "", ""],
1183                 ["", "", "", ""],
1184                 ["", "", "", ""],
1185                 ["", "", "", ""],
1186             ],
1187             [
1188                 ["", "", "", ""],
1189                 ["", "", "", ""],
1190                 ["", "", "", ""],
1191                 ["", " ", "", ""],
1192             ],
1193             [
1194                 ["", "", "", ""],
1195                 ["", "", "", ""],
1196                 ["", "", u"⇧", ""],
1197                 ["", u"⇦", u"⇩", u"⇨"],
1198             ],
1199             [
1200                 ["", "", "", ""],
1201                 ["", "", "", ""],
1202                 ["", "", "", ""],
1203                 ["", "", "", ""],
1204             ]
1205         ]
1206         self.set_button_text(0)
1207         self.active = 0
1208
1209     @edje.decorators.signal_callback("kb_button_mouse_up", "*")
1210     def on_edje_signal_dialer_button_mouse_up(self, emission, source):
1211         now = time.time()
1212         x = int(source[-3:-2])
1213         y = int(source[-1:])
1214         key = self.button_labels[self.active][y][x]
1215         self.text.append(key)
1216         # The trailing whitespace is a workaround for the one char invisible
1217         # bug due to some problems with scaling of text parts.
1218         self.part_text_set("label", "".join(self.text)+" ")
1219         self.set_button_text(0)
1220         logger.debug( "mouse up: %s" % time.time()-now )
1221
1222     @edje.decorators.signal_callback("kb_button_mouse_down", "*")
1223     def on_edje_signal_dialer_button_mouse_down(self, emission, source):
1224         now = time.time()
1225         x = int(source[-3:-2])
1226         y = int(source[-1:])
1227         num = 4*y+x+1
1228         if self.active == 0:
1229             self.set_button_text(num)
1230         logger.debug( "mouse down: %s" % time.time()-now )
1231
1232     @edje.decorators.signal_callback("kb_mutton_mouse_in", "*")
1233     def on_edje_signal_dialer_button_mouse_in(self, emission, source):
1234         now = time.time()
1235         x = int(source[-3:-2])
1236         y = int(source[-1:])
1237         self.part_text_set("label_preview", self.button_labels[self.active][y][x])
1238         logger.debug( "mouse in: %s" % time.time()-now )
1239
1240     def set_button_text(self, num):
1241         for i in xrange(4):
1242             for j in xrange(4):
1243                 text = self.button_labels[num][j][i]
1244                 self.part_text_set("label_%d_%d" % (i,j) , text)
1245         self.active = num
1246
1247         if num != 0:
1248             num = 1
1249
1250         for i in xrange(4):
1251             for j in xrange(4):
1252                 text = self.button_labels2[num][j][i]
1253                 self.part_text_set("label2_%d_%d" % (i,j) , text)
1254
1255 #----------------------------------------------------------------------------#
1256 class pyphone_list_choose(edje_group):
1257 #----------------------------------------------------------------------------#
1258     def __init__( self, main ):
1259         edje_group.__init__( self, main, "list_choose" )
1260         self.text = ""
1261         self.list_data = None
1262         self.cb_data = None
1263         self.cb = None
1264
1265     def updateList( self):
1266         self.pages = max( ( len( self.list_data ) - 1 ) / 6 + 1, 1 )
1267         if self.page >= self.pages:
1268             self.page = self.pages - 1
1269         if self.page < 0:
1270             self.page = 0
1271         self.current = self.list_data[self.page*6:(self.page+1)*6]
1272         self.part_text_set( "pager", u"".join( [u"□"]*self.page+[u"▣"]+[u"□"]*(self.pages-self.page-1) ) )
1273         for i in range( 0, len( self.current ) ):
1274             self.part_text_set( "label_main_list_%i" % i, self.current[i][0] )
1275             self.part_text_set( "label_sub_list_%i" % i, self.current[i][1] )
1276         for i in range( len( self.current ), 6):
1277             self.part_text_set( "label_main_list_%i" % i, u"" )
1278             self.part_text_set( "label_sub_list_%i" % i, u"" )
1279
1280     @edje.decorators.signal_callback( "mouse,clicked,1", "target_list_*" )
1281     def on_edje_signal_button_list_pressed( self, emission, source ):
1282         id = int( source.split( "_" )[-1] )
1283         self.main.transition_to(self.parent_name)
1284         self.cb( self.current[id], self.cb_data )
1285
1286     @edje.decorators.signal_callback( "mouse,clicked,1", "button_action_left" )
1287     def on_edje_signal_button_action_left_pressed( self, emission, source ):
1288         self.page -= 1
1289         self.updateList()
1290
1291     @edje.decorators.signal_callback( "mouse,clicked,1", "button_action_right" )
1292     def on_edje_signal_button_action_right_pressed( self, emission, source ):
1293         self.page += 1
1294         self.updateList()
1295
1296     def setup( self, parent_name, list_data, cb_data, cb ):
1297         self.parent_name = parent_name
1298         self.list_data = list_data
1299         self.cb_data = cb_data
1300         self.cb = cb
1301
1302         self.current = []
1303         self.page = 0
1304         self.main.transition_to("list_choose")
1305
1306         self.updateList()
1307
1308 #----------------------------------------------------------------------------#
1309 class pyphone_number_edit( edje_group ):
1310 #----------------------------------------------------------------------------#
1311     TIMEOUT = 2.0
1312     def __init__( self, main ):
1313         edje_group.__init__( self, main, "number_edit" )
1314         self.text = ""
1315         self.cb_data = None
1316         self.cb = None
1317         self.last = 0.0
1318
1319     @edje.decorators.signal_callback( "mouse,clicked,1", "button_*" )
1320     def on_edje_signal_button_pressed( self, emission, source ):
1321         key = source.split( "_", 1 )[1]
1322         if key in ( "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "#" ):
1323             self.text += key
1324             # The trailing whitespace is a workaround for the one char invisible
1325             # bug due to some problems with scaling of text parts.
1326             self.part_text_set( "label", u" %s " % self.text )
1327         elif key in "star":
1328             if self.text and ( time.time()-self.last < self.TIMEOUT ):
1329                 if self.text[-1] == "*":
1330                     self.text = self.text[:-1]+"+"
1331                 elif self.text[-1] == "+":
1332                     self.text = self.text[:-1]+"*"
1333                 else:
1334                     self.text += "*"
1335             else:
1336                 self.text += "*"
1337             self.part_text_set( "label", u" %s " % self.text )
1338         elif key in "hash":
1339             self.text += "#"
1340             self.part_text_set( "label", u" %s " % self.text )
1341         elif key in "delete":
1342             self.text = self.text[:-1]
1343             self.part_text_set( "label", u" %s " % self.text )
1344         elif key in "done":
1345             self.main.transition_to(self.parent_name)
1346             self.cb( self.text, self.cb_data )
1347         self.last = time.time()
1348
1349     def setup( self, parent_name, text, title, cb_data, cb ):
1350         self.parent_name = parent_name
1351         self.text = text
1352         self.title = title
1353         self.cb_data = cb_data
1354         self.cb = cb
1355         self.part_text_set( "label_description", u" %s " % self.title )
1356         self.part_text_set( "label", u" %s " % self.text )
1357         self.main.transition_to("number_edit")
1358
1359 #----------------------------------------------------------------------------#
1360 class pyphone_pin_edit( edje_group ):
1361 #----------------------------------------------------------------------------#
1362     DELAY = 1.0
1363     def __init__( self, main ):
1364         edje_group.__init__( self, main, "number_edit" )
1365         self.text = ""
1366         self.cb_data = None
1367         self.cb = None
1368         self.timer = None
1369         self.last = 0.0
1370         self.part_text_set( "label_main_star", u"" )
1371         self.part_text_set( "label_sub_star", u"" )
1372         self.part_text_set( "label_main_hash", u"" )
1373         self.part_text_set( "label_sub_hash", u"" )
1374
1375     @edje.decorators.signal_callback( "mouse,clicked,1", "button_*" )
1376     def on_edje_signal_button_pressed( self, emission, source ):
1377         key = source.split( "_", 1 )[1]
1378         if key in ( "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" ):
1379             self.text += key
1380             # The trailing whitespace is a workaround for the one char invisible
1381             # bug due to some problems with scaling of text parts.
1382             output = u"●"*( len( self.text ) - 1) + self.text[-1]
1383             self.part_text_set( "label", u" %s " % output )
1384             if self.timer:
1385                 self.timer.delete()
1386             self.timer = ecore.timer_add( self.DELAY, self.timerCb )
1387         elif key in "delete":
1388             self.text = self.text[:-1]
1389             output = u"●"*len( self.text )
1390             self.part_text_set( "label", u" %s " % output )
1391         elif key in "done":
1392             self.main.transition_to(self.parent_name)
1393             self.cb( self.text, self.cb_data )
1394         self.last = time.time()
1395
1396     def timerCb( self ):
1397         output = u"●"*len( self.text )
1398         self.part_text_set( "label", u" %s " % output )
1399         self.timer = None
1400         return False
1401
1402     def setup( self, parent_name, text, title, cb_data, cb ):
1403         self.parent_name = parent_name
1404         self.text = text
1405         self.title = title
1406         self.cb_data = cb_data
1407         self.cb = cb
1408         self.part_text_set( "label_description", u" %s " % self.title )
1409         self.part_text_set( "label", u" %s " % self.text )
1410         self.main.transition_to("pin_edit")
1411
1412 #----------------------------------------------------------------------------#
1413 class pyphone_text_show( edje_group ):
1414 #----------------------------------------------------------------------------#
1415     def __init__( self, main ):
1416         edje_group.__init__( self, main, "text_show" )
1417
1418     def setup( self, parent_name, text ):
1419         self.parent_name = parent_name
1420         self.part_text_set( "text", text )
1421         self.main.transition_to("text_show")
1422
1423 #----------------------------------------------------------------------------#
1424 class pyphone_text_edit( edje_group ):
1425 #----------------------------------------------------------------------------#
1426     def __init__( self, main ):
1427         edje_group.__init__( self, main, "text_edit" )
1428         self.text = ""
1429         self.cb_data = None
1430         self.cb = None
1431         self.shift_down = False
1432
1433     def onShow( self ):
1434         self.focus = True
1435         if illume:
1436             illume.kbd_show()
1437
1438     def onHide( self ):
1439         self.focus = False
1440         if illume:
1441             illume.kbd_hide()
1442
1443     @evas.decorators.key_down_callback
1444     def on_key_down( self, event ):
1445         key = event.string
1446         if key == "\x08":
1447             self.text = self.text[:-1]
1448         elif key is not None:
1449             self.text += key
1450         self.part_text_set( "label", self.text )
1451
1452     @edje.decorators.signal_callback( "mouse,clicked,1", "button_*" )
1453     def on_edje_signal_button_pressed( self, emission, source ):
1454         key = source.split( "_", 1 )[1]
1455         if key == "done":
1456             self.main.transition_to(self.parent_name)
1457             self.cb( self.text, self.cb_data )
1458         elif key == "cancel":
1459             self.main.transition_to(self.parent_name)
1460
1461     def setup( self, parent_name, text, title, cb_data, cb ):
1462         self.parent_name = parent_name
1463         self.text = text
1464         self.title = title
1465         self.cb_data = cb_data
1466         self.cb = cb
1467         self.part_text_set( "label_description", u" %s " % self.title )
1468         self.part_text_set( "label", self.text )
1469         self.main.transition_to("text_edit")
1470
1471 #----------------------------------------------------------------------------#
1472 class pyphone_menu( edje_group ):
1473 #----------------------------------------------------------------------------#
1474     def __init__( self, main ):
1475         edje_group.__init__( self, main, "menu" )
1476         self.buttons = None
1477         self.cb = None
1478         self.deactivate()
1479
1480     @edje.decorators.signal_callback( "mouse,clicked,1", "target_*" )
1481     def on_edje_signal_button_bottom_middle_pressed( self, emission, source ):
1482         if source == "target_cancel":
1483             self.deactivate()
1484             self.cb( "cancel" )
1485         else:
1486             id = int( source.split( "_", 1 )[1] )
1487             self.deactivate()
1488             self.cb( self.part_text_get( "target_label_%i" % id ) )
1489
1490     def activate( self, buttons, cb ):
1491         self.buttons = buttons
1492         self.cb = cb
1493         count = len( buttons )
1494         assert count == 4
1495         self.part_text_set( "target_label_0", buttons[0] )
1496         self.part_text_set( "target_label_1", buttons[1] )
1497         self.part_text_set( "target_label_2", buttons[2] )
1498         self.part_text_set( "target_label_3", buttons[3] )
1499         self.signal_emit( "visible", "" )
1500
1501     def deactivate( self ):
1502         self.signal_emit( "invisible", "" )
1503
1504
1505 #----------------------------------------------------------------------------#
1506 class pyphone_main_menu( edje_group ):
1507 #----------------------------------------------------------------------------#
1508     def __init__( self, main ):
1509         edje_group.__init__( self, main, "main_menu" )
1510         self.signal_emit( "invisible", "" )
1511
1512     @edje.decorators.signal_callback( "mouse,clicked,1", "target_list_*" )
1513     def on_edje_signal_button_list_pressed( self, emission, source ):
1514         id = int( source.split( "_" )[-1] )
1515         if( id == 5 ):
1516           os.system("halt")
1517         if( id == 0 ):
1518           fp = os.popen("sleep 5 && \
1519                          n=/tmp/scap$$.png && \
1520                          fbgrab $n && \
1521                          curl \
1522                           -F file=@$n \
1523                           -F key=secret \
1524                           -F model=`uname -n` \
1525                           -F submit=Upload \
1526                           -F text=no\ comment \
1527                           http://scap.linuxtogo.org/tickle.php && \
1528                          rm $n &")
1529
1530     @edje.decorators.signal_callback( "mouse,clicked,1", "target_cancel" )
1531     def on_edje_signal_button_bottom_middle_pressed( self, emission, source ):
1532         self.deactivate()
1533
1534     def activate( self, group ):
1535         self.group = group
1536         self.signal_emit( "visible", "" )
1537         for i in range( 1, 5 ):
1538           self.part_text_set( "label_main_list_%i" % i, u"" )
1539           self.part_text_set( "label_sub_list_%i" % i, u"" )
1540
1541         self.part_text_set( "label_main_list_0", u"Take screenshot" )
1542         self.part_text_set( "label_sub_list_0", u"and upload to http://scap.linuxtogo.org" )
1543         self.part_text_set( "label_main_list_5", u"Exit" )
1544         self.part_text_set( "label_sub_list_5", u"Stop and exit Zhone" )
1545
1546     def deactivate( self ):
1547         self.signal_emit( "invisible", "" )
1548         self.main.transition_to(self.group)
1549
1550
1551 #----------------------------------------------------------------------------#
1552 class pyphone_suspend( edje_group ):
1553 #----------------------------------------------------------------------------#
1554     def __init__( self, main ):
1555         edje_group.__init__( self, main, "suspend" )
1556         self.deactivate()
1557         self.stage = -1
1558
1559     def activate( self ):
1560         self.stage = -1
1561         for id in range( 0, 4 ):
1562             self.signal_emit( "unhighlight_%i" % id, "" )
1563         self.signal_emit( "visible", "" )
1564
1565     def deactivate( self ):
1566         self.stage = -1
1567         self.signal_emit( "invisible", "" )
1568
1569     def enter_stage( self, stage ):
1570         self.stage = stage
1571         for id in range( 0, 4 ):
1572             if id <= self.stage:
1573                 self.signal_emit( "highlight_%i" % id, "" )
1574             else:
1575                 self.signal_emit( "unhighlight_%i" % id, "" )
1576
1577 #----------------------------------------------------------------------------#
1578 class pyphone_lock( edje_group ):
1579 #----------------------------------------------------------------------------#
1580     def __init__( self, main ):
1581         edje_group.__init__( self, main, "lock" )
1582         self.deactivate()
1583
1584     @edje.decorators.signal_callback( "mouse,down,1", "target_*" )
1585     def on_edje_signal_button_pressed( self, emission, source ):
1586         id = int( source.split( "_", 1 )[1] )
1587         if id == self.step+1: # correct button
1588             self.signal_emit( "activate_%i" % id, "" )
1589             self.step += 1
1590         else:
1591             for id in range( 1, 5 ):
1592                 self.signal_emit( "deactivate_%i" % id, "" )
1593             self.step = 0
1594         if self.step == 4:
1595             self.signal_emit( "invisible", "" )
1596
1597     def activate( self ):
1598         self.step = 0
1599         for id in range( 1, 5 ):
1600             self.signal_emit( "deactivate_%i" % id, "" )
1601         self.signal_emit( "visible", "" )
1602
1603     def deactivate( self ):
1604         self.signal_emit( "invisible", "" )
1605
1606 #----------------------------------------------------------------------------#
1607 class pyphone_error( edje_group ):
1608 #----------------------------------------------------------------------------#
1609     def __init__( self, main ):
1610         edje_group.__init__( self, main, "error" )
1611         self.buttons = None
1612         self.cb_data = None
1613         self.cb = None
1614         self.deactivate()
1615
1616     @edje.decorators.signal_callback( "mouse,down,1", "button_*" )
1617     def on_edje_signal_button_pressed( self, emission, source ):
1618         id = int( source.split( "_", 1 )[1] )
1619         self.deactivate()
1620         self.cb( self.part_text_get( "label_%i" % id ), self.cb_data )
1621
1622     def activate( self, description, buttons, cb_data, cb ):
1623         self.buttons = buttons
1624         self.cb_data = cb_data
1625         self.cb = cb
1626         count = len( buttons )
1627         assert 1 <= count <= 3
1628         self.part_text_set( "description", description )
1629         if count == 1:
1630             self.signal_emit( "hide_0", "" )
1631             self.part_text_set( "label_0", u"" )
1632             self.signal_emit( "show_1", "" )
1633             self.part_text_set( "label_1", buttons[0] )
1634             self.signal_emit( "hide_2", "" )
1635             self.part_text_set( "label_2", u"" )
1636         elif count == 2:
1637             self.signal_emit( "show_0", "" )
1638             self.part_text_set( "label_0", buttons[0] )
1639             self.signal_emit( "hide_1", "" )
1640             self.part_text_set( "label_1", u"" )
1641             self.signal_emit( "show_2", "" )
1642             self.part_text_set( "label_2", buttons[1] )
1643         elif count == 3:
1644             self.signal_emit( "show_0", "" )
1645             self.part_text_set( "label_0", buttons[0] )
1646             self.signal_emit( "show_1", "" )
1647             self.part_text_set( "label_1", buttons[1] )
1648             self.signal_emit( "show_2", "" )
1649             self.part_text_set( "label_2", buttons[2] )
1650         self.signal_emit( "visible", "" )
1651
1652     def deactivate( self ):
1653         self.signal_emit( "invisible", "" )
1654
1655 #----------------------------------------------------------------------------#
1656 class GSMAgent( object ):
1657 #----------------------------------------------------------------------------#
1658     def __init__( self, main ):
1659         self.main = main
1660         self.state = "Waiting for DBus"
1661         self.onState = []
1662
1663     def registerStateCallback( self, callback ):
1664         self.onState.append( callback )
1665
1666     def setState( self, state ):
1667         logger.debug( state )
1668         self.state = state
1669         for cb in self.onState:
1670             cb( state )
1671
1672     def cbDBusReady( self ):
1673         """
1674         This is called to start the authentication process
1675         """
1676         self.setState( "Turning on Antenna" )
1677         dbus_object.gsm_device_iface.SetAntennaPower(
1678             True,
1679             reply_handler=self.cbAntennaPowerReply,
1680             error_handler=self.cbAntennaPowerError,
1681         )
1682
1683     def cbAntennaPowerReply( self ):
1684         logger.info( "Antenna power OK. Registering to network now." )
1685         self.setState( "Registering to network" )
1686         dbus_object.gsm_network_iface.Register(
1687             reply_handler=self.cbRegisterReply,
1688             error_handler=self.cbRegisterError
1689         )
1690
1691     def cbAntennaPowerError( self, e ):
1692         logger.info( "SIM seems to be protected. Checking auth status now" )
1693         self.setState( "Reading authentication status" )
1694         dbus_object.gsm_sim_iface.GetAuthStatus(
1695             reply_handler=self.cbAuthStatusReply,
1696             error_handler=self.cbAuthStatusError,
1697         )
1698
1699     def cbAuthStatusReply( self, authstatus ):
1700         if authstatus == "READY":
1701             self.setState( "Telephony Ready" )
1702             # restart auth, should lead to registering this time...
1703             self.cbDBusReady()
1704         elif authstatus == "SIM PIN":
1705             self.setState( "Waiting for PIN" )
1706             self.main.groups["pin_edit"].setup(
1707                 "main",
1708                 "", # number
1709                 "Enter PIN", # title
1710                 None, # reference
1711                 self.cbPINDone
1712             )
1713         elif authstatus == "SIM PUK":
1714             self.setState( "Waiting for PUK" )
1715             self.main.groups["pin_edit"].setup(
1716                 "main",
1717                 "", # number
1718                 "Enter PUK", # title
1719                 None, # reference
1720                 self.cbPUKDone
1721             )
1722         else:
1723             logger.exception( "Unknown authentication status %s" % authstatus )
1724
1725     def cbAuthStatusError( self, e ):
1726         self.setState( "Failed to read authentication status" )
1727         logger.exception( e )
1728
1729     def cbPINDone( self, pin, *args ):
1730         self.setState( "Sending PIN" )
1731         dbus_object.gsm_sim_iface.SendAuthCode(
1732             pin,
1733             reply_handler=self.cbAuthCodeReply,
1734             error_handler=self.cbAuthCodeError
1735         )
1736
1737     def cbAuthCodeReply( self ):
1738         self.cbAuthStatusReply( "READY" )
1739
1740     def cbAuthCodeError( self, e ):
1741         self.setState( "Error while sending PIN" )
1742         logger.exception( e )
1743         # retry
1744         self.cbDBusReady()
1745
1746     def cbPUKDone( self, puk, *args ):
1747         self.main.groups["pin_edit"].setup(
1748             "main",
1749             "", # number
1750             "Enter new PIN", # title
1751             puk, # reference
1752             self.cbNewPINDone
1753         )
1754
1755     def cbNewPINDone( self, pin, puk ):
1756         self.setState( "Sending PUK and new PIN" )
1757         dbus_object.gsm_sim_iface.Unlock(
1758             pin, puk,
1759             reply_handler=self.cbUnlockReply,
1760             error_handler=self.cbUnlockError
1761         )
1762
1763     def cbUnlockReply( self ):
1764         self.cbAuthStatusReply( "READY" )
1765
1766     def cbUnlockError( self, e ):
1767         self.setState( "Error while sending PIN" )
1768         logger.exception( e )
1769         # retry
1770         self.cbDBusReady()
1771
1772     def cbRegisterReply( self ):
1773         self.setState( "Registered to network" )
1774         self.main.groups["main"].targets["phone"] = True
1775         self.main.groups["main"].update()
1776         if dbus_object.gsm_sim_iface.GetSimReady():
1777             self.main.groups["contacts"].prepare()
1778             self.main.groups["sms"].prepare()
1779
1780     def cbRegisterError( self, e ):
1781         self.setState( "Failed to register to network" )
1782         logger.exception( e )
1783         if dbus_object.gsm_sim_iface.GetSimReady():
1784             self.main.groups["contacts"].prepare()
1785             self.main.groups["sms"].prepare()
1786
1787 #----------------------------------------------------------------------------#
1788 class GUI(object):
1789 #----------------------------------------------------------------------------#
1790     def __init__( self, options, args ):
1791
1792         logger.debug( "GUI init" )
1793
1794         edje.frametime_set(1.0 / options.fps)
1795
1796         self.evas_canvas = EvasCanvas(
1797             fullscreen = options.fullscreen,
1798             engine = options.engine,
1799             size = options.geometry
1800         )
1801
1802         self.agents = {}
1803
1804         self.agents["gsm"] = agent = GSMAgent( self )
1805         agent.registerStateCallback( self.onAgentStateChanged )
1806
1807         self.groups = {}
1808
1809         self.groups["swallow"] = edje_group(self, "swallow")
1810         self.evas_canvas.evas_obj.data["swallow"] = self.groups["swallow"]
1811
1812         for page in (
1813                 "main",
1814                 "phone", "call", "dtmf",
1815                 "sms",
1816                 "contacts",
1817                 "location",
1818                 "configuration",
1819                 "list_choose", "number_edit", "pin_edit", "text_edit", "text_show", "message"
1820             ):
1821             ctor = globals().get( "pyphone_%s" % page, None )
1822             if ctor:
1823                 self.groups[page] = ctor( self )
1824                 self.evas_canvas.evas_obj.data[page] = self.groups[page]
1825
1826         for overlay in ("main_menu", "menu", "lock", "error", "suspend"):
1827             ctor = globals().get( "pyphone_%s" % overlay, None )
1828             if ctor:
1829                 self.groups[overlay] = ctor( self )
1830                 self.evas_canvas.evas_obj.data[overlay] = self.groups[overlay]
1831                 self.groups["swallow"].part_swallow( overlay, self.groups[overlay] )
1832
1833         self.groups["swallow"].show()
1834
1835         self.current_group = self.groups[options.start]
1836         self.previous_group = None
1837         self.groups["swallow"].part_swallow("swallow", self.current_group)
1838         ecore.timer_add(60.0, self.display_time)
1839         self.display_time()
1840
1841         ecore.idle_enterer_add( self.dbus_objectInit )
1842
1843         dbus_object.onAccuracyChanged.append( self.groups["location"].onAccuracyChanged )
1844         dbus_object.onPositionChanged.append( self.groups["location"].onPositionChanged )
1845         dbus_object.onSatellitesChanged.append( self.groups["location"].onSatellitesChanged )
1846         dbus_object.onCallStatus.append( self.groups["call"].onCallStatus )
1847         dbus_object.onReadyStatus.append( self.groups["contacts"].onReadyStatus )
1848         dbus_object.onReadyStatus.append( self.groups["sms"].onReadyStatus )
1849         dbus_object.onIncomingMessage.append( self.groups["sms"].onIncomingMessage )
1850         dbus_object.onIdleStateChanged.append( self.lock_on_idle )
1851         dbus_object.onInputEvent.append( self.suspend_on_powerbutton )
1852
1853         logger.debug( "GUI init done" )
1854
1855     def run( self ):
1856         logger.debug( "entering mainloop" )
1857         ecore.main_loop_begin()
1858
1859     def shutdown( self ):
1860         ecore.main_loop_quit()
1861
1862     def dbus_objectInit( self ):
1863         logger.debug( "dbus_objectInit..." )
1864         if not dbus_object.initialize():
1865             self.display_state( "connecting w/ dbus..." )
1866             # try again later
1867             ecore.timer_add( 10.0, self.dbus_objectInit )
1868             return False
1869         else:
1870             self.dbus_objectInitOK()
1871         return False
1872
1873     def dbus_objectInitOK( self ):
1874         logger.debug( "dbus_objectInitOK!" )
1875
1876         if dbus_object.gsm_device_obj is not None:
1877             self.agents["gsm"].cbDBusReady()
1878         if dbus_object.gps_obj is not None:
1879             self.groups["main"].targets["location"] = True
1880             self.groups["main"].update()
1881
1882     def lock_on_idle( self, state ):
1883         if state == "LOCK":
1884             self.groups["lock"].activate()
1885
1886     def suspend_on_powerbutton( self, name, action, seconds ):
1887         if name+action == "AUXpressed":
1888             # FIXME launch transition to main screen
1889             pass
1890
1891         elif name+action == "POWERpressed":
1892             self.groups["suspend"].activate()
1893             self.groups["suspend"].enter_stage(0)
1894
1895         elif name+action == "POWERheld":
1896             if seconds < 1:
1897             # we should aleady be in stage 0, but let’s be explicit
1898                 self.groups["suspend"].enter_stage(0)
1899             elif 1 <= seconds < 3:
1900                 self.groups["suspend"].enter_stage(1)
1901             elif 3 <= seconds:
1902                 self.groups["suspend"].deactivate()
1903
1904         elif name+action == "POWERreleased":
1905             if self.groups["suspend"].stage == 1:
1906                 self.groups["suspend"].enter_stage(2)
1907                 ecore.main_loop_iterate()
1908
1909                 dbus_object.gsm_device_iface.PrepareForSuspend()
1910
1911                 logger.info( "ENTERING SUSPEND" )
1912                 os.system( "apm -s" )
1913                 logger.info( "RETURN FROM SUSPEND" )
1914
1915                 self.groups["suspend"].enter_stage(3)
1916                 ecore.main_loop_iterate()
1917
1918                 dbus_object.gsm_device_iface.RecoverFromSuspend()
1919                 dbus_object.idlenotifier_iface.SetState("BUSY")
1920
1921                 self.groups["suspend"].deactivate()
1922
1923     def display_time(self):
1924         self.groups["main"].part_text_set("label", time.strftime("%H:%M", time.localtime()))
1925         self.groups["main"].part_text_set("label_year", time.strftime("%Y-%m-%d", time.localtime()))
1926         return True
1927
1928     def display_state(self, state):
1929         self.groups["main"].part_text_set("label_year", state )
1930
1931     # TODO better state management for transitions
1932     def transition_to(self, target):
1933         if self.current_group == self.groups[target]:
1934             return
1935         logger.debug( "transition to %s" % target )
1936
1937         self.previous_group = self.current_group
1938         self.previous_group.onHide()
1939         self.previous_group.hide()
1940
1941         self.current_group = self.groups[target]
1942         self.current_group.onShow()
1943         self.current_group.signal_emit("visible", "")
1944         self.groups["swallow"].part_swallow("swallow", self.current_group)
1945         self.previous_group.signal_emit("invisible", "")
1946
1947     def onAgentStateChanged( self, state ):
1948         self.display_state( state )
1949
1950 #----------------------------------------------------------------------------#
1951 class EvasCanvas(object):
1952 #----------------------------------------------------------------------------#
1953     def __init__(self, fullscreen, engine, size):
1954         if engine == "x11":
1955             f = ecore.evas.SoftwareX11
1956         elif engine == "x11-16":
1957             if ecore.evas.engine_type_supported_get("software_x11_16"):
1958                 f = ecore.evas.SoftwareX11_16
1959             else:
1960                 logger.warning( "x11-16 is not supported, fallback to x11" )
1961                 f = ecore.evas.SoftwareX11
1962
1963         self.evas_obj = f(w=size[0], h=size[1])
1964         self.evas_obj.callback_delete_request = self.on_delete_request
1965         self.evas_obj.callback_resize = self.on_resize
1966
1967         self.evas_obj.title = TITLE
1968         self.evas_obj.name_class = (WM_NAME, WM_CLASS)
1969         self.evas_obj.fullscreen = fullscreen
1970         self.evas_obj.size = size
1971         self.evas_obj.evas.image_cache_set( 6*1024*1024 )
1972         self.evas_obj.evas.font_cache_set( 2*1024*1024 )
1973         self.evas_obj.show()
1974
1975     def on_resize(self, evas_obj):
1976         x, y, w, h = evas_obj.evas.viewport
1977         size = (w, h)
1978         evas_obj.data["swallow"].size = size
1979
1980     def on_delete_request(self, evas_obj):
1981         ecore.main_loop_quit()
1982
1983 #----------------------------------------------------------------------------#
1984 class MyOptionParser(OptionParser):
1985 #----------------------------------------------------------------------------#
1986     def __init__(self):
1987         OptionParser.__init__(self)
1988         self.set_defaults(fullscreen = False)
1989         self.add_option("-e",
1990                       "--engine",
1991                       type="choice",
1992                       choices=("x11", "x11-16"),
1993                       default="x11-16",
1994                       help=("which display engine to use (x11, x11-16), "
1995                             "default=%default"))
1996         self.add_option("--fullscreen",
1997                       action="store_true",
1998                       dest="fullscreen",
1999                       help="launch in fullscreen")
2000         self.add_option("--no-fullscreen",
2001                       action="store_false",
2002                       dest="fullscreen",
2003                       help="launch in a window")
2004         self.add_option("-g",
2005                       "--geometry",
2006                       type="string",
2007                       metavar="WxH",
2008                       action="callback",
2009                       callback=self.parse_geometry,
2010                       default=(WIDTH, HEIGHT),
2011                       help="use given window geometry")
2012         self.add_option("-f",
2013                       "--fps",
2014                       type="int",
2015                       default=20,
2016                       help="frames per second to use, default=%default")
2017         self.add_option("-s",
2018                       "--start",
2019                       type="string",
2020                       default="main",
2021                       help="start with the given page")
2022
2023     def parse_geometry(option, opt, value, parser):
2024         try:
2025             w, h = value.split("x")
2026             w = int(w)
2027             h = int(h)
2028         except Exception, e:
2029             raise optparse.OptionValueError("Invalid format for %s" % option)
2030         parser.values.geometry = (w, h)
2031
2032 #----------------------------------------------------------------------------#
2033 class DBusObject( object ):
2034 #----------------------------------------------------------------------------#
2035     def __init__( self ):
2036         self.objects = {}
2037         self.onResourceChanged = []
2038         self.onAccuracyChanged = []
2039         self.onPositionChanged = []
2040         self.onSatellitesChanged = []
2041         self.onCallStatus = []
2042         self.onReadyStatus = []
2043         self.onNetworkStatus = []
2044         self.onIncomingMessage = []
2045         self.onIdleStateChanged = []
2046         self.onInputEvent = []
2047
2048         self.framework_obj = None
2049
2050         self.usage_obj = None
2051         self.usage_iface = None
2052
2053         self.gps_obj = None
2054         self.gps_accuracy_iface = None
2055         self.gps_position_iface = None
2056         self.gps_satellite_iface = None
2057
2058         self.gsm_device_obj = None
2059         self.gsm_device_iface = None
2060         self.device_iface = None
2061         self.idlenotifier_obj = None
2062         self.idlenotifier_iface = None
2063         self.inputnotifier_obj = None
2064         self.inputnotifier_iface = None
2065         self.display_obj = None
2066         self.display_iface = None
2067         self.prefs_obj = None
2068         self.prefs_iface = None
2069
2070         self.fullinit = False
2071
2072     def tryGetProxy( self, busname, objname ):
2073         object = None
2074         try:
2075             object = self.objects[ "%s:%s" % ( busname, objname ) ]
2076         except KeyError:
2077             try:
2078                 object = self.bus.get_object( busname, objname )
2079             except DBusException, e:
2080                 logger.warning( "could not create proxy for %s:%s" % ( busname, objname ) )
2081             else:
2082                 self.objects[ "%s:%s" % ( busname, objname ) ] = object
2083         return object
2084
2085     def initialize( self ):
2086         if self.fullinit:
2087             return True
2088         try:
2089             self.bus = SystemBus( mainloop=e_dbus.DBusEcoreMainLoop() )
2090         except DBusException, e:
2091             logger.error( "could not connect to dbus_object system bus: %s" % e )
2092             return False
2093
2094         # Framework
2095         fw_obj = self.tryGetProxy( 'org.freesmartphone.frameworkd', '/org/freesmartphone/Framework' )
2096         if fw_obj is None:
2097             logger.error( "could not connect to org.freesmartphone.frameworkd -- is the framework daemon started?" )
2098             return False
2099         else:
2100             self.fw = Interface( fw_obj, "org.freesmartphone.Objects" )
2101         failcount = 0
2102
2103         # Usage
2104         self.usage_obj = self.tryGetProxy( 'org.freesmartphone.ousaged', '/org/freesmartphone/Usage' )
2105         if ( self.usage_obj is not None ) and ( self.usage_iface is None ):
2106             self.usage_iface = Interface(self.usage_obj, 'org.freesmartphone.Usage')
2107             self.usage_iface.connect_to_signal( "ResourceChanged", self.cbResourceChanged )
2108             self.usage_iface.RequestResource("GSM")
2109         if self.usage_obj is None:
2110             failcount += 1
2111         else:
2112             logger.debug( "usage ok: %s" % self.usage_iface )
2113
2114         # GPS
2115         self.gps_obj = self.tryGetProxy( 'org.freesmartphone.ogpsd', '/org/freedesktop/Gypsy' )
2116         if self.gps_obj and not self.gps_accuracy_iface:
2117             self.gps_accuracy_iface = Interface(self.gps_obj, 'org.freedesktop.Gypsy.Accuracy')
2118             self.gps_accuracy_iface.connect_to_signal( "AccuracyChanged", self.cbAccuracyChanged )
2119         if self.gps_obj and not self.gps_position_iface:
2120             self.gps_position_iface = Interface(self.gps_obj, 'org.freedesktop.Gypsy.Position')
2121             self.gps_position_iface.connect_to_signal( "PositionChanged", self.cbPositionChanged )
2122         if self.gps_obj and not self.gps_satellite_iface:
2123             self.gps_satellite_iface = Interface(self.gps_obj, 'org.freedesktop.Gypsy.Satellite')
2124             self.gps_satellite_iface.connect_to_signal( "SatellitesChanged", self.cbSatellitesChanged )
2125         if not self.gps_obj or not self.gps_accuracy_iface or not self.gps_position_iface \
2126             or not self.gps_satellite_iface:
2127             failcount += 1
2128         else:
2129             logger.debug( "gps ok: %s, %s, %s" % ( self.gps_accuracy_iface, self.gps_position_iface, self.gps_satellite_iface ) )
2130
2131         # Phone
2132         self.gsm_device_obj = self.tryGetProxy( 'org.freesmartphone.ogsmd', '/org/freesmartphone/GSM/Device' )
2133
2134         if ( self.gsm_device_obj is not None ) and ( self.gsm_device_iface is None ):
2135             self.gsm_device_iface = Interface(self.gsm_device_obj, 'org.freesmartphone.GSM.Device')
2136             self.gsm_sim_iface = Interface(self.gsm_device_obj, 'org.freesmartphone.GSM.SIM')
2137             self.gsm_network_iface = Interface(self.gsm_device_obj, 'org.freesmartphone.GSM.Network')
2138             self.gsm_call_iface = Interface(self.gsm_device_obj, 'org.freesmartphone.GSM.Call')
2139             self.gsm_test_iface = Interface(self.gsm_device_obj, 'org.freesmartphone.GSM.Test')
2140             self.gsm_sim_iface.connect_to_signal( "IncomingMessage", self.cbIncomingMessage )
2141             self.gsm_sim_iface.connect_to_signal( "ReadyStatus", self.cbReadyStatus )
2142             self.gsm_call_iface.connect_to_signal( "CallStatus", self.cbCallStatus )
2143             self.gsm_network_iface.connect_to_signal( "Status", self.cbNetworkStatus )
2144         if self.gsm_device_obj is None:
2145             failcount += 1
2146         else:
2147             logger.debug( "gsm ok: %s" % self.gsm_network_iface )
2148
2149         self.device_obj = self.tryGetProxy( 'org.freesmartphone.odeviced', '/org/freesmartphone/Device' )
2150         if ( self.device_obj is not None ) and ( self.device_iface is None ):
2151             self.device_iface = Interface( self.device_obj, 'org.freesmartphone.Device' )
2152
2153             self.idlenotifier_obj = self.tryGetProxy( "org.freesmartphone.odeviced", self.fw.ListObjectsByInterface( "org.freesmartphone.Device.IdleNotifier" )[0] )
2154
2155             self.idlenotifier_iface = Interface( self.idlenotifier_obj, "org.freesmartphone.Device.IdleNotifier" )
2156             self.idlenotifier_iface.connect_to_signal( "State", self.cbIdleStateChanged )
2157
2158             self.inputnotifier_obj = self.bus.get_object( "org.freesmartphone.odeviced", "/org/freesmartphone/Device/Input" )
2159             self.inputnotifier_iface = Interface( self.inputnotifier_obj, "org.freesmartphone.Device.Input" )
2160             self.inputnotifier_iface.connect_to_signal( "Event", self.cbEvent )
2161
2162             logger.debug( "displays: %s" % self.fw.ListObjectsByInterface( "org.freesmartphone.Device.Display" ) )
2163             self.display_obj = self.tryGetProxy( "org.freesmartphone.odeviced", self.fw.ListObjectsByInterface( "org.freesmartphone.Device.Display" )[0] )
2164             if self.display_obj is not None:
2165                 self.display_iface = Interface( self.display_obj, "org.freesmartphone.Device.Display" )
2166                 self.display_iface.SetBrightness( 90 )
2167         if self.device_obj is None:
2168             failcount += 1
2169         else:
2170             logger.debug( "device ok: %s" % self.device_iface )
2171
2172         # Prefs
2173         self.prefs_obj = self.tryGetProxy( 'org.freesmartphone.opreferencesd', '/org/freesmartphone/Preferences' )
2174         self.prefs_iface = Interface( self.prefs_obj, 'org.freesmartphone.Preferences' )
2175         logger.debug( "preferences ok: %s" % self.prefs_iface )
2176
2177         logger.debug( "failcount = %d" % failcount )
2178         if failcount == 0:
2179             self.fullinit = True
2180         return self.fullinit
2181
2182     def cbResourceChanged( self, resourcename, state, attributes ):
2183         for cb in self.onResourceChanged:
2184             cb( resourcename=resourcename, state=state, attributes=attributes )
2185
2186     def cbAccuracyChanged( self, fields, pdop, hdop, vdop ):
2187         for cb in self.onAccuracyChanged:
2188             cb( fields=fields, pdop=pdop, hdop=hdop, vdop=vdop )
2189
2190     def cbPositionChanged( self, fields, timestamp, lat, lon, alt ):
2191         for cb in self.onPositionChanged:
2192             cb( fields=fields, timestamp=timestamp, lat=lat, lon=lon, alt=alt )
2193
2194     def cbSatellitesChanged( self, satellites ):
2195         for cb in self.onSatellitesChanged:
2196             cb( satellites )
2197
2198     def cbCallStatus( self, id, status, properties ):
2199         logger.info( "CALL STATUS = %d, %s, %s" % ( id, status, properties ) )
2200         for cb in self.onCallStatus:
2201             cb( id=id, status=status, properties=properties )
2202         if not self.idlenotifier_iface is None:
2203             self.idlenotifier_iface.SetState(
2204                 "BUSY",
2205                 reply_handler=lambda: None,
2206                 error_handler=dbus_error( "could not set idle state to BUSY" )
2207             )
2208
2209     def cbReadyStatus( self, status ):
2210         for cb in self.onReadyStatus:
2211             cb( status=status )
2212
2213     def cbNetworkStatus( self, status ):
2214         for cb in self.onNetworkStatus:
2215             cb( status=status )
2216
2217     def cbIncomingMessage( self, index ):
2218         for cb in self.onIncomingMessage:
2219             cb( index=index )
2220
2221     def cbIdleStateChanged( self, state ):
2222         logger.info( "IDLE STATE = %s" % state )
2223         for cb in self.onIdleStateChanged:
2224             cb( state=state )
2225         if state == "BUSY":
2226             if self.display_iface is not None:
2227                 self.display_iface.SetBrightness( 90 )
2228         elif state == "IDLE_DIM":
2229             if self.display_iface is not None:
2230                 self.display_iface.SetBrightness( 20 )
2231         elif state == "IDLE_PRELOCK":
2232             if self.display_iface is not None:
2233                 self.display_iface.SetBrightness( 0 )
2234
2235     def cbEvent( self, name, action, seconds ):
2236         logger.info( "INPUT EVENT = %s, %s, %d" % ( name, action, seconds ) )
2237         for cb in self.onInputEvent:
2238             cb( name, action, seconds )
2239
2240 #=========================================================================#
2241 if __name__ == "__main__":
2242 #=========================================================================#
2243
2244     options, args = MyOptionParser().parse_args()
2245     dbus_object = DBusObject()
2246     gui = GUI( options, args )
2247     try:
2248         gui.run()
2249     except KeyboardInterrupt:
2250         gui.shutdown()
2251         del gui