version 0.50: localisation using gettext
[electrum-nvc.git] / electrum4a.py
1 #!/usr/bin/env python
2 #
3 # Electrum - lightweight Bitcoin client
4 # Copyright (C) 2011 thomasv@gitorious
5 #
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
18
19
20
21
22 import android
23 from interface import WalletSynchronizer
24 from wallet import Wallet
25 from wallet import format_satoshis
26 from decimal import Decimal
27 import mnemonic
28
29 import datetime, re
30
31
32
33 def modal_dialog(title, msg = None):
34     droid.dialogCreateAlert(title,msg)
35     droid.dialogSetPositiveButtonText('OK')
36     droid.dialogShow()
37     droid.dialogGetResponse()
38     droid.dialogDismiss()
39
40 def modal_input(title, msg, value = None, etype=None):
41     droid.dialogCreateInput(title, msg, value, etype)
42     droid.dialogSetPositiveButtonText('OK')
43     droid.dialogSetNegativeButtonText('Cancel')
44     droid.dialogShow()
45     response = droid.dialogGetResponse().result
46     droid.dialogDismiss()
47     if response.get('which') == 'positive':
48         return response.get('value')
49
50 def modal_question(q, msg, pos_text = 'OK', neg_text = 'Cancel'):
51     droid.dialogCreateAlert(q, msg)
52     droid.dialogSetPositiveButtonText(pos_text)
53     droid.dialogSetNegativeButtonText(neg_text)
54     droid.dialogShow()
55     response = droid.dialogGetResponse().result
56     droid.dialogDismiss()
57     return response.get('which') == 'positive'
58
59 def edit_label(addr):
60     v = modal_input('Edit label',None,wallet.labels.get(addr))
61     if v is not None:
62         if v:
63             wallet.labels[addr] = v
64         else:
65             if addr in wallet.labels.keys():
66                 wallet.labels.pop(addr)
67         wallet.update_tx_history()
68         wallet.save()
69         droid.fullSetProperty("labelTextView", "text", v)
70
71 def select_from_contacts():
72     title = 'Contacts:'
73     droid.dialogCreateAlert(title)
74     l = []
75     for i in range(len(wallet.addressbook)):
76         addr = wallet.addressbook[i]
77         label = wallet.labels.get(addr,addr)
78         l.append( label )
79     droid.dialogSetItems(l)
80     droid.dialogSetPositiveButtonText('New contact')
81     droid.dialogShow()
82     response = droid.dialogGetResponse().result
83     droid.dialogDismiss()
84
85     if response.get('which') == 'positive':
86         return 'newcontact'
87
88     result = response.get('item')
89     print result
90     if result is not None:
91         addr = wallet.addressbook[result]
92         return addr
93
94
95 def select_from_addresses():
96     droid.dialogCreateAlert("Addresses:")
97     l = []
98     for i in range(len(wallet.addresses)):
99         addr = wallet.addresses[i]
100         label = wallet.labels.get(addr,addr)
101         l.append( label )
102     droid.dialogSetItems(l)
103     droid.dialogShow()
104     response = droid.dialogGetResponse()
105     result = response.result.get('item')
106     droid.dialogDismiss()
107     if result is not None:
108         addr = wallet.addresses[result]
109         return addr
110
111
112 def protocol_name(p):
113     if p == 't': return 'TCP/stratum'
114     if p == 'h': return 'HTTP/Stratum'
115     if p == 'n': return 'TCP/native'
116
117 def protocol_dialog(host, protocol, z):
118     droid.dialogCreateAlert('Protocol',host)
119     if z:
120         protocols = z.keys()
121     else:
122         protocols = ['t','h','n']
123     l = []
124     current = protocols.index(protocol)
125     for p in protocols:
126         l.append(protocol_name(p))
127     droid.dialogSetSingleChoiceItems(l, current)
128     droid.dialogSetPositiveButtonText('OK')
129     droid.dialogSetNegativeButtonText('Cancel')
130     droid.dialogShow()
131     response = droid.dialogGetResponse().result
132     if not response: return
133     if response.get('which') == 'positive':
134         response = droid.dialogGetSelectedItems().result[0]
135         droid.dialogDismiss()
136         p = protocols[response]
137         port = z[p]
138         return host + ':' + port + ':' + p
139
140
141
142
143 def make_layout(s, scrollable = False):
144     content = """
145
146       <LinearLayout 
147         android:id="@+id/zz"
148         android:layout_width="match_parent"
149         android:layout_height="wrap_content" 
150         android:background="#ff222222">
151
152         <TextView
153           android:id="@+id/textElectrum"
154           android:text="Electrum"
155           android:textSize="7pt"
156           android:textColor="#ff4444ff"
157           android:gravity="left"
158           android:layout_height="wrap_content"
159           android:layout_width="match_parent"
160         />
161       </LinearLayout>
162
163         %s   """%s
164
165     if scrollable:
166         content = """
167       <ScrollView 
168         android:id="@+id/scrollview"
169         android:layout_width="match_parent"
170         android:layout_height="match_parent" >
171
172       <LinearLayout
173         android:orientation="vertical" 
174         android:layout_width="match_parent"
175         android:layout_height="wrap_content" >
176
177       %s
178
179       </LinearLayout>
180       </ScrollView>
181       """%content
182
183
184     return """<?xml version="1.0" encoding="utf-8"?>
185       <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
186         android:id="@+id/background"
187         android:orientation="vertical" 
188         android:layout_width="match_parent"
189         android:layout_height="match_parent" 
190         android:background="#ff000022">
191
192       %s 
193       </LinearLayout>"""%content
194
195
196
197
198 def main_layout():
199     return make_layout("""
200         <TextView android:id="@+id/balanceTextView" 
201                 android:layout_width="match_parent"
202                 android:text=""
203                 android:textColor="#ffffffff"
204                 android:textAppearance="?android:attr/textAppearanceLarge" 
205                 android:padding="7dip"
206                 android:textSize="8pt"
207                 android:gravity="center_vertical|center_horizontal|left">
208         </TextView>
209
210         <TextView android:id="@+id/historyTextView" 
211                 android:layout_width="match_parent"
212                 android:layout_height="wrap_content" 
213                 android:text="Recent transactions"
214                 android:textAppearance="?android:attr/textAppearanceLarge" 
215                 android:gravity="center_vertical|center_horizontal|center">
216         </TextView>
217
218         %s """%get_history_layout(15),True)
219
220
221
222 def qr_layout(addr):
223     return make_layout("""
224
225      <TextView android:id="@+id/addrTextView" 
226                 android:layout_width="match_parent"
227                 android:layout_height="50" 
228                 android:text="%s"
229                 android:textAppearance="?android:attr/textAppearanceLarge" 
230                 android:gravity="center_vertical|center_horizontal|center">
231      </TextView>
232
233      <ImageView
234         android:id="@+id/qrView"
235         android:gravity="center"
236         android:layout_width="match_parent"
237         android:layout_height="350"
238         android:antialias="false"
239         android:src="file:///sdcard/sl4a/qrcode.bmp" /> 
240
241      <TextView android:id="@+id/labelTextView" 
242                 android:layout_width="match_parent"
243                 android:layout_height="50" 
244                 android:text="%s"
245                 android:textAppearance="?android:attr/textAppearanceLarge" 
246                 android:gravity="center_vertical|center_horizontal|center">
247      </TextView>
248
249      """%(addr,wallet.labels.get(addr,'')), True)
250
251 payto_layout = make_layout("""
252
253         <TextView android:id="@+id/recipientTextView" 
254                 android:layout_width="match_parent"
255                 android:layout_height="wrap_content" 
256                 android:text="Pay to:"
257                 android:textAppearance="?android:attr/textAppearanceLarge" 
258                 android:gravity="left">
259         </TextView>
260
261
262         <EditText android:id="@+id/recipient"
263                 android:layout_width="match_parent"
264                 android:layout_height="wrap_content" 
265                 android:tag="Tag Me" android:inputType="text">
266         </EditText>
267
268         <LinearLayout android:id="@+id/linearLayout1"
269                 android:layout_width="match_parent"
270                 android:layout_height="wrap_content">
271                 <Button android:id="@+id/buttonQR" android:layout_width="wrap_content"
272                         android:layout_height="wrap_content" android:text="From QR code"></Button>
273                 <Button android:id="@+id/buttonContacts" android:layout_width="wrap_content"
274                         android:layout_height="wrap_content" android:text="From Contacts"></Button>
275         </LinearLayout>
276
277
278         <TextView android:id="@+id/labelTextView" 
279                 android:layout_width="match_parent"
280                 android:layout_height="wrap_content" 
281                 android:text="Description:"
282                 android:textAppearance="?android:attr/textAppearanceLarge" 
283                 android:gravity="left">
284         </TextView>
285
286         <EditText android:id="@+id/label"
287                 android:layout_width="match_parent"
288                 android:layout_height="wrap_content" 
289                 android:tag="Tag Me" android:inputType="text">
290         </EditText>
291
292         <TextView android:id="@+id/amountLabelTextView" 
293                 android:layout_width="match_parent"
294                 android:layout_height="wrap_content" 
295                 android:text="Amount:"
296                 android:textAppearance="?android:attr/textAppearanceLarge" 
297                 android:gravity="left">
298         </TextView>
299
300         <EditText android:id="@+id/amount"
301                 android:layout_width="match_parent"
302                 android:layout_height="wrap_content" 
303                 android:tag="Tag Me" android:inputType="numberDecimal">
304         </EditText>
305
306         <LinearLayout android:layout_width="match_parent"
307                 android:layout_height="wrap_content" android:id="@+id/linearLayout1">
308                 <Button android:id="@+id/buttonPay" android:layout_width="wrap_content"
309                         android:layout_height="wrap_content" android:text="Send"></Button>
310         </LinearLayout>""",False)
311
312
313
314 settings_layout = make_layout(""" <ListView
315            android:id="@+id/myListView" 
316            android:layout_width="match_parent"
317            android:layout_height="wrap_content" />""")
318
319
320
321 def get_history_values(n):
322     values = []
323     h = wallet.get_tx_history()
324
325     length = min(n, len(h))
326     for i in range(length):
327         line = h[-i-1]
328         v = line['value']
329         try:
330             dt = datetime.datetime.fromtimestamp( line['timestamp'] )
331             if dt.date() == dt.today().date():
332                 time_str = str( dt.time() )
333             else:
334                 time_str = str( dt.date() )
335             conf = 'v'
336
337         except:
338             print line['timestamp']
339             time_str = 'pending'
340             conf = 'o'
341
342         tx_hash = line['tx_hash']
343         label = wallet.labels.get(tx_hash)
344         is_default_label = (label == '') or (label is None)
345         if is_default_label: label = line['default_label']
346         values.append((conf, '  ' + time_str, '  ' + format_satoshis(v,True), '  ' + label ))
347
348     return values
349
350
351 def get_history_layout(n):
352     rows = ""
353     i = 0
354     values = get_history_values(n)
355     for v in values:
356         a,b,c,d = v
357         color = "#ff00ff00" if a == 'v' else "#ffff0000"
358         rows += """
359         <TableRow>
360           <TextView
361             android:id="@+id/hl_%d_col1" 
362             android:layout_column="0"
363             android:text="%s"
364             android:textColor="%s"
365             android:padding="3" />
366           <TextView
367             android:id="@+id/hl_%d_col2" 
368             android:layout_column="1"
369             android:text="%s"
370             android:padding="3" />
371           <TextView
372             android:id="@+id/hl_%d_col3" 
373             android:layout_column="2"
374             android:text="%s"
375             android:padding="3" />
376           <TextView
377             android:id="@+id/hl_%d_col4" 
378             android:layout_column="3"
379             android:text="%s"
380             android:padding="4" />
381         </TableRow>"""%(i,a,color,i,b,i,c,i,d)
382         i += 1
383
384     output = """
385 <TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
386     android:layout_width="fill_parent"
387     android:layout_height="wrap_content"
388     android:stretchColumns="0,1,2,3">
389     %s
390 </TableLayout>"""% rows
391     return output
392
393
394 def set_history_layout(n):
395     values = get_history_values(n)
396     i = 0
397     for v in values:
398         a,b,c,d = v
399         droid.fullSetProperty("hl_%d_col1"%i,"text", a)
400
401         if a == 'v':
402             droid.fullSetProperty("hl_%d_col1"%i, "textColor","#ff00ff00")
403         else:
404             droid.fullSetProperty("hl_%d_col1"%i, "textColor","#ffff0000")
405
406         droid.fullSetProperty("hl_%d_col2"%i,"text", b)
407         droid.fullSetProperty("hl_%d_col3"%i,"text", c)
408         droid.fullSetProperty("hl_%d_col4"%i,"text", d)
409         i += 1
410
411
412
413
414 status_text = ''
415 def update_layout():
416     global status_text
417     if not wallet.interface.is_connected:
418         text = "Not connected..."
419     elif wallet.blocks == 0:
420         text = "Server not ready"
421     elif not wallet.up_to_date:
422         text = "Synchronizing..."
423     else:
424         c, u = wallet.get_balance()
425         text = "Balance:"+format_satoshis(c) 
426         if u : text += '   [' + format_satoshis(u,True).strip() + ']'
427
428
429     # vibrate if status changed
430     if text != status_text:
431         if status_text and wallet.interface.is_connected and wallet.up_to_date:
432             droid.vibrate()
433         status_text = text
434
435     droid.fullSetProperty("balanceTextView", "text", status_text)
436
437     if wallet.up_to_date:
438         set_history_layout(15)
439
440
441
442
443 def pay_to(recipient, amount, fee, label):
444
445     if wallet.use_encryption:
446         password  = droid.dialogGetPassword('Password').result
447         if not password: return
448     else:
449         password = None
450
451     droid.dialogCreateSpinnerProgress("Electrum", "signing transaction...")
452     droid.dialogShow()
453
454     try:
455         tx = wallet.mktx( recipient, amount, label, password, fee)
456     except BaseException, e:
457         modal_dialog('error', e.message)
458         droid.dialogDismiss()
459         return
460
461     droid.dialogDismiss()
462
463     r, h = wallet.sendtx( tx )
464     if r:
465         modal_dialog('Payment sent', h)
466         return True
467     else:
468         modal_dialog('Error', h)
469
470
471
472
473
474 def recover():
475
476     droid.dialogCreateAlert("Wallet not found","Do you want to create a new wallet, or restore an existing one?")
477     droid.dialogSetPositiveButtonText('Create')
478     droid.dialogSetNeutralButtonText('Restore')
479     droid.dialogSetNegativeButtonText('Cancel')
480     droid.dialogShow()
481     response = droid.dialogGetResponse().result
482     droid.dialogDismiss()
483     if response.get('which') == 'negative':
484         exit(1)
485
486     is_recovery = response.get('which') == 'neutral'
487
488     if not is_recovery:
489         wallet.new_seed(None)
490     else:
491         if modal_question("Input method",None,'QR Code', 'mnemonic'):
492             code = droid.scanBarcode()
493             r = code.result
494             if r:
495                 seed = r['extras']['SCAN_RESULT']
496             else:
497                 exit(1)
498         else:
499             m = modal_input('Mnemonic','please enter your code')
500             try:
501                 seed = mnemonic.mn_decode(m.split(' '))
502             except:
503                 modal_dialog('error: could not decode this seed')
504                 exit(1)
505
506         wallet.seed = str(seed)
507
508     modal_dialog('Your seed is:', wallet.seed)
509     modal_dialog('Mnemonic code:', ' '.join(mnemonic.mn_encode(wallet.seed)) )
510
511     msg = "recovering wallet..." if is_recovery else "creating wallet..."
512     droid.dialogCreateSpinnerProgress("Electrum", msg)
513     droid.dialogShow()
514
515     wallet.init_mpk( wallet.seed )
516     WalletSynchronizer(wallet,True).start()
517     wallet.update()
518
519     droid.dialogDismiss()
520     droid.vibrate()
521
522     if is_recovery:
523         if wallet.is_found():
524             wallet.update_tx_history()
525             wallet.fill_addressbook()
526             modal_dialog("recovery successful")
527         else:
528             if not modal_question("no transactions found for this seed","do you want to keep this wallet?"):
529                 exit(1)
530
531     change_password_dialog()
532     wallet.save()
533
534
535
536 def make_new_contact():
537     code = droid.scanBarcode()
538     r = code.result
539     if r:
540         address = r['extras']['SCAN_RESULT']
541         if address:
542             if wallet.is_valid(address):
543                 if modal_question('Add to contacts?', address):
544                     wallet.addressbook.append(address)
545                     wallet.save()
546         else:
547             modal_dialog('Invalid address', address)
548
549
550 do_refresh = False
551
552 def update_callback():
553     global do_refresh
554     print "gui callback", wallet.interface.is_connected, wallet.up_to_date
555     do_refresh = True
556     droid.eventPost("refresh",'z')
557
558 def main_loop():
559     global do_refresh
560
561     update_layout()
562     out = None
563     quitting = False
564     while out is None:
565
566         event = droid.eventWait(1000).result
567         if event is None:
568             if do_refresh: 
569                 update_layout()
570                 do_refresh = False
571             continue
572
573         print "got event in main loop", repr(event)
574         if event == 'OK': continue
575         if event is None: continue
576         #if event["name"]=="refresh":
577
578
579         # request 2 taps before we exit
580         if event["name"]=="key":
581             if event["data"]["key"] == '4':
582                 if quitting:
583                     out = 'quit'
584                 else: 
585                     quitting = True
586         else: quitting = False
587
588         if event["name"]=="click":
589             id=event["data"]["id"]
590
591         elif event["name"]=="settings":
592             out = 'settings'
593
594         elif event["name"] in menu_commands:
595             out = event["name"]
596
597             if out == 'contacts':
598                 global contact_addr
599                 contact_addr = select_from_contacts()
600                 if contact_addr == 'newcontact':
601                     make_new_contact()
602                     contact_addr = None
603                 if not contact_addr:
604                     out = None
605
606             elif out == "receive":
607                 global receive_addr
608                 receive_addr = select_from_addresses()
609                 if receive_addr:
610                     amount = modal_input('Amount', 'Amount you want receive. ', '', "numberDecimal")
611                     if amount:
612                         receive_addr = 'bitcoin:%s?amount=%s'%(receive_addr, amount)
613
614                 if not receive_addr:
615                     out = None
616
617
618     return out
619                     
620
621 def payto_loop():
622     global recipient
623     if recipient:
624         droid.fullSetProperty("recipient","text",recipient)
625         recipient = None
626
627     out = None
628     while out is None:
629         event = droid.eventWait().result
630         print "got event in payto loop", event
631
632         if event["name"] == "click":
633             id = event["data"]["id"]
634
635             if id=="buttonPay":
636
637                 droid.fullQuery()
638                 recipient = droid.fullQueryDetail("recipient").result.get('text')
639                 label  = droid.fullQueryDetail("label").result.get('text')
640                 amount = droid.fullQueryDetail('amount').result.get('text')
641
642                 if not wallet.is_valid(recipient):
643                     modal_dialog('Error','Invalid Bitcoin address')
644                     continue
645
646                 try:
647                     amount = int( 100000000 * Decimal(amount) )
648                 except:
649                     modal_dialog('Error','Invalid amount')
650                     continue
651
652                 result = pay_to(recipient, amount, wallet.fee, label)
653                 if result:
654                     out = 'main'
655
656             elif id=="buttonContacts":
657                 addr = select_from_contacts()
658                 droid.fullSetProperty("recipient","text",addr)
659
660             elif id=="buttonQR":
661                 code = droid.scanBarcode()
662                 r = code.result
663                 if r:
664                     data = r['extras']['SCAN_RESULT']
665                     if data:
666                         if re.match('^bitcoin:', data):
667                             payto, amount, label, _, _, _, _ = wallet.parse_url(data, None, None)
668                             droid.fullSetProperty("recipient", "text",payto)
669                             droid.fullSetProperty("amount", "text", amount)
670                             droid.fullSetProperty("label", "text", label)
671                         else:
672                             droid.fullSetProperty("recipient", "text", data)
673
674                     
675         elif event["name"] in menu_commands:
676             out = event["name"]
677
678         elif event["name"]=="key":
679             if event["data"]["key"] == '4':
680                 out = 'main'
681
682         #elif event["name"]=="screen":
683         #    if event["data"]=="destroy":
684         #        out = 'main'
685
686     return out
687
688
689 receive_addr = ''
690 contact_addr = ''
691 recipient = ''
692
693 def receive_loop():
694     out = None
695     while out is None:
696         event = droid.eventWait().result
697         print "got event", event
698         if event["name"]=="key":
699             if event["data"]["key"] == '4':
700                 out = 'main'
701
702         elif event["name"]=="clipboard":
703             droid.setClipboard(receive_addr)
704             modal_dialog('Address copied to clipboard',receive_addr)
705
706         elif event["name"]=="edit":
707             edit_label(receive_addr)
708
709     return out
710
711 def contacts_loop():
712     global recipient
713     out = None
714     while out is None:
715         event = droid.eventWait().result
716         print "got event", event
717         if event["name"]=="key":
718             if event["data"]["key"] == '4':
719                 out = 'main'
720
721         elif event["name"]=="clipboard":
722             droid.setClipboard(contact_addr)
723             modal_dialog('Address copied to clipboard',contact_addr)
724
725         elif event["name"]=="edit":
726             edit_label(contact_addr)
727
728         elif event["name"]=="paytocontact":
729             recipient = contact_addr
730             out = 'send'
731
732         elif event["name"]=="deletecontact":
733             if modal_question('delete contact', contact_addr):
734                 out = 'main'
735
736     return out
737
738
739 def server_dialog(plist):
740     droid.dialogCreateAlert("Public servers")
741     droid.dialogSetItems( plist.keys() )
742     droid.dialogSetPositiveButtonText('Private server')
743     droid.dialogShow()
744     response = droid.dialogGetResponse().result
745     droid.dialogDismiss()
746
747     if response.get('which') == 'positive':
748         return modal_input('Private server', None)
749
750     i = response.get('item')
751     if i is not None:
752         response = plist.keys()[i]
753         return response
754
755
756 def seed_dialog():
757     if wallet.use_encryption:
758         password  = droid.dialogGetPassword('Seed').result
759         if not password: return
760     else:
761         password = None
762     
763     try:
764         seed = wallet.pw_decode( wallet.seed, password)
765     except:
766         modal_dialog('error','incorrect password')
767         return
768
769     modal_dialog('Your seed is',seed)
770     modal_dialog('Mnemonic code:', ' '.join(mnemonic.mn_encode(seed)) )
771
772 def change_password_dialog():
773     if wallet.use_encryption:
774         password  = droid.dialogGetPassword('Your wallet is encrypted').result
775         if password is None: return
776     else:
777         password = None
778
779     try:
780         seed = wallet.pw_decode( wallet.seed, password)
781     except:
782         modal_dialog('error','incorrect password')
783         return
784
785     new_password  = droid.dialogGetPassword('Choose a password').result
786     if new_password == None:
787         return
788
789     if new_password != '':
790         password2  = droid.dialogGetPassword('Confirm new password').result
791         if new_password != password2:
792             modal_dialog('error','passwords do not match')
793             return
794
795     wallet.update_password(seed, password, new_password)
796     if new_password:
797         modal_dialog('Password updated','your wallet is encrypted')
798     else:
799         modal_dialog('No password','your wallet is not encrypted')
800     return True
801
802
803 def settings_loop():
804
805
806     def set_listview():
807         server, port, p = wallet.server.split(':')
808         fee = str( Decimal( wallet.fee)/100000000 )
809         is_encrypted = 'yes' if wallet.use_encryption else 'no'
810         protocol = protocol_name(p)
811         droid.fullShow(settings_layout)
812         droid.fullSetList("myListView",['Server: ' + server, 'Protocol: '+ protocol, 'Port: '+port, 'Transaction fee: '+fee, 'Password: '+is_encrypted, 'Seed'])
813
814     set_listview()
815
816     out = None
817     while out is None:
818         event = droid.eventWait().result
819         print "got event", event
820         if event == 'OK': continue
821         if not event: continue
822
823         plist = {}
824         for item in wallet.interface.servers:
825             host, pp = item
826             z = {}
827             for item2 in pp:
828                 protocol, port = item2
829                 z[protocol] = port
830             plist[host] = z
831
832         if event["name"] == "itemclick":
833             pos = event["data"]["position"]
834             host, port, protocol = wallet.server.split(':')
835
836             if pos == "0": #server
837                 host = server_dialog(plist)
838                 if host:
839                     p = plist[host]
840                     port = p['t']
841                     srv = host + ':' + port + ':t'
842                     try:
843                         wallet.set_server(srv)
844                     except:
845                         modal_dialog('error','invalid server')
846                     set_listview()
847
848             elif pos == "1": #protocol
849                 if host in plist:
850                     srv = protocol_dialog(host, protocol, plist[host])
851                     if srv:
852                         try:
853                             wallet.set_server(srv)
854                         except:
855                             modal_dialog('error','invalid server')
856                         set_listview()
857
858             elif pos == "2": #port
859                 a_port = modal_input('Port number', 'If you use a public server, this field is set automatically when you set the protocol', port, "number")
860                 if a_port:
861                     if a_port != port:
862                         srv = host + ':' + a_port + ':'+ protocol
863                         try:
864                             wallet.set_server(srv)
865                         except:
866                             modal_dialog('error','invalid port number')
867                         set_listview()
868
869             elif pos == "3": #fee
870                 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")
871                 if fee:
872                     try:
873                         fee = int( 100000000 * Decimal(fee) )
874                     except:
875                         modal_dialog('error','invalid fee value')
876                     if wallet.fee != fee:
877                         wallet.fee = fee
878                         wallet.save()
879                         set_listview()
880         
881             elif pos == "4":
882                 if change_password_dialog():
883                     set_listview()
884
885             elif pos == "5":
886                 seed_dialog()
887
888
889         elif event["name"] in menu_commands:
890             out = event["name"]
891
892         elif event["name"] == 'cancel':
893             out = 'main'
894
895         elif event["name"] == "key":
896             if event["data"]["key"] == '4':
897                 out = 'main'
898
899     return out
900
901
902
903
904 menu_commands = ["send", "receive", "settings", "contacts", "main"]
905 droid = android.Android()
906 wallet = Wallet(update_callback)
907
908 wallet.set_path("/sdcard/electrum.dat")
909 wallet.read()
910 if not wallet.file_exists:
911     recover()
912 else:
913     WalletSynchronizer(wallet,True).start()
914
915
916 s = 'main'
917
918 def add_menu(s):
919     droid.clearOptionsMenu()
920     if s == 'main':
921         droid.addOptionsMenuItem("Send","send",None,"")
922         droid.addOptionsMenuItem("Receive","receive",None,"")
923         droid.addOptionsMenuItem("Contacts","contacts",None,"")
924         droid.addOptionsMenuItem("Settings","settings",None,"")
925     elif s == 'receive':
926         droid.addOptionsMenuItem("Copy","clipboard",None,"")
927         droid.addOptionsMenuItem("Label","edit",None,"")
928     elif s == 'contacts':
929         droid.addOptionsMenuItem("Copy","clipboard",None,"")
930         droid.addOptionsMenuItem("Label","edit",None,"")
931         droid.addOptionsMenuItem("Pay to","paytocontact",None,"")
932         #droid.addOptionsMenuItem("Delete","deletecontact",None,"")
933
934 def make_bitmap(addr):
935     # fixme: this is highly inefficient
936     droid.dialogCreateSpinnerProgress("please wait")
937     droid.dialogShow()
938     try:
939         import pyqrnative, bmp
940         qr = pyqrnative.QRCode(4, pyqrnative.QRErrorCorrectLevel.L)
941         qr.addData(addr)
942         qr.make()
943         k = qr.getModuleCount()
944         assert k == 33
945         bmp.save_qrcode(qr,"/sdcard/sl4a/qrcode.bmp")
946     finally:
947         droid.dialogDismiss()
948
949         
950
951 while True:
952     add_menu(s)
953     if s == 'main':
954         droid.fullShow(main_layout())
955         s = main_loop()
956         #droid.fullDismiss()
957
958     elif s == 'send':
959         droid.fullShow(payto_layout)
960         s = payto_loop()
961         #droid.fullDismiss()
962
963     elif s == 'receive':
964         make_bitmap(receive_addr)
965         droid.fullShow(qr_layout(receive_addr))
966         s = receive_loop()
967
968     elif s == 'contacts':
969         make_bitmap(contact_addr)
970         droid.fullShow(qr_layout(contact_addr))
971         s = contacts_loop()
972
973     elif s == 'settings':
974         #droid.fullShow(settings_layout)
975         s = settings_loop()
976         #droid.fullDismiss()
977     else:
978         break
979
980 droid.makeToast("Bye!")