#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Zen Phone - A Phone UI

(C) 2007 Johannes 'Josch' Schauer
(C) 2008 Michael 'Mickey' Lauer <mlauer@vanille-media.de>
(C) 2008 Jan 'Shoragan' Luebbe
(C) 2008 Daniel 'Alphaone' Willmann
(C) 2008 Openmoko, Inc.
GPLv2 or later

Warning: Please don't let yourself be inspired too much by this software,
it's lacking proper design, modularity, and stability. We did this as a
quick hack to demonstrate the feasibility of our dbus service level
framework. A proper redesign would be in order, but alas the framework
team has no resources to do that.

You have been warned :)

"""

__version__ = "0.0.0"
MODULE_NAME = "zhone"

# Locale support
import gettext

try:
    cat = gettext.Catalog("zhone", "/usr/share/zhone/locale")
    _ = cat.gettext
except IOError:
    _ = lambda x: x

import logging
logger = logging.getLogger( MODULE_NAME )
logging.basicConfig( level    = logging.DEBUG,
                    format   = '%(asctime)s %(levelname)s %(message)s',
                    filename = '/tmp/zhone.log',
                    filemode = 'w' )
handler = logging.StreamHandler()
handler.setFormatter( logging.Formatter( '%(asctime)s %(levelname)s %(message)s' ))
logger.addHandler( handler )
logger.setLevel( logging.DEBUG )

def log_dbus_error( e, desc ):
    logger.error( "%s (%s %s: %s)" % ( desc, e.__class__.__name__, e.get_dbus_name(), e.get_dbus_message() ) )

def handle_dbus_error( desc ):
    return lambda e: log_dbus_error( e, desc = desc )

def textblock_escape( text ):
    text = text.replace('&', '&amp;')
    text = text.replace('<', '&lt;')
    text = text.replace('>', '&gt;')
    return text

#----------------------------------------------------------------------------#
WIDTH = 480
HEIGHT = 640

TITLE = "zhone"
WM_NAME = "zhone"
WM_CLASS = "zhone"

#----------------------------------------------------------------------------#
import os
import sys
import e_dbus
import evas
import evas.decorators
import edje
import edje.decorators
import ecore
import ecore.evas
import cairo
from dbus import SystemBus, Interface
from dbus.exceptions import DBusException
from optparse import OptionParser
import time
import math

illume = None
try:
    import illume
except ImportError:
    logger.warning( "could not load illume interface module" )

#----------------------------------------------------------------------------#

edjepaths = "./zhone.edj ../data/themes/zhone.edj ../share/zhone.edj /usr/local/share/zhone/zhone.edj /usr/share/zhone/zhone.edj".split()

for i in edjepaths:
    if os.path.exists( i ):
       global edjepath
       edjepath = i
       break
else:
    raise Exception( "zhone.edj not found. looked in %s" % edjepaths )

#----------------------------------------------------------------------------#
class edje_group(edje.Edje):
#----------------------------------------------------------------------------#
    def __init__(self, main, group, parent_name="main"):
        self.main = main
        self.parent_name = parent_name
        self.group = group
        global edjepath
        f = edjepath
        try:
            edje.Edje.__init__(self, self.main.evas_canvas.evas_obj.evas, file=f, group=group)
        except edje.EdjeLoadError, e:
            raise SystemExit("error loading %s: %s" % (f, e))
        self.size = self.main.evas_canvas.evas_obj.evas.size

    def onShow( self ):
        pass

    def onHide( self ):
        pass

    @edje.decorators.signal_callback("mouse,clicked,1", "button_bottom_right")
    def on_edje_signal_button_bottom_right_pressed(self, emission, source):
        self.main.transition_to(self.parent_name)

    @edje.decorators.signal_callback("mouse,clicked,1", "button_bottom_left")
    def on_edje_signal_button_bottom_left_pressed(self, emission, source):
        self.main.groups["main_menu"].activate( self.group )
        self.main.transition_to("main_menu")

#----------------------------------------------------------------------------#
class pyphone_main(edje_group):
#----------------------------------------------------------------------------#
    def __init__(self, main):
        edje_group.__init__(self, main, "main")
        self.targets = {
            "phone": False,
            "calls": False,
            "contacts": False,
            "sms": False,
            "wireless": True,
            "location": False,
            "configuration": True,
        }
        self.update()

    def update( self ):
        for key, value in self.targets.items():
            if value:
                self.signal_emit( "activate_target_icon_%s" % key, "" )
            else:
                self.signal_emit( "deactivate_target_icon_%s" % key, "" )

    @edje.decorators.signal_callback("mouse,clicked,1", "target_*")
    def on_edje_signal_button_pressed(self, emission, source):
        target = source.split('_', 1)[1]
        if not self.targets[target]:
            return
        if target == "phone" and not self.main.groups["call"].status in ["idle" , "release"]:
            target = "call"
        self.main.transition_to(target)

#----------------------------------------------------------------------------#
class pyphone_phone(edje_group):
#----------------------------------------------------------------------------#
    TIMEOUT = 2.0
    def __init__(self, main):
        edje_group.__init__(self, main, "phone")
        self.text = []
        self.last = 0.0

    @edje.decorators.signal_callback( "mouse,clicked,1", "button_*" )
    def on_edje_signal_dialer_button_pressed(self, emission, source):
        key = source.split("_", 1)[1]
        if key in ("0", "1", "2", "3", "4", "5", "6", "7", "8", "9"):
            self.text.append(key)
            # The trailing whitespace is a workaround for the one char invisible
            # bug due to some problems with scaling of text parts.
            self.part_text_set("label", u"".join(self.text)+u" ")
        elif key in "star":
            if self.text and ( time.time()-self.last < self.TIMEOUT ):
                if self.text[-1] == "*":
                    del self.text[-1]
                    self.text.append( "+" )
                elif self.text[-1] == "+":
                    del self.text[-1]
                    self.text.append( "*" )
                else:
                    self.text.append( "*" )
            else:
                self.text.append("*")
            self.part_text_set( "label", u"".join(self.text)+u" " )
        elif key in "hash":
            self.text += "#"
            self.part_text_set( "label", u"".join(self.text)+u" " )
        elif key in "delete":
            self.text = self.text[:-1]
            self.part_text_set("label", u"".join(self.text)+u" ")
        elif key in "dial":
            if dbus_object.gsm_device_obj:
                if "".join(self.text)[0] == "*":
                    dbus_object.gsm_network_iface.SendUssdRequest( "".join(self.text) )
                else:
                    dbus_object.gsm_call_iface.Initiate( "".join(self.text), "voice" )
            else:
                # Fake onCallStatus...
                self.main.groups["call"].onCallStatus( None, "outgoing", {"peer": "".join(self.text)} )
        self.last = time.time()

    def onNetworkStatus( self, status ):
        logger.info( "network status changed: %s" % status )
        #if(status.has_key("registration") != "busy")  # XXX this might work too.  which is better?
        if(status.has_key('provider')):
            self.main.agents["gsm"].setState(_("Registered: %s") % status['provider'])
            self.main.groups["main"].targets["phone"] = True
            self.main.groups["main"].update()
            if dbus_object.gsm_sim_iface.GetSimReady():
                self.main.groups["contacts"].prepare()
                self.main.groups["sms"].prepare()
        else:
            self.main.agents["gsm"].setState(_("Failed to register to network"))
            self.main.groups["main"].targets["phone"] = False
            self.main.groups["main"].update()

    def onIncomingUssd( self, mode, message ):
        logger.info( "USSD Message: %s" % message )
        self.main.groups["alert"].activate( "<title>Operator Message</title>%s" % message, [("OK")] )

_("active")
_("incoming")
_("held")
_("outgoing")
_("released")
#----------------------------------------------------------------------------#
class pyphone_call(edje_group):
#----------------------------------------------------------------------------#
    def __init__(self, main):
        edje_group.__init__(self, main, "call")
        self.update_status("idle")
        self.call = None

    def onCallStatus( self, id, status, properties ):
        self.call = id
        self.update_status(status)
        if not status in ["release"]:
            if "peer" in properties:
                self.part_text_set( "label", self.main.groups["contacts"].tryNumberToName( properties[ "peer" ] ) )
                self.part_text_set( "sublabel", properties[ "peer" ] )
            else:
                self.part_text_set( "label", _("unknown number") )
                self.part_text_set( "sublabel", "" )

    @edje.decorators.signal_callback("call_button_pressed", "button_left")
    def on_edje_signal_call_button_left_pressed(self, emission, source):
        if self.status == "active":
            if dbus_object.gsm_device_obj:
                #dbus_object.gsm_call_iface.Hold(self.call)
                pass
            else:
                self.update_status("held")
        elif self.status in ["incoming", "held"]:
            if dbus_object.gsm_device_obj:
                dbus_object.gsm_call_iface.Activate(self.call)
            else:
                self.update_status("active")

    @edje.decorators.signal_callback("call_button_pressed", "button_right")
    def on_edje_signal_call_button_right_pressed(self, emission, source):
        if self.status in ["outgoing", "active", "incoming", "held"]:
            if dbus_object.gsm_device_obj:
                dbus_object.gsm_call_iface.Release(self.call)
            else:
                self.update_status("release")

    def update_status(self, status):
        self.part_text_set( "label_description", _(status) )
        if status == "outgoing":
            self.part_text_set( "label_left", u"" )
            self.part_text_set( "label_right", _("cancel") )
        elif status == "active":
            self.part_text_set( "label_left", _("hold") )
            self.part_text_set( "label_right", _("hangup") )
        elif status == "incoming":
            self.main.groups["lock"].deactivate()
            self.part_text_set( "label_left", _("answer") )
            self.part_text_set( "label_right", _("reject") )
        elif status == "held":
            self.part_text_set( "label_left", _("resume") )
            self.part_text_set( "label_right", _("hangup") )
        else:
            self.part_text_set( "label_left", u"" )
            self.part_text_set( "label_right", u"" )
        if status in ["incoming", "outgoing"] and not self.status == status:
            # TODO make sure we get displayed
            self.main.transition_to("call")
        self.status = status

    @edje.decorators.signal_callback( "mouse,clicked,1", "button_bottom_middle" )
    def on_edje_signal_button_bottom_middle_pressed( self, emission, source ):
        self.main.transition_to("dtmf")

#----------------------------------------------------------------------------#
class pyphone_dtmf(edje_group):
#----------------------------------------------------------------------------#
    def __init__(self, main):
        edje_group.__init__(self, main, "dtmf")
        self.text = []

    @edje.decorators.signal_callback( "mouse,clicked,1", "button_*" )
    def on_edje_signal_dialer_button_pressed(self, emission, source):
        key = source.split("_", 1)[1]
        if key in ("0", "1", "2", "3", "4", "5", "6", "7", "8", "9"):
            dbus_object.gsm_call_iface.SendDtmf( key )
            self.text.append(key)
        elif key in "star":
            dbus_object.gsm_call_iface.SendDtmf( "*" )
            self.text.append("*")
        elif key in "hash":
            dbus_object.gsm_call_iface.SendDtmf( "#" )
            self.text += "#"
        # The trailing whitespace is a workaround for the one char invisible
        # bug due to some problems with scaling of text parts.
        self.part_text_set("label", "".join(self.text)+" ")

    @edje.decorators.signal_callback("call_button_pressed", "button_right")
    def on_edje_signal_call_button_right_pressed(self, emission, source):
        self.main.transition_to("call")

#----------------------------------------------------------------------------#
class pyphone_sms(edje_group):
#----------------------------------------------------------------------------#
    def __init__(self, main):
        edje_group.__init__(self, main, "sms")
        self.ready = False
        self.busy = False
        self.messagebook = []
        self.current = []
        self.page = 0
        self.selected = None
        self.newindex = None

        self.part_text_set( "label_action_open", _("open") )

    def sendMessage( self, index ):
        logger.info( "trying to send message w/ index %d..." % index )
        dbus_object.gsm_sim_iface.SendStoredMessage( index, reply_handler=self.cbSendReply, error_handler=self.cbSendError )

    def cbSendReply( self, reference, timestamp ):
        logger.info( "sent message successfully w/ reference number %d (timestamp: %s)" % ( reference, timestamp ) )

    def cbSendError( self, e ):
        logger.error( "could not send message. leaving in outgoing. error was: %s" % e )

    def cbStoreReply( self, result ):
        logger.info( "stored message lives at SIM position %d" % result )
        self.sendMessage( result )
        if not self.busy:
            dbus_object.gsm_sim_iface.RetrieveMessagebook(
                "all",
                reply_handler=self.cbMessagebookReply,
                error_handler=self.cbMessagebookError
            )
            self.busy = True

    def cbStoreError( self, e ):
        logger.warning( "error while storing message - %s" % e.get_dbus_name() )
        if e.get_dbus_name() == "org.freesmartphone.GSM.SIM.MemoryFull":
            self.main.groups["alert"].activate( "<title>Failed to send message</title>SIM Memory Full", [("OK")] )

    def cbSend1( self, contact, cb_data ):
        self.main.groups["text_edit"].setup(
            "sms",
            "", # text
            contact[0], # title
            contact[1], # reference
            self.cbSend2
        )

    def cbSend2( self, text, number ):
        if dbus_object.gsm_device_obj:
            dbus_object.gsm_sim_iface.StoreMessage(
                number, text, {},
                reply_handler=self.cbStoreReply,
                error_handler=self.cbStoreError
            )

    def cbDelete( self, result, reference ):
        if result == "abort":
            return
        if dbus_object.gsm_device_obj:
            dbus_object.gsm_sim_iface.DeleteMessage(
                reference
            )
        for i in range( len( self.messagebook ) ):
            if self.messagebook[i][0] == reference:
                del self.messagebook[i]
                break
        self.updateList()
        self.main.transition_to( "sms" )

    def cbForward( self, contact, text ):
        if dbus_object.gsm_device_obj:
            dbus_object.gsm_sim_iface.StoreMessage(
                contact[1], text, {},
                reply_handler=self.cbStoreReply,
                error_handler=self.cbStoreError
            )

    def cbReply( self, text, number):
        if dbus_object.gsm_device_obj:
            dbus_object.gsm_sim_iface.StoreMessage(
                number, text, {},
                reply_handler=self.cbStoreReply,
                error_handler=self.cbStoreError
            )

    def cbMenu( self, result ):
        if result == _("send"):
            self.main.groups["contacts"].prepare()
            if self.main.groups["contacts"].ready:
                self.main.groups["list_choose"].setup(
                    "sms",
                    [ (x[1], x[2]) for x in  self.main.groups["contacts"].phonebook],
                    None,
                    self.cbSend1
                )
        elif result == _("delete"):
            self.main.groups["alert"].activate(
                _("delete?"),
                (_("abort"), _("delete")),
                self.current[self.selected][0], # reference
                self.cbDelete
            )
        elif result == _("forward"):
            self.main.groups["contacts"].prepare()
            if self.main.groups["contacts"].ready:
                self.main.groups["list_choose"].setup(
                    "sms",
                    [ (x[1], x[2] ) for x in  self.main.groups["contacts"].phonebook],
                    self.current[self.selected][3],
                    self.cbForward
                )
        elif result == _("reply"):
            self.main.groups["text_edit"].setup(
                "sms",
                "", # text
                self.main.groups["contacts"].tryNumberToName( self.current[self.selected][2] ), # title = contact name or number
                self.current[self.selected][2], # reference = number
                self.cbReply
            )

    def cbMessagebookReply( self, result ):
        logger.info( "retrieved messagebook: %s" % result )
        self.busy = False
        self.messagebook = result
        self.ready = True
        self.updateList()
        self.main.groups["main"].targets["sms"] = True
        self.main.groups["main"].update()
        if not self.newindex is None:
            message = [x for x in self.messagebook if x[0] == self.newindex]
            self.newindex = None
            if message and self.main.current_group == self.main.groups["main"]:
                self.displayMessage( message[0] )
            if dbus_object.gsm_device_obj:
                messagebookInfo = dbus_object.gsm_sim_iface.GetMessagebookInfo()
                if messagebookInfo["used"] == messagebookInfo["last"]:
                    self.main.groups["alert"].activate( "<title>Warning</title>SIM Memory Full", [("OK")] )

    def displayMessage( self, message ):
        if "read" in message[1]:
            from_to = _("From")
            timestamp = message[4]["timestamp"]
        else:
            from_to = _("To")
            timestamp = _("Unknown")
        from_text = self.main.groups["contacts"].tryNumberToName( message[2] )
        self.main.groups["text_show"].setup(
            "sms",
            _("%s: %s<br>Date: %s<p>%s") % (
                from_to,
                from_text,
                timestamp,
                textblock_escape( message[3] ).replace( '\n', '<br>' )
                )
            )

    def cbMessagebookError( self, e ):
        logger.warning( "error while retrieving messagebook" )
        self.busy = False

    def onIncomingMessage( self, index ):
        logger.info( "new message! Retrieving messagebook..." )
        self.newindex = index
        if not self.busy:
            dbus_object.gsm_sim_iface.RetrieveMessagebook(
                "all",
                reply_handler=self.cbMessagebookReply,
                error_handler=self.cbMessagebookError
            )
            self.busy = True

    def prepare( self ):
        if not self.ready and not self.busy:
            if dbus_object.gsm_device_obj:
                logger.info( "retrieving messagebook..." )
                dbus_object.gsm_sim_iface.RetrieveMessagebook(
                    "all",
                    reply_handler=self.cbMessagebookReply,
                    error_handler=self.cbMessagebookError
                )
                self.busy = True
            else:
                # Fake messagebook...
                self.cbMessagebookReply( [
                    (0, "read", "+4544555", "Hello World!"),
                    (1, "read", "+456663443", "Zhone!"),
                    (2, "read", "+456663443", "Hi Guy\nGuess what, I now "+
                        "know to write multi-line SMSs.\nIsn't that "+
                        "nice?\n\nSome Buddy"),
                    (3, "read", "Flash SMS", "An SMS without digits. Strange, isn't it?"),
                ] )

    def onReadyStatus( self, status ):
        logger.debug( "SIM is ready: %s" % status )
        if status:
            # Force update
            self.ready = False
            self.prepare()

    def onShow( self ):
        self.prepare()

    def updateList( self):
        self.main.groups["contacts"].prepare()
        self.pages = max( ( len( self.messagebook ) - 1 ) / 6 + 1, 1 )
        if self.page >= self.pages:
            self.page = 0
        if self.page < 0:
            self.page = self.pages - 1
        self.current = self.messagebook[self.page*6:(self.page+1)*6]
        text = u"".join( [u"□"]*self.page+[u"▣"]+[u"□"]*(self.pages-self.page-1) )
        self.part_text_set( "pager", text )
        for i in range( 0, len( self.current ) ):
            main_text = self.main.groups["contacts"].tryNumberToName( self.current[i][2] )
            self.part_text_set( "label_main_list_%i" % i, main_text )
            sub_text = " ".join(self.current[i][3].splitlines())
            self.part_text_set( "label_sub_list_%i" % i, u"(%s) %s" % ( self.current[i][1], sub_text ) )
        for i in range( len( self.current ), 6):
            self.part_text_set( "label_main_list_%i" % i, u"" )
            self.part_text_set( "label_sub_list_%i" % i, u"" )
        self.selected = None
        for i in range( 6 ):
            self.signal_emit( "deactivate_target_list_%i" % i, "" )


    @edje.decorators.signal_callback( "mouse,clicked,1", "target_list_*" )
    def on_edje_signal_button_list_pressed( self, emission, source ):
        id = int( source.split( "_" )[-1] )
        if self.selected == id:
            return
        if self.selected is not None:
            self.signal_emit( "deactivate_target_list_%i" % self.selected, "" )
        self.signal_emit( "activate_target_list_%i" % id, "" )
        self.selected = id

    @edje.decorators.signal_callback( "mouse,clicked,1", "button_action_left" )
    def on_edje_signal_button_action_left_pressed( self, emission, source ):
        self.page -= 1
        self.updateList()

    @edje.decorators.signal_callback( "mouse,clicked,1", "button_action_right" )
    def on_edje_signal_button_action_right_pressed( self, emission, source ):
        self.page += 1
        self.updateList()

    @edje.decorators.signal_callback( "mouse,clicked,1", "button_action_open" )
    def on_edje_signal_button_action_open_pressed( self, emission, source ):
        if self.selected is not None:
            self.displayMessage( self.current[self.selected] )

    @edje.decorators.signal_callback( "mouse,clicked,1", "button_bottom_middle" )
    def on_edje_signal_button_bottom_middle_pressed( self, emission, source ):
        self.main.groups["menu"].activate( ( _("send"), _("delete"), _("forward"), _("reply") ), self.cbMenu )
        self.main.groups["menu"].part_text_set( "target_label_cancel", _("cancel") )


#----------------------------------------------------------------------------#
class pyphone_configuration(edje_group):
#----------------------------------------------------------------------------#
    def __init__(self, main):
        edje_group.__init__(self, main, "configuration")
        self.selected = None
        self.profiles = []
        self.page = 0

        self.part_text_set( "label_action_use", _("use") )

    def onShow( self ):
        self.prepare()
        self.updateList()

    def prepare( self ):
        # Set the current profile label
        if dbus_object.prefs_obj:
            current = dbus_object.prefs_iface.GetProfile()
            self.profiles = dbus_object.prefs_iface.GetProfiles()
        else:
            current = 'Test1'
            self.profiles = ["Test1", "Test2"]
        self.part_text_set( "current_label", _("Current : %s") % current )

    def updateList( self):
        self.main.groups["configuration"].prepare()
        self.pages = max( ( len( self.profiles ) - 1 ) / 6 + 1, 1 )
        if self.page >= self.pages:
            self.page = 0
        if self.page < 0:
            self.page = self.pages - 1
        self.current = self.profiles[self.page*6:(self.page+1)*6]
        text = u"".join( [u"□"]*self.page+[u"▣"]+[u"□"]*(self.pages-self.page-1) )
        self.part_text_set( "pager", text )
        for i in range( 0, len( self.current ) ):
            self.part_text_set( "label_main_list_%i" % i, self.current[i] )
            self.part_text_set( "label_sub_list_%i" % i, u"" )
        for i in range( len( self.current ), 6):
            self.part_text_set( "label_main_list_%i" % i, u"" )
            self.part_text_set( "label_sub_list_%i" % i, u"" )
        self.selected = None
        for i in range( 6 ):
            self.signal_emit( "deactivate_target_list_%i" % i, "" )

    @edje.decorators.signal_callback( "mouse,clicked,1", "target_list_*" )
    def on_edje_signal_button_list_pressed( self, emission, source ):
        id = int( source.split( "_" )[-1] )
        if self.selected == id:
            return
        if self.selected is not None:
            self.signal_emit( "deactivate_target_list_%i" % self.selected, "" )
        self.signal_emit( "activate_target_list_%i" % id, "" )
        self.selected = id

    @edje.decorators.signal_callback( "mouse,clicked,1", "button_action_left" )
    def on_edje_signal_button_action_left_pressed( self, emission, source ):
        self.page -= 1
        self.updateList()

    @edje.decorators.signal_callback( "mouse,clicked,1", "button_action_right" )
    def on_edje_signal_button_action_right_pressed( self, emission, source ):
        self.page += 1
        self.updateList()

    @edje.decorators.signal_callback( "mouse,clicked,1", "button_action_use" )
    def on_edje_signal_button_action_use_pressed( self, emission, source ):
        if self.selected is None:
            return
        if self.selected >= len(self.profiles):
            return
        profile = self.profiles[self.selected]
        self.part_text_set( "current_label", _("Current : %s") % profile )
        if dbus_object.prefs_obj:
            dbus_object.prefs_iface.SetProfile(profile)

#----------------------------------------------------------------------------#
class pyphone_contacts(edje_group):
#----------------------------------------------------------------------------#
    def __init__(self, main):
        edje_group.__init__(self, main, "contacts")
        self.ready = False
        self.busy = False
        self.phonebook = []
        self.current = []
        self.page = 0
        self.selected = None

        self.part_text_set( "label_action_dial", _("dial") )

    def cbNameEdit( self, name, reference ):
        for i in range( len( self.phonebook ) ):
            if self.phonebook[i][0] == reference:
                self.phonebook[i] = ( reference, name, self.phonebook[i][2] )
                if dbus_object.gsm_device_obj:
                    dbus_object.gsm_sim_iface.StoreEntry(
                        "contacts",
                        reference,
                        name,
                        self.phonebook[i][2]
                    )
                break
        self.phonebook.sort( key = lambda x: x[1].lower() )
        self.updateList()

    def cbNumberEdit( self, number, reference ):
        for i in range( len( self.phonebook ) ):
            if self.phonebook[i][0] == reference:
                self.phonebook[i] = ( reference, self.phonebook[i][1], number )
                if dbus_object.gsm_device_obj:
                    dbus_object.gsm_sim_iface.StoreEntry(
                        "contacts",
                        reference,
                        self.phonebook[i][1],
                        number
                    )
                break
        self.updateList()

    def cbNew1( self, number, none ):
        self.main.groups["text_edit"].setup(
            "contacts",
            "", # name
            number, # title = new number
            number, # reference
            self.cbNew2
        )

    def cbNew2( self, name, number ):
        ids = [ x[0] for x in self.phonebook ]
        ids.sort()
        reference = None
        for i in range(1, 250):
            if not i in ids:
                reference = i
                break
        if reference is None:
            return # no space?
        if dbus_object.gsm_device_obj:
            dbus_object.gsm_sim_iface.StoreEntry(
                "contacts",
                reference,
                name,
                number
            )
        self.phonebook.append( ( reference, name, number ) )
        self.phonebook.sort( key = lambda x: x[1].lower() )
        self.updateList()

    def cbDelete( self, result, reference ):
        if result == "abort":
            return
        if dbus_object.gsm_device_obj:
            dbus_object.gsm_sim_iface.DeleteEntry(
                "contacts",
                reference
            )
        for i in range( len( self.phonebook ) ):
            if self.phonebook[i][0] == reference:
                del self.phonebook[i]
                break
        self.updateList()

    def cbMenu( self, result ):
        if result == _("edit name"):
            self.main.groups["text_edit"].setup(
                "contacts",
                self.current[self.selected][1], # name
                self.current[self.selected][2], # title = number
                self.current[self.selected][0], # reference
                self.cbNameEdit
            )
        elif result == _("edit number"):
            self.main.groups["number_edit"].setup(
                "contacts",
                self.current[self.selected][2], # number
                self.current[self.selected][1], # title = name
                self.current[self.selected][0], # reference
                self.cbNumberEdit
            )
        elif result == _("new"):
            self.main.groups["number_edit"].setup(
                "contacts",
                "", # number
                _("New entry?"), # title
                None, # reference
                self.cbNew1
            )
        elif result == _("delete"):
            self.main.groups["alert"].activate(
                _("delete '%s'?") % self.current[self.selected][1],
                (_("abort"), _("delete")),
                self.current[self.selected][0], # reference
                self.cbDelete
            )

    def cbPhonebookReply( self, result ):
        logger.info( "retrieved phonebook: %s" % result )
        self.busy = False
        self.phonebook = result
        self.phonebook.sort( key = lambda x: x[1].lower() )
        self.ready = True
        self.updateList()
        self.main.groups["main"].targets["contacts"] = True
        self.main.groups["main"].update()
        self.main.groups["sms"].updateList()

    def cbPhonebookError( self, e ):
        logger.error( "error while retrieving phonebook %s" % e )
        self.busy = False

    def prepare( self ):
        if not self.ready and not self.busy:
            if dbus_object.gsm_device_obj:
                logger.info( "retrieving phonebook..." )
                try:
                    dbus_object.gsm_sim_iface.RetrievePhonebook(
                        "contacts", -1, -1,
                        reply_handler=self.cbPhonebookReply,
                        error_handler=self.cbPhonebookError
                    )
                    self.busy = True
                except:
                   logger.info( "error retrieving phonebook, trying old API" )
                   dbus_object.gsm_sim_iface.RetrievePhonebook(
                       "contacts",
                       reply_handler=self.cbPhonebookReply,
                       error_handler=self.cbPhonebookError
                   )
                   self.busy = True
            else:
                # Fake phonebook...
                self.cbPhonebookReply( [
                    (1, u'Kirk', '+023224433'),
                    (2, u'Spock', '+034433463'),
                    (3, u'McCoy', '+013244344'),
                    (4, u'Scott', '+013244344'),
                    (5, u'Uhura', '+013244344'),
                    (6, u'Sulu', '+013244344'),
                    (7, u'Chekov', '+456663443'),
                ] )

    def onReadyStatus( self, status ):
        logger.debug( "SIM is ready: %s" % status )
        if status:
            # Force update
            self.ready = False
            self.prepare()

    def onShow( self ):
        self.prepare()
        self.updateList()

    def comparePhoneNumber(self, number1, number2):
        '''
        Compares two phone numbers. They are considered equal if:
          a) One does not contain digits, and they are equal as strings
        or
          b) Both start with a "+", and all following digits are equal
        or
          c) At least one of them does not start with a "+", and the
             last 7 digits are equal
        '''
        digits1 = filter (lambda c: c.isdigit() or c == '+', number1)
        digits2 = filter (lambda c: c.isdigit() or c == '+', number2)

        if digits1 == '' or digits2 == '':
            return number1 == number2
        if digits1[0] == digits2[0] == '+':
            return digits1 == digits2
        else:
            return digits1[-7:] == digits2[-7:]

    def tryNumberToName( self, number ):
        for i in range( len( self.phonebook ) ):
            if self.comparePhoneNumber(self.phonebook[i][2], number):
                return self.phonebook[i][1]
        return number

    def updateList( self ):
        self.pages = max( ( len( self.phonebook ) - 1 ) / 6 + 1, 1 )
        if self.page >= self.pages:
            self.page = 0
        if self.page < 0:
            self.page = self.pages -1
        self.current = self.phonebook[self.page*6:(self.page+1)*6]
        text = u"".join( [u"□"]*self.page+[u"▣"]+[u"□"]*(self.pages-self.page-1) )
        self.part_text_set( "pager", text )
        for i in range( 0, len( self.current ) ):
            self.part_text_set( "label_main_list_%i" % i, self.current[i][1] )
            self.part_text_set( "label_sub_list_%i" % i, self.current[i][2] )
        for i in range( len( self.current ), 6):
            self.part_text_set( "label_main_list_%i" % i, u"" )
            self.part_text_set( "label_sub_list_%i" % i, u"" )
        self.selected = None
        for i in range( 6 ):
            self.signal_emit( "deactivate_target_list_%i" % i, "" )

    @edje.decorators.signal_callback( "mouse,clicked,1", "target_list_*" )
    def on_edje_signal_button_list_pressed( self, emission, source ):
        id = int( source.split( "_" )[-1] )
        if self.selected == id:
            return
        if self.selected is not None:
            self.signal_emit( "deactivate_target_list_%i" % self.selected, "" )
        self.signal_emit( "activate_target_list_%i" % id, "" )
        self.selected = id

    @edje.decorators.signal_callback( "mouse,clicked,1", "button_action_left" )
    def on_edje_signal_button_action_left_pressed( self, emission, source ):
        self.page -= 1
        self.updateList()

    @edje.decorators.signal_callback( "mouse,clicked,1", "button_action_right" )
    def on_edje_signal_button_action_right_pressed( self, emission, source ):
        self.page += 1
        self.updateList()

    @edje.decorators.signal_callback( "mouse,clicked,1", "button_action_dial" )
    def on_edje_signal_button_action_dial_pressed( self, emission, source ):
        if self.selected is not None:
            if dbus_object.gsm_device_obj:
                if self.current[self.selected][2][0] == "*":
                    dbus_object.gsm_network_iface.SendUssdRequest( self.current[self.selected][2] )
                else:
                    dbus_object.gsm_call_iface.Initiate( self.current[self.selected][2], "voice" )

    @edje.decorators.signal_callback( "mouse,clicked,1", "button_bottom_middle" )
    def on_edje_signal_button_bottom_middle_pressed( self, emission, source ):
        self.main.groups["menu"].activate((_("edit name"), _("edit number"), _("new"), _("delete")), self.cbMenu)
        self.main.groups["menu"].part_text_set( "target_label_cancel", _("cancel") )

#----------------------------------------------------------------------------#
class pyphone_calls(edje_group):
#----------------------------------------------------------------------------#
    def __init__(self, main):
        edje_group.__init__(self, main, "calls")
        self.busy = False
        self.phonebook = []
        self.current = []
        self.page = 0
        self.selected = None

        self.part_text_set( "label_action_dial", _("dial") )

    def cbDelete( self, result, reference ):
        if result == "abort":
            return
        if dbus_object.gsm_device_obj:
            dbus_object.gsm_sim_iface.DeleteEntry(
                "missed",
                reference
            )
        for i in range( len( self.phonebook ) ):
            if self.phonebook[i][0] == reference:
                del self.phonebook[i]
                break
        self.updateList()

    def cbMenu( self, result ):
        if result == _("xxx"):
           return
        elif result == _("yyy"):
           return
        elif result == _("add"):
            self.main.groups["number_edit"].setup(
                "contacts",
                self.current[self.selected][2], # number
                _("Add entry?"), # title
                None, # reference
                self.main.groups["contacts"].cbNew1
            )
        elif result == _("delete"):
            self.main.groups["alert"].activate(
                _("delete '%s'?") % self.current[self.selected][1],
                (_("abort"), _("delete")),
                self.current[self.selected][0], # reference
                self.cbDelete
            )

    def cbPhonebookReply( self, result ):
        logger.info( "retrieved phonebook: %s" % result )
        self.busy = False
        self.phonebook = result
        self.updateList()

    def cbPhonebookError( self, e ):
        logger.error( "error while retrieving phonebook %s" % e )
        self.busy = False

    def prepare( self ):
        if not self.busy:
            if dbus_object.gsm_device_obj:
                logger.info( "retrieving missed calls..." )
                try:
                    dbus_object.gsm_sim_iface.RetrievePhonebook(
                        "missed", -1, -1,
                        reply_handler=self.cbPhonebookReply,
                        error_handler=self.cbPhonebookError
                    )
                    self.busy = True
                except:
                   logger.debug( "error retrieving missed calls, trying old API" )
                   dbus_object.gsm_sim_iface.RetrievePhonebook(
                       "missed",
                       reply_handler=self.cbPhonebookReply,
                       error_handler=self.cbPhonebookError
                   )
                   self.busy = True
            else:
                # Fake missed calls
                self.cbPhonebookReply( [
                    (1, u'Nothing missed so far', '+012345'),
                ] )

    def onReadyStatus( self, status ):                                                       
        logger.debug( "SIM is ready: %s" % status )                                          
        if status:                                                                           
           self.main.groups["main"].targets["calls"] = True                                                         
           self.main.groups["main"].update()                                                                        

    def onShow( self ):
        self.prepare()
        self.updateList()

    def updateList( self ):
        self.pages = max( ( len( self.phonebook ) - 1 ) / 6 + 1, 1 )
        if self.page >= self.pages:
            self.page = 0
        if self.page < 0:
            self.page = self.pages -1
        self.current = self.phonebook[self.page*6:(self.page+1)*6]
        text = u"".join( [u"□"]*self.page+[u"▣"]+[u"□"]*(self.pages-self.page-1) )
        self.part_text_set( "pager", text )
        for i in range( 0, len( self.current ) ):
            self.part_text_set( "label_main_list_%i" % i, self.current[i][1] )
            self.part_text_set( "label_sub_list_%i" % i, self.current[i][2] )
        for i in range( len( self.current ), 6):
            self.part_text_set( "label_main_list_%i" % i, u"" )
            self.part_text_set( "label_sub_list_%i" % i, u"" )
        self.selected = None
        for i in range( 6 ):
            self.signal_emit( "deactivate_target_list_%i" % i, "" )

    @edje.decorators.signal_callback( "mouse,clicked,1", "target_list_*" )
    def on_edje_signal_button_list_pressed( self, emission, source ):
        id = int( source.split( "_" )[-1] )
        if self.selected == id:
            return
        if self.selected is not None:
            self.signal_emit( "deactivate_target_list_%i" % self.selected, "" )
        self.signal_emit( "activate_target_list_%i" % id, "" )
        self.selected = id

    @edje.decorators.signal_callback( "mouse,clicked,1", "button_action_left" )
    def on_edje_signal_button_action_left_pressed( self, emission, source ):
        self.page -= 1
        self.updateList()

    @edje.decorators.signal_callback( "mouse,clicked,1", "button_action_right" )
    def on_edje_signal_button_action_right_pressed( self, emission, source ):
        self.page += 1
        self.updateList()

    @edje.decorators.signal_callback( "mouse,clicked,1", "button_action_dial" )
    def on_edje_signal_button_action_dial_pressed( self, emission, source ):
        if self.selected is not None:
            if dbus_object.gsm_device_obj:
                if self.current[self.selected][2][0] == "*":
                    dbus_object.gsm_network_iface.SendUssdRequest( self.current[self.selected][2] )
                else:
                    dbus_object.gsm_call_iface.Initiate( self.current[self.selected][2], "voice" )

    @edje.decorators.signal_callback( "mouse,clicked,1", "button_bottom_middle" )
    def on_edje_signal_button_bottom_middle_pressed( self, emission, source ):
        self.main.groups["menu"].activate((_("xxx"), _("yyy"), _("add"), _("delete")), self.cbMenu)
        self.main.groups["menu"].part_text_set( "target_label_cancel", _("cancel") )

#----------------------------------------------------------------------------#
class pyphone_wireless( edje_group ):
#----------------------------------------------------------------------------#
    class NeighbourGraph( evas.ClippedSmartObject ):
        MAXCELLS = 7
        MAXRXLEV = 64
        def __init__( self, *args, **kargs ):
            evas.ClippedSmartObject.__init__( self, *args, **kargs )
            self.img = self.Image()
            self.img.alpha = True
            self.img.colorspace = evas.EVAS_COLORSPACE_ARGB8888
            self.img.pos = self.pos
            self.img.show()
            self.surface = None
            self.ctx = None
            self.cells = None
            self.maxsignal = self.MAXRXLEV
            self.current = False

        def show( self ):
            evas.ClippedSmartObject.show( self )
            self.current = True
            self.update()

        def hide( self ):
            evas.ClippedSmartObject.hide( self )
            self.current = False

        def resize( self, w, h ):
            self.surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, w, h)
            self.ctx = cairo.Context(self.surface)
            self.ctx.scale( w, h )
            #self.ctx.set_antialias( cairo.ANTIALIAS_NONE )
            self.img.image_size = ( w, h )
            self.update()
            self.img.resize( *self.img.image_size )
            self.img.fill_set(0, 0, *self.img.image_size )

        def update( self, cells = None ):
            if cells is not None:
                self.cells = cells
            if self.ctx and self.current:
                self.ctx.set_operator( cairo.OPERATOR_CLEAR )
                self.ctx.paint()
                self.ctx.set_operator( cairo.OPERATOR_OVER )
                pixel = self.ctx.device_to_user_distance( 1, 1 )
                self.ctx.set_line_width( max( pixel ) )
                self.ctx.set_source_rgba(1, 1, 1, 0.5)
                self.ctx.rectangle(0.0, 0.0, 1.0, 1.0)
                self.ctx.fill()
                rows = []
                if self.cells is not None:
                    logger.debug( "updating %d cells", len(self.cells) )
                    for cell in self.cells:
                        signal = cell["rxlev"]
                        text = []
                        text.append( "%s" % cell["arfcn"] )
                        text.append( "%s/%s" % ( cell["lac"], cell["cid"] ) )
                        text.append( "%s" % cell["bsic"] )
                        text.append( "%s" % cell["rxlev"] )
                        rows.append( ( signal, text ) )
                if rows:
                    count = 1 + len( rows )
                    self.maxsignal = float( max( [x[0] for x in rows] + [self.maxsignal] ) )
                    barwidth = 1.0/(1 + self.MAXCELLS)
                    self.ctx.set_font_size( barwidth*0.4 )
                    self.ctx.set_source_rgba(0, 0, 0, 1)
                    head = [ "ARFCN", "LAC/CID", "BSIC", "RXLEV" ]
                    x_bearing, y_bearing, width, height = self.ctx.text_extents( " ".join( head ) )[:4]
                    w = 0.5*self.ctx.text_extents( u"\u25CF" )[2]
                    x = pixel[0]*3
                    cols = []
                    for h in head:
                        width = 0.5*self.ctx.text_extents( h )[2]
                        self.ctx.move_to( x + w, 0.5*barwidth - height / 2 - y_bearing )
                        cols.append( x + w + width )
                        x += w + width + width + w
                        self.ctx.show_text( h )
                    cols.append( x + w)
                    index = 1
                    for signal, text in rows:
                        barlength = signal/self.maxsignal
                        self.ctx.set_source_rgba(0.0, 1.0, 0.0, 1.0)
                        self.ctx.rectangle( 0.0, index*barwidth+pixel[1]*2, barlength, barwidth-pixel[1]*4 )
                        self.ctx.fill()
                        self.ctx.set_source_rgba(0, 0, 0, 1)
                        x_bearing, y_bearing, width, height = self.ctx.text_extents( " ".join( text ) )[:4]
                        for i in range( len( text ) ):
                            w = 0.5*self.ctx.text_extents( text[i] )[2]
                            self.ctx.move_to( cols[i] - w, (index+0.5)*barwidth - height / 2 - y_bearing )
                            self.ctx.show_text( text[i] )
                        index += 1
                self.img.image_data_set( self.surface.get_data() )
                self.img.pixels_dirty = True

    DELAY = 10.0
    def __init__(self, main):
        edje_group.__init__( self, main, "wireless" )
        self.neighbourgraph = self.NeighbourGraph( self.evas )
        self.page = "left"
        self.oldpage = None
        self.signal_emit( "activate_button_select_%s" % self.page, "" )

        self.timer = None

        self.code = None
        self.gsm_serving = None
        self.gsm_neighbour = None
        self.location = None
        self.update()

    def update( self ):
        text = []
        if self.code:
            text.append( "MCC/MNC:<tab>%s/%s" % ( self.code[:3], self.code[3:] ) )
        else:
            text.append( "MCC/MNC:<tab>N/A" )
        if self.gsm_serving:
            text.append( "ARFCN:<tab>%s" % self.gsm_serving['arfcn'] )
            text.append( "LAC/CID:<tab>%s/%s" % ( self.gsm_serving['lac'], self.gsm_serving['cid'] ) )
            text.append( "RXLEV/C1/C2:<tab>%s/%s/%s" % (
                self.gsm_serving['rxlev'], self.gsm_serving['c1'], self.gsm_serving['c2']
            ) )
            text.append( "RXLEV-F/-S:<tab>%s/%s" % ( self.gsm_serving['rxlev_f'], self.gsm_serving['rxlev_s'] ) )
            text.append( "TXLEV:<tab>%s" % self.gsm_serving['txlev'] )
            text.append( "TN:<tab>%s" % self.gsm_serving['tn'] )
            text.append( "TAV:<tab>%s" % self.gsm_serving['tav'] )
            text.append( "DSC:<tab>%s" % self.gsm_serving['dsc'] )
            text.append( "Cell Type:<tab>%s" % self.gsm_serving['ctype'] )
        else:
            text.append( "Serving Cell:<tab>N/A" )
        celldb = []
        if self.location:
            celldb.append( "Lat:<tab>%s" % self.location[1] )
            celldb.append( "Lon:<tab>%s" % self.location[2] )
            celldb.append( "Alt:<tab>%s" % self.location[3] )
            celldb.append( "Size:<tab>%s" % self.location[4] )
            celldb.append( "Count:<tab>%s" % self.location[5] )
        else:
            celldb.append( "Cell DB:<tab>N/A" )
        cells = []
        if self.gsm_serving:
            cells.append(self.gsm_serving)
        if self.gsm_neighbour:
            cells.extend(self.gsm_neighbour)
        if self.page != self.oldpage:
            self.oldpage = self.page
            if self.part_swallow_get( "swallow" ):
                self.part_swallow_get( "swallow" ).hide()
                self.part_unswallow( self.part_swallow_get( "swallow" ) )
            if self.page == "left":
                pass
            elif self.page == "middle":
                self.part_swallow( "swallow", self.neighbourgraph )
                self.neighbourgraph.show()
            elif self.page == "right":
                pass
        if self.page == "left":
            self.part_text_set( "status", u"<br>".join( text ) )
        elif self.page == "middle":
            self.part_text_set( "status", u"" )
            self.neighbourgraph.update(cells)
        elif self.page == "right":
            self.part_text_set( "status", u"<br>".join( celldb ) )

    def cbServingCellReply( self, serving ):
        logger.debug( "gsm serving cell status updated: %s" % serving )
        self.gsm_serving = serving
        self.update()

    def cbServingCellError( self, e ):
        log_dbus_error( e, "error while requesting serving cell info" )
        self.gsm_serving = None
        self.update()

    def cbNeighbourCellReply( self, neighbour ):
        logger.debug( "gsm neighbour cell status updated: %s" % neighbour )
        self.gsm_neighbour = neighbour
        self.update()

    def cbNeighbourCellError( self, e ):
        log_dbus_error( e, "error while requesting neighbour cell info" )
        self.gsm_neighbour = None
        self.update()

    def onShow( self ):
        if self.timer:
            self.timer.delete()
        self.cbTimer()
        self.timer = ecore.timer_add( self.DELAY, self.cbTimer )

    def onHide( self ):
        if self.timer:
            self.timer.delete()

    def cbTimer( self ):
        dbus_object.gsm_monitor_iface.GetServingCellInformation(
            reply_handler=self.cbServingCellReply,
            error_handler=self.cbServingCellError,
        )
        dbus_object.gsm_monitor_iface.GetNeighbourCellInformation(
            reply_handler=self.cbNeighbourCellReply,
            error_handler=self.cbNeighbourCellError,
        )
        cells = []
        if self.gsm_serving:
            cells.append(self.gsm_serving)
        if self.gsm_neighbour:
            cells.extend(self.gsm_neighbour)
        if self.code and cells:
            ids = []
            for cell in cells:
                ids.append((int(cell['lac'], 16), int(cell['cid'], 16)))
            print (self.code[:3], self.code[3:], ids)
            location = dbus_object.gsm_data_iface.GetCellLocation(self.code[:3], self.code[3:], ids)
            print location
            if location is None:
                self.location = None
            if location[0] is False:
                self.location = None
            else:
                self.location = location
        else:
            self.location = None
        return True

    def onNetworkStatus( self, status ):
        if 'code' in status:
            self.code = str(status['code'])
        else:
            self.code = None

    @edje.decorators.signal_callback( "mouse,clicked,1", "button_select_*" )
    def on_edje_signal_button_list_pressed( self, emission, source ):
        self.page = source.split( "_" )[-1]
        self.signal_emit( "deactivate_button_select_left", "" )
        self.signal_emit( "deactivate_button_select_middle", "" )
        self.signal_emit( "deactivate_button_select_right", "" )
        self.signal_emit( "activate_button_select_%s" % self.page, "" )
        self.update()

#----------------------------------------------------------------------------#
class pyphone_location( edje_group ):
#----------------------------------------------------------------------------#
    class SignalGraph( evas.ClippedSmartObject ):
        def __init__( self, *args, **kargs ):
            evas.ClippedSmartObject.__init__( self, *args, **kargs )
            self.img = self.Image()
            self.img.alpha = True
            self.img.colorspace = evas.EVAS_COLORSPACE_ARGB8888
            self.img.pos = self.pos
            self.img.show()
            self.surface = None
            self.ctx = None
            self.generic = None
            self.ubxinfo = None
            self.maxsignal = 50.0
            self.current = False

        def show( self ):
            super( pyphone_location.SignalGraph, self ).show()
            self.current = True
            self.update()

        def hide( self ):
            super( pyphone_location.SignalGraph, self ).hide()
            self.current = False

        def resize( self, w, h ):
            self.surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, w, h)
            self.ctx = cairo.Context(self.surface)
            self.ctx.scale( w, h )
            #self.ctx.set_antialias( cairo.ANTIALIAS_NONE )
            self.img.image_size = ( w, h )
            self.update()
            self.img.resize( *self.img.image_size )
            self.img.fill_set(0, 0, *self.img.image_size )

        def update( self, generic = None, ubxinfo = None ):
            if generic is not None:
                self.generic = {}
                for sv, used, signal in generic:
                    self.generic[sv] = ( used, signal )
            if ubxinfo is not None:
                self.ubxinfo = ubxinfo[1:]
            if self.ctx and self.current:
                self.ctx.set_operator( cairo.OPERATOR_CLEAR )
                self.ctx.paint()
                self.ctx.set_operator( cairo.OPERATOR_OVER )
                pixel = self.ctx.device_to_user_distance( 1, 1 )
                self.ctx.set_line_width( max( pixel ) )
                self.ctx.set_source_rgba(1, 1, 1, 0.5)
                self.ctx.rectangle(0.0, 0.0, 1.0, 1.0)
                self.ctx.fill()
                rows = []
                if self.ubxinfo:
                    for sv in self.ubxinfo:
                        chn = sv["chn"]
                        svid = sv["SVID"]
                        used = sv["Flags"] & 0x01
                        diff = sv["Flags"] & 0x02
                        almoreph = sv["Flags"] & 0x04
                        eph = sv["Flags"] & 0x08
                        bad = sv["Flags"] & 0x10
                        qi = ("%i: " % sv["QI"]) + {
                            0: "idle",
                            1: "searching",
                            2: "signal acquired",
                            3: "signal unusable",
                            4: "code lock",
                            5: "code&carrier lock",
                            6: "code&carrier lock",
                            7: "receiving data"
                        }[sv["QI"]]
                        cno = sv["CNO"]
                        elev = sv["Elev"]
                        azim = sv["Azim"]
                        text = []
                        text.append( "%i" % chn )
                        text.append( "%i" % svid )
                        text.append( "%i" % cno )
                        if eph: text.append( u"\u25CF" )
                        elif almoreph: text.append( u"\u25CB" )
                        else: text.append( "" )
                        if diff: text.append( u"\u25CF" )
                        else: text.append( "" )
                        rows.append( ( cno, used, bad, text, qi ) )
                elif self.generic:
                    for sv, (used, signal) in self.generic.items():
                        text = [ "", "%i" % sv, "%i" % signal, "", "", "" ]
                        rows.append( ( signal, used, 0, text, "" ) )
                if rows:
                    count = 1 + len( rows )
                    self.maxsignal = float( max( [x[0] for x in rows] + [self.maxsignal] ) )
                    barwidth = 1.0/count
                    self.ctx.set_font_size( barwidth*0.6 )
                    self.ctx.set_source_rgba(0, 0, 0, 1)
                    head = [ "CH", "PRN", "SNR", "A/E", "DIFF" ]
                    x_bearing, y_bearing, width, height = self.ctx.text_extents( " ".join( head ) )[:4]
                    w = 0.5*self.ctx.text_extents( u"\u25CF" )[2]
                    x = pixel[0]*3
                    cols = []
                    for h in head:
                        width = 0.5*self.ctx.text_extents( h )[2]
                        self.ctx.move_to( x + w, 0.5*barwidth - height / 2 - y_bearing )
                        cols.append( x + w + width )
                        x += w + width + width + w
                        self.ctx.show_text( h )
                    cols.append( x + w)
                    self.ctx.move_to( x + w, 0.5*barwidth - height / 2 - y_bearing )
                    self.ctx.show_text( "Quality" )
                    index = 1
                    for signal, used, bad, text, qi in rows:
                        barlength = signal/self.maxsignal
                        if used:
                            self.ctx.set_source_rgba(0.0, 1.0, 0.0, 1.0)
                        else:
                            self.ctx.set_source_rgba(0.75, 0.75, 0.75, 1.0)
                        self.ctx.rectangle( 0.0, index*barwidth+pixel[1]*2, barlength, barwidth-pixel[1]*4 )
                        self.ctx.fill()
                        self.ctx.set_source_rgba(0, 0, 0, 1)
                        x_bearing, y_bearing, width, height = self.ctx.text_extents( " ".join( text ) )[:4]
                        for i in range( len( text ) ):
                            w = 0.5*self.ctx.text_extents( text[i] )[2]
                            self.ctx.move_to( cols[i] - w, (index+0.5)*barwidth - height / 2 - y_bearing )
                            self.ctx.show_text( text[i] )
                        self.ctx.move_to( cols[len( text )], (index+0.5)*barwidth - height / 2 - y_bearing )
                        self.ctx.show_text( qi )
                        if bad:
                            self.ctx.move_to( pixel[0]*2, (index+0.5)*barwidth )
                            self.ctx.rel_line_to( 1-(pixel[0]*4), 0 )
                            self.ctx.close_path()
                            self.ctx.stroke()
                        index += 1
                self.img.image_data_set( self.surface.get_data() )
                self.img.pixels_dirty = True

    class PositionGraph( evas.ClippedSmartObject ):
        def __init__( self, *args, **kargs ):
            evas.ClippedSmartObject.__init__( self, *args, **kargs )
            self.img = self.Image()
            self.img.alpha = True
            self.img.colorspace = evas.EVAS_COLORSPACE_ARGB8888
            self.img.pos = self.pos
            self.img.show()
            self.surface = None
            self.ctx = None
            self.values = []
            self.current = False

        def show( self ):
            super( pyphone_location.PositionGraph, self ).show()
            self.current = True
            self.update()

        def hide( self ):
            super( pyphone_location.PositionGraph, self ).hide()
            self.current = False

        def proj( self, azim, elev ):
            x = math.sin( math.radians( azim ) )*( ( 90.0 - elev )/90.0 )
            y = - math.cos( math.radians( azim ) )*( ( 90.0 - elev )/90.0 )
            return ( x, y )

        def resize( self, w, h ):
            self.surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, w, h)
            self.ctx = cairo.Context(self.surface)
            self.ctx.scale( w, h )
            #self.ctx.set_antialias( cairo.ANTIALIAS_NONE )
            self.img.image_size = ( w, h )
            self.update()
            self.img.resize( *self.img.image_size )
            self.img.fill_set(0, 0, *self.img.image_size )

        def draw_blip( self, azim, elev, name = None ):
            x, y = self.proj( azim, elev )
            self.ctx.arc(
                0.50+x*0.45,
                0.50+y*0.45,
                max( self.ctx.device_to_user_distance( 15, 15 ) ),
                0.0, math.pi*2
            )
            self.ctx.fill()
            if name is not None:
                self.ctx.set_source_rgba(0, 0, 0, 1)
                self.ctx.set_font_size( max( self.ctx.device_to_user_distance( 20, 20 ) ))
                x_bearing, y_bearing, width, height = self.ctx.text_extents(name)[:4]
                self.ctx.move_to(
                    0.50+x*0.45 - width / 2 - x_bearing,
                    0.50+y*0.45 - height / 2 - y_bearing
                )
                self.ctx.show_text( name )

        def update( self, values = None ):
            if values is not None:
                self.values = values
            if self.ctx and self.current:
                self.ctx.set_operator( cairo.OPERATOR_CLEAR )
                self.ctx.paint()
                self.ctx.set_operator( cairo.OPERATOR_OVER )
                self.ctx.set_line_width( max( self.ctx.device_to_user_distance( 2, 2 ) ) )
                #self.ctx.move_to(0.20, 0.10)
                #self.ctx.line_to(0.40, 0.30)
                #self.ctx.rel_line_to(-0.20, 0.0)
                #self.ctx.close_path()
                #self.ctx.stroke()
                # background
                self.ctx.set_source_rgba(1, 1, 1, 0.5)
                self.ctx.arc( 0.50, 0.50, 0.45, 0.0, math.pi*2 )
                self.ctx.fill()
                self.ctx.set_source_rgba(0, 0, 0, 1)
                self.ctx.arc( 0.50, 0.50, 0.45, 0.0, math.pi*2 )
                self.ctx.new_sub_path()
                self.ctx.arc( 0.50, 0.50, 0.30, 0.0, math.pi*2 )
                self.ctx.new_sub_path()
                self.ctx.arc( 0.50, 0.50, 0.15, 0.0, math.pi*2 )
                self.ctx.stroke()
                # refs
                d = max( self.ctx.device_to_user_distance( 5, 5 ) )
                x_bearing, y_bearing, width, height = self.ctx.text_extents("N")[:4]
                self.ctx.move_to(
                    0.50 - width / 2 - x_bearing,
                    0.05 + d - y_bearing
                )
                self.ctx.show_text( "N" )
                x_bearing, y_bearing, width, height = self.ctx.text_extents("E")[:4]
                self.ctx.move_to(
                    0.95 - width - d - x_bearing,
                    0.50 - height / 2 - y_bearing
                )
                self.ctx.show_text( "E" )
                x_bearing, y_bearing, width, height = self.ctx.text_extents("S")[:4]
                self.ctx.move_to(
                    0.50 - width / 2 - x_bearing,
                    0.95 - height - d - y_bearing
                )
                self.ctx.show_text( "S" )
                x_bearing, y_bearing, width, height = self.ctx.text_extents("W")[:4]
                self.ctx.move_to(
                    0.05 + d - x_bearing,
                    0.50 - height / 2 - y_bearing
                )
                self.ctx.show_text( "W" )
                self.ctx.stroke()
                # locations
                for sv, used, azim, elev in self.values:
                    if used:
                        self.ctx.set_source_rgba(0.0, 1.0, 0.0, 1.0)
                    else:
                        self.ctx.set_source_rgba(0.75, 0.75, 0.75, 1.0)
                    self.draw_blip( azim, elev, str( sv ) )
                #self.surface.write_to_png( "/tmp/zhone-location.png" )
                #self.img.file_set( "/tmp/zhone-location.png" )
                self.img.image_data_set( self.surface.get_data() )
                self.img.pixels_dirty = True

    def __init__(self, main):
        edje_group.__init__( self, main, "location" )
        self.signalgraph = self.SignalGraph( self.evas )
        self.positiongraph = self.PositionGraph( self.evas )

        self.page = "left"
        self.oldpage = None
        self.signal_emit( "activate_button_select_%s" % self.page, "" )

        self.time = None
        self.fix = None
        self.dgps = None
        self.accuracy = None
        self.course = None
        self.position = None
        self.ttff = None
        self.update()

        #self.signalgraph.update( values = [(1, True, 20.0), (2, False, 34.0), (5, True, 25.0), (10, False, 0.0)] )
        #self.positiongraph.update( values = [(1,  True, 20.0, 90.0), (2, False, 34.3, 45.0), (5, True, 225.9, 0.0), (10, False, 0.5, 30.0)] )

    def update( self ):
        text = []
        if self.time:
            text.append( _("GPS Time:<tab>%s") % time.strftime("%H:%M:%S", time.gmtime(self.time)) )
        else:
            text.append( _("GPS Time:<tab>N/A") )
        if self.fix and self.ttff:
            text.append( _("Fix:<tab>%s (%.1fs)") % ( { 1: _("none"), 2: "2D", 3: "3D" }[self.fix], self.ttff ) )
        elif self.fix:
            text.append( _("Fix:<tab>%s") % { 1: _("none"), 2: "2D", 3: "3D" }[self.fix] )
        else:
            text.append( _("Fix:<tab>N/A") )
        if self.dgps == None:
            text.append( _("DGPS:<tab>N/A") )
        else:
            text.append( _("DGPS:<tab>%s") % _({ 0: "no correction", 1: "PR+PRR", 2: "PR+PRR+CP", 3: "HA PR+PRR+CP" }[self.dgps]) )
        if self.position:
            text.append( _("Lat:<tab>%s<br>Lon:<tab>%s<br>Alt:<tab>%s") % self.position )
        else:
            text.append( _("Lat:<tab>N/A<br>Lon:<tab>N/A<br>Alt:<tab>N/A") )
        if self.course:
            text.append( _("Course:<tab>%s<br>Speed:<tab>%s<br>Climb:<tab>%s") % self.course )
        else:
            text.append( _("Course:<tab>N/A<br>Speed:<tab>N/A<br>Climb:<tab>N/A") )
        if self.accuracy:
            text.append( _("P/H/V-DOP:<tab>%s/%s/%s") % self.accuracy )
        else:
            text.append( _("P/H/V-DOP:<tab>N/A") )
        if self.page != self.oldpage:
            self.oldpage = self.page
            if self.part_swallow_get( "swallow" ):
                self.part_swallow_get( "swallow" ).hide()
                self.part_unswallow( self.part_swallow_get( "swallow" ) )
            if self.page == "left":
                pass
            elif self.page == "middle":
                self.part_swallow( "swallow", self.signalgraph )
                self.signalgraph.show()
            elif self.page == "right":
                self.part_swallow( "swallow", self.positiongraph )
                self.positiongraph.show()
        if self.page == "left":
            self.part_text_set( "status", u"<br>".join( text ) )
        elif self.page == "middle":
            self.part_text_set( "status", u"" )
        elif self.page == "right":
            self.part_text_set( "status", u"" )

    def onFixStatusChanged( self, fixstatus ):
        logger.debug( "gps fix status changed: %s" % fixstatus )
        self.fix = fixstatus
        self.update()

    def onAccuracyChanged( self, fields, pdop, hdop, vdop ):
        logger.debug( "gps accuracy changed: %s/%s/%s" % ( pdop, hdop, vdop ) )
        self.accuracy = ( pdop, hdop, vdop )
        self.update()

    def onCourseChanged( self, fields, timestamp, speed, heading, climb ):
        logger.debug( "gps course changed: %s/%s/%s" % ( speed, heading, climb ) )
        self.time = timestamp
        self.course = ( heading, speed, climb )
        self.update()

    def onPositionChanged( self, fields, timestamp, lat, lon, alt ):
        logger.debug( "gps position changed: %s/%s/%s" % ( lat, lon, alt ) )
        self.time = timestamp
        self.position = ( lat, lon, alt )
        self.update()

    def onSatellitesChanged( self, satellites ):
        logger.debug( "gps satellites changed" )
        satellites.sort()
        signallist = [ (int(sat[0]), sat[1], float(sat[4])) for sat in satellites ]
        self.signalgraph.update( generic = signallist )
        positionlist = [ (int(sat[0]), sat[1], int(sat[3]), int(sat[2])) for sat in satellites ]
        self.positiongraph.update( positionlist )

    def onTimeChanged( self, timestamp ):
        logger.debug( "gps time changed: %s (%s)" % (time.strftime("%H:%M:%S", time.gmtime(timestamp)), timestamp) )
        self.time = timestamp
        self.update()

    def onUBXDebugPacket( self, clid, length, data ):
        logger.debug( "gps got ubxdebug packet" )
        if clid == "NAV-STATUS" and data and data[0]:
            data = data[0]
            if 'TTFF' in data and data['TTFF']:
                self.ttff = data['TTFF']/1000.0
            else:
                self.ttff = None
            if 'DiffS' in data:
                self.dgps = data["DiffS"] & 3
            else:
                self.dgps = None
            self.update()
        elif clid == "NAV-SVINFO":
            self.signalgraph.update( ubxinfo = data )

    def onShow( self ):
        self.main.agents["usage"].request( "GPS" )
        self.update()

    def onHide( self ):
        self.main.agents["usage"].release( "GPS" )

    @edje.decorators.signal_callback( "mouse,clicked,1", "button_select_*" )
    def on_edje_signal_button_list_pressed( self, emission, source ):
        self.page = source.split( "_" )[-1]
        self.signal_emit( "deactivate_button_select_left", "" )
        self.signal_emit( "deactivate_button_select_middle", "" )
        self.signal_emit( "deactivate_button_select_right", "" )
        self.signal_emit( "activate_button_select_%s" % self.page, "" )
        self.update()

#----------------------------------------------------------------------------#
class pyphone_message(edje_group):
#----------------------------------------------------------------------------#
    MODE = 1
    def __init__(self, main):
        edje_group.__init__(self, main, "message")
        self.mode1_labels = [
            [u" .,?!'-@<>", u"abc", "def", " "],
            [u"ghi", u"jkl", "mno", " "],
            [u"pqrs", u"tuv", "wxyz", u"⇦"]
        ]
        self.button_labels2 = [
            [
                [u".,?!", u"abc", "def", ""],
                [u"ghi", u"jkl", "mno", ""],
                [u"pqrs", u"tuv", "wxyz", ""],
                [u"", u"", u"⇦⇧⇨", ""],
            ],
            [
                ["", "", "", ""],
                ["", "", "", ""],
                ["", "", "", ""],
                ["", "", "", ""],
            ]
        ]
        self.button_labels = [
            [
                ["1", "2", "3", u"↤"],
                ["4", "5", "6", u"↲"],
                ["7", "8", "9", "Abc"],
                ["+", "0", u"⇩", "+"],
            ],
            [
                ["1", "?", "", ""],
                [".", ",", "", ""],
                ["!", "", "", ""],
                ["", "", "", ""],
            ],
            [
                ["", "2", "c", ""],
                ["", "a", "b", ""],
                ["", "", "", ""],
                ["", "", "", ""],
            ],
            [
                ["", "", "3", "f"],
                ["", "", "d", "e"],
                ["", "", "", ""],
                ["", "", "", ""],
            ],
            [
                ["", "", "", u"↤"],
                ["", "", "", ""],
                ["", "", "", ""],
                ["", "", "", ""],
            ],
            [
                ["", "", "", ""],
                ["4", "i", "", ""],
                ["g", "h", "", ""],
                ["", "", "", ""],
            ],
            [
                ["", "", "", ""],
                ["", "5", "l", ""],
                ["", "j", "k", ""],
                ["", "", "", ""],
            ],
            [
                ["", "", "", ""],
                ["", "", "6", "o"],
                ["", "", "m", "n"],
                ["", "", "", ""],
            ],
            [
                ["", "", "", ""],
                ["", "", "", ""],
                ["", "", "", ""],
                ["", "", "", ""],
            ],
            [
                ["", "", "", ""],
                ["", "s", "", ""],
                ["7", "r", "", ""],
                ["p", "q", "", ""],
            ],
            [
                ["", "", "", ""],
                ["", "", "", ""],
                ["", "8", "v", ""],
                ["", "t", "u", ""],
            ],
            [
                ["", "", "", ""],
                ["", "", "", "z"],
                ["", "", "9", "y"],
                ["", "", "w", "x"],
            ],
            [
                ["", "", "", ""],
                ["", "", "", ""],
                ["", "", "", ""],
                ["", "", "", ""],
            ],
            [
                ["", "", "", ""],
                ["", "", "", ""],
                ["", "", "", ""],
                ["", "", "", ""],
            ],
            [
                ["", "", "", ""],
                ["", "", "", ""],
                ["", "", "", ""],
                ["", " ", "", ""],
            ],
            [
                ["", "", "", ""],
                ["", "", "", ""],
                ["", "", u"⇧", ""],
                ["", u"⇦", u"⇩", u"⇨"],
            ],
            [
                ["", "", "", ""],
                ["", "", "", ""],
                ["", "", "", ""],
                ["", "", "", ""],
            ]
        ]
        self.x = None
        self.y = None
        self.char_index = 0
        self.pending_char = None
        self.kbdTimer = None

    def addPendingChar(self):
        self.stopKbdTimer()
        self.part_text_set("label_preview", '')
        if self.pending_char == u"⇦":
            if len(self.text) > 0:
                self.text = self.text[:len(self.text)-1]
        elif self.pending_char is not None:
            self.text.append(self.pending_char)
        # The trailing whitespace is a workaround for the one char
        # invisible bug due to some problems with scaling of text
        # parts.
        self.part_text_set("label", "".join(self.text)+" ")
        self.pending_char = None
        self.x = None
        self.y = None
        self.char_index = 0
        return 0

    def stopKbdTimer(self):
        if self.kbdTimer is not None:
            self.kbdTimer.delete()
            self.kbdTimer = None

    def restartKbdTimer(self):
        self.stopKbdTimer()
        self.kbdTimer = ecore.timer_add(1.5, self.addPendingChar)

    @edje.decorators.signal_callback("kb_button_mouse_up", "*")
    def on_edje_signal_dialer_button_mouse_up(self, emission, source):
        if self.MODE == 1:
            x = int(source[-3:-2])
            y = int(source[-1:])
            if (self.x is None) or (x != self.x) or (y != self.y):
                if self.pending_char is not None:
                    self.addPendingChar()
                self.x = x
                self.y = y
            self.pending_char = self.mode1_labels[y][x][self.char_index]
            self.part_text_set("label_preview", self.pending_char)
            self.char_index += 1
            if self.char_index >= len(self.mode1_labels[y][x]):
                self.char_index = 0
            if self.pending_char == u"⇦":
                self.addPendingChar()
            else:
                self.restartKbdTimer()
            return
        #now = time.time()
        x = int(source[-3:-2])
        y = int(source[-1:])
        key = self.button_labels[self.active][y][x]
        self.text.append(key)
        # The trailing whitespace is a workaround for the one char invisible
        # bug due to some problems with scaling of text parts.
        self.part_text_set("label", "".join(self.text)+" ")
        self.set_button_text(0)
        #logger.debug( "mouse up: %s" % time.time()-now )

    @edje.decorators.signal_callback("kb_button_mouse_down", "*")
    def on_edje_signal_dialer_button_mouse_down(self, emission, source):
        if self.MODE == 1:
            return
        #now = time.time()
        x = int(source[-3:-2])
        y = int(source[-1:])
        num = 4*y+x+1
        if self.active == 0:
            self.set_button_text(num)
        #logger.debug( "mouse down: %s" % time.time()-now )

    @edje.decorators.signal_callback("kb_mutton_mouse_in", "*")
    def on_edje_signal_dialer_button_mouse_in(self, emission, source):
        if self.MODE == 1:
            return
        #now = time.time()
        x = int(source[-3:-2])
        y = int(source[-1:])
        self.part_text_set("label_preview", self.button_labels[self.active][y][x])
        #logger.debug( "mouse in: %s" % time.time()-now )

    def set_button_text(self, num):
        for i in xrange(4):
            for j in xrange(3):
                if self.MODE == 1:
                    text = self.mode1_labels[j][i][0:4]
                else:
                    text = self.button_labels[num][j][i]
                self.part_text_set("label_%d_%d" % (i,j) , text)
        self.active = num

        if num != 0:
            num = 1

        for i in xrange(4):
            for j in xrange(3):
                if self.MODE == 1:
                    text = self.mode1_labels[j][i][4:]
                else:
                    text = self.button_labels2[num][j][i]
                self.part_text_set("label2_%d_%d" % (i,j) , text)

    @edje.decorators.signal_callback( "mouse,clicked,1", "button_*" )
    def on_edje_signal_button_pressed( self, emission, source ):
        key = source.split( "_", 1 )[1]
        if key == "done":
            if self.pending_char is not None:
                self.addPendingChar()
            if len(self.text) == 0:
                self.main.groups["text_edit"].setup(
                    self.parent_name,
                    "",
                    self.title,
                    self.cb_data,
                    self.cb
                    )
            else:
                self.main.transition_to(self.parent_name)
                self.cb( "".join(self.text), self.cb_data )
        elif key == "cancel":
            self.main.transition_to(self.parent_name)

    def setup( self, parent_name, text, title, cb_data, cb ):
        self.parent_name = parent_name
        self.text = []
        self.title = title
        self.cb_data = cb_data
        self.cb = cb
        #self.part_text_set( "label_description", u" %s " % self.title )
        self.part_text_set( "label", u"" )
        self.set_button_text(0)
        self.active = 0
        self.main.transition_to("message")

#----------------------------------------------------------------------------#
class pyphone_list_choose(edje_group):
#----------------------------------------------------------------------------#
    def __init__( self, main ):
        edje_group.__init__( self, main, "list_choose" )
        self.text = ""
        self.list_data = None
        self.cb_data = None
        self.cb = None

    def updateList( self):
        self.pages = max( ( len( self.list_data ) - 1 ) / 6 + 1, 1 )
        if self.page >= self.pages:
            self.page = 0
        if self.page < 0:
            self.page = self.pages - 1
        self.current = self.list_data[self.page*6:(self.page+1)*6]
        self.part_text_set( "pager", u"".join( [u"□"]*self.page+[u"▣"]+[u"□"]*(self.pages-self.page-1) ) )
        for i in range( 0, len( self.current ) ):
            self.part_text_set( "label_main_list_%i" % i, self.current[i][0] )
            self.part_text_set( "label_sub_list_%i" % i, self.current[i][1] )
        for i in range( len( self.current ), 6):
            self.part_text_set( "label_main_list_%i" % i, u"" )
            self.part_text_set( "label_sub_list_%i" % i, u"" )

    @edje.decorators.signal_callback( "mouse,clicked,1", "target_list_*" )
    def on_edje_signal_button_list_pressed( self, emission, source ):
        id = int( source.split( "_" )[-1] )
        self.main.transition_to(self.parent_name)
        self.cb( self.current[id], self.cb_data )

    @edje.decorators.signal_callback( "mouse,clicked,1", "button_action_left" )
    def on_edje_signal_button_action_left_pressed( self, emission, source ):
        self.page -= 1
        self.updateList()

    @edje.decorators.signal_callback( "mouse,clicked,1", "button_action_right" )
    def on_edje_signal_button_action_right_pressed( self, emission, source ):
        self.page += 1
        self.updateList()

    def setup( self, parent_name, list_data, cb_data, cb ):
        self.parent_name = parent_name
        self.list_data = list_data
        self.cb_data = cb_data
        self.cb = cb

        self.current = []
        self.page = 0
        self.main.transition_to("list_choose")

        self.updateList()

#----------------------------------------------------------------------------#
class pyphone_number_edit( edje_group ):
#----------------------------------------------------------------------------#
    TIMEOUT = 2.0
    def __init__( self, main ):
        edje_group.__init__( self, main, "number_edit" )
        self.text = ""
        self.cb_data = None
        self.cb = None
        self.last = 0.0

    @edje.decorators.signal_callback( "mouse,clicked,1", "button_*" )
    def on_edje_signal_button_pressed( self, emission, source ):
        key = source.split( "_", 1 )[1]
        if key in ( "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "#" ):
            self.text += key
            # The trailing whitespace is a workaround for the one char invisible
            # bug due to some problems with scaling of text parts.
            self.part_text_set( "label", u" %s " % self.text )
        elif key in "star":
            if self.text and ( time.time()-self.last < self.TIMEOUT ):
                if self.text[-1] == "*":
                    self.text = self.text[:-1]+"+"
                elif self.text[-1] == "+":
                    self.text = self.text[:-1]+"*"
                else:
                    self.text += "*"
            else:
                self.text += "*"
            self.part_text_set( "label", u" %s " % self.text )
        elif key in "hash":
            self.text += "#"
            self.part_text_set( "label", u" %s " % self.text )
        elif key in "delete":
            self.text = self.text[:-1]
            self.part_text_set( "label", u" %s " % self.text )
        elif key in "done":
            self.main.transition_to(self.parent_name)
            self.cb( self.text, self.cb_data )
        self.last = time.time()

    def setup( self, parent_name, text, title, cb_data, cb ):
        self.parent_name = parent_name
        self.text = text
        self.title = title
        self.cb_data = cb_data
        self.cb = cb
        self.part_text_set( "label_description", u" %s " % self.title )
        self.part_text_set( "label", u" %s " % self.text )
        self.main.transition_to("number_edit")

#----------------------------------------------------------------------------#
class pyphone_pin_edit( edje_group ):
#----------------------------------------------------------------------------#
    DELAY = 0.1
    def __init__( self, main ):
        edje_group.__init__( self, main, "number_edit" )
        self.text = ""
        self.cb_data = None
        self.cb = None
        self.timer = None
        self.last = 0.0
        self.part_text_set( "label_main_star", u"" )
        self.part_text_set( "label_sub_star", u"" )
        self.part_text_set( "label_main_hash", u"" )
        self.part_text_set( "label_sub_hash", u"" )

    @edje.decorators.signal_callback( "mouse,clicked,1", "button_*" )
    def on_edje_signal_button_pressed( self, emission, source ):
        key = source.split( "_", 1 )[1]
        if key in ( "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" ):
            self.text += key
            # The trailing whitespace is a workaround for the one char invisible
            # bug due to some problems with scaling of text parts.
            output = u"●"*( len( self.text ) - 1) + self.text[-1]
            self.part_text_set( "label", u" %s " % output )
            if self.timer:
                self.timer.delete()
            self.timer = ecore.timer_add( self.DELAY, self.timerCb )
        elif key in "delete":
            self.text = self.text[:-1]
            output = u"●"*len( self.text )
            self.part_text_set( "label", u" %s " % output )
        elif key in "done":
            self.main.transition_to(self.parent_name)
            self.cb( self.text, self.cb_data )
        self.last = time.time()

    def timerCb( self ):
        output = u"●"*len( self.text )
        self.part_text_set( "label", u" %s " % output )
        self.timer = None
        return False

    def setup( self, parent_name, text, title, cb_data, cb ):
        self.parent_name = parent_name
        self.text = text
        self.title = title
        self.cb_data = cb_data
        self.cb = cb
        self.part_text_set( "label_description", u" %s " % self.title )
        self.part_text_set( "label", u" %s " % self.text )
        self.main.transition_to("pin_edit")

#----------------------------------------------------------------------------#
class pyphone_text_show( edje_group ):
#----------------------------------------------------------------------------#
    def __init__( self, main ):
        edje_group.__init__( self, main, "text_show" )

    def setup( self, parent_name, text ):
        self.parent_name = parent_name
        self.part_text_set( "text", text )
        self.main.transition_to("text_show")

    @edje.decorators.signal_callback( "mouse,clicked,1", "button_bottom_middle" )
    def on_edje_signal_button_bottom_middle_pressed( self, emission, source ):
        self.main.groups["menu"].activate( ( _("send"), _("delete"), _("forward"), _("reply") ),
                                           self.main.groups["sms"].cbMenu )
        self.main.groups["menu"].part_text_set( "target_label_cancel", _("cancel") )

#----------------------------------------------------------------------------#
class pyphone_text_edit( edje_group ):
#----------------------------------------------------------------------------#
    def __init__( self, main ):
        edje_group.__init__( self, main, "text_edit" )
        self.text = ""
        self.cb_data = None
        self.cb = None
        self.shift_down = False

    def onShow( self ):
        self.focus = True
        if illume:
            illume.kbd_show()

    def onHide( self ):
        self.focus = False
        if illume:
            illume.kbd_hide()

    @evas.decorators.key_down_callback
    def on_key_down( self, event ):
        key = event.string
        if key == "\x08":
            self.text = self.text[:-1]
        # Check if the key is aumlaut or oumlaut
        elif event.key == "adiaeresis":
            self.text += u"ä"
        elif event.key == "odiaeresis":
            self.text += u"ö"
        elif event.key == "Adiaeresis":
            self.text += u"Ä"
        elif event.key == "Odiaeresis":
            self.text += u"Ö"
        elif key is not None:
            self.text += key
        self.part_text_set( "label", self.text )

    @edje.decorators.signal_callback( "mouse,clicked,1", "button_*" )
    def on_edje_signal_button_pressed( self, emission, source ):
        key = source.split( "_", 1 )[1]
        if key == "done":
            if self.text == "":
                self.main.groups["message"].setup(
                    self.parent_name,
                    self.text,
                    self.title,
                    self.cb_data,
                    self.cb
                    )
            else:
                self.main.transition_to(self.parent_name)
                self.cb( self.text, self.cb_data )
        elif key == "cancel":
            self.main.transition_to(self.parent_name)

    def setup( self, parent_name, text, title, cb_data, cb ):
        self.parent_name = parent_name
        self.text = text
        self.title = title
        self.cb_data = cb_data
        self.cb = cb
        self.part_text_set( "label_description", u" %s " % self.title )
        self.part_text_set( "label", self.text )
        self.main.transition_to("text_edit")

#----------------------------------------------------------------------------#
class pyphone_menu( edje_group ):
#----------------------------------------------------------------------------#
    def __init__( self, main ):
        edje_group.__init__( self, main, "menu" )
        self.buttons = None
        self.cb = None
        self.deactivate()

    @edje.decorators.signal_callback( "mouse,clicked,1", "target_*" )
    def on_edje_signal_button_bottom_middle_pressed( self, emission, source ):
        if source == "target_cancel":
            self.deactivate()
            self.cb( "cancel" )
        else:
            id = int( source.split( "_", 1 )[1] )
            self.deactivate()
            self.cb( self.part_text_get( "target_label_%i" % id ) )

    def activate( self, buttons, cb ):
        self.buttons = buttons
        self.cb = cb
        count = len( buttons )
        assert count == 4
        self.part_text_set( "target_label_0", buttons[0] )
        self.part_text_set( "target_label_1", buttons[1] )
        self.part_text_set( "target_label_2", buttons[2] )
        self.part_text_set( "target_label_3", buttons[3] )
        self.signal_emit( "visible", "" )

    def deactivate( self ):
        self.signal_emit( "invisible", "" )


#----------------------------------------------------------------------------#
class pyphone_main_menu( edje_group ):
#----------------------------------------------------------------------------#
    def __init__( self, main ):
        edje_group.__init__( self, main, "main_menu" )
        self.signal_emit( "invisible", "" )

    @edje.decorators.signal_callback( "mouse,clicked,1", "target_list_*" )
    def on_edje_signal_button_list_pressed( self, emission, source ):
        id = int( source.split( "_" )[-1] )
        if( id == 5 ):
          gui.shutdown()
        if( id == 0 ):
          fp = os.popen("sleep 5 && \
                         n=/tmp/scap$$.png && \
                         fbgrab $n && \
                         curl \
                          -F file=@$n \
                          -F key=secret \
                          -F model=`uname -n` \
                          -F submit=Upload \
                          -F text=no\ comment \
                          http://scap.linuxtogo.org/tickle.php && \
                         rm $n &")

    @edje.decorators.signal_callback( "mouse,clicked,1", "target_cancel" )
    def on_edje_signal_button_bottom_middle_pressed( self, emission, source ):
        self.deactivate()

    def activate( self, group ):
        self.group = group
        self.signal_emit( "visible", "" )
        for i in range( 1, 5 ):
          self.part_text_set( "label_main_list_%i" % i, u"" )
          self.part_text_set( "label_sub_list_%i" % i, u"" )

        self.part_text_set( "label_main_list_0", _("Take screenshot") )
        self.part_text_set( "label_sub_list_0", _("and upload to http://scap.linuxtogo.org") )
        self.part_text_set( "label_main_list_5", _("Exit") )
        self.part_text_set( "label_sub_list_5", _("Stop and exit Zhone") )

        self.part_text_set( "target_label_cancel", _("cancel") )

    def deactivate( self ):
        self.signal_emit( "invisible", "" )
        self.main.transition_to(self.group)

#----------------------------------------------------------------------------#
class pyphone_lock( edje_group ):
#----------------------------------------------------------------------------#
    def __init__( self, main ):
        edje_group.__init__( self, main, "lock" )
        self.deactivate()

    @edje.decorators.signal_callback( "mouse,down,1", "target_*" )
    def on_edje_signal_button_pressed( self, emission, source ):
        id = int( source.split( "_", 1 )[1] )
        if id == self.step+1: # correct button
            self.signal_emit( "activate_%i" % id, "" )
            self.step += 1
        else:
            for id in range( 1, 5 ):
                self.signal_emit( "deactivate_%i" % id, "" )
            self.step = 0
        if self.step == 4:
            self.signal_emit( "invisible", "" )

    def activate( self ):
        self.step = 0
        for id in range( 1, 5 ):
            self.signal_emit( "deactivate_%i" % id, "" )
        self.signal_emit( "visible", "" )

    def deactivate( self ):
        self.signal_emit( "invisible", "" )

#----------------------------------------------------------------------------#
class pyphone_alert( edje_group ):
#----------------------------------------------------------------------------#
    def __init__( self, main ):
        edje_group.__init__( self, main, "alert" )
        self.buttons = None
        self.cb_data = None
        self.cb = None
        self.deactivate()

    @edje.decorators.signal_callback( "mouse,down,1", "button_*" )
    def on_edje_signal_button_pressed( self, emission, source ):
        id = int( source.split( "_", 1 )[1] )
        self.deactivate()
        if self.cb_data is not None:
            self.cb( self.part_text_get( "label_%i" % id ), self.cb_data )

    def activate( self, description, buttons, cb_data = None, cb = None ):
        self.buttons = buttons
        self.cb_data = cb_data
        self.cb = cb
        count = len( buttons )
        assert 1 <= count <= 3
        self.part_text_set( "description", description )
        if count == 1:
            self.signal_emit( "hide_0", "" )
            self.part_text_set( "label_0", u"" )
            self.signal_emit( "show_1", "" )
            self.part_text_set( "label_1", buttons[0] )
            self.signal_emit( "hide_2", "" )
            self.part_text_set( "label_2", u"" )
        elif count == 2:
            self.signal_emit( "show_0", "" )
            self.part_text_set( "label_0", buttons[0] )
            self.signal_emit( "hide_1", "" )
            self.part_text_set( "label_1", u"" )
            self.signal_emit( "show_2", "" )
            self.part_text_set( "label_2", buttons[1] )
        elif count == 3:
            self.signal_emit( "show_0", "" )
            self.part_text_set( "label_0", buttons[0] )
            self.signal_emit( "show_1", "" )
            self.part_text_set( "label_1", buttons[1] )
            self.signal_emit( "show_2", "" )
            self.part_text_set( "label_2", buttons[2] )
        self.signal_emit( "visible", "" )

    def deactivate( self ):
        self.signal_emit( "invisible", "" )

#----------------------------------------------------------------------------#
class Agent( object ):
#----------------------------------------------------------------------------#
    def __init__( self, main ):
        super( Agent, self ).__init__()
        self.main = main
        self.state = _("Waiting for DBus")
        self.onState = []

    def registerStateCallback( self, callback ):
        self.onState.append( callback )

    def setState( self, state ):
        logger.debug( state )
        self.state = state
        for cb in self.onState:
            cb( self.name, state )

#----------------------------------------------------------------------------#
class UsageAgent( Agent ):
#----------------------------------------------------------------------------#
    def __init__( self, main ):
        super( UsageAgent, self ).__init__( main )
        self.name = _("Usage")
        self.busy = None
        self.want = set( ["GSM"] )
        self.avail = set()
        self.have = set()
        self.error = set()

    def _update( self ):
        self.main.groups["main"].targets["location"] = "GPS" in (self.avail - self.error)
        self.main.groups["main"].update()
        if self.busy is None:
            pendingRequests = (self.avail & self.want) - self.have - self.error
            pendingReleases = self.have - self.want
            if pendingRequests:
                self.busy = pendingRequests.pop()
                self.setState( _("Requesting resource %s") % self.busy )
                dbus_object.usage_iface.RequestResource(
                    self.busy,
                    reply_handler=self.cbRequestReply,
                    error_handler=self.cbRequestError,
                )
            elif pendingReleases:
                self.busy = pendingReleases.pop()
                self.setState( _("Releasing resource %s") % self.busy )
                dbus_object.usage_iface.ReleaseResource(
                    self.busy,
                    reply_handler=self.cbReleaseReply,
                    error_handler=self.cbReleaseError,
                )

    def cbRequestReply( self ):
        self.have.add( self.busy )
        self.setState( _("Requested resource %s") % self.busy )
        if self.busy == "GSM":
            self.main.agents["gsm"].cbResourceReady()
        elif self.busy == "GPS":
            self.main.agents["gps"].cbResourceReady()
        self.busy = None
        self._update()

    def cbRequestError( self, e ):
        log_dbus_error( e, "error while requesting resource %s" % self.busy )
        self.setState( _("Requested resource %s with error") % self.busy )
        self.error.add( self.busy )
        self.busy = None
        self._update()

    def cbReleaseReply( self ):
        self.have.discard( self.busy )
        self.setState( _("Released resource %s") % self.busy )
        self.busy = None
        self._update()

    def cbReleaseError( self, e ):
        log_dbus_error( e, "error while releasing resource %s" % self.busy )
        self.setState( _("Released resource %s with error") % self.busy )
        self.have.discard( self.busy )
        self.error.add( self.busy )
        self.busy = None
        self._update()

    def cbDBusReady( self ):
        self.setState( _("Requesting resource list") )
        self.avail = set( dbus_object.usage_iface.ListResources() )
        self._update()

    def cbResourceAvailable( self, resourcename, state ):
        if state:
            self.setState( _("Resource added %s") % resourcename )
            self.avail.add( resourcename )
        else:
            self.setState( _("Resource removed %s") % resourcename )
            self.avail.discard( resourcename )
        self._update()

    def request( self, resourcename ):
        self.want.add( resourcename )
        self._update()

    def release( self, resourcename ):
        self.want.remove( resourcename )
        self._update()

#----------------------------------------------------------------------------#
class GSMAgent( Agent ):
#----------------------------------------------------------------------------#
    def __init__( self, main ):
        super( GSMAgent, self ).__init__( main )
        self.name = "GSM"

    def cbResourceReady( self ):
        """
        This is called to start the authentication process
        """
        self.turnOnAntenna()

    def turnOnAntenna( self ):
        self.setState( _("Turning on Antenna") )
        dbus_object.gsm_device_iface.SetAntennaPower(
            True,
            reply_handler=self.cbAntennaPowerReply,
            error_handler=self.cbAntennaPowerError,
        )

    def cbAntennaPowerReply( self ):
        logger.info( "Antenna power OK. Registering to network now." )
        self.setState( _("Registering to network") )
        dbus_object.gsm_network_iface.Register(
            reply_handler=self.cbRegisterReply,
            error_handler=self.cbRegisterError
        )

    def cbAntennaPowerError( self, e ):
        logger.info( "SIM seems to be protected. Checking auth status now." )
        self.handleAuth()

    def handleAuth( self ):
        self.setState( _("Reading authentication status") )
        dbus_object.gsm_sim_iface.GetAuthStatus(
            reply_handler=self.cbAuthStatusReply,
            error_handler=self.cbAuthStatusError,
        )

    def cbAuthStatusReply( self, authstatus ):
        if authstatus == "READY":
            self.setState( _("Telephony Ready") )
            # restart auth, should lead to registering this time...
            self.cbResourceReady()
        elif authstatus == "SIM PIN":
            self.setState( _("Waiting for PIN") )
            self.main.groups["pin_edit"].setup(
                "main",
                "", # number
                _("Enter PIN"), # title
                None, # reference
                self.cbPINDone
            )
        elif authstatus == "SIM PUK":
            self.setState( _("Waiting for PUK") )
            self.main.groups["pin_edit"].setup(
                "main",
                "", # number
                _("Enter PUK"), # title
                None, # reference
                self.cbPUKDone
            )
        else:
            logger.exception( "Unknown authentication status %s" % authstatus )

    def cbAuthStatusError( self, e ):
        self.setState( _("Failed to read authentication status") )
        logger.exception( e )

    def cbPINDone( self, pin, *args ):
        self.setState( _("Sending PIN") )
        dbus_object.gsm_sim_iface.SendAuthCode(
            pin,
            reply_handler=self.cbAuthCodeReply,
            error_handler=self.cbAuthCodeError
        )

    def cbAuthCodeReply( self ):
        self.cbAuthStatusReply( "READY" )

    def cbAuthCodeError( self, e ):
        self.setState( _("Error while sending PIN") )
        logger.exception( e )
        # retry
        self.cbDBusReady()

    def cbPUKDone( self, puk, *args ):
        self.main.groups["pin_edit"].setup(
            "main",
            "", # number
            _("Enter new PIN"), # title
            puk, # reference
            self.cbNewPINDone
        )

    def cbNewPINDone( self, pin, puk ):
        self.setState( _("Sending PUK and new PIN") )
        dbus_object.gsm_sim_iface.Unlock(
            pin, puk,
            reply_handler=self.cbUnlockReply,
            error_handler=self.cbUnlockError
        )

    def cbUnlockReply( self ):
        self.cbAuthStatusReply( "READY" )

    def cbUnlockError( self, e ):
        self.setState( _("Error while sending PIN") )
        logger.exception( e )
        # retry
        self.cbResourceReady()

    def cbRegisterReply( self ):
        pass

    def cbRegisterError( self, e ):
        logger.exception( e )
        if dbus_object.gsm_sim_iface.GetSimReady():
            self.main.groups["calls"].prepare()
            self.main.groups["contacts"].prepare()
            self.main.groups["sms"].prepare()

#----------------------------------------------------------------------------#
class GPSAgent( Agent ):
#----------------------------------------------------------------------------#
    def __init__( self, main ):
        super( GPSAgent, self ).__init__( main )
        self.name = "GPS"
        self.busy = None
        self.want = set( ["NAV-STATUS", "NAV-SVINFO"] )
        self.have = set()
        self.error = set()

    def _update( self ):
        if self.busy is None:
            pending = self.want - self.have - self.error
            if pending:
                self.busy = pending.pop()
                self.setState( _("Requesting packet %s") % self.busy )
                dbus_object.gps_ubx_iface.SetDebugFilter(
                    self.busy,
                    True,
                    reply_handler=self.cbSetDebugReply,
                    error_handler=self.cbSetDebugError,
                )

    def cbResourceReady( self ):
        self.setState( _("Requesting debug packets.") )
        self.have = set()
        self._update()

    def cbSetDebugReply( self ):
        self.have.add( self.busy )
        self.setState( _("Requested debug packet %s") % self.busy )
        self.busy = None
        self._update()

    def cbSetDebugError( self, e ):
        log_dbus_error( e, "error while requesting debug packet %s" % self.busy )
        self.setState( _("Requested debug packet %s with error") % self.busy )
        self.error.add( self.busy )
        self.busy = None
        self._update()

#----------------------------------------------------------------------------#
class GUI(object):
#----------------------------------------------------------------------------#
    def __init__( self, options, args ):

        logger.debug( "GUI init" )

        edje.frametime_set(1.0 / options.fps)

        self.evas_canvas = EvasCanvas(
            fullscreen = options.fullscreen,
            engine = options.engine,
            size = options.geometry
        )

        self.agents = {}

        self.agents["usage"] = agent = UsageAgent( self )
        agent.registerStateCallback( self.onAgentStateChanged )
        dbus_object.onResourceAvailable.append( self.agents["usage"].cbResourceAvailable )

        self.agents["gsm"] = agent = GSMAgent( self )
        agent.registerStateCallback( self.onAgentStateChanged )

        self.agents["gps"] = agent = GPSAgent( self )
        agent.registerStateCallback( self.onAgentStateChanged )

        self.groups = {}

        self.groups["swallow"] = edje_group(self, "swallow")
        self.evas_canvas.evas_obj.data["swallow"] = self.groups["swallow"]

        for page in (
                "main",
                "phone", "call", "dtmf",
                "sms",
                "calls",
                "contacts",
                "location",
                "wireless",
                "configuration",
                "list_choose", "number_edit", "pin_edit", "text_edit", "text_show", "message"
            ):
            ctor = globals().get( "pyphone_%s" % page, None )
            if ctor:
                self.groups[page] = ctor( self )
                self.evas_canvas.evas_obj.data[page] = self.groups[page]

        for overlay in ("main_menu", "menu", "lock", "alert"):
            ctor = globals().get( "pyphone_%s" % overlay, None )
            if ctor:
                self.groups[overlay] = ctor( self )
                self.evas_canvas.evas_obj.data[overlay] = self.groups[overlay]
                self.groups["swallow"].part_swallow( overlay, self.groups[overlay] )

        self.groups["swallow"].show()

        self.current_group = self.groups[options.start]
        self.previous_group = None
        self.groups["swallow"].part_swallow("swallow", self.current_group)
        ecore.timer_add(60.0, self.display_time)
        self.display_time()

        ecore.idle_enterer_add( self.dbus_objectInit )

        dbus_object.onFixStatusChanged.append( self.groups["location"].onFixStatusChanged )
        dbus_object.onTimeChanged.append( self.groups["location"].onTimeChanged )
        dbus_object.onAccuracyChanged.append( self.groups["location"].onAccuracyChanged )
        dbus_object.onCourseChanged.append( self.groups["location"].onCourseChanged )
        dbus_object.onPositionChanged.append( self.groups["location"].onPositionChanged )
        dbus_object.onSatellitesChanged.append( self.groups["location"].onSatellitesChanged )
        dbus_object.onUBXDebugPacket.append( self.groups["location"].onUBXDebugPacket )
        dbus_object.onCallStatus.append( self.groups["call"].onCallStatus )
        dbus_object.onReadyStatus.append( self.groups["calls"].onReadyStatus )
        dbus_object.onReadyStatus.append( self.groups["contacts"].onReadyStatus )
        dbus_object.onReadyStatus.append( self.groups["sms"].onReadyStatus )
        dbus_object.onIncomingMessage.append( self.groups["sms"].onIncomingMessage )
        dbus_object.onIncomingUssd.append( self.groups["phone"].onIncomingUssd )
        dbus_object.onIdleStateChanged.append( self.lock_on_idle )
        dbus_object.onNetworkStatus.append( self.groups["phone"].onNetworkStatus )
        dbus_object.onNetworkStatus.append( self.groups["wireless"].onNetworkStatus )

        logger.debug( "GUI init done" )

    def run( self ):
        logger.debug( "entering mainloop" )
        ecore.main_loop_begin()

    def shutdown( self ):
        ecore.main_loop_quit()

    def dbus_objectInit( self ):
        logger.debug( "dbus_objectInit..." )
        if not dbus_object.initialize():
            self.display_state( _("connecting w/ dbus...") )
            # try again later
            ecore.timer_add( 10.0, self.dbus_objectInit )
            return False
        else:
            logger.debug( "dbus_objectInitOK!" )
            self.agents["usage"].cbDBusReady()
        return False

    def lock_on_idle( self, state ):
        if state == "lock":
            self.groups["lock"].activate()

    def display_time(self):
        self.groups["main"].part_text_set("label", time.strftime("%H:%M %Z".strip(), time.localtime()))
        self.groups["main"].part_text_set("label_year", time.strftime("%Y-%m-%d", time.localtime()))
        return True

    def display_state(self, state):
        self.groups["main"].part_text_set("label_year", state )

    # TODO better state management for transitions
    def transition_to(self, target):
        if self.current_group == self.groups[target]:
            return
        logger.debug( "transition to %s" % target )

        self.previous_group = self.current_group
        self.previous_group.onHide()
        self.previous_group.hide()

        self.current_group = self.groups[target]
        self.current_group.onShow()
        self.current_group.signal_emit("visible", "")
        self.groups["swallow"].part_swallow("swallow", self.current_group)
        self.previous_group.signal_emit("invisible", "")

    def onAgentStateChanged( self, agentname, state ):
        self.display_state( "%s: %s" % ( agentname, state ) )

#----------------------------------------------------------------------------#
class EvasCanvas(object):
#----------------------------------------------------------------------------#
    def __init__(self, fullscreen, engine, size):
        f = ecore.evas.SoftwareX11
        self.evas_obj = f(w=size[0], h=size[1])
        self.evas_obj.callback_delete_request = self.on_delete_request
        self.evas_obj.callback_resize = self.on_resize

        self.evas_obj.title = TITLE
        self.evas_obj.name_class = (WM_NAME, WM_CLASS)
        self.evas_obj.fullscreen = fullscreen
        self.evas_obj.size = size
        self.evas_obj.evas.image_cache_set( 6*1024*1024 )
        self.evas_obj.evas.font_cache_set( 2*1024*1024 )
        self.evas_obj.show()

    def on_resize(self, evas_obj):
        x, y, w, h = evas_obj.evas.viewport
        size = (w, h)
        evas_obj.data["swallow"].size = size

    def on_delete_request(self, evas_obj):
        ecore.main_loop_quit()

#----------------------------------------------------------------------------#
class MyOptionParser(OptionParser):
#----------------------------------------------------------------------------#
    def __init__(self):
        OptionParser.__init__(self)
        self.set_defaults(fullscreen = False)
        self.add_option("-e",
                      "--engine",
                      type="choice",
                      choices=("x11", "x11-16"),
                      default="x11-16",
                      help=("which display engine to use (x11, x11-16), "
                            "default=%default"))
        self.add_option("--fullscreen",
                      action="store_true",
                      dest="fullscreen",
                      help="launch in fullscreen")
        self.add_option("--no-fullscreen",
                      action="store_false",
                      dest="fullscreen",
                      help="launch in a window")
        self.add_option("-g",
                      "--geometry",
                      type="string",
                      metavar="WxH",
                      action="callback",
                      callback=self.parse_geometry,
                      default=(WIDTH, HEIGHT),
                      help="use given window geometry")
        self.add_option("-f",
                      "--fps",
                      type="int",
                      default=20,
                      help="frames per second to use, default=%default")
        self.add_option("-s",
                      "--start",
                      type="string",
                      default="main",
                      help="start with the given page")

    def parse_geometry(option, opt, value, parser):
        try:
            w, h = value.split("x")
            w = int(w)
            h = int(h)
        except Exception, e:
            raise optparse.OptionValueError("Invalid format for %s" % option)
        parser.values.geometry = (w, h)

#----------------------------------------------------------------------------#
class DBusObject( object ):
#----------------------------------------------------------------------------#
    def __init__( self ):
        self.objects = {}
        self.onResourceChanged = []
        self.onResourceAvailable = []
        self.onFixStatusChanged = []
        self.onTimeChanged = []
        self.onAccuracyChanged = []
        self.onCourseChanged = []
        self.onPositionChanged = []
        self.onSatellitesChanged = []
        self.onUBXDebugPacket = []
        self.onCallStatus = []
        self.onReadyStatus = []
        self.onNetworkStatus = []
        self.onIncomingMessage = []
        self.onIncomingUssd = []
        self.onIdleStateChanged = []
        self.onInputEvent = []

        self.framework_obj = None

        self.usage_obj = None
        self.usage_iface = None

        self.gps_obj = None
        self.gps_device_iface = None
        self.gps_accuracy_iface = None
        self.gps_course_iface = None
        self.gps_position_iface = None
        self.gps_satellite_iface = None
        self.gps_time_iface = None
        self.gps_ubx_iface = None

        self.gsm_device_obj = None
        self.gsm_device_iface = None
        self.device_iface = None
        self.idlenotifier_obj = None
        self.idlenotifier_iface = None
        self.inputnotifier_obj = None
        self.inputnotifier_iface = None
        self.prefs_obj = None
        self.prefs_iface = None
        self.gsm_server_obj = None
        self.gsm_data_iface = None

        self.fullinit = False

    def tryGetProxy( self, busname, objname ):
        object = None
        try:
            object = self.objects[ "%s:%s" % ( busname, objname ) ]
            logger.info( "use cached proxy for %s:%s" % ( busname, objname ) )
        except KeyError:
            try:
                object = self.bus.get_object( busname, objname )
            except DBusException, e:
                logger.warning( "could not create proxy for %s:%s" % ( busname, objname ) )
            else:
                self.objects[ "%s:%s" % ( busname, objname ) ] = object
                logger.info( "cached proxy for %s:%s" % ( busname, objname ) )
        return object

    def initialize( self ):
        if self.fullinit:
            return True
        try:
            self.bus = SystemBus( mainloop=e_dbus.DBusEcoreMainLoop() )
        except DBusException, e:
            logger.error( "could not connect to dbus_object system bus: %s" % e )
            return False

        # Framework
        fw_obj = self.tryGetProxy( 'org.freesmartphone.frameworkd', '/org/freesmartphone/Framework' )
        if fw_obj is None:
            logger.error( "could not connect to org.freesmartphone.frameworkd -- is the framework daemon started?" )
            return False
        else:
            self.fw = Interface( fw_obj, "org.freesmartphone.Framework" )
        failcount = 0

        # Usage
        self.usage_obj = self.tryGetProxy( 'org.freesmartphone.ousaged', '/org/freesmartphone/Usage' )
        if ( self.usage_obj is not None ) and ( self.usage_iface is None ):
            self.usage_iface = Interface(self.usage_obj, 'org.freesmartphone.Usage')
            self.usage_iface.connect_to_signal( "ResourceChanged", self.cbResourceChanged )
            self.usage_iface.connect_to_signal( "ResourceAvailable", self.cbResourceAvailable )
        if self.usage_obj is None:
            failcount += 1
        else:
            logger.debug( "usage ok: %s" % self.usage_iface )

        # GPS
        self.gps_obj = self.tryGetProxy( 'org.freesmartphone.ogpsd', '/org/freedesktop/Gypsy' )
        if self.gps_obj and not self.gps_device_iface:
            self.gps_device_iface = Interface(self.gps_obj, 'org.freedesktop.Gypsy.Device')
            self.gps_device_iface.connect_to_signal( "FixStatusChanged", self.cbFixStatusChanged )
        if self.gps_obj and not self.gps_accuracy_iface:
            self.gps_accuracy_iface = Interface(self.gps_obj, 'org.freedesktop.Gypsy.Accuracy')
            self.gps_accuracy_iface.connect_to_signal( "AccuracyChanged", self.cbAccuracyChanged )
        if self.gps_obj and not self.gps_course_iface:
            self.gps_course_iface = Interface(self.gps_obj, 'org.freedesktop.Gypsy.Course')
            self.gps_course_iface.connect_to_signal( "CourseChanged", self.cbCourseChanged )
        if self.gps_obj and not self.gps_position_iface:
            self.gps_position_iface = Interface(self.gps_obj, 'org.freedesktop.Gypsy.Position')
            self.gps_position_iface.connect_to_signal( "PositionChanged", self.cbPositionChanged )
        if self.gps_obj and not self.gps_satellite_iface:
            self.gps_satellite_iface = Interface(self.gps_obj, 'org.freedesktop.Gypsy.Satellite')
            self.gps_satellite_iface.connect_to_signal( "SatellitesChanged", self.cbSatellitesChanged )
        if self.gps_obj and not self.gps_time_iface:
            self.gps_time_iface = Interface(self.gps_obj, 'org.freedesktop.Gypsy.Time')
            self.gps_time_iface.connect_to_signal( "TimeChanged", self.cbTimeChanged )
        if self.gps_obj and not self.gps_ubx_iface:
            self.gps_ubx_iface = Interface(self.gps_obj, 'org.freesmartphone.GPS.UBX')
            self.gps_ubx_iface.connect_to_signal( "DebugPacket", self.cbUBXDebugPacket )
        if not self.gps_obj or not self.gps_accuracy_iface or not self.gps_position_iface \
            or not self.gps_satellite_iface:
            failcount += 1
        else:
            logger.debug( "gps ok: %s, %s, %s" % ( self.gps_accuracy_iface, self.gps_position_iface, self.gps_satellite_iface ) )

        # Phone
        self.gsm_device_obj = self.tryGetProxy( 'org.freesmartphone.ogsmd', '/org/freesmartphone/GSM/Device' )
        self.gsm_server_obj = self.tryGetProxy( 'org.freesmartphone.ogsmd', '/org/freesmartphone/GSM/Server' )
        if ( self.gsm_device_obj is not None ) and ( self.gsm_device_iface is None ):
            self.gsm_device_iface = Interface(self.gsm_device_obj, 'org.freesmartphone.GSM.Device')
            self.gsm_sim_iface = Interface(self.gsm_device_obj, 'org.freesmartphone.GSM.SIM')
            self.gsm_monitor_iface = Interface(self.gsm_device_obj, 'org.freesmartphone.GSM.Monitor')
            self.gsm_network_iface = Interface(self.gsm_device_obj, 'org.freesmartphone.GSM.Network')
            self.gsm_call_iface = Interface(self.gsm_device_obj, 'org.freesmartphone.GSM.Call')
            self.gsm_test_iface = Interface(self.gsm_device_obj, 'org.freesmartphone.GSM.Test')
            self.gsm_sim_iface.connect_to_signal( "IncomingStoredMessage", self.cbIncomingMessage )
            self.gsm_sim_iface.connect_to_signal( "ReadyStatus", self.cbReadyStatus )
            self.gsm_call_iface.connect_to_signal( "CallStatus", self.cbCallStatus )
            self.gsm_network_iface.connect_to_signal( "Status", self.cbNetworkStatus )
            self.gsm_network_iface.connect_to_signal( "IncomingUssd", self.cbIncomingUssd )
        if ( self.gsm_server_obj is not None ) and ( self.gsm_data_iface is None ):
            self.gsm_data_iface = Interface(self.gsm_server_obj, 'org.freesmartphone.GSM.Data')
        if self.gsm_device_obj is None or self.gsm_server_obj is None:
            failcount += 1
        else:
            logger.debug( "gsm ok: %s" % self.gsm_network_iface )

        self.device_obj = self.tryGetProxy( 'org.freesmartphone.odeviced', '/org/freesmartphone/Device' )
        if ( self.device_obj is not None ) and ( self.device_iface is None ):
            self.device_iface = Interface( self.device_obj, 'org.freesmartphone.Device' )

            self.idlenotifier_obj = self.tryGetProxy( "org.freesmartphone.odeviced", "/org/freesmartphone/Device/IdleNotifier/0" )
            self.idlenotifier_iface = Interface( self.idlenotifier_obj, "org.freesmartphone.Device.IdleNotifier" )
            self.idlenotifier_iface.connect_to_signal( "State", self.cbIdleStateChanged )

            self.inputnotifier_obj = self.bus.get_object( "org.freesmartphone.odeviced", "/org/freesmartphone/Device/Input" )
            self.inputnotifier_iface = Interface( self.inputnotifier_obj, "org.freesmartphone.Device.Input" )
            self.inputnotifier_iface.connect_to_signal( "Event", self.cbEvent )
        if self.device_obj is None:
            failcount += 1
        else:
            logger.debug( "device ok: %s" % self.device_iface )

        # Prefs
        self.prefs_obj = self.tryGetProxy( 'org.freesmartphone.opreferencesd', '/org/freesmartphone/Preferences' )
        self.prefs_iface = Interface( self.prefs_obj, 'org.freesmartphone.Preferences' )
	if self.prefs_obj is None:
	    failcount += 1
	else:
            logger.debug( "preferences ok: %s" % self.prefs_iface )

        logger.debug( "failcount = %d" % failcount )
        if failcount == 0:
            self.fullinit = True
        return self.fullinit

    def cbResourceChanged( self, resourcename, state, attributes ):
        for cb in self.onResourceChanged:
            cb( resourcename=resourcename, state=state, attributes=attributes )

    def cbResourceAvailable( self, resourcename, state ):
        for cb in self.onResourceAvailable:
            cb( resourcename=resourcename, state=state )

    def cbFixStatusChanged( self, fixstatus ):
        for cb in self.onFixStatusChanged:
            cb( fixstatus=fixstatus )

    def cbAccuracyChanged( self, fields, pdop, hdop, vdop ):
        for cb in self.onAccuracyChanged:
            cb( fields=fields, pdop=pdop, hdop=hdop, vdop=vdop )

    def cbCourseChanged( self, fields, timestamp, speed, heading, climb ):
        for cb in self.onCourseChanged:
            cb( fields=fields, timestamp=timestamp, speed=speed, heading=heading, climb=climb )

    def cbPositionChanged( self, fields, timestamp, lat, lon, alt ):
        for cb in self.onPositionChanged:
            cb( fields=fields, timestamp=timestamp, lat=lat, lon=lon, alt=alt )

    def cbSatellitesChanged( self, satellites ):
        for cb in self.onSatellitesChanged:
            cb( satellites )

    def cbTimeChanged( self, timestamp ):
        for cb in self.onTimeChanged:
            cb( timestamp )

    def cbUBXDebugPacket( self, clid, length, data ):
        try:
            for cb in self.onUBXDebugPacket:
                cb( clid, length, data )
        except e:
            logger.exception("error in callback")

    def cbCallStatus( self, id, status, properties ):
        logger.info( "CALL STATUS = %d, %s, %s" % ( id, status, properties ) )
        for cb in self.onCallStatus:
            cb( id=id, status=status, properties=properties )
        if not self.idlenotifier_iface is None:
            self.idlenotifier_iface.SetState(
                "busy",
                reply_handler=lambda: None,
                error_handler=handle_dbus_error( "could not set idle state to busy" )
            )

    def cbReadyStatus( self, status ):
        for cb in self.onReadyStatus:
            cb( status=status )

    def cbNetworkStatus( self, status ):
        for cb in self.onNetworkStatus:
            cb( status=status )

    def cbIncomingUssd( self, mode, message ):
        logger.info("New USSID")
        for cb in self.onIncomingUssd:
            cb( mode=mode, message=message )

    def cbIncomingMessage( self, index ):
        for cb in self.onIncomingMessage:
            cb( index=index )

    def cbIdleStateChanged( self, state ):
        logger.info( "IDLE STATE = %s" % state )
        for cb in self.onIdleStateChanged:
            cb( state=state )

    def cbEvent( self, name, action, seconds ):
        logger.info( "INPUT EVENT = %s, %s, %d" % ( name, action, seconds ) )
        for cb in self.onInputEvent:
            cb( name, action, seconds )

#=========================================================================#
if __name__ == "__main__":
#=========================================================================#
    print "zhone starting"
    options, args = MyOptionParser().parse_args()
    dbus_object = DBusObject()
    gui = GUI( options, args )
    try:
        gui.run()
    except KeyboardInterrupt:
        gui.shutdown()
        del gui
