tMerge branch 'master' of gitorious.org:electrum/electrum - electrum - Electrum Bitcoin wallet
 (HTM) git clone https://git.parazyd.org/electrum
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) Submodules
       ---
 (DIR) commit e42cd36318c5924409adf4189bab5c06662705c7
 (DIR) parent e4cefa8bc5d30b5df12e1cf851380ccad0145a87
 (HTM) Author: ThomasV <thomasv@gitorious>
       Date:   Mon, 16 Apr 2012 18:38:27 +0400
       
       Merge branch 'master' of gitorious.org:electrum/electrum
       
       Diffstat:
         A client/ANDROID                      |      33 +++++++++++++++++++++++++++++++
         A client/bmp.py                       |     206 +++++++++++++++++++++++++++++++
         M client/electrum4a.py                |    1011 +++++++++++++++++++++----------
         A client/electrum_text_320.png        |       0 
         M client/gui.py                       |       2 +-
         M client/gui_qt.py                    |      55 ++++++++++++++-----------------
         M client/interface.py                 |      39 ++++++++++++++++++-------------
         M client/wallet.py                    |       8 +++++---
       
       8 files changed, 1000 insertions(+), 354 deletions(-)
       ---
 (DIR) diff --git a/client/ANDROID b/client/ANDROID
       t@@ -0,0 +1,33 @@
       +INSTALLATION INSTRUCTIONS FOR ANDROID
       +
       +
       +1. Install sl4a and py4a : you will need at least revision r5x12 of sl4a
       +
       +To install these APKs, just visit the links below with your phone and
       +click on the apk link, or scan the qr code
       +
       +sl4a: http://code.google.com/p/android-scripting/wiki/Unofficial
       +py4a: http://code.google.com/p/python-for-android/downloads/detail?name=PythonForAndroid_r5.apk
       +
       +
       +
       +2. copy the following files in /sdcard/sl4a/scripts:
       +
       +bmp.py
       +electrum4a.py
       +interface.py
       +mnemonic.py
       +msqr.py
       +pyqrnative.py
       +ripemd.py
       +version.py
       +wallet.py
       +aes (directory)
       +ecdsa (directory)
       +
       +Note: The aes and ecdsa directories are not included in the git
       +repository.  You will have to find them on your system, or you can
       +find them in the distributed version, Electrum tar.gz or Electrum.zip
       +
       +
       +3. to run the application, open sl4a and click on electrum4a.py
 (DIR) diff --git a/client/bmp.py b/client/bmp.py
       t@@ -0,0 +1,206 @@
       +# -*- coding: utf-8 -*-
       +"""
       +bmp.py - module for constructing simple BMP graphics files
       +
       + Permission is hereby granted, free of charge, to any person obtaining
       + a copy of this software and associated documentation files (the
       + "Software"), to deal in the Software without restriction, including
       + without limitation the rights to use, copy, modify, merge, publish,
       + distribute, sublicense, and/or sell copies of the Software, and to
       + permit persons to whom the Software is furnished to do so, subject to
       + the following conditions:
       +
       + The above copyright notice and this permission notice shall be
       + included in all copies or substantial portions of the Software.
       +
       + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
       + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
       + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
       + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
       + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
       + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
       + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
       +
       +"""
       +__version__ = "0.3"
       +__about =  "bmp module, version %s, written by Paul McGuire, October, 2003, updated by Margus Laak, September, 2009" % __version__ 
       +
       +from math import ceil, hypot
       +
       +
       +def shortToString(i):
       +  hi = (i & 0xff00) >> 8
       +  lo = i & 0x00ff
       +  return chr(lo) + chr(hi)
       +
       +def longToString(i):
       +  hi = (long(i) & 0x7fff0000) >> 16
       +  lo = long(i) & 0x0000ffff
       +  return shortToString(lo) + shortToString(hi)
       +
       +def long24ToString(i):
       +  return chr(i & 0xff) + chr(i >> 8 & 0xff) + chr(i >> 16 & 0xff)
       +
       +def stringToLong(input_string, offset):
       +  return ord(input_string[offset+3]) << 24 | ord(input_string[offset+2]) << 16 | ord(input_string[offset+1]) << 8 | ord(input_string[offset])
       +
       +def stringToLong24(input_string, offset):
       +  return ord(input_string[offset+2]) << 16 | ord(input_string[offset+1]) << 8 | ord(input_string[offset])
       +
       +class Color(object):
       +  """class for specifying colors while drawing BitMap elements"""
       +  __slots__ = [ 'red', 'grn', 'blu' ]
       +  __shade = 32
       +  
       +  def __init__( self, r=0, g=0, b=0 ):
       +    self.red = r
       +    self.grn = g
       +    self.blu = b
       +
       +  def __setattr__(self, name, value):
       +    if hasattr(self, name):
       +      raise AttributeError, "Color is immutable"
       +    else:
       +      object.__setattr__(self, name, value)
       +
       +  def __str__( self ):
       +    return "R:%d G:%d B:%d" % (self.red, self.grn, self.blu )
       +    
       +  def __hash__( self ):
       +    return ( ( long(self.blu) ) + 
       +              ( long(self.grn) <<  8 ) + 
       +              ( long(self.red) << 16 ) )
       +  
       +  def __eq__( self, other ):
       +    return (self is other) or (self.toLong == other.toLong)
       +
       +  def lighten( self ):
       +    return Color( 
       +      min( self.red + Color.__shade, 255), 
       +      min( self.grn + Color.__shade, 255), 
       +      min( self.blu + Color.__shade, 255)  
       +      )
       +  
       +  def darken( self ):
       +    return Color( 
       +      max( self.red - Color.__shade, 0), 
       +      max( self.grn - Color.__shade, 0), 
       +      max( self.blu - Color.__shade, 0)  
       +      )
       +       
       +  def toLong( self ):
       +    return self.__hash__()
       +    
       +  def fromLong( l ):
       +    b = l & 0xff
       +    l = l >> 8
       +    g = l & 0xff
       +    l = l >> 8
       +    r = l & 0xff
       +    return Color( r, g, b )
       +  fromLong = staticmethod(fromLong)
       +
       +# define class constants for common colors
       +Color.BLACK    = Color(   0,   0,   0 )
       +Color.RED      = Color( 255,   0,   0 )
       +Color.GREEN    = Color(   0, 255,   0 )
       +Color.BLUE     = Color(   0,   0, 255 )
       +Color.CYAN     = Color(   0, 255, 255 )
       +Color.MAGENTA  = Color( 255,   0, 255 )
       +Color.YELLOW   = Color( 255, 255,   0 )
       +Color.WHITE    = Color( 255, 255, 255 )
       +Color.DKRED    = Color( 128,   0,   0 )
       +Color.DKGREEN  = Color(   0, 128,   0 )
       +Color.DKBLUE   = Color(   0,   0, 128 )
       +Color.TEAL     = Color(   0, 128, 128 )
       +Color.PURPLE   = Color( 128,   0, 128 )
       +Color.BROWN    = Color( 128, 128,   0 )
       +Color.GRAY     = Color( 128, 128, 128 )
       +
       +
       +class BitMap(object):
       +  """class for drawing and saving simple Windows bitmap files"""
       +  
       +  LINE_SOLID  = 0
       +  LINE_DASHED = 1
       +  LINE_DOTTED = 2
       +  LINE_DOT_DASH=3
       +  _DASH_LEN = 12.0
       +  _DOT_LEN = 6.0
       +  _DOT_DASH_LEN = _DOT_LEN + _DASH_LEN
       +  
       +  def __init__( self, width, height, 
       +                 bkgd = Color.WHITE, frgd = Color.BLACK ):
       +    self.wd = int( ceil(width) )
       +    self.ht = int( ceil(height) )
       +    self.bgcolor = 0
       +    self.fgcolor = 1
       +    self.palette = []
       +    self.palette.append( bkgd.toLong() )
       +    self.palette.append( frgd.toLong() )
       +    self.currentPen = self.fgcolor
       +
       +    tmparray = [ self.bgcolor ] * self.wd
       +    self.bitarray = [ tmparray[:] for i in range( self.ht ) ]
       +    self.currentPen = 1
       +    
       +
       +  def plotPoint( self, x, y ):
       +    if ( 0 <= x < self.wd and 0 <= y < self.ht ):
       +      x = int(x)
       +      y = int(y)
       +      self.bitarray[y][x] = self.currentPen
       +      
       +
       +  def _saveBitMapNoCompression( self ):
       +    line_padding = (4 - (self.wd % 4)) % 4
       +    
       +    # write bitmap header
       +    _bitmap = "BM"
       +    _bitmap += longToString( 54 + self.ht*(self.wd*3 + line_padding) )   # DWORD size in bytes of the file
       +    _bitmap += longToString( 0 )    # DWORD 0
       +    _bitmap += longToString( 54  )
       +    _bitmap += longToString( 40 )    # DWORD header size = 40
       +    _bitmap += longToString( self.wd )    # DWORD image width
       +    _bitmap += longToString( self.ht )    # DWORD image height
       +    _bitmap += shortToString( 1 )    # WORD planes = 1
       +    _bitmap += shortToString( 24 )    # WORD bits per pixel = 8
       +    _bitmap += longToString( 0 )    # DWORD compression = 0
       +    _bitmap += longToString( self.ht * (self.wd * 3 + line_padding) )    # DWORD sizeimage = size in bytes of the bitmap = width * height
       +    _bitmap += longToString( 0 )    # DWORD horiz pixels per meter (?)
       +    _bitmap += longToString( 0 )    # DWORD ver pixels per meter (?)
       +    _bitmap += longToString( 0 )    # DWORD number of colors used = 256
       +    _bitmap += longToString( 0 )    # DWORD number of "import colors = len( self.palette )
       +
       +    # write pixels
       +    self.bitarray.reverse()
       +    for row in self.bitarray:
       +      for pixel in row:
       +        c = self.palette[pixel]
       +        _bitmap += long24ToString(c)
       +      for i in range(line_padding):
       +        _bitmap += chr( 0 )
       +
       +    return _bitmap
       +
       +    
       +    
       +  def saveFile( self, filename):
       +    _b = self._saveBitMapNoCompression( )
       +    
       +    f = file(filename, 'wb')
       +    f.write(_b)
       +    f.close()
       +  
       +
       +
       +
       +  
       +    
       +if __name__ == "__main__":
       +  
       +  bmp = BitMap( 10, 10 )
       +  bmp.plotPoint( 5, 5 )
       +  bmp.plotPoint( 0, 0 )
       +  bmp.saveFile( "test.bmp" )
       +
 (DIR) diff --git a/client/electrum4a.py b/client/electrum4a.py
       t@@ -18,141 +18,237 @@
        
        
        
       +
        import android
        from interface import WalletSynchronizer
        from wallet import Wallet
        from wallet import format_satoshis
        from decimal import Decimal
       -
       +import mnemonic
        
        import datetime
        
        
       -droid = android.Android()
       -wallet = Wallet()
       -wallet.set_path("/sdcard/electrum.dat")
       -wallet.read()
       -
        
       -def get_history_layout(n):
       -    rows = ""
       -    for line in wallet.get_tx_history()[-n:]:
       -        v = line['value'] 
       -        try:
       -            dt = datetime.datetime.fromtimestamp( line['timestamp'] )
       -            if dt.date() == dt.today().date():
       -                time_str = str( dt.time() )
       -            else:
       -                time_str = str( dt.date() )
       -        except:
       -            print line['timestamp']
       -            time_str = 'pending'
       +def modal_dialog(title, msg = None):
       +    droid.dialogCreateAlert(title,msg)
       +    droid.dialogSetPositiveButtonText('OK')
       +    droid.dialogShow()
       +    droid.dialogGetResponse()
       +    droid.dialogDismiss()
        
       -        label = line.get('label')
       -        #if not label: label = line['tx_hash']
       -        is_default_label = (label == '') or (label is None)
       -        if is_default_label: label = line['default_label']
       +def modal_input(title, msg, value = None, etype=None):
       +    droid.dialogCreateInput(title, msg, value, etype)
       +    droid.dialogSetPositiveButtonText('OK')
       +    droid.dialogSetNegativeButtonText('Cancel')
       +    droid.dialogShow()
       +    response = droid.dialogGetResponse().result
       +    droid.dialogDismiss()
       +    if response.get('which') == 'positive':
       +        return response.get('value')
        
       -        rows += """
       -    <TableRow
       -       android:layout_width="fill_parent">
       -        <TextView
       -            android:layout_column="1"
       -            android:text="%s"
       -            android:padding="2px" />
       -        <TextView
       -            android:text="%s"
       -            android:padding="2px" />
       -        <TextView
       -            android:text="%s"
       -            android:gravity="right"
       -            android:padding="2px" />
       -    </TableRow>"""%(time_str, ' '+ label[0:10]+ '... ',  format_satoshis(v))
       +def modal_question(q, msg, pos_text = 'OK', neg_text = 'Cancel'):
       +    droid.dialogCreateAlert(q, msg)
       +    droid.dialogSetPositiveButtonText(pos_text)
       +    droid.dialogSetNegativeButtonText(neg_text)
       +    droid.dialogShow()
       +    response = droid.dialogGetResponse().result
       +    droid.dialogDismiss()
       +    return response.get('which') == 'positive'
       +
       +def edit_label(addr):
       +    v = modal_input('Edit label',None,wallet.labels.get(addr))
       +    if v is not None:
       +        if v:
       +            wallet.labels[addr] = v
       +        else:
       +            if addr in wallet.labels.keys():
       +                wallet.labels.pop(addr)
       +        wallet.update_tx_history()
       +        wallet.save()
       +        droid.fullSetProperty("labelTextView", "text", v)
        
       +def select_from_contacts():
       +    title = 'Contacts:'
       +    droid.dialogCreateAlert(title)
       +    l = []
       +    for i in range(len(wallet.addressbook)):
       +        addr = wallet.addressbook[i]
       +        label = wallet.labels.get(addr,addr)
       +        l.append( label )
       +    droid.dialogSetItems(l)
       +    droid.dialogSetPositiveButtonText('New contact')
       +    droid.dialogShow()
       +    response = droid.dialogGetResponse().result
       +    droid.dialogDismiss()
        
       -    output = """
       -<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
       -    android:layout_width="fill_parent"
       -    android:layout_height="wrap_content"
       -    android:stretchColumns="1">
       -    %s
       -</TableLayout>
       -"""% rows
       -    return output
       +    if response.get('which') == 'positive':
       +        return 'newcontact'
        
       +    result = response.get('item')
       +    print result
       +    if result is not None:
       +        addr = wallet.addressbook[result]
       +        return addr
        
        
       -def show_addresses():
       +def select_from_addresses():
            droid.dialogCreateAlert("Addresses:")
            l = []
            for i in range(len(wallet.addresses)):
                addr = wallet.addresses[i]
       -        l.append( wallet.labels.get(addr,'') + ' ' + addr)
       -
       +        label = wallet.labels.get(addr,addr)
       +        l.append( label )
            droid.dialogSetItems(l)
            droid.dialogShow()
       -    response = droid.dialogGetResponse().result
       +    response = droid.dialogGetResponse()
       +    result = response.result.get('item')
            droid.dialogDismiss()
       +    if result is not None:
       +        addr = wallet.addresses[result]
       +        return addr
        
       -    # show qr code
       -    print response
        
       +def protocol_name(p):
       +    if p == 't': return 'TCP/stratum'
       +    if p == 'h': return 'HTTP/Stratum'
       +    if p == 'n': return 'TCP/native'
       +
       +def protocol_dialog(host, protocol, z):
       +    droid.dialogCreateAlert('Protocol',host)
       +    if z:
       +        protocols = z.keys()
       +    else:
       +        protocols = ['t','h','n']
       +    l = []
       +    current = protocols.index(protocol)
       +    for p in protocols:
       +        l.append(protocol_name(p))
       +    droid.dialogSetSingleChoiceItems(l, current)
       +    droid.dialogSetPositiveButtonText('OK')
       +    droid.dialogSetNegativeButtonText('Cancel')
       +    droid.dialogShow()
       +    response = droid.dialogGetResponse().result
       +    if not response: return
       +    if response.get('which') == 'positive':
       +        response = droid.dialogGetSelectedItems().result[0]
       +        droid.dialogDismiss()
       +        p = protocols[response]
       +        port = z[p]
       +        return host + ':' + port + ':' + p
       +
       +
       +
       +
       +def make_layout(s, scrollable = False):
       +    content = """
       +
       +      <LinearLayout 
       +        android:id="@+id/zz"
       +        android:layout_width="match_parent"
       +        android:layout_height="wrap_content" 
       +        android:background="#ff222222">
       +
       +        <TextView
       +          android:id="@+id/textElectrum"
       +          android:text="Electrum"
       +          android:textSize="7pt"
       +          android:textColor="#ff4444ff"
       +          android:gravity="left"
       +          android:layout_height="wrap_content"
       +          android:layout_width="match_parent"
       +        />
       +      </LinearLayout>
       +
       +        %s   """%s
       +
       +    if scrollable:
       +        content = """
       +      <ScrollView 
       +        android:id="@+id/scrollview"
       +        android:layout_width="match_parent"
       +        android:layout_height="match_parent" >
       +
       +      <LinearLayout
       +        android:orientation="vertical" 
       +        android:layout_width="match_parent"
       +        android:layout_height="wrap_content" >
       +
       +      %s
       +
       +      </LinearLayout>
       +      </ScrollView>
       +      """%content
        
        
       -def main_layout():
            return """<?xml version="1.0" encoding="utf-8"?>
       -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
       +      <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
                android:id="@+id/background"
                android:orientation="vertical" 
                android:layout_width="match_parent"
                android:layout_height="match_parent" 
       -        android:background="#ff000000">
       +        android:background="#ff000022">
       +
       +      %s 
       +      </LinearLayout>"""%content
       +
       +
       +
       +
       +def main_layout():
       +    return make_layout("""
       +        <TextView android:id="@+id/balanceTextView" 
       +                android:layout_width="match_parent"
       +                android:text=""
       +                android:textColor="#ffffffff"
       +                android:textAppearance="?android:attr/textAppearanceLarge" 
       +                android:padding="7dip"
       +                android:textSize="8pt"
       +                android:gravity="center_vertical|center_horizontal|left">
       +        </TextView>
        
                <TextView android:id="@+id/historyTextView" 
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content" 
       -                android:text="Electrum"
       +                android:text="Recent transactions"
                        android:textAppearance="?android:attr/textAppearanceLarge" 
                        android:gravity="center_vertical|center_horizontal|center">
                </TextView>
        
       -        %s
       +        %s """%get_history_layout(15),True)
        
       -        <TextView android:id="@+id/balanceTextView" 
       +
       +
       +def qr_layout(addr):
       +    return make_layout("""
       +
       +     <TextView android:id="@+id/addrTextView" 
                        android:layout_width="match_parent"
       -                android:layout_height="wrap_content" 
       -                android:text=""
       +                android:layout_height="50" 
       +                android:text="%s"
                        android:textAppearance="?android:attr/textAppearanceLarge" 
       -                android:gravity="left">
       -        </TextView>
       -
       -        <LinearLayout android:layout_width="match_parent"
       -                android:layout_height="wrap_content" android:id="@+id/linearLayout1">
       -                <Button android:id="@+id/buttonSend" android:layout_width="wrap_content"
       -                        android:layout_height="wrap_content" android:text="Send"></Button>
       -                <Button android:id="@+id/buttonReceive" android:layout_width="wrap_content"
       -                        android:layout_height="wrap_content" android:text="Receive"></Button>
       -                <Button android:id="@+id/buttonQuit" android:layout_width="wrap_content"
       -                        android:layout_height="wrap_content" android:text="Quit"></Button>
       -        </LinearLayout>
       +                android:gravity="center_vertical|center_horizontal|center">
       +     </TextView>
        
       -</LinearLayout>
       -"""%get_history_layout(10)
       +     <ImageView
       +        android:id="@+id/qrView"
       +        android:gravity="center"
       +        android:layout_width="match_parent"
       +        android:layout_height="350"
       +        android:antialias="false"
       +        android:src="file:///sdcard/sl4a/qrcode.bmp" /> 
        
       +     <TextView android:id="@+id/labelTextView" 
       +                android:layout_width="match_parent"
       +                android:layout_height="50" 
       +                android:text="%s"
       +                android:textAppearance="?android:attr/textAppearanceLarge" 
       +                android:gravity="center_vertical|center_horizontal|center">
       +     </TextView>
        
       -payto_layout="""<?xml version="1.0" encoding="utf-8"?>
       -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
       - android:id="@+id/background"
       - android:orientation="vertical" 
       - android:layout_width="match_parent"
       - android:layout_height="match_parent" 
       - android:background="#ff000000">
       +     """%(addr,wallet.labels.get(addr,'')), True)
        
       -    <LinearLayout android:id="@+id/linearLayout0"
       -     android:orientation="vertical" 
       -     android:layout_width="match_parent"
       -     android:layout_height="wrap_content" 
       -     android:background="#44ffffff">
       +payto_layout = make_layout("""
        
                <TextView android:id="@+id/recipientTextView" 
                        android:layout_width="match_parent"
       t@@ -166,21 +262,18 @@ payto_layout="""<?xml version="1.0" encoding="utf-8"?>
                <EditText android:id="@+id/recipient"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content" 
       -                android:tag="Tag Me" android:inputType="textCapWords|textPhonetic|number">
       +                android:tag="Tag Me" android:inputType="text">
                </EditText>
        
                <LinearLayout android:id="@+id/linearLayout1"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content">
                        <Button android:id="@+id/buttonQR" android:layout_width="wrap_content"
       -                        android:layout_height="wrap_content" android:text="Scan QR"></Button>
       +                        android:layout_height="wrap_content" android:text="From QR code"></Button>
                        <Button android:id="@+id/buttonContacts" android:layout_width="wrap_content"
       -                        android:layout_height="wrap_content" android:text="Contacts"></Button>
       +                        android:layout_height="wrap_content" android:text="From Contacts"></Button>
                </LinearLayout>
        
       -    </LinearLayout>
       -
       -
        
                <TextView android:id="@+id/labelTextView" 
                        android:layout_width="match_parent"
       t@@ -193,7 +286,7 @@ payto_layout="""<?xml version="1.0" encoding="utf-8"?>
                <EditText android:id="@+id/label"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content" 
       -                android:tag="Tag Me" android:inputType="textCapWords|textPhonetic|number">
       +                android:tag="Tag Me" android:inputType="text">
                </EditText>
        
                <TextView android:id="@+id/amountLabelTextView" 
       t@@ -214,201 +307,318 @@ payto_layout="""<?xml version="1.0" encoding="utf-8"?>
                        android:layout_height="wrap_content" android:id="@+id/linearLayout1">
                        <Button android:id="@+id/buttonPay" android:layout_width="wrap_content"
                                android:layout_height="wrap_content" android:text="Send"></Button>
       -                <Button android:id="@+id/buttonCancelSend" android:layout_width="wrap_content"
       -                        android:layout_height="wrap_content" android:text="Cancel"></Button>
       -        </LinearLayout>
       -</LinearLayout>
       -"""
       +        </LinearLayout>""",False)
        
        
       -settings_layout = """<?xml version="1.0" encoding="utf-8"?>
       -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
       -        android:id="@+id/background"
       -        android:orientation="vertical" 
       -        android:layout_width="match_parent"
       -        android:layout_height="match_parent" 
       -        android:background="#ff000000">
        
       -        <TextView android:id="@+id/serverTextView" 
       -                android:layout_width="match_parent"
       -                android:layout_height="wrap_content" 
       -                android:text="Server:"
       -                android:textAppearance="?android:attr/textAppearanceLarge" 
       -                android:gravity="left">
       -        </TextView>
       +settings_layout = make_layout(""" <ListView
       +           android:id="@+id/myListView" 
       +           android:layout_width="match_parent"
       +           android:layout_height="wrap_content" />""")
        
       -        <EditText android:id="@+id/server"
       -                android:layout_width="match_parent"
       -                android:layout_height="wrap_content" 
       -                android:tag="Tag Me" android:inputType="*">
       -        </EditText>
        
       -        <LinearLayout android:layout_width="match_parent"
       -                android:layout_height="wrap_content" android:id="@+id/linearLayout1">
       -                <Button android:id="@+id/buttonServer" android:layout_width="wrap_content"
       -                        android:layout_height="wrap_content" android:text="Server List"></Button>
       -                <Button android:id="@+id/buttonSave" android:layout_width="wrap_content"
       -                        android:layout_height="wrap_content" android:text="Save"></Button>
       -                <Button android:id="@+id/buttonCancel" android:layout_width="wrap_content"
       -                        android:layout_height="wrap_content" android:text="Cancel"></Button>
       -        </LinearLayout>
        
       -</LinearLayout>
       -"""
       +def get_history_values(n):
       +    values = []
       +    h = wallet.get_tx_history()
        
       +    length = min(n, len(h))
       +    for i in range(length):
       +        line = h[-i-1]
       +        v = line['value']
       +        try:
       +            dt = datetime.datetime.fromtimestamp( line['timestamp'] )
       +            if dt.date() == dt.today().date():
       +                time_str = str( dt.time() )
       +            else:
       +                time_str = str( dt.date() )
       +            conf = 'v'
        
       +        except:
       +            print line['timestamp']
       +            time_str = 'pending'
       +            conf = 'o'
        
       +        tx_hash = line['tx_hash']
       +        label = wallet.labels.get(tx_hash)
       +        is_default_label = (label == '') or (label is None)
       +        if is_default_label: label = line['default_label']
       +        values.append((conf, '  ' + time_str, '  ' + format_satoshis(v,True), '  ' + label ))
        
       +    return values
        
       -def show_balance():
       -    c, u = wallet.get_balance()
       -    droid.fullSetProperty("balanceTextView","text","Balance:"+format_satoshis(c))
       +
       +def get_history_layout(n):
       +    rows = ""
       +    i = 0
       +    values = get_history_values(n)
       +    for v in values:
       +        a,b,c,d = v
       +        color = "#ff00ff00" if a == 'v' else "#ffff0000"
       +        rows += """
       +        <TableRow>
       +          <TextView
       +            android:id="@+id/hl_%d_col1" 
       +            android:layout_column="0"
       +            android:text="%s"
       +            android:textColor="%s"
       +            android:padding="3" />
       +          <TextView
       +            android:id="@+id/hl_%d_col2" 
       +            android:layout_column="1"
       +            android:text="%s"
       +            android:padding="3" />
       +          <TextView
       +            android:id="@+id/hl_%d_col3" 
       +            android:layout_column="2"
       +            android:text="%s"
       +            android:padding="3" />
       +          <TextView
       +            android:id="@+id/hl_%d_col4" 
       +            android:layout_column="3"
       +            android:text="%s"
       +            android:padding="4" />
       +        </TableRow>"""%(i,a,color,i,b,i,c,i,d)
       +        i += 1
       +
       +    output = """
       +<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
       +    android:layout_width="fill_parent"
       +    android:layout_height="wrap_content"
       +    android:stretchColumns="0,1,2,3">
       +    %s
       +</TableLayout>"""% rows
       +    return output
       +
       +
       +def set_history_layout(n):
       +    values = get_history_values(n)
       +    i = 0
       +    for v in values:
       +        a,b,c,d = v
       +        droid.fullSetProperty("hl_%d_col1"%i,"text", a)
       +
       +        if a == 'v':
       +            droid.fullSetProperty("hl_%d_col1"%i, "textColor","#ff00ff00")
       +        else:
       +            droid.fullSetProperty("hl_%d_col1"%i, "textColor","#ffff0000")
       +
       +        droid.fullSetProperty("hl_%d_col2"%i,"text", b)
       +        droid.fullSetProperty("hl_%d_col3"%i,"text", c)
       +        droid.fullSetProperty("hl_%d_col4"%i,"text", d)
       +        i += 1
       +
       +
       +
       +
       +status_text = ''
       +def update_layout():
       +    global status_text
       +    if not wallet.interface.is_connected:
       +        text = "Not connected..."
       +    elif wallet.blocks == 0:
       +        text = "Server not ready"
       +    elif not wallet.up_to_date:
       +        text = "Synchronizing..."
       +    else:
       +        c, u = wallet.get_balance()
       +        text = "Balance:"+format_satoshis(c) 
       +        if u : text += '   [' + format_satoshis(u,True).strip() + ']'
       +
       +
       +    # vibrate if status changed
       +    if text != status_text:
       +        if status_text and wallet.interface.is_connected and wallet.up_to_date:
       +            droid.vibrate()
       +        status_text = text
       +
       +    droid.fullSetProperty("balanceTextView", "text", status_text)
       +
       +    if wallet.up_to_date:
       +        set_history_layout(15)
        
        
       -def recipient_dialog():
       -    title = 'Pay to:'
       -    message = ('Select recipient')
       -    droid.dialogCreateAlert(title, message)
       -    droid.dialogSetItems(wallet.addressbook)
       -    droid.dialogShow()
       -    response = droid.dialogGetResponse()
       -    result = response.result.get('item')
       -    droid.dialogDismiss()
       -    if result is not None:
       -        addr = wallet.addressbook[result]
       -        return addr
        
        
        def pay_to(recipient, amount, fee, label):
        
            if wallet.use_encryption:
                password  = droid.dialogGetPassword('Password').result
       -        print "password", password
       +        if not password: return
            else:
                password = None
        
            droid.dialogCreateSpinnerProgress("Electrum", "signing transaction...")
            droid.dialogShow()
       -    tx = wallet.mktx( recipient, amount, label, password, fee)
       -    print tx
       -    droid.dialogDismiss()
        
       -    if tx:
       -        r, h = wallet.sendtx( tx )
       -        droid.dialogCreateAlert('tx sent', h)
       -        droid.dialogSetPositiveButtonText('OK')
       -        droid.dialogShow()
       -        response = droid.dialogGetResponse().result
       +    try:
       +        tx = wallet.mktx( recipient, amount, label, password, fee)
       +    except BaseException, e:
       +        modal_dialog('error', e.message)
                droid.dialogDismiss()
       -        return h
       -    else:
       -        return 'error'
       -
       +        return
        
       +    droid.dialogDismiss()
        
       +    r, h = wallet.sendtx( tx )
       +    if r:
       +        modal_dialog('Payment sent', h)
       +        return True
       +    else:
       +        modal_dialog('Error', h)
        
        
        
        
       -if not wallet.file_exists:
       -    droid.dialogCreateAlert("wallet file not found")
       -    droid.dialogSetPositiveButtonText('OK')
       -    droid.dialogShow()
       -    resp = droid.dialogGetResponse().result
       -    print resp
        
       -    code = droid.scanBarcode()
       -    r = code.result
       -    if r:
       -        seed = r['extras']['SCAN_RESULT']
       -    else:
       -        exit(1)
       +def recover():
        
       -    droid.dialogCreateAlert('seed', seed)
       -    droid.dialogSetPositiveButtonText('OK')
       +    droid.dialogCreateAlert("Wallet not found","Do you want to create a new wallet, or restore an existing one?")
       +    droid.dialogSetPositiveButtonText('Create')
       +    droid.dialogSetNeutralButtonText('Restore')
            droid.dialogSetNegativeButtonText('Cancel')
            droid.dialogShow()
            response = droid.dialogGetResponse().result
            droid.dialogDismiss()
       -    print response
       +    if response.get('which') == 'negative':
       +        exit(1)
        
       -    wallet.seed = str(seed)
       -    wallet.init_mpk( wallet.seed )
       -    droid.dialogCreateSpinnerProgress("Electrum", "recovering keys")
       +    is_recovery = response.get('which') == 'neutral'
       +
       +    if not is_recovery:
       +        wallet.new_seed(None)
       +    else:
       +        if modal_question("Input method",None,'QR Code', 'mnemonic'):
       +            code = droid.scanBarcode()
       +            r = code.result
       +            if r:
       +                seed = r['extras']['SCAN_RESULT']
       +            else:
       +                exit(1)
       +        else:
       +            m = modal_input('Mnemonic','please enter your code')
       +            try:
       +                seed = mnemonic.mn_decode(m.split(' '))
       +            except:
       +                modal_dialog('error: could not decode this seed')
       +                exit(1)
       +
       +        wallet.seed = str(seed)
       +
       +    modal_dialog('Your seed is:', wallet.seed)
       +    modal_dialog('Mnemonic code:', ' '.join(mnemonic.mn_encode(wallet.seed)) )
       +
       +    msg = "recovering wallet..." if is_recovery else "creating wallet..."
       +    droid.dialogCreateSpinnerProgress("Electrum", msg)
            droid.dialogShow()
       +
       +    wallet.init_mpk( wallet.seed )
            WalletSynchronizer(wallet,True).start()
            wallet.update()
       -    wallet.save()
       +
            droid.dialogDismiss()
            droid.vibrate()
        
       -    if wallet.is_found():
       -        # history and addressbook
       -        wallet.update_tx_history()
       -        wallet.fill_addressbook()
       -        droid.dialogCreateAlert("recovery successful")
       -        droid.dialogShow()
       -        wallet.save()
       -    else:
       -        droid.dialogCreateSpinnerProgress("wallet not found")
       -        droid.dialogShow()
       -        exit(1)
       +    if is_recovery:
       +        if wallet.is_found():
       +            wallet.update_tx_history()
       +            wallet.fill_addressbook()
       +            modal_dialog("recovery successful")
       +        else:
       +            if not modal_question("no transactions found for this seed","do you want to keep this wallet?"):
       +                exit(1)
        
       -else:
       -    droid.dialogCreateSpinnerProgress("Electrum", "synchronizing")
       -    droid.dialogShow()
       -    WalletSynchronizer(wallet,True).start()
       -    wallet.update()
       +    change_password_dialog()
            wallet.save()
       -    droid.dialogDismiss()
       -    droid.vibrate()
        
        
       -def add_menu():
       -    droid.addOptionsMenuItem("Settings","settings",None,"")
       -    droid.addOptionsMenuItem("Quit","quit",None,"")
        
       -add_menu()
       +def make_new_contact():
       +    code = droid.scanBarcode()
       +    r = code.result
       +    if r:
       +        address = r['extras']['SCAN_RESULT']
       +        if address:
       +            if wallet.is_valid(address):
       +                if modal_question('Add to contacts?', address):
       +                    wallet.addressbook.append(address)
       +                    wallet.save()
       +        else:
       +            modal_dialog('Invalid address', address)
       +
       +
       +do_refresh = False
        
       +def update_callback():
       +    global do_refresh
       +    print "gui callback", wallet.interface.is_connected, wallet.up_to_date
       +    do_refresh = True
       +    droid.eventPost("refresh",'z')
        
        def main_loop():
       -    droid.fullShow(main_layout())
       -    show_balance()
       +    global do_refresh
       +
       +    update_layout()
            out = None
       +    quitting = False
            while out is None:
        
       -        event = droid.eventWait().result
       -        print "got event in main loop", event
       +        event = droid.eventWait(1000).result
       +        if event is None:
       +            if do_refresh: 
       +                update_layout()
       +                do_refresh = False
       +            continue
        
       -        if event["name"]=="click":
       -            id=event["data"]["id"]
       -            if id=="buttonQuit":
       -                out = 'exit'
       +        print "got event in main loop", repr(event)
       +        if event == 'OK': continue
       +        if event is None: continue
       +        #if event["name"]=="refresh":
        
       -            elif id=="buttonSend":
       -                out = 'payto'
        
       -            elif id=="buttonReceive":
       -                show_addresses()
       +        # request 2 taps before we exit
       +        if event["name"]=="key":
       +            if event["data"]["key"] == '4':
       +                if quitting:
       +                    out = 'quit'
       +                else: 
       +                    quitting = True
       +        else: quitting = False
        
       -            elif id=="buttonQuit":
       -                out = 'quit'
       +        if event["name"]=="click":
       +            id=event["data"]["id"]
        
                elif event["name"]=="settings":
                    out = 'settings'
        
       -        elif event["name"]=="quit":
       -            out = 'quit'
       +        elif event["name"] in menu_commands:
       +            out = event["name"]
       +
       +            if out == 'contacts':
       +                global contact_addr
       +                contact_addr = select_from_contacts()
       +                if contact_addr == 'newcontact':
       +                    make_new_contact()
       +                    contact_addr = None
       +                if not contact_addr:
       +                    out = None
       +
       +            elif out == "receive":
       +                global receive_addr
       +                receive_addr = select_from_addresses()
       +                if not receive_addr:
       +                    out = None
        
       -        # print droid.fullSetProperty("background","backgroundColor","0xff7f0000")
       -        # elif event["name"]=="screen":
       -        #    if event["data"]=="destroy":
       -        #        out = 'exit'
        
            return out
                            
       +
        def payto_loop():
       -    droid.fullShow(payto_layout)
       +    global recipient
       +    if recipient:
       +        droid.fullSetProperty("recipient","text",recipient)
       +        recipient = None
       +
            out = None
            while out is None:
                event = droid.eventWait().result
       t@@ -423,20 +633,23 @@ def payto_loop():
                        recipient = droid.fullQueryDetail("recipient").result.get('text')
                        label  = droid.fullQueryDetail("label").result.get('text')
                        amount = droid.fullQueryDetail('amount').result.get('text')
       -                fee    = '0.001'
       -                amount = int( 100000000 * Decimal(amount) )
       -                fee    = int( 100000000 * Decimal(fee) )
       -                result = pay_to(recipient, amount, fee, label)
       -
       -                droid.dialogCreateAlert('result', result)
       -                droid.dialogSetPositiveButtonText('OK')
       -                droid.dialogShow()
       -                droid.dialogGetResponse()
       -                droid.dialogDismiss()
       -                out = 'main'
       +
       +                if not wallet.is_valid(recipient):
       +                    modal_dialog('Error','Invalid Bitcoin address')
       +                    continue
       +
       +                try:
       +                    amount = int( 100000000 * Decimal(amount) )
       +                except:
       +                    modal_dialog('Error','Invalid amount')
       +                    continue
       +
       +                result = pay_to(recipient, amount, wallet.fee, label)
       +                if result:
       +                    out = 'main'
        
                    elif id=="buttonContacts":
       -                addr = recipient_dialog()
       +                addr = select_from_contacts()
                        droid.fullSetProperty("recipient","text",addr)
        
                    elif id=="buttonQR":
       t@@ -447,14 +660,12 @@ def payto_loop():
                            if addr:
                                droid.fullSetProperty("recipient","text",addr)
                            
       -            elif id=="buttonCancelSend":
       -                out = 'main'
       +        elif event["name"] in menu_commands:
       +            out = event["name"]
        
       -        elif event["name"]=="settings":
       -            out = 'settings'
       -
       -        elif event["name"]=="quit":
       -            out = 'quit'
       +        elif event["name"]=="key":
       +            if event["data"]["key"] == '4':
       +                out = 'main'
        
                #elif event["name"]=="screen":
                #    if event["data"]=="destroy":
       t@@ -463,116 +674,308 @@ def payto_loop():
            return out
        
        
       -def history_loop():
       -    layout = get_history_layout()
       -    droid.fullShow(layout)
       +receive_addr = ''
       +contact_addr = ''
       +recipient = ''
       +
       +def receive_loop():
            out = None
            while out is None:
                event = droid.eventWait().result
       -        print "got event in history loop", event
       -        if event["name"] == "click":
       -
       -            if event["data"]["text"] == "OK":
       +        print "got event", event
       +        if event["name"]=="key":
       +            if event["data"]["key"] == '4':
                        out = 'main'
        
       -        elif event["name"] == "key":
       -            print repr(event["data"]["key"])
       +        elif event["name"]=="clipboard":
       +            droid.setClipboard(receive_addr)
       +            modal_dialog('Address copied to clipboard',receive_addr)
       +
       +        elif event["name"]=="edit":
       +            edit_label(receive_addr)
       +
       +    return out
       +
       +def contacts_loop():
       +    global recipient
       +    out = None
       +    while out is None:
       +        event = droid.eventWait().result
       +        print "got event", event
       +        if event["name"]=="key":
                    if event["data"]["key"] == '4':
                        out = 'main'
        
       -        #elif event["name"]=="screen":
       -        #    if event["data"]=="destroy":
       -        #        out = 'main'
       +        elif event["name"]=="clipboard":
       +            droid.setClipboard(contact_addr)
       +            modal_dialog('Address copied to clipboard',contact_addr)
       +
       +        elif event["name"]=="edit":
       +            edit_label(contact_addr)
       +
       +        elif event["name"]=="paytocontact":
       +            recipient = contact_addr
       +            out = 'send'
       +
       +        elif event["name"]=="deletecontact":
       +            if modal_question('delete contact', contact_addr):
       +                out = 'main'
        
            return out
        
       +
        def server_dialog(plist):
       -    droid.dialogCreateAlert("servers")
       +    droid.dialogCreateAlert("Public servers")
            droid.dialogSetItems( plist.keys() )
       +    droid.dialogSetPositiveButtonText('Private server')
            droid.dialogShow()
       -    i = droid.dialogGetResponse().result.get('item')
       +    response = droid.dialogGetResponse().result
            droid.dialogDismiss()
       +
       +    if response.get('which') == 'positive':
       +        return modal_input('Private server', None)
       +
       +    i = response.get('item')
            if i is not None:
                response = plist.keys()[i]
                return response
        
       -def protocol_dialog(plist):
       -    options=["TCP","HTTP","native"]
       -    droid.dialogCreateAlert("Protocol")
       -    droid.dialogSetSingleChoiceItems(options)
        
       +def seed_dialog():
       +    if wallet.use_encryption:
       +        password  = droid.dialogGetPassword('Seed').result
       +        if not password: return
       +    else:
       +        password = None
       +    
       +    try:
       +        seed = wallet.pw_decode( wallet.seed, password)
       +    except:
       +        modal_dialog('error','incorrect password')
       +        return
       +
       +    modal_dialog('Your seed is',seed)
       +    modal_dialog('Mnemonic code:', ' '.join(mnemonic.mn_encode(seed)) )
       +
       +def change_password_dialog():
       +    if wallet.use_encryption:
       +        password  = droid.dialogGetPassword('Your wallet is encrypted').result
       +        if password is None: return
       +    else:
       +        password = None
       +
       +    try:
       +        seed = wallet.pw_decode( wallet.seed, password)
       +    except:
       +        modal_dialog('error','incorrect password')
       +        return
       +
       +    new_password  = droid.dialogGetPassword('Choose a password').result
       +    if new_password == None:
       +        return
       +
       +    if new_password != '':
       +        password2  = droid.dialogGetPassword('Confirm new password').result
       +        if new_password != password2:
       +            modal_dialog('error','passwords do not match')
       +            return
       +
       +    wallet.update_password(seed, new_password)
       +    if new_password:
       +        modal_dialog('Password updated','your wallet is encrypted')
       +    else:
       +        modal_dialog('No password','your wallet is not encrypted')
       +    return True
        
        
        def settings_loop():
       -    droid.fullShow(settings_layout)
       -    droid.fullSetProperty("server","text",wallet.server)
        
       -    out = None
       -    while out is None:
       -        event = droid.eventWait().result
       -        if event["name"] == "click":
        
       -            id = event["data"]["id"]
       +    def set_listview():
       +        server, port, p = wallet.server.split(':')
       +        fee = str( Decimal( wallet.fee)/100000000 )
       +        is_encrypted = 'yes' if wallet.use_encryption else 'no'
       +        protocol = protocol_name(p)
       +        droid.fullShow(settings_layout)
       +        droid.fullSetList("myListView",['Server: ' + server, 'Protocol: '+ protocol, 'Port: '+port, 'Transaction fee: '+fee, 'Password: '+is_encrypted, 'Seed'])
        
       -            if id=="buttonServer":
       -                plist = {}
       -                for item in wallet.interface.servers:
       -                    host, pp = item
       -                    z = {}
       -                    for item2 in pp:
       -                        protocol, port = item2
       -                        z[protocol] = port
       -                    plist[host] = z
       +    set_listview()
        
       +    out = None
       +    while out is None:
       +        event = droid.eventWait().result
       +        print "got event", event
       +        if event == 'OK': continue
       +        if not event: continue
       +
       +        plist = {}
       +        for item in wallet.interface.servers:
       +            host, pp = item
       +            z = {}
       +            for item2 in pp:
       +                protocol, port = item2
       +                z[protocol] = port
       +            plist[host] = z
       +
       +        if event["name"] == "itemclick":
       +            pos = event["data"]["position"]
       +            host, port, protocol = wallet.server.split(':')
       +
       +            if pos == "0": #server
                        host = server_dialog(plist)
       -                p = plist[host]
       -                port = p['t']
       -                srv = host + ':' + port + ':t'
       -                droid.fullSetProperty("server","text",srv)
       -
       -            elif id=="buttonSave":
       -                droid.fullQuery()
       -                srv = droid.fullQueryDetail("server").result.get('text')
       -                try:
       -                    wallet.set_server(srv)
       -                    out = 'main'
       -                except:
       -                    droid.dialogCreateAlert('error')
       -                    droid.dialogSetPositiveButtonText('OK')
       -                    droid.dialogShow()
       -                    droid.dialogGetResponse()
       -                    droid.dialogDismiss()
       -                    
       -            elif id=="buttonCancel":
       -                out = 'main'
       +                if host:
       +                    p = plist[host]
       +                    port = p['t']
       +                    srv = host + ':' + port + ':t'
       +                    try:
       +                        wallet.set_server(srv)
       +                    except:
       +                        modal_dialog('error','invalid server')
       +                    set_listview()
       +
       +            elif pos == "1": #protocol
       +                if host in plist:
       +                    srv = protocol_dialog(host, protocol, plist[host])
       +                    if srv:
       +                        try:
       +                            wallet.set_server(srv)
       +                        except:
       +                            modal_dialog('error','invalid server')
       +                        set_listview()
       +
       +            elif pos == "2": #port
       +                a_port = modal_input('Port number', 'If you use a public server, this field is set automatically when you set the protocol', port, "number")
       +                if a_port:
       +                    if a_port != port:
       +                        srv = host + ':' + a_port + ':'+ protocol
       +                        try:
       +                            wallet.set_server(srv)
       +                        except:
       +                            modal_dialog('error','invalid port number')
       +                        set_listview()
       +
       +            elif pos == "3": #fee
       +                fee = modal_input('Transaction fee', 'The fee will be this amount multiplied by the number of inputs in your transaction. ', str( Decimal( wallet.fee)/100000000 ), "numberDecimal")
       +                if fee:
       +                    try:
       +                        fee = int( 100000000 * Decimal(fee) )
       +                    except:
       +                        modal_dialog('error','invalid fee value')
       +                    if wallet.fee != fee:
       +                        wallet.fee = fee
       +                        wallet.save()
       +                        set_listview()
       +        
       +            elif pos == "4":
       +                if change_password_dialog():
       +                    set_listview()
       +
       +            elif pos == "5":
       +                seed_dialog()
       +
       +
       +        elif event["name"] in menu_commands:
       +            out = event["name"]
       +
       +        elif event["name"] == 'cancel':
       +            out = 'main'
        
                elif event["name"] == "key":
       -            print repr(event["data"]["key"])
                    if event["data"]["key"] == '4':
                        out = 'main'
        
       -        elif event["name"]=="quit":
       -            out = 'quit'
       -
            return out
        
       -                
       +
       +
       +
       +menu_commands = ["send", "receive", "settings", "contacts", "main"]
       +droid = android.Android()
       +wallet = Wallet(update_callback)
       +
       +wallet.set_path("/sdcard/electrum.dat")
       +wallet.read()
       +if not wallet.file_exists:
       +    recover()
       +else:
       +    WalletSynchronizer(wallet,True).start()
        
        
        s = 'main'
       +
       +def add_menu(s):
       +    droid.clearOptionsMenu()
       +    if s == 'main':
       +        droid.addOptionsMenuItem("Send","send",None,"")
       +        droid.addOptionsMenuItem("Receive","receive",None,"")
       +        droid.addOptionsMenuItem("Contacts","contacts",None,"")
       +        droid.addOptionsMenuItem("Settings","settings",None,"")
       +    elif s == 'receive':
       +        droid.addOptionsMenuItem("Copy","clipboard",None,"")
       +        droid.addOptionsMenuItem("Label","edit",None,"")
       +    elif s == 'contacts':
       +        droid.addOptionsMenuItem("Copy","clipboard",None,"")
       +        droid.addOptionsMenuItem("Label","edit",None,"")
       +        droid.addOptionsMenuItem("Pay to","paytocontact",None,"")
       +        #droid.addOptionsMenuItem("Delete","deletecontact",None,"")
       +
       +def make_bitmap(addr):
       +    # fixme: this is highly inefficient
       +    droid.dialogCreateSpinnerProgress("please wait")
       +    droid.dialogShow()
       +    import pyqrnative, bmp
       +    qr = pyqrnative.QRCode(4, pyqrnative.QRErrorCorrectLevel.H)
       +    qr.addData(addr)
       +    qr.make()
       +    k = qr.getModuleCount()
       +    bitmap = bmp.BitMap( 35*8, 35*8 )
       +    print len(bitmap.bitarray)
       +    bitmap.bitarray = []
       +    assert k == 33
       +
       +    for r in range(35):
       +        tmparray = [ 0 ] * 35*8
       +
       +        if 0 < r < 34:
       +            for c in range(k):
       +                if qr.isDark(r-1, c):
       +                    tmparray[ (1+c)*8:(2+c)*8] = [1]*8
       +
       +        for i in range(8):
       +            bitmap.bitarray.append( tmparray[:] )
       +
       +    bitmap.saveFile( "/sdcard/sl4a/qrcode.bmp" )
       +    droid.dialogDismiss()
       +
       +        
       +
        while True:
       +    add_menu(s)
            if s == 'main':
       +        droid.fullShow(main_layout())
                s = main_loop()
       -    elif s == 'payto':
       +        #droid.fullDismiss()
       +
       +    elif s == 'send':
       +        droid.fullShow(payto_layout)
                s = payto_loop()
       -    elif s == 'settings':
       -        s = settings_loop()
       -    elif s == 'history':
       -        s = history_loop()
       +        #droid.fullDismiss()
       +
       +    elif s == 'receive':
       +        make_bitmap(receive_addr)
       +        droid.fullShow(qr_layout(receive_addr))
       +        s = receive_loop()
       +
            elif s == 'contacts':
       +        make_bitmap(contact_addr)
       +        droid.fullShow(qr_layout(contact_addr))
                s = contacts_loop()
       +
       +    elif s == 'settings':
       +        #droid.fullShow(settings_layout)
       +        s = settings_loop()
       +        #droid.fullDismiss()
            else:
                break
        
       -droid.fullDismiss()
        droid.makeToast("Bye!")
 (DIR) diff --git a/client/electrum_text_320.png b/client/electrum_text_320.png
       Binary files differ.
 (DIR) diff --git a/client/gui.py b/client/gui.py
       t@@ -1111,7 +1111,7 @@ class ElectrumWindow:
                        self.network_button.set_tooltip_text("Connected to %s:%d.\n%d blocks\nresponse time: %f"%(interface.host, interface.port, self.wallet.blocks, interface.rtime))
                        c, u = self.wallet.get_balance()
                        text =  "Balance: %s "%( format_satoshis(c) )
       -                if u: text +=  "[%s unconfirmed]"%( format_satoshis(u,True) )
       +                if u: text +=  "[%s unconfirmed]"%( format_satoshis(u,True).strip() )
                else:
                    self.status_image.set_from_stock(gtk.STOCK_NO, gtk.ICON_SIZE_MENU)
                    self.network_button.set_tooltip_text("Trying to contact %s.\n%d blocks"%(interface.host, self.wallet.blocks))
 (DIR) diff --git a/client/gui_qt.py b/client/gui_qt.py
       t@@ -100,8 +100,6 @@ class QRCodeWidget(QWidget):
                super(QRCodeWidget, self).__init__()
                self.addr = addr
                self.setGeometry(300, 300, 350, 350)
       -        self.setWindowTitle('Colors')
       -        self.show()
                self.qr = pyqrnative.QRCode(4, pyqrnative.QRErrorCorrectLevel.H)
                self.qr.addData(addr)
                self.qr.make()
       t@@ -144,6 +142,8 @@ class ElectrumWindow(QMainWindow):
            def __init__(self, wallet):
                QMainWindow.__init__(self)
                self.wallet = wallet
       +        self.wallet.gui_callback = self.update_callback
       +
                self.funds_error = False
        
                self.tabs = tabs = QTabWidget(self)
       t@@ -164,11 +164,11 @@ class ElectrumWindow(QMainWindow):
                QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
                QShortcut(QKeySequence("Ctrl+PgUp"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() - 1 )%tabs.count() ))
                QShortcut(QKeySequence("Ctrl+PgDown"), self, lambda: tabs.setCurrentIndex( (tabs.currentIndex() + 1 )%tabs.count() ))
       -
       +        
       +        self.connect(self, QtCore.SIGNAL('updatesignal'), self.update_wallet)
        
        
            def connect_slots(self, sender):
       -        self.connect(sender, QtCore.SIGNAL('timersignal'), self.update_wallet)
                self.connect(sender, QtCore.SIGNAL('timersignal'), self.check_recipient)
                self.previous_payto_e=''
        
       t@@ -189,6 +189,9 @@ class ElectrumWindow(QMainWindow):
                            self.payto_e.setText(s)
        
        
       +    def update_callback(self):
       +        self.emit(QtCore.SIGNAL('updatesignal'))
       +
            def update_wallet(self):
                if self.wallet.interface.is_connected:
                    if self.wallet.blocks == -1:
       t@@ -203,7 +206,7 @@ class ElectrumWindow(QMainWindow):
                    else:
                        c, u = self.wallet.get_balance()
                        text =  "Balance: %s "%( format_satoshis(c) )
       -                if u: text +=  "[%s unconfirmed]"%( format_satoshis(u,True) )
       +                if u: text +=  "[%s unconfirmed]"%( format_satoshis(u,True).strip() )
                        icon = QIcon(":icons/status_connected.png")
                else:
                    text = "Not connected"
       t@@ -215,8 +218,7 @@ class ElectrumWindow(QMainWindow):
                self.statusBar().showMessage(text)
                self.status_button.setIcon( icon )
        
       -        if self.wallet.was_updated and self.wallet.up_to_date:
       -            self.wallet.was_updated = False
       +        if self.wallet.up_to_date:
                    self.textbox.setText( self.wallet.banner )
                    self.update_history_tab()
                    self.update_receive_tab()
       t@@ -522,17 +524,7 @@ class ElectrumWindow(QMainWindow):
                    addr = unicode( i.text(0) )
                    return addr
        
       -        def showqrcode(address):
       -            if not address: return
       -            d = QDialog(self)
       -            d.setModal(1)
       -            d.setMinimumSize(270, 300)
       -            vbox = QVBoxLayout()
       -            vbox.addWidget(QRCodeWidget(address))
       -            vbox.addLayout(ok_cancel_buttons(d))
       -            d.setLayout(vbox)
       -            d.exec_()
       -        qrButton = EnterButton("QR",lambda: showqrcode(get_addr(l)))
       +        qrButton = EnterButton("QR",lambda: ElectrumWindow.showqrcode(get_addr(l)))
        
                def copy2clipboard(addr):
                    self.app.clipboard().setText(addr)
       t@@ -665,19 +657,20 @@ class ElectrumWindow(QMainWindow):
                      + ' '.join(mnemonic.mn_encode(seed)) + "\""
        
                QMessageBox.information(parent, 'Seed', msg, 'OK')
       +        ElectrumWindow.showqrcode(seed)
        
       -        def showqrcode(address):
       -            if not address: return
       -            d = QDialog(None)
       -            d.setModal(1)
       -            d.setMinimumSize(270, 300)
       -            vbox = QVBoxLayout()
       -            vbox.addWidget(QRCodeWidget(address))
       -            vbox.addLayout(ok_cancel_buttons(d))
       -            d.setLayout(vbox)
       -            d.exec_()
       -        showqrcode(seed)
       -
       +    @staticmethod
       +    def showqrcode(address):
       +        if not address: return
       +        d = QDialog(None)
       +        d.setModal(1)
       +        d.setWindowTitle(address)
       +        d.setMinimumSize(270, 300)
       +        vbox = QVBoxLayout()
       +        vbox.addWidget(QRCodeWidget(address))
       +        vbox.addLayout(ok_cancel_buttons(d))
       +        d.setLayout(vbox)
       +        d.exec_()
        
            def question(self, msg):
                return QMessageBox.question(self, 'Message', msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes
       t@@ -1059,4 +1052,6 @@ class ElectrumGui():
                if url: w.set_url(url)
                w.app = self.app
                w.connect_slots(s)
       +        w.update_wallet()
       +
                self.app.exec_()
 (DIR) diff --git a/client/interface.py b/client/interface.py
       t@@ -316,6 +316,7 @@ class TcpStratumInterface(Interface):
                    traceback.print_exc(file=sys.stdout)
        
                self.is_connected = False
       +        print "poking"
                self.poke()
        
            def send(self, messages):
       t@@ -448,27 +449,33 @@ class WalletSynchronizer(threading.Thread):
            def run(self):
                import socket, time
                while True:
       -            try:
       -                while self.interface.is_connected:
       -                    new_addresses = self.wallet.synchronize()
       -                    if new_addresses:
       -                        self.interface.subscribe(new_addresses)
       -                        for addr in new_addresses:
       -                            with self.wallet.lock:
       -                                self.wallet.addresses_waiting_for_status.append(addr)
       -
       -                    if self.wallet.is_up_to_date():
       +            while self.interface.is_connected:
       +                new_addresses = self.wallet.synchronize()
       +                if new_addresses:
       +                    self.interface.subscribe(new_addresses)
       +                    for addr in new_addresses:
       +                        with self.wallet.lock:
       +                            self.wallet.addresses_waiting_for_status.append(addr)
       +
       +                if self.wallet.is_up_to_date():
       +                    if not self.wallet.up_to_date:
                                self.wallet.up_to_date = True
       +                        self.wallet.was_updated = True
                                self.wallet.up_to_date_event.set()
       -                    else:
       +                else:
       +                    if self.wallet.up_to_date:
                                self.wallet.up_to_date = False
       +                        self.wallet.was_updated = True
        
       -                    response = self.interface.responses.get()#True,100000000000) # workaround so that it can be keyboard interrupted
       -                    self.handle_response(response)
       -            except socket.error:
       -                print "socket error"
       -                wallet.interface.is_connected = False
       +                if self.wallet.was_updated:
       +                    self.wallet.gui_callback()
       +                    self.wallet.was_updated = False
       +
       +                response = self.interface.responses.get()
       +                self.handle_response(response)
        
       +            print "disconnected, gui callback"
       +            self.wallet.gui_callback()
                    if self.loop:
                        time.sleep(5)
                        self.start_interface()
 (DIR) diff --git a/client/wallet.py b/client/wallet.py
       t@@ -242,10 +242,11 @@ from interface import DEFAULT_SERVERS
        
        
        class Wallet:
       -    def __init__(self):
       +    def __init__(self, gui_callback = lambda: None):
        
                self.electrum_version = ELECTRUM_VERSION
                self.seed_version = SEED_VERSION
       +        self.gui_callback = gui_callback
        
                self.gap_limit = 5           # configuration
                self.fee = 100000
       t@@ -339,7 +340,7 @@ class Wallet:
        
            def new_seed(self, password):
                seed = "%032x"%ecdsa.util.randrange( pow(2,128) )
       -        self.init_mpk(seed)
       +        #self.init_mpk(seed)
                # encrypt
                self.seed = self.pw_encode( seed, password )
        
       t@@ -849,7 +850,8 @@ class Wallet:
                return target, signing_addr, auth_name
        
            def update_password(self, seed, new_password):
       -        self.use_encryption = (new_password != '')
       +        if new_password == '': new_password = None
       +        self.use_encryption = (new_password != None)
                self.seed = self.pw_encode( seed, new_password)
                for k in self.imported_keys.keys():
                    a = self.imported_keys[k]